diff --git a/CHANGELOG.md b/CHANGELOG.md index 25392df6..b9a48eed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ See [keep a changelog] for information about writing changes to this log. ## [Unreleased] +- Updated fixtures. Made event organizer required. + ## [1.2.2] - 2025-10-07 - [PR-73](https://github.com/itk-dev/event-database-imports/pull/73) Set deploy user for rabbitmq container @@ -119,7 +121,9 @@ See [keep a changelog] for information about writing changes to this log. - Consolidate scheduled feed import and index populate in one command [keep a changelog]: https://keepachangelog.com/en/1.1.0/ -[Unreleased]: https://github.com/itk-dev/event-database-imports/compare/1.2.0...HEAD +[Unreleased]: https://github.com/itk-dev/event-database-imports/compare/1.2.2...HEAD +[1.2.2]: https://github.com/itk-dev/event-database-imports/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/itk-dev/event-database-imports/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/itk-dev/event-database-imports/compare/1.1.6...1.2.0 [1.1.6]: https://github.com/itk-dev/event-database-imports/releases/tag/1.1.6 [1.1.5]: https://github.com/itk-dev/event-database-imports/releases/tag/1.1.5 diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 46f3b371..073baa91 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -11,7 +11,7 @@ security: firewalls: dev: - pattern: ^/(_(profiler|wdt)|css|images|js)/ + pattern: ^/(admin/)?(_(profiler|wdt))|^/(css|images|js)/ security: false image_resolver: pattern: ^/images/cache/resolve diff --git a/public/scripts/form-scroll-to-error.js b/public/scripts/form-scroll-to-error.js new file mode 100644 index 00000000..ea813964 --- /dev/null +++ b/public/scripts/form-scroll-to-error.js @@ -0,0 +1,9 @@ +document.addEventListener('ea.form.error', function (event) { + const form = event.detail.form; + const firstInvalid = form.querySelector(':invalid:not(:disabled)'); + + if (firstInvalid) { + firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' }); + firstInvalid.focus({ preventScroll: true }); + } +}); diff --git a/src/Command/Event/FixEventsWithoutOrganizerCommand.php b/src/Command/Event/FixEventsWithoutOrganizerCommand.php new file mode 100644 index 00000000..90516d15 --- /dev/null +++ b/src/Command/Event/FixEventsWithoutOrganizerCommand.php @@ -0,0 +1,98 @@ +addOption('dry-run', null, InputOption::VALUE_NONE, 'Show what would be changed without persisting'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $dryRun = $input->getOption('dry-run'); + + if ($dryRun) { + $io->note('Running in dry-run mode. No changes will be persisted.'); + } + + $events = $this->eventRepository->findBy(['organization' => null]); + + if (0 === \count($events)) { + $io->success('No events found without an organization.'); + + return Command::SUCCESS; + } + + $io->info(sprintf('Found %d event(s) without an organization.', \count($events))); + + $fixed = 0; + $skipped = []; + + foreach ($events as $event) { + $createdBy = $event->getCreatedBy(); + + if ('' === $createdBy) { + $skipped[] = [$event->getId(), $event->getTitle(), 'Not created by a user']; + continue; + } + + $user = $this->userRepository->findOneBy(['mail' => $createdBy]); + + if (null === $user) { + $skipped[] = [$event->getId(), $event->getTitle(), sprintf('User "%s" not found', $createdBy)]; + continue; + } + + $organizations = $user->getOrganizations(); + + if (1 !== $organizations->count()) { + $skipped[] = [$event->getId(), $event->getTitle(), sprintf('User "%s" has %d organizations', $createdBy, $organizations->count())]; + continue; + } + + $organization = $organizations->first(); + $event->setOrganization($organization); + ++$fixed; + + $io->text(sprintf('Event #%d "%s" → Organization "%s"', $event->getId(), $event->getTitle(), $organization)); + } + + if (\count($skipped) > 0) { + $io->section('Skipped events'); + $io->table(['ID', 'Title', 'Reason'], $skipped); + } + + if ($fixed > 0 && !$dryRun) { + $this->entityManager->flush(); + } + + $io->success(sprintf('%s %d event(s).', $dryRun ? 'Would fix' : 'Fixed', $fixed)); + + return Command::SUCCESS; + } +} diff --git a/src/Command/User/ListUsersWithoutOrganizationCommand.php b/src/Command/User/ListUsersWithoutOrganizationCommand.php new file mode 100644 index 00000000..d148e94d --- /dev/null +++ b/src/Command/User/ListUsersWithoutOrganizationCommand.php @@ -0,0 +1,72 @@ +value, + UserRoles::ROLE_ADMIN->value, + UserRoles::ROLE_SUPER_ADMIN->value, + ]; + + $qb = $this->userRepository->createQueryBuilder('u') + ->leftJoin('u.organizations', 'o') + ->where('o.id IS NULL'); + + // Roles are stored as a JSON array in the database, so we use `LIKE` to checks if a role is in the array. + foreach ($editorOrAbove as $i => $role) { + $qb->andWhere("u.roles NOT LIKE :role{$i}") + ->setParameter("role{$i}", "%\"{$role}\"%"); + } + + $users = $qb->orderBy('u.name', 'ASC') + ->getQuery() + ->getResult(); + + if (0 === \count($users)) { + $io->success('No users found matching the criteria.'); + + return Command::SUCCESS; + } + + $rows = array_map(fn ($user) => [ + $user->getId(), + $user->getName(), + $user->getMail(), + implode(', ', $user->getRoles()), + $user->isEnabled() ? 'Yes' : 'No', + ], $users); + + $io->table( + ['ID', 'Name', 'Email', 'Roles', 'Enabled'], + $rows, + ); + + $io->note(sprintf('Found %d user(s) without an organization who are not at least editor.', \count($users))); + + return Command::SUCCESS; + } +} diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index ec410aec..d6cad342 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -149,6 +149,8 @@ public function configureUserMenu(UserInterface $user): UserMenu public function configureAssets(): Assets { - return Assets::new()->addCssFile('/admin/styles/admin.css'); + return Assets::new() + ->addCssFile('/admin/styles/admin.css') + ->addJsFile('/scripts/form-scroll-to-error.js'); } } diff --git a/src/Controller/Admin/EventCrudController.php b/src/Controller/Admin/EventCrudController.php index 926cada8..93338100 100644 --- a/src/Controller/Admin/EventCrudController.php +++ b/src/Controller/Admin/EventCrudController.php @@ -2,17 +2,16 @@ namespace App\Controller\Admin; +use App\EasyAdmin\Filter\HasOrganizationFilter; use App\Entity\Event; use App\Entity\Organization; use App\Service\ImageServiceInterface; use App\Types\UserRoles; use Doctrine\Common\Collections\Order; -use Doctrine\ORM\QueryBuilder; use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Asset; -use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore; @@ -43,6 +42,20 @@ public static function getEntityFqcn(): string return Event::class; } + public function createEntity(string $entityFqcn): Event + { + $event = new Event(); + + if (!$this->isGranted(UserRoles::ROLE_EDITOR->value)) { + $userOrganizations = $this->getUser()->getOrganizations(); + if (1 === $userOrganizations->count()) { + $event->setOrganization($userOrganizations->first()); + } + } + + return $event; + } + public function configureCrud(Crud $crud): Crud { return $crud @@ -75,18 +88,18 @@ public function configureFields(string $pageName): iterable yield TextField::new('title') ->setLabel(new TranslatableMessage('admin.event.title')); yield TextareaField::new('excerpt') - ->setLabel(new TranslatableMessage('admin.event.basic.excerpt')) - ->setMaxLength(Event::EXCERPT_MAX_LENGTH) - ->hideOnIndex(); + ->setLabel(new TranslatableMessage('admin.event.basic.excerpt')) + ->setMaxLength(Event::EXCERPT_MAX_LENGTH) + ->hideOnIndex(); yield TextEditorField::new('description') - ->setLabel(new TranslatableMessage('admin.event.basic.description')) - ->hideOnDetail() - ->hideOnIndex(); + ->setLabel(new TranslatableMessage('admin.event.basic.description')) + ->hideOnDetail() + ->hideOnIndex(); yield TextareaField::new('description') - ->setLabel(new TranslatableMessage('admin.event.basic.description')) - ->renderAsHtml() - ->hideOnIndex() - ->hideOnForm(); + ->setLabel(new TranslatableMessage('admin.event.basic.description')) + ->renderAsHtml() + ->hideOnIndex() + ->hideOnForm(); // Image / Detail view yield ImageField::new('image') @@ -97,81 +110,103 @@ public function configureFields(string $pageName): iterable return $transformed['large'] ?? null; }) - ->hideOnIndex()->hideOnForm(); + ->hideOnIndex()->hideOnForm(); // Image / Form view // @see self::getFieldAssets() yield AssociationField::new('image') - ->setLabel(new TranslatableMessage('admin.event.basic.image')) - ->hideOnIndex() - ->renderAsEmbeddedForm(EmbedImageController::class); + ->setLabel(new TranslatableMessage('admin.event.basic.image')) + ->hideOnIndex() + ->renderAsEmbeddedForm(EmbedImageController::class); yield AssociationField::new('tags') - ->setLabel(new TranslatableMessage('admin.event.basic.tags')) - ->hideOnDetail(); + ->setLabel(new TranslatableMessage('admin.event.basic.tags')) + ->hideOnDetail(); yield ArrayField::new('tags') - ->setLabel(new TranslatableMessage('admin.event.basic.tags')) - ->onlyOnDetail(); + ->setLabel(new TranslatableMessage('admin.event.basic.tags')) + ->onlyOnDetail(); yield FormField::addFieldset('Occurrences') - ->setLabel(new TranslatableMessage('admin.event.occurrences')); + ->setLabel(new TranslatableMessage('admin.event.occurrences')); yield CollectionField::new('occurrences') - ->setLabel(new TranslatableMessage('admin.event.occurrences')) - ->hideOnIndex() - ->renderExpanded(false) - ->useEntryCrudForm(); + ->setLabel(new TranslatableMessage('admin.event.occurrences')) + ->hideOnIndex() + ->renderExpanded(false) + ->useEntryCrudForm(); yield FormField::addFieldset('Location information') - ->setLabel(new TranslatableMessage('admin.event.location.headline')); + ->setLabel(new TranslatableMessage('admin.event.location.headline')); yield UrlField::new('url') - ->setLabel(new TranslatableMessage('admin.event.location.url')) - ->hideOnIndex(); + ->setLabel(new TranslatableMessage('admin.event.location.url')) + ->hideOnIndex(); yield UrlField::new('ticketUrl') - ->setLabel(new TranslatableMessage('admin.event.location.ticketUrl')) - ->hideOnIndex(); + ->setLabel(new TranslatableMessage('admin.event.location.ticketUrl')) + ->hideOnIndex(); yield AssociationField::new('location') - ->setLabel(new TranslatableMessage('admin.event.location.location')); + ->setLabel(new TranslatableMessage('admin.event.location.location')); yield FormField::addFieldset('Organizer information') - ->setLabel(new TranslatableMessage('admin.event.organizer.headline')); + ->setLabel(new TranslatableMessage('admin.event.organizer.headline')); - if ($this->isGranted(UserRoles::ROLE_EDITOR->value)) { - yield AssociationField::new('organization') - ->setLabel(new TranslatableMessage('admin.event.edited.organization')); - } else { - yield AssociationField::new('organization') - ->setLabel(new TranslatableMessage('admin.event.edited.organization')) - ->setQueryBuilder( - fn (QueryBuilder $queryBuilder) => $queryBuilder - ->select('o') - ->from(Organization::class, 'o') - ->where(':user MEMBER OF o.users') - ->setParameter('user', $this->getUser()) - ); + $organizationField = AssociationField::new('organization') + ->setLabel(new TranslatableMessage('admin.event.edited.organization')) + // We assume at least one organization exist for non-editor users + // (cf. editor stuff below). + ->setRequired(true) + ->setFormTypeOption('placeholder', new TranslatableMessage('admin.event.organizer.placeholder')); + // Limit organization choices for non-editors to the organizations the user is a member of. + if (!$this->isGranted(UserRoles::ROLE_EDITOR->value)) { + $userOrganizations = $this->getUser()->getOrganizations(); + $organizationField + ->setFormTypeOption('choices', $userOrganizations) + // Disable the field if the user only has one organization as we set default value in createEntity() + ->setDisabled(1 === $userOrganizations->count()); } + yield $organizationField; + yield AssociationField::new('partners') - ->setLabel(new TranslatableMessage('admin.event.edited.partners')) - ->hideOnDetail(); + ->setLabel(new TranslatableMessage('admin.event.edited.partners')) + ->hideOnDetail(); yield ArrayField::new('partners') ->setLabel(new TranslatableMessage('admin.event.edited.partners')) ->onlyOnDetail(); yield FormField::addFieldset('Edited') - ->setLabel(new TranslatableMessage('admin.event.edited.headline')) - ->hideWhenCreating(); - yield AssociationField::new('feed') + ->setLabel(new TranslatableMessage('admin.event.edited.headline')) + ->hideWhenCreating(); + + $event = $this->getContext()?->getEntity()?->getInstance(); + if (!$event instanceof Event || null !== $event->getFeed()) { + yield AssociationField::new('feed') ->setLabel(new TranslatableMessage('admin.event.edited.feed')) ->hideOnForm() ->hideOnIndex(); - yield DateTimeField::new('updated_at') - ->setLabel(new TranslatableMessage('admin.event.edited.updated')) - ->setDisabled() - ->hideWhenCreating(); + } + + yield TextField::new('createdBy') + ->setLabel(new TranslatableMessage('admin.event.edited.created_by')) + ->setDisabled() + ->hideWhenCreating(); + yield TextField::new('updatedBy') + ->setLabel(new TranslatableMessage('admin.event.edited.updated_by')) + ->setDisabled() + ->hideWhenCreating(); + + yield DateTimeField::new('createdAt') + ->setLabel(new TranslatableMessage('admin.event.edited.created')) + ->setDisabled() + ->hideWhenCreating(); + yield DateTimeField::new('updatedAt') + ->setLabel(new TranslatableMessage('admin.event.edited.updated')) + ->setDisabled() + ->hideWhenCreating(); } public function configureFilters(Filters $filters): Filters { if ($this->isGranted(UserRoles::ROLE_EDITOR->value)) { - $filters->add('feed'); + $filters + ->add('feed') + ->add(HasOrganizationFilter::new('organization', new TranslatableMessage('admin.event.filter.has_organization'))); } // 'organization' filter has additional config when added in MyEventCrudController @@ -189,8 +224,7 @@ protected function configureBaseFilters(Filters $filters): Filters ->add('tags') ->add('title') ->add('url') - ->add('ticketUrl') - ; + ->add('ticketUrl'); } /** diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index e64a5d2f..50d42ea3 100644 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -2,6 +2,8 @@ namespace App\Controller\Admin; +use App\EasyAdmin\Filter\HasOrganizationFilter; +use App\EasyAdmin\Filter\JsonContainsFilter; use App\Entity\User; use App\Types\UserRoles; use Doctrine\ORM\QueryBuilder; @@ -10,6 +12,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; +use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore; use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; @@ -23,6 +26,8 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; +use EasyCorp\Bundle\EasyAdminBundle\Filter\EntityFilter; +use EasyCorp\Bundle\EasyAdminBundle\Filter\TextFilter; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\FormBuilderInterface; @@ -63,6 +68,22 @@ public function configureActions(Actions $actions): Actions return $actions; } + public function configureFilters(Filters $filters): Filters + { + $userRolesChoices = []; + foreach (UserRoles::cases() as $role) { + $userRolesChoices[$role->value] = $role->value; + } + + return $filters + ->add(EntityFilter::new('organizations', new TranslatableMessage('admin.user.organizers'))) + ->add(HasOrganizationFilter::new('organizations', new TranslatableMessage('admin.user.filter.has_organization'))) + ->add(JsonContainsFilter::new('roles', new TranslatableMessage('admin.user.roles')) + ->setChoices($userRolesChoices)) + ->add(TextFilter::new('mail', new TranslatableMessage('admin.user.mail'))) + ; + } + public function configureFields(string $pageName): iterable { $userRolesChoices = [ diff --git a/src/EasyAdmin/Filter/HasOrganizationFilter.php b/src/EasyAdmin/Filter/HasOrganizationFilter.php new file mode 100644 index 00000000..c47b7e29 --- /dev/null +++ b/src/EasyAdmin/Filter/HasOrganizationFilter.php @@ -0,0 +1,50 @@ +setFilterFqcn(__CLASS__) + ->setProperty('hasOrganization') + ->setLabel($label) + ->setFormType(BooleanFilterType::class) + ->setFormTypeOption('translation_domain', 'EasyAdminBundle'); + + $filter->associationProperty = $associationProperty; + + return $filter; + } + + public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void + { + $alias = $filterDataDto->getEntityAlias(); + $assocAlias = 'ea_has_org_'.$filterDataDto->getParameterName(); + + $queryBuilder->leftJoin(sprintf('%s.%s', $alias, $this->associationProperty), $assocAlias); + + if ($filterDataDto->getValue()) { + $queryBuilder->andWhere(sprintf('%s IS NOT NULL', $assocAlias)); + } else { + $queryBuilder->andWhere(sprintf('%s IS NULL', $assocAlias)); + } + } +} diff --git a/src/EasyAdmin/Filter/JsonContainsFilter.php b/src/EasyAdmin/Filter/JsonContainsFilter.php new file mode 100644 index 00000000..92d2f06e --- /dev/null +++ b/src/EasyAdmin/Filter/JsonContainsFilter.php @@ -0,0 +1,75 @@ +setFilterFqcn(__CLASS__) + ->setProperty($propertyName) + ->setLabel($label) + ->setFormType(ChoiceFilterType::class) + ->setFormTypeOption('translation_domain', 'EasyAdminBundle'); + } + + /** + * @param array $choices + */ + public function setChoices(array $choices): self + { + $this->dto->setFormTypeOption('value_type_options.choices', $choices); + + return $this; + } + + /** + * @param array $choiceGenerator + */ + public function setTranslatableChoices(array $choiceGenerator): self + { + $this->dto->setFormTypeOption('value_type_options.choices', array_keys($choiceGenerator)); + $this->dto->setFormTypeOption('value_type_options.choice_label', fn ($value) => $choiceGenerator[$value]); + + return $this; + } + + public function canSelectMultiple(bool $selectMultiple = true): self + { + $this->dto->setFormTypeOption('value_type_options.multiple', $selectMultiple); + + return $this; + } + + public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void + { + $alias = $filterDataDto->getEntityAlias(); + $property = $filterDataDto->getProperty(); + $value = $filterDataDto->getValue(); + $isMultiple = (bool) $filterDataDto->getFormTypeOption('value_type_options.multiple'); + + $values = $isMultiple ? $value : [$value]; + + foreach ($values as $i => $val) { + $paramName = $filterDataDto->getParameterName().'_'.$i; + $queryBuilder + ->andWhere(sprintf('%s.%s LIKE :%s', $alias, $property, $paramName)) + ->setParameter($paramName, '%"'.$val.'"%'); + } + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 001b85cb..77f697fe 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -4,6 +4,7 @@ use App\Repository\UserRepository; use App\Security\UserInterface; +use App\Types\UserRoles; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; @@ -14,6 +15,7 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Entity(repositoryClass: UserRepository::class)] #[UniqueEntity( @@ -211,6 +213,22 @@ public function setRoles(array $roles): self return $this; } + #[Assert\Callback] + public function validateOrganizationRequired(ExecutionContextInterface $context, mixed $payload): void + { + $organizationRoles = [ + UserRoles::ROLE_ORGANIZATION_ADMIN->value, + UserRoles::ROLE_ORGANIZATION_EDITOR->value, + ]; + + if (!empty(array_intersect($this->roles, $organizationRoles)) && $this->organizations->isEmpty()) { + $context + ->buildViolation('entity.user.organizations.organization_required') + ->atPath('organizations') + ->addViolation(); + } + } + public function eraseCredentials(): void { // TODO: Implement eraseCredentials() method. diff --git a/translations/EasyAdminBundle.da.xlf b/translations/EasyAdminBundle.da.xlf index 935e4cbf..646e757b 100644 --- a/translations/EasyAdminBundle.da.xlf +++ b/translations/EasyAdminBundle.da.xlf @@ -5,183 +5,183 @@ - + page_title.exception Fejl|Fejl - + datagrid.hidden_results Nogle resultater kan ikke vises fordi du ikke har nok rettigheder - + datagrid.no_results Intet resultat. - + paginator.previous Forrige - + paginator.next Næste - + paginator.results 1 resultat|]1,Inf] %count% resultater]]> - + label.empty Tom - + label.null Null - + label.inaccessible Utilgængelig - + label.inaccessible.explanation Der findes ingen getter metode for dette felt eller også er det ikke et tilgængeligt felt - + field.code_editor.view_code Vis kode - + field.text_editor.view_content Vis indhold - + action.entity_actions Handlinger - + action.search Søg - + action.delete Slet - + action.cancel Afbryd - + action.add_new_item Tilføj nyt emne - + action.remove_item Slet emnet - + action.choose_file Vælg fil - + action.close Luk - + batch_action_modal.title Du vil anvende "%action_name%" handlingen på %num_items% element(er). - + batch_action_modal.content Du kan ikke fortryde denne handling. - + batch_action_modal.action Udfør handling - + delete_modal.title Er du sikker på du vil slette dette element? - + delete_modal.content Denne operation kan ikke fortrydes. - + filter.title Filtre - + filter.button.clear Ryd - + filter.button.apply Aktiver - + form.tab.error_badge_title En ugyldig indtastning|%count% ugyldige indtastninger - + user.logged_in_as Logget ind som - + user.anonymous Anonym bruger - + settings.appearance.label Udseende - + settings.appearance.light Lys - + settings.appearance.dark Mørk - + settings.appearance.auto Automatisk - + settings.locale Sprog - + login_page.username Username - + login_page.password Password - + login_page.sign_in Sign in - + login_page.forgot_password Glemt din adgangskode? - + login_page.remember_me Husk mig - + autocomplete.no-results-found Ingen resultater fundet - + autocomplete.loading-more-results Indlæser flere resultater… - + autocomplete.no-more-results __autocomplete.no-more-results - + files __files diff --git a/translations/EasyAdminBundle.en.xlf b/translations/EasyAdminBundle.en.xlf index ddcd64c8..629a328a 100644 --- a/translations/EasyAdminBundle.en.xlf +++ b/translations/EasyAdminBundle.en.xlf @@ -5,183 +5,183 @@ - + page_title.exception Error|Errors - + datagrid.hidden_results Some results can't be displayed because you don't have enough permissions - + datagrid.no_results No results found. - + paginator.previous Previous - + paginator.next Next - + paginator.results 1 result|]1,Inf] %count% results]]> - + label.empty Empty - + label.null Null - + label.inaccessible Inaccessible - + label.inaccessible.explanation Getter method does not exist for this field or the field is not public - + field.code_editor.view_code View code - + field.text_editor.view_content View content - + action.entity_actions Actions - + action.search Search - + action.delete Delete - + action.cancel Cancel - + action.add_new_item Add a new item - + action.remove_item Remove the item - + action.choose_file Choose file - + action.close Close - + batch_action_modal.title You are going to apply the "%action_name%" action to %num_items% item(s). - + batch_action_modal.content There is no undo for this operation. - + batch_action_modal.action Proceed - + delete_modal.title Do you really want to delete this item? - + delete_modal.content There is no undo for this operation. - + filter.title Filters - + filter.button.clear Clear - + filter.button.apply Apply - + form.tab.error_badge_title One invalid input|%count% invalid inputs - + user.logged_in_as Logged in as - + user.anonymous Anonymous User - + settings.appearance.label Appearance - + settings.appearance.light Light - + settings.appearance.dark Dark - + settings.appearance.auto Auto - + settings.locale Language - + login_page.username Username - + login_page.password Password - + login_page.sign_in Sign in - + login_page.forgot_password Forgot Your Password? - + login_page.remember_me Remember me - + autocomplete.no-results-found No results found - + autocomplete.no-more-results No more results - + autocomplete.loading-more-results Loading more results… - + files files diff --git a/translations/VichUploaderBundle+intl-icu.da.xlf b/translations/VichUploaderBundle+intl-icu.da.xlf index 5c7d2479..3488c0d9 100644 --- a/translations/VichUploaderBundle+intl-icu.da.xlf +++ b/translations/VichUploaderBundle+intl-icu.da.xlf @@ -5,7 +5,7 @@ - + download __download diff --git a/translations/VichUploaderBundle+intl-icu.en.xlf b/translations/VichUploaderBundle+intl-icu.en.xlf index f1425b21..967df2c0 100644 --- a/translations/VichUploaderBundle+intl-icu.en.xlf +++ b/translations/VichUploaderBundle+intl-icu.en.xlf @@ -5,7 +5,7 @@ - + download download diff --git a/translations/messages+intl-icu.da.xlf b/translations/messages+intl-icu.da.xlf index b8430000..58e094f8 100644 --- a/translations/messages+intl-icu.da.xlf +++ b/translations/messages+intl-icu.da.xlf @@ -5,654 +5,678 @@ - + admin.address.headline Adresse - + admin.address.street Vej - + admin.address.region Region - + admin.address.city By - + admin.address.country Land - + admin.address.postalCode Postnummer - + admin.address.id Id - + admin.link.events Begivenheder - + admin.link.location Steder - + admin.link.address Addresser - + admin.link.feeds Feeds - + admin.link.tags Tags - + admin.link.users Brugere - + admin.address.location.headline Steder - + admin.address.location.latitude Breddegrad - + admin.address.location.longitude Længdegrad - + admin.address.edited.headline Adresse - + admin.address.edited.updated Opdateret - + admin.event.basic.headline Basis information - + admin.event.basic.excerpt Uddrag - + admin.event.basic.description Beskrivelse - + admin.event.basic.image Billede - + admin.event.basic.tags Tags - + admin.event.location.headline Sted - + admin.event.location.url Url - + admin.event.location.ticketUrl Billet Url - + admin.event.location.location Sted - + admin.event.edited.headline Redigeret - + admin.event.edited.organization Arrangør - + admin.event.edited.updated Opdateret - + admin.feed.id Id - + admin.feed.name Navn - + admin.feed.configuration Konfiguration - + admin.feed.edited.headline Redigeret - + admin.feed.edited.update Opdateret - + admin.image.title Titel - + admin.location.id Id - + admin.location.basic.headline Basis Information - + admin.location.basic.name Navn - + admin.location.enriched.headline Beriget Information - + admin.location.enriched.url Url - + admin.location.enriched.mail Email - + admin.location.enriched.image Billede - + admin.location.enriched.telephone Telefon - + admin.location.enriched.disability-access Handicap Adgang - + admin.location.edited.headline Redigeret - + admin.location.edited.updated Opdateret - + admin.occurrence.dates.headline - + admin.occurrence.dates.start Start - + admin.occurrence.dates.end Slut - + admin.occurrence.basic.headline Basis Information - + admin.occurrence.basic.price Pris - + admin.occurrence.basic.room Sal/Lokale - + admin.organization.id Id - + admin.organization.name Navn - + admin.organization.mail Email - + admin.organization.url Url - + admin.organization.edited.headline Redigeret - + admin.organization.edited.updated Opdateret - + admin.tag.name Navn - + admin.tag.vocabularies Vokabularer - + admin.tag.edited.headline Redigeret - + admin.tag.edited.updated Opdateret - + admin.user.id Id - + admin.user.name Navn - + admin.user.password Kodeord - + admin.user.password2 Gentag kodeord - + admin.user.enabled Aktiveret - + admin.user.edited.headline Redigeret - + admin.user.edited.updated Opdateret - + login.user.mail Email - + login.user.password Kodeord - + login.label Log ind - + login.page.remember Husk mig - + admin.user.roles Roller - + registration.page.confirm_email Bekræft email - + registration.page.please_register Registrer dig - + registration.page.email_verified Email bekræftet - + registration.form.agree_terms Godkend Brugeraftale - + registration.form.enter_password Angiv venligst kodeord - + page.confirm.confirmation_link_sent Der er sendt en email til { email } med et link til bekræftelse af kontoen - + registration.page.btn.register Registrer dig - + page.confirm.click_to_activate Klik venligst på linket for at aktivere din konto - + page.confirm.already_confirmed Allerede aktiveret? - + page.confirm.log_in Log ind - + email.confirm.please_confirm Bekræft venligst din email - + email.confirm.click_link Bekræft venligst din email ved at klikke på følgende link - + email.confirm.confirm_my_email Bekræft min email - + email.confirm.link_will_expire_in Dette liunk vil udløbe om - + admin.feed.configuration.help eventDbKey]]> - + admin.event.admin.image.local Lokalt billede - + admin.event.occurrences - + admin.event.edited.feed Feed - + admin.link.vocabularies Vokabular - + admin.link.organizations Arrangører - + admin.image.local Billede - + admin.vocabulary.name Navn - + admin.vocabulary.tags Tags - + admin.vocabulary.edited.headline Redigeret - + admin.vocabulary.edited.updated Opdateret - + admin.feed.last_read.headline Sidst læst - + admin.feed.last_read.count Antal - + admin.feed.last_read.error Fejl - + admin.event.organizer.headline - + admin.event.edited.partners Partnere - + terms.page.accept_terms Godkend Brugeraftale - + terms.page.btn.accept Godkend - + admin.user.role.role_admin Administrator - + admin.user.mail Email - + admin.user.role.role_user Bruger - + admin.user.role.role_api_user API Bruger - + admin.user.role.role_editor Redaktør - + admin.tag.tag Tag - + admin.user.role.role_organization_admin Arrangør administrator - + admin.user.role.role_organization_editor Arrangør redaktør - + admin.user.email_verified Email bekræftet - + admin.user.terms_accepted_at Brugeraftale godkendt - + admin.address.locations Steder - + admin.label.my_content Mit indhold - + admin.label.all_content Alt indhold - + admin.label.feeds Feeds - + admin.label.users Brugere - + admin.event.id Id - + admin.event.title Titel - + admin.user_menu.my_profile Min profil - + admin.user.edit.title Bruger - + admin.user.index.title Brugere - + admin.user.organizers Arrangører - + admin.user_menu.terms Brugeraftale - + admin.event.edit.title Begivenhed - + admin.event.index.title Begivenheder - + admin.organizer.edit.title Arrangør - + admin.organizer.index.title Arrangører - + admin.location.edit.title Sted - + admin.location.index.title Steder - + admin.vocabulary.edit.title Vokabular - + admin.vocabulary.index.title Vokabularer - + admin.address.edit.title Adresse - + admin.address.index.title Adresser - + admin.address.location.coordinates Koordinater - + admin.my.event.edit.title Rediger begivenhed - + admin.my.event.index.title Mine Begivenheder - + admin.feed.organization Arrangør - + admin.feed.last_read.datetime Sidst læst - + admin.link.feedItems Feedkilder - + admin.feeditem.id id - + admin.feeditem.feed Feed - + admin.feeditem.feeditemid Kilde id - + admin.feeditem.event Begivenhed - + admin.feeditem.message Fejl - + admin.feeditem.last_read.datetime Sidst læst - + admin.feeditem.data Data - + admin.my.organizer.edit.title Rediger Arrangør - + admin.my.organizer.index.title Mine Arrangører - + admin.my.event.index.help Arrangører"]]> - + registration.form.name Navn - + registration.form.mail Email - + admin.location.enriched.events Begivenheder - + admin.location.basic.address Adresse - + registration.form.organizations Hvilke arrangører arbejder du for? - + admin.user.role.role_super_admin - __admin.user.role.role_super_admin + Super Admin - + admin.user.registrationNotes Ønskede arrangører + + admin.event.organizer.placeholder + Vælg arrangør + + + admin.event.filter.has_organization + Har arrangør + + + admin.user.filter.has_organization + Har arrangør + + + admin.event.edited.created_by + Oprettet af + + + admin.event.edited.updated_by + Opdateret af + + + admin.event.edited.created + Oprettet + diff --git a/translations/messages+intl-icu.en.xlf b/translations/messages+intl-icu.en.xlf index dff86a17..275672f6 100644 --- a/translations/messages+intl-icu.en.xlf +++ b/translations/messages+intl-icu.en.xlf @@ -5,654 +5,678 @@ - + admin.address.headline Address - + admin.address.street Street - + admin.address.region Region - + admin.address.city City - + admin.address.country Country - + admin.address.postalCode Postal code - + admin.address.location.headline Geographical location - + admin.address.location.latitude Latitude - + admin.address.location.longitude Longitude - + admin.address.edited.headline Authoring information - + admin.address.edited.updated Last updated - + admin.address.id ID - + admin.link.events Events - + admin.link.location Locations - + admin.link.address Addresses - + admin.link.feeds Feeds - + admin.link.tags Tags - + admin.link.organizations Organizations - + admin.link.users Users - + admin.event.basic.headline Basic information - + admin.event.basic.excerpt Excerpt - + admin.event.basic.description Description - + admin.event.basic.image Image - + admin.event.basic.tags Tags - + admin.event.location.headline Location information - + admin.event.location.url Homepage link (URL) - + admin.event.location.ticketUrl Ticket link (URL) - + admin.event.location.location Location - + admin.event.edited.headline Authoring information - + admin.event.edited.organization Organization - + admin.event.edited.updated Last update - + admin.feed.id ID - + admin.feed.name Name - + admin.feed.configuration Configuration - + admin.feed.configuration.help eventDbKey]]> - + admin.feed.edited.headline Authoring information - + admin.feed.edited.update Last updated - + admin.image.title Title - + admin.location.id ID - + admin.location.basic.headline Basic information - + admin.location.basic.name Name - + admin.location.enriched.headline Enriched information - + admin.location.enriched.url Homepage (URL) - + admin.location.enriched.mail Mail - + admin.location.enriched.image Image (URL) - + admin.location.enriched.telephone Telephone - + admin.location.enriched.disability-access Disability access - + admin.location.edited.headline Authoring information - + admin.location.edited.updated Last updated - + admin.occurrence.dates.headline Event date - + admin.occurrence.dates.start Start - + admin.occurrence.dates.end End - + admin.occurrence.basic.headline Basic information - + admin.occurrence.basic.price Ticket price range - + admin.occurrence.basic.room Room - + admin.organization.id ID - + admin.organization.name Name - + admin.organization.mail Mail - + admin.organization.url Homepage URL - + admin.organization.edited.headline Authoring information - + admin.organization.edited.updated Last updated - + admin.tag.name Name - + admin.tag.vocabularies Vocabularies - + admin.tag.edited.headline Authoring information - + admin.tag.edited.updated Last updated - + admin.user.id ID - + admin.user.name Name - + admin.user.password Password - + admin.user.password2 Repeat password - + admin.user.enabled Enabled - + admin.user.edited.headline Authoring information - + admin.user.edited.updated Last updated - + login.user.mail Mail address - + login.user.password Password - + login.label Log in - + login.page.remember Remember me - + admin.user.roles Roles - + registration.page.confirm_email Please Confirm your Email - + registration.page.please_register Please register - + registration.page.email_verified Your email address has been verified - + registration.form.agree_terms You should agree to our terms - + registration.form.enter_password Please enter a password - + page.confirm.confirmation_link_sent An email with a confirmation link has been sent to { email } - + registration.page.btn.register Register - + page.confirm.click_to_activate Please click the link to confirm your email and activate your account - + page.confirm.already_confirmed Already confirmed? Please - + page.confirm.log_in Log in - + email.confirm.please_confirm Please confirm your email! - + email.confirm.click_link Please confirm your email address by clicking the following link - + email.confirm.confirm_my_email Confirm my Email - + email.confirm.link_will_expire_in This link will expire in - + admin.event.admin.image.local Image - + admin.event.occurrences Occurrences - + admin.event.edited.feed Feed - + admin.link.vocabularies Vocabularies - + admin.image.local Image - + admin.vocabulary.name Name - + admin.vocabulary.tags Tags - + admin.vocabulary.edited.headline Authoring information - + admin.vocabulary.edited.updated Updated - + admin.feed.last_read.headline Last Read - + admin.feed.last_read.count Count - + admin.feed.last_read.error Error - + admin.event.organizer.headline - + admin.event.edited.partners Partners - + terms.page.accept_terms Please Accept the Terms - + terms.page.btn.accept Accept terms - + admin.user.role.role_admin Admin - + admin.user.mail Mail - + admin.user.role.role_user User - + admin.user.role.role_api_user API User - + admin.user.role.role_editor Editor - + admin.tag.tag Tag - + admin.user.role.role_organization_admin Organization admin - + admin.user.role.role_organization_editor Organization editor - + admin.user.email_verified Email verified - + admin.user.terms_accepted_at Terms accepted - + admin.address.locations Locations - + admin.label.my_content My Content - + admin.label.all_content All Content - + admin.label.feeds Feeds - + admin.label.users Users - + admin.event.id id - + admin.event.title Title - + admin.user_menu.my_profile My Profile - + admin.user.edit.title Edit User - + admin.user.index.title Users - + admin.user.organizers Organizers - + admin.user_menu.terms Terms - + admin.event.edit.title Edit Event - + admin.event.index.title Events - + admin.organizer.edit.title Edit Organizer - + admin.organizer.index.title Organizers - + admin.location.edit.title Edit Location - + admin.location.index.title Locations - + admin.vocabulary.edit.title Edit Vocabulary - + admin.vocabulary.index.title Vocabularies - + admin.address.edit.title Edit Address - + admin.address.index.title Addresses - + admin.address.location.coordinates Coordinates - + admin.my.event.edit.title Edit Event - + admin.my.event.index.title My Events - + admin.feed.organization Organization - + admin.feed.last_read.datetime Last read - + admin.link.feedItems Feed Items - + admin.feeditem.id id - + admin.feeditem.feed Feed - + admin.feeditem.feeditemid Item ID - + admin.feeditem.event Event - + admin.feeditem.message Message - + admin.feeditem.last_read.datetime Last read - + admin.feeditem.data Data - + admin.my.organizer.edit.title Edit Organization - + admin.my.organizer.index.title My Organizers - + admin.my.event.index.help Organizers"]]> - + registration.form.name Name - + registration.form.mail Mail - + admin.location.enriched.events admin.location.enriched.events - + admin.location.basic.address Address - + registration.form.organizations Which organizers do you work for? - + admin.user.role.role_super_admin Super Admin - + admin.user.registrationNotes Requested organizers + + admin.event.organizer.placeholder + admin.event.organizer.placeholder + + + admin.event.filter.has_organization + Has organization + + + admin.user.filter.has_organization + Has organization + + + admin.event.edited.created_by + Created by + + + admin.event.edited.updated_by + Updated by + + + admin.event.edited.created + Created at + diff --git a/translations/validators.da.xlf b/translations/validators.da.xlf index 5da6cb7d..8faf28fa 100644 --- a/translations/validators.da.xlf +++ b/translations/validators.da.xlf @@ -5,54 +5,62 @@ - + Error Fejl - + admin.feed.configuration.json_invalid Fejl i JSON - + Your password should be at least {{ limit }} characters Dit kodeord skal være mindst {{ limit }} karakterer langt - + entity.address.street_postcode.not_unique Der findes allerede en adresse med det gadenavn i det postnummer - + entity.user.mail.not_unique Der findes allerede en bruger med den email - + entity.user.mail.not_blank Email adresse skal udfyldes - + entity.user.mail.not_valid Den indtastede email er ikke gyldig - + + entity.user.organizations.organization_required + Brugere med en organisationsrolle skal have mindst én organisation tilknyttet + + entity.vocabulary.name.not_unique Der findes allerede et vokabular me det navn - + entity.vocabulary.slug.not_unique Der findes allerede et vokabular med det slug - + entity.location.unique Der findes allerede et sted med det navn, url og mail - + entity.feed_item.feed_feedItemId_unique Der findes allerede en feed kilde med det ID - + entity.organization.name.not_unique Der findes allerede en arrangør med det navn + + The start date must be before the end date + __The start date must be before the end date + diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 398a9cd0..d2b1bab4 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -5,54 +5,62 @@ - + Error Error - + admin.feed.configuration.json_invalid Configuration JSON is not valid - + Your password should be at least {{ limit }} characters Your password should be at least {{ limit }} characters - + entity.address.street_postcode.not_unique An address all ready exists with the sam street and postcode - + entity.user.mail.not_unique A user all ready exists with the same email - + entity.user.mail.not_blank Email cannot be empty - + entity.user.mail.not_valid Email is not valid - + + entity.user.organizations.organization_required + Users with an organization role must be assigned at least one organization + + entity.vocabulary.name.not_unique A vocabulary with that name all ready exits - + entity.vocabulary.slug.not_unique A vocabulary with that slug all ready exits - + entity.location.unique A location with the samme name, url and email all ready exits - + entity.feed_item.feed_feedItemId_unique A feed item with the same ID from the same feed all ready exists - + entity.organization.name.not_unique An organization with the same name all ready exists + + The start date must be before the end date + The start date must be before the end date +