feat: remove easy admin bundle, adopt admin theme

This commit is contained in:
Björn Fromme
2026-06-22 08:59:47 +02:00
parent 52ee6b26b1
commit 2fc7ffe458
52 changed files with 1947 additions and 690 deletions
-19
View File
@@ -1,19 +0,0 @@
<?php
namespace App\Admin\Field;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
class JsonDataField implements FieldInterface
{
use FieldTrait;
public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplatePath('admin/field/json.html.twig');
}
}
@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\BookingEditDraft;
use App\Htmx\HxRedirectResponse;
use App\Repository\BookingEditDraftRepository;
use App\Service\BookingExporter;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\PaginatorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BookingEditDraftController extends AbstractController
{
use ReturnUrlTrait;
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly BookingEditDraftRepository $bookingEditDraftRepository,
private readonly PaginatorInterface $paginator,
private readonly BookingExporter $bookingExporter,
private readonly LoggerInterface $adminLogger,
) {
}
#[Route('/admin/booking-edit-draft', name: 'app_admin_bookingeditdraft')]
public function index(Request $request): Response
{
$qb = $this
->bookingEditDraftRepository
->createQueryBuilder('booking_edit_draft')
->leftJoin('booking_edit_draft.user', 'user')
;
$pagination = $this->paginator->paginate(
$qb,
$request->query->getInt('page', 1),
$request->query->getInt('limit', 20),
[
'defaultSortFieldName' => 'booking_edit_draft.createdAt',
'defaultSortDirection' => 'DESC',
]
);
return $this->render('admin/booking_edit_draft/index.html.twig', [
'pagination' => $pagination,
]);
}
#[Route('/admin/booking-edit-draft/{id}/show', name: 'app_admin_bookingeditdraft_show')]
public function show(BookingEditDraft $draft, Request $request): Response
{
$returnUrl = $this->getReturnUrl($request, 'app_admin_bookingeditdraft');
return $this->render('admin/booking_edit_draft/show.html.twig', [
'draft' => $draft,
'returnUrl' => $returnUrl,
]);
}
#[Route('/admin/booking-edit-draft/{id}/delete', name: 'app_admin_bookingeditdraft_delete')]
public function delete(BookingEditDraft $draft, Request $request): Response
{
if (true === $request->isMethod(Request::METHOD_POST)) {
$this->entityManager->remove($draft);
$this->entityManager->flush();
$this->adminLogger->info('Delete booking edit draft', [
'booking_number' => $draft->getBookingNumber(),
]);
$this->addFlash('success', 'Der Buchungsentwurf wurde gelöscht');
$returnUrl = $this->getReturnUrl($request, 'app_admin_bookingeditdraft');
return new HxRedirectResponse($returnUrl);
}
return $this->render('admin/booking_edit_draft/modal_delete.html.twig', [
'draft' => $draft,
]);
}
#[Route('/admin/booking-edit-draft/{id}/export', name: 'app_admin_bookingeditdraft_export')]
public function export(BookingEditDraft $draft): Response
{
if (false === $draft->hasExportData()) {
$this->addFlash('warning', 'Der Export ist fehlgeschlagen');
return $this->redirectToRoute('app_admin_bookingeditdraft');
}
try {
return $this->bookingExporter->createExportResponse($draft);
} catch (\RuntimeException $e) {
$this->addFlash('warning', 'Der Export ist fehlgeschlagen: '.$e->getMessage());
return $this->redirectToRoute('app_admin_bookingeditdraft');
}
}
}
@@ -1,101 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Admin\Field\JsonDataField;
use App\Entity\BookingEditDraft;
use App\Entity\User;
use App\Service\BookingExporter;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
/** @extends AbstractCrudController<BookingEditDraft> */
class BookingEditDraftCrudController extends AbstractCrudController
{
public function __construct(
private readonly BookingExporter $exportService,
private readonly AdminUrlGenerator $adminUrlGenerator,
) {
}
public static function getEntityFqcn(): string
{
return BookingEditDraft::class;
}
public function configureActions(Actions $actions): Actions
{
$exportAction = Action::new('exportExcel', 'Excel Export', 'fa fa-file-excel')
->linkToRoute(
'admin_booking_draft_export',
static fn (BookingEditDraft $entity): array => ['id' => $entity->getId()]
)
->displayIf(static fn (BookingEditDraft $entity): bool => $entity->hasExportData());
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $exportAction)
->add(Crud::PAGE_DETAIL, $exportAction)
->remove(Crud::PAGE_INDEX, Action::NEW)
->remove(Crud::PAGE_INDEX, Action::EDIT)
->remove(Crud::PAGE_DETAIL, Action::EDIT)
;
}
#[Route('/admin/booking-draft/{id}/export', name: 'admin_booking_draft_export', requirements: ['id' => '\d+'])]
public function export(BookingEditDraft $draft): Response
{
if (false === $draft->hasExportData()) {
$this->addFlash('danger', 'Export nicht möglich: Reisedaten fehlen');
$url = $this->adminUrlGenerator
->setController(self::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
try {
return $this->exportService->createExportResponse($draft);
} catch (\RuntimeException $e) {
$this->addFlash('danger', 'Export fehlgeschlagen: '.$e->getMessage());
$url = $this->adminUrlGenerator
->setController(self::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Buchungsentwurf')
->setEntityLabelInPlural('Buchungsentwürfe')
->setDefaultSort(['createdAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
return [
IntegerField::new('bookingNumber', 'Vorgang'),
AssociationField::new('user', 'Kundenaccount')->formatValue(fn (User $user) => $user->getEmail()),
DateTimeField::new('createdAt', 'erstellt am'),
DateTimeField::new('updatedAt', 'aktualisiert am'),
JsonDataField::new('formData', 'Daten')->onlyOnDetail(),
];
}
}
+7 -37
View File
@@ -1,48 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\BookingEditDraft;
use App\Entity\LogEntry;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Config\UserMenu;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Routing\Attribute\Route;
#[AdminDashboard(routePath: '/admin', routeName: 'admin')]
class DashboardController extends AbstractDashboardController
class DashboardController extends AbstractController
{
#[Route('/admin/dashboard', name: 'app_admin_dashboard')]
public function index(): Response
{
return $this->render('admin/dashboard/index.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('MyE&P')
->setLocales(['de'])
->setFaviconPath('build/favicon/icon.svg')
;
}
public function configureUserMenu(UserInterface $user): UserMenu
{
return parent::configureUserMenu($user)
->addMenuItems([
MenuItem::linkToRoute('MyE&P', 'fa fa-user', 'app_account'),
]);
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::linkToCrud('Log', 'fa fa-list', LogEntry::class);
yield MenuItem::linkToCrud('Buchungsentwürfe', 'fa fa-pen-to-square', BookingEditDraft::class);
yield MenuItem::linkToCrud('Benutzeraccounts', 'fa fa-users', User::class);
return $this->render('admin/dashboard.html.twig');
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Log;
use App\Entity\LogEntry;
use App\Repository\LogEntryRepository;
use App\Service\XmlDumpReader;
use Knp\Component\Pager\PaginatorInterface;
use League\Flysystem\FilesystemException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
class LogController extends AbstractController
{
public function __construct(
private readonly LogEntryRepository $logEntryRepository,
private readonly PaginatorInterface $paginator,
private readonly XmlDumpReader $xmlDumpReader,
) {
}
#[Route('/admin/log', name: 'app_admin_log')]
public function index(Request $request): Response
{
$qb = $this
->logEntryRepository
->createQueryBuilder('log_entry')
;
$pagination = $this->paginator->paginate(
$qb,
$request->query->getInt('page', 1),
$request->query->getInt('limit', 50),
[
'defaultSortFieldName' => 'log_entry.createdAt',
'defaultSortDirection' => 'DESC',
]
);
return $this->render('admin/log/index.html.twig', [
'pagination' => $pagination,
]);
}
#[Route('/admin/log/{id}/xml-dumps', name: 'app_admin_log_xmldumps')]
public function xmlDumps(LogEntry $logEntry): Response
{
$dumps = [];
try {
$dumps = $this->xmlDumpReader->findDumpsForRequestId($logEntry->getRequestId());
} catch (FilesystemException) {
}
return $this->render('admin/log/xml_dumps.html.twig', [
'logEntry' => $logEntry,
'dumps' => $dumps,
]);
}
#[Route('/admin/log/download/{filename}', name: 'app_admin_log_xmldump_download', requirements: ['filename' => '.+'])]
public function downloadXmlDump(string $filename): Response
{
try {
if (false === $this->xmlDumpReader->fileExists($filename)) {
throw $this->createNotFoundException('Dump file not found. It may have been cleaned up.');
}
$content = $this->xmlDumpReader->getContent($filename);
} catch (FilesystemException $e) {
throw $this->createNotFoundException('Failed to read dump file: '.$e->getMessage());
}
$response = new Response($content);
$response->headers->set('Content-Type', 'application/xml');
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
basename($filename)
);
$response->headers->set('Content-Disposition', $disposition);
return $response;
}
}
@@ -1,98 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Admin\Field\JsonDataField;
use App\Entity\LogEntry;
use App\Repository\LogEntryRepository;
use App\Service\XmlDumpReader;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use League\Flysystem\FilesystemException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/** @extends AbstractCrudController<LogEntry> */
class LogEntryCrudController extends AbstractCrudController
{
public function __construct(
private readonly LogEntryRepository $logEntryRepository,
) {
}
public static function getEntityFqcn(): string
{
return LogEntry::class;
}
public function configureActions(Actions $actions): Actions
{
$viewDumps = Action::new('viewDumps', 'XML Dumps', 'fa fa-file-code')
->linkToCrudAction('xmlDumps')
->displayIf(static fn (LogEntry $entity): bool => 'bpn' === $entity->getChannel());
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $viewDumps)
->add(Crud::PAGE_DETAIL, $viewDumps)
->remove(Crud::PAGE_INDEX, Action::NEW)
->remove(Crud::PAGE_INDEX, Action::EDIT)
->remove(Crud::PAGE_INDEX, Action::DELETE)
->remove(Crud::PAGE_DETAIL, Action::EDIT)
->remove(Crud::PAGE_DETAIL, Action::DELETE)
;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Logeintrag')
->setEntityLabelInPlural('Logeinträge')
->setDefaultSort(['createdAt' => 'DESC'])
->setSearchFields(['errorCode', 'message', 'channel', 'extra']);
}
public function configureFields(string $pageName): iterable
{
yield DateTimeField::new('createdAt', 'Zeitstempel');
yield TextField::new('errorCode', 'Fehlercode')
->formatValue(static fn (?string $value): string => $value ?? '-');
yield TextField::new('username', 'Benutzer');
yield TextField::new('message', 'Aktion');
yield TextField::new('requestId', 'Request ID');
yield TextField::new('uri', 'URI');
yield JsonDataField::new('context', 'Kontext')->onlyOnDetail();
yield JsonDataField::new('extra', 'Extra')->onlyOnDetail();
}
public function xmlDumps(Request $request, XmlDumpReader $xmlDumpService): Response
{
$entityId = $request->query->getInt('entityId');
if ($entityId <= 0) {
throw $this->createNotFoundException('Log entry not found.');
}
$entity = $this->logEntryRepository->find($entityId);
if (!$entity instanceof LogEntry) {
throw $this->createNotFoundException('Log entry not found.');
}
$dumps = [];
try {
$dumps = $xmlDumpService->findDumpsForRequestId($entity->getRequestId());
} catch (FilesystemException) {
}
return $this->render('admin/xml_dump/list.html.twig', [
'logEntry' => $entity,
'requestId' => $entity->getRequestId(),
'dumps' => $dumps,
]);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Repository\UserRepository;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class UserController extends AbstractController
{
public function __construct(
private readonly UserRepository $userRepository,
private readonly PaginatorInterface $paginator,
) {
}
#[Route('/admin/user', name: 'app_admin_user')]
public function index(Request $request): Response
{
$qb = $this
->userRepository
->createQueryBuilder('user')
;
$pagination = $this->paginator->paginate(
$qb,
$request->query->getInt('page', 1),
$request->query->getInt('limit', 50),
[
'defaultSortFieldName' => 'user.lastLoginAt',
'defaultSortDirection' => 'DESC',
]
);
return $this->render('admin/user/index.html.twig', [
'pagination' => $pagination,
]);
}
}
@@ -1,45 +0,0 @@
<?php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
/** @extends AbstractCrudController<User> */
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureActions(Actions $actions): Actions
{
return $actions
->remove(Crud::PAGE_INDEX, Action::NEW)
->remove(Crud::PAGE_INDEX, Action::EDIT)
->remove(Crud::PAGE_INDEX, Action::DELETE)
;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Benutzeraccount')
->setEntityLabelInPlural('Benutzeraccounts')
->setDefaultSort(['lastLoginAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
return [
TextField::new('email', 'E-Mail'),
DateTimeField::new('lastLoginAt', 'letzter Login'),
];
}
}
@@ -1,80 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Repository\LogEntryRepository;
use App\Service\XmlDumpReader;
use League\Flysystem\FilesystemException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/admin/xml-dump')]
#[IsGranted('ROLE_ADMIN')]
class XmlDumpController extends AbstractController
{
public function __construct(
private readonly XmlDumpReader $xmlDumpService,
private readonly LogEntryRepository $logEntryRepository,
) {
}
#[Route('/{id}', name: 'admin_xml_dump_list', methods: ['GET'])]
public function list(int $id): Response
{
$logEntry = $this->logEntryRepository->find($id);
if (null === $logEntry) {
throw new NotFoundHttpException('Log entry not found.');
}
if ('bpn' !== $logEntry->getChannel()) {
throw new NotFoundHttpException('XML dumps are only available for BPN channel entries.');
}
$requestId = $logEntry->getRequestId();
$dumps = [];
try {
$dumps = $this->xmlDumpService->findDumpsForRequestId($requestId);
} catch (FilesystemException) {
// Dumps directory may not exist or be inaccessible
}
return $this->render('admin/xml_dump/list.html.twig', [
'logEntry' => $logEntry,
'requestId' => $requestId,
'dumps' => $dumps,
]);
}
#[Route('/download/{filename}', name: 'admin_xml_dump_download', methods: ['GET'], requirements: ['filename' => '.+'])]
public function download(string $filename): Response
{
try {
if (false === $this->xmlDumpService->fileExists($filename)) {
throw new NotFoundHttpException('Dump file not found. It may have been cleaned up.');
}
$content = $this->xmlDumpService->getContent($filename);
} catch (FilesystemException $e) {
throw new NotFoundHttpException('Failed to read dump file: '.$e->getMessage());
}
$response = new Response($content);
$response->headers->set('Content-Type', 'application/xml');
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
basename($filename)
);
$response->headers->set('Content-Disposition', $disposition);
return $response;
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace App\Controller\Traits;
use Symfony\Component\HttpFoundation\Request;
trait ReturnUrlTrait
{
public function getReturnUrl(Request $request, string $defaultRoute, array $parameters = []): string
{
$defaultUrl = $this->generateUrl($defaultRoute, $parameters);
return rawurldecode($request->query->get('r', $defaultUrl));
}
/**
* Remove specified query parameters from a URL.
*
* @param string $url The URL to process
* @param string[] $parametersKeys Array of query parameter keys to remove
*
* @return string The URL without the specified query parameters
*/
public function removeQueryParameters(string $url, array $parametersKeys): string
{
if ([] === $parametersKeys) {
return $url;
}
$urlParts = parse_url($url);
if (false === isset($urlParts['query'])) {
return $url;
}
parse_str($urlParts['query'], $queryParams);
foreach ($parametersKeys as $key) {
unset($queryParams[$key]);
}
$urlParts['query'] = http_build_query($queryParams);
if ('' === $urlParts['query']) {
unset($urlParts['query']);
}
// Rebuild URL
$result = '';
if (isset($urlParts['scheme'])) {
$result .= $urlParts['scheme'].'://';
}
if (isset($urlParts['host'])) {
$result .= $urlParts['host'];
}
if (isset($urlParts['port'])) {
$result .= ':'.$urlParts['port'];
}
if (isset($urlParts['path'])) {
$result .= $urlParts['path'];
}
if (isset($urlParts['query'])) {
$result .= '?'.$urlParts['query'];
}
if (isset($urlParts['fragment'])) {
$result .= '#'.$urlParts['fragment'];
}
return $result;
}
}
+2 -1
View File
@@ -2,11 +2,12 @@
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity]
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
+133
View File
@@ -0,0 +1,133 @@
<?php
namespace App\Menu;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Uid\Uuid;
use Symfony\Contracts\Translation\TranslatorInterface;
abstract class AbstractMenuBuilder
{
public function __construct(
protected FactoryInterface $factory,
protected Security $security,
protected RequestStack $requestStack,
protected TranslatorInterface $translator,
) {
}
protected function createRootElement(): ItemInterface
{
return $this->factory->createItem('root');
}
protected function getDefaultRouteParameters(string $parameter = 'id', string $default = '0'): array
{
$request = $this->requestStack->getMainRequest();
return [
$parameter => $request->get($parameter, $default),
'r' => $request->get('r'),
];
}
protected function addAdminItem(ItemInterface $menu): void
{
if ($this->security->isGranted('ROLE_ADMIN')) {
$this->addDivider($menu);
$menu->addChild('zum Adminbereich', [
'route' => 'app_admin_index',
'linkAttributes' => [
'title' => 'zum Adminbereich',
],
'extras' => [
'icon' => 'user',
],
]);
}
}
protected function addManagerItem(ItemInterface $menu): void
{
if ($this->security->isGranted('ROLE_MANAGER')) {
$this->addDivider($menu);
$menu->addChild('zum Managementbereich', [
'route' => 'app_manager_index',
'linkAttributes' => [
'title' => 'zum Managementbereich',
],
'extras' => [
'icon' => 'user',
],
]);
}
}
protected function addTeamerItem(ItemInterface $menu): void
{
if ($this->security->isGranted('ROLE_TEAMER')) {
$this->addDivider($menu);
$menu->addChild('zum Teambereich', [
'route' => 'app_teamer_index',
'linkAttributes' => [
'title' => 'zum Teambereich',
],
'extras' => [
'icon' => 'user',
],
]);
}
}
protected function addHouseManagerItem(ItemInterface $menu): void
{
if ($this->security->isGranted('ROLE_HOUSE_MANAGER')) {
$this->addDivider($menu);
$menu->addChild('zum Hausleitungsbereich', [
'route' => 'app_house_manager_index',
'linkAttributes' => [
'title' => 'zum Hausleitungsbereich',
],
'extras' => [
'icon' => 'user',
],
]);
}
}
protected function addLogoutItem(ItemInterface $menu): void
{
$this->addDivider($menu);
$menu->addChild('MyE&P', [
'route' => 'app_account',
'linkAttributes' => [
'title' => 'zu MyE&P',
],
'extras' => [
'icon' => 'logout',
],
]);
$menu->addChild('Logout', [
'route' => 'app_logout',
'linkAttributes' => [
'title' => 'Logout',
],
'extras' => [
'icon' => 'logout',
],
]);
}
protected function addDivider(ItemInterface $menu): void
{
$menu->addChild(Uuid::v4(), [
'extras' => [
'divider' => true,
],
]);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace App\Menu;
use Knp\Menu\ItemInterface;
class AdminMenuBuilder extends AbstractMenuBuilder
{
public function createMainMenu(array $options): ItemInterface
{
$menu = $this->createRootElement();
$menu->addChild('Dashboard', [
'route' => 'app_admin_dashboard',
'linkAttributes' => [
'title' => 'Dashboard',
],
'extras' => [
'icon' => 'chart',
],
]);
$menu->addChild('Buchungsentwürfe', [
'route' => 'app_admin_bookingeditdraft',
'linkAttributes' => [
'title' => 'Buchungsentwürfe',
],
'extras' => [
'icon' => 'edit',
],
]);
$menu->addChild('Benutzer', [
'route' => 'app_admin_user',
'linkAttributes' => [
'title' => 'Benutzer',
],
'extras' => [
'icon' => 'users',
],
]);
$menu->addChild('Logs', [
'route' => 'app_admin_log',
'linkAttributes' => [
'title' => 'Logs',
],
'extras' => [
'icon' => 'list',
],
]);
$this->addLogoutItem($menu);
return $menu;
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<User>
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
}
+2
View File
@@ -33,6 +33,7 @@ class AppExtension extends AbstractExtension
public function getFunctions(): array
{
return [
new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]),
new TwigFunction('is_participant_eligible', [AppRuntime::class, 'isParticipantEligible']),
new TwigFunction('qa_attribute', [AppRuntime::class, 'renderQaAttribute'], ['is_safe' => ['html']]),
new TwigFunction('is_static_text', [AppRuntime::class, 'isStaticText']),
@@ -42,6 +43,7 @@ class AppExtension extends AbstractExtension
new TwigFunction('booking_theme', [AppRuntime::class, 'getBookingTheme']),
new TwigFunction('gtm_id', [AppRuntime::class, 'getGtmId']),
new TwigFunction('cmp_url', [AppRuntime::class, 'getCmpUrl']),
new TwigFunction('return_url', [AppRuntime::class, 'getEncodedReturnUrl']),
];
}
}
+20
View File
@@ -14,6 +14,7 @@ use App\Model\DomainConfig;
use App\Service\ParticipantEligibilityChecker;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\RequestStack;
use Twig\Environment;
use Twig\Extension\RuntimeExtensionInterface;
use Twig\Extra\Intl\IntlExtension;
@@ -148,6 +149,14 @@ class AppRuntime implements RuntimeExtensionInterface
return sprintf(' data-qa-%s="%s"', strtolower($label), $value);
}
public function renderIcon(Environment $environment, string $icon, string $classes = 'w-5 h-5'): string
{
return $environment->render('_partials/_icon.html.twig', [
'icon' => $icon,
'class' => $classes,
]);
}
/**
* Checks if a form field should be rendered as static text.
*
@@ -268,6 +277,17 @@ class AppRuntime implements RuntimeExtensionInterface
return $this->getDomainConfig()->cmpUrl;
}
public function getEncodedReturnUrl(): string
{
$masterRequest = $this->requestStack->getMainRequest();
if (null === $masterRequest) {
return '';
}
return rawurlencode($masterRequest->getRequestUri());
}
private function getDomainConfig(): DomainConfig
{
$request = $this->requestStack->getCurrentRequest();