Initial bundle implementation
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GymnasiumNossenBundle\Contao\Manager;
|
||||
|
||||
use Contao\CalendarBundle\ContaoCalendarBundle;
|
||||
use Contao\CoreBundle\ContaoCoreBundle;
|
||||
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
|
||||
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
|
||||
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
|
||||
use Contao\ManagerPlugin\Routing\RoutingPluginInterface;
|
||||
use GymnasiumNossenBundle\GymnasiumNossenBundle;
|
||||
use Symfony\Component\Config\Loader\LoaderResolverInterface;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
class Plugin implements BundlePluginInterface, RoutingPluginInterface
|
||||
{
|
||||
public function getBundles(ParserInterface $parser): iterable
|
||||
{
|
||||
return [
|
||||
BundleConfig::create(GymnasiumNossenBundle::class)
|
||||
->setLoadAfter([ContaoCoreBundle::class, ContaoCalendarBundle::class]),
|
||||
];
|
||||
}
|
||||
|
||||
public function getRouteCollection(LoaderResolverInterface $resolver, KernelInterface $kernel): RouteCollection|null
|
||||
{
|
||||
return $resolver
|
||||
->resolve(__DIR__.'/../../Resources/config/routes.yaml')
|
||||
->load(__DIR__.'/../../Resources/config/routes.yaml')
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GymnasiumNossenBundle\Controller;
|
||||
|
||||
use Contao\CalendarEventsModel;
|
||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||
use Contao\CoreBundle\Routing\ContentUrlGenerator;
|
||||
use Contao\CoreBundle\Twig\FragmentTemplate;
|
||||
use Contao\CoreBundle\Twig\Interop\ContextFactory;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\ModuleModel;
|
||||
use Contao\StringUtil;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Exception\ExceptionInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Twig\Environment;
|
||||
|
||||
class EventListModuleController extends AbstractFrontendModuleController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Connection $connection,
|
||||
private readonly RequestStack $requestStack,
|
||||
private readonly ContentUrlGenerator $contentUrlGenerator,
|
||||
private readonly ContextFactory $contextFactory,
|
||||
private readonly Security $security,
|
||||
private readonly Environment $twig,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
|
||||
{
|
||||
$activeRequest = $this->requestStack->getMainRequest() ?? $request;
|
||||
|
||||
$configuredArchiveIds = $this->getConfiguredArchiveIds($model);
|
||||
$availableArchives = $this->fetchAllowedArchives($configuredArchiveIds);
|
||||
$allowedArchiveIds = array_map(static fn (array $archive): int => (int) $archive['id'], $availableArchives);
|
||||
$archiveFilterApplied = '1' === (string) $activeRequest->query->get('archive_filter_applied', '0');
|
||||
$selectedArchiveIds = $this->resolveSelectedArchiveIds($activeRequest->query->all('archive_ids'), $allowedArchiveIds, $archiveFilterApplied);
|
||||
|
||||
$fromRaw = $activeRequest->query->get('from');
|
||||
$toRaw = $activeRequest->query->get('to');
|
||||
|
||||
$from = $this->parseDateParameter(is_string($fromRaw) ? $fromRaw : null);
|
||||
$to = $this->parseDateParameter(is_string($toRaw) ? $toRaw : null);
|
||||
|
||||
$isDateRangeFilterActive = null !== $from && null !== $to && $from <= $to;
|
||||
|
||||
$events = $this->fetchEvents($selectedArchiveIds, $from, $to, $isDateRangeFilterActive);
|
||||
|
||||
$template->set('events', $events);
|
||||
$template->set('availableArchives', $availableArchives);
|
||||
$template->set('selectedArchiveIds', $selectedArchiveIds);
|
||||
$template->set('from', is_string($fromRaw) ? $fromRaw : '');
|
||||
$template->set('to', is_string($toRaw) ? $toRaw : '');
|
||||
$template->set('isFiltered', $isDateRangeFilterActive);
|
||||
$template->set('resetUrl', ($activeRequest->getBaseUrl() ?: '').$activeRequest->getPathInfo());
|
||||
$template->set('pdfError', $this->normalizePdfError($activeRequest->query->get('pdf_error')));
|
||||
|
||||
$templatePath = __DIR__.'/../../Resources/views/frontend/module_event_list.html.twig';
|
||||
$templateContent = file_get_contents($templatePath);
|
||||
|
||||
if (false === $templateContent) {
|
||||
throw new \RuntimeException('The module template file could not be read: '.$templatePath);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->twig->createTemplate($templateContent)->render($this->contextFactory->fromData($template->getData())),
|
||||
);
|
||||
}
|
||||
|
||||
private function parseDateParameter(?string $rawValue): ?DateTimeImmutable
|
||||
{
|
||||
if (null === $rawValue || '' === $rawValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $rawValue);
|
||||
$errors = DateTimeImmutable::getLastErrors();
|
||||
|
||||
if (
|
||||
false === $date
|
||||
|| (is_array($errors) && (($errors['warning_count'] ?? 0) > 0 || ($errors['error_count'] ?? 0) > 0))
|
||||
|| $date->format('Y-m-d') !== $rawValue
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{id: int, pid: int, isFeatured: bool, title: string, startDate: string, endDate: string|null, url: string|null}>
|
||||
*/
|
||||
private function fetchEvents(array $archiveIds, ?DateTimeImmutable $from, ?DateTimeImmutable $to, bool $isDateRangeFilterActive): array
|
||||
{
|
||||
if ([] === $archiveIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$effectiveStartExpression = 'COALESCE(NULLIF(e.startTime, 0), NULLIF(e.startDate, 0), 0)';
|
||||
$effectiveEndExpression = 'COALESCE(NULLIF(e.endTime, 0), NULLIF(e.endDate, 0), '.$effectiveStartExpression.')';
|
||||
|
||||
$queryBuilder = $this->connection->createQueryBuilder();
|
||||
|
||||
$queryBuilder
|
||||
->select('e.id', 'e.pid', 'e.featured', 'e.alias', 'e.source', 'e.url', 'e.jumpTo', 'e.articleId', 'e.title', 'e.addTime', 'e.startDate', 'e.endDate', 'e.startTime', 'e.endTime')
|
||||
->from('tl_calendar_events', 'e')
|
||||
->where('e.pid IN (:archiveIds)')
|
||||
->andWhere('e.published = :published')
|
||||
->setParameter('archiveIds', $archiveIds, ArrayParameterType::INTEGER)
|
||||
->setParameter('published', 1)
|
||||
->orderBy($effectiveStartExpression, 'ASC')
|
||||
;
|
||||
|
||||
if ($isDateRangeFilterActive && null !== $from && null !== $to) {
|
||||
$fromTimestamp = $from->setTime(0, 0)->getTimestamp();
|
||||
$toTimestamp = $to->setTime(23, 59, 59)->getTimestamp();
|
||||
|
||||
$queryBuilder
|
||||
->andWhere($effectiveStartExpression.' <= :toTimestamp')
|
||||
->andWhere($effectiveEndExpression.' >= :fromTimestamp')
|
||||
->setParameter('toTimestamp', $toTimestamp)
|
||||
->setParameter('fromTimestamp', $fromTimestamp)
|
||||
;
|
||||
} else {
|
||||
$todayStart = (new DateTimeImmutable('today'))->setTime(0, 0)->getTimestamp();
|
||||
|
||||
$queryBuilder
|
||||
->andWhere($effectiveStartExpression.' >= :todayStart')
|
||||
->setParameter('todayStart', $todayStart)
|
||||
;
|
||||
}
|
||||
|
||||
$rows = $queryBuilder->executeQuery()->fetchAllAssociative();
|
||||
|
||||
$events = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
['startDateText' => $startDateText, 'endDateText' => $endDateText] = $this->formatEventDateTimeTexts($row);
|
||||
|
||||
$eventModel = new CalendarEventsModel($row);
|
||||
|
||||
try {
|
||||
$eventUrl = $this->contentUrlGenerator->generate($eventModel);
|
||||
} catch (ExceptionInterface) {
|
||||
$eventUrl = null;
|
||||
}
|
||||
|
||||
$events[] = [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'pid' => (int) ($row['pid'] ?? 0),
|
||||
'isFeatured' => '1' === (string) ($row['featured'] ?? ''),
|
||||
'title' => $this->normalizeTitle((string) ($row['title'] ?? '')),
|
||||
'startDate' => $startDateText,
|
||||
'endDate' => $endDateText,
|
||||
'url' => $eventUrl,
|
||||
];
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
private function normalizeTitle(string $title): string
|
||||
{
|
||||
$decoded = $title;
|
||||
|
||||
for ($index = 0; $index < 2; ++$index) {
|
||||
$next = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
if ($next === $decoded) {
|
||||
break;
|
||||
}
|
||||
|
||||
$decoded = $next;
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*
|
||||
* @return array{startDateText: string, endDateText: string|null}
|
||||
*/
|
||||
private function formatEventDateTimeTexts(array $row): array
|
||||
{
|
||||
$addTime = (int) ($row['addTime'] ?? 0) === 1;
|
||||
$startDateTimestamp = (int) ($row['startDate'] ?? 0);
|
||||
$endDateTimestamp = (int) ($row['endDate'] ?? 0);
|
||||
$startTimeTimestamp = (int) ($row['startTime'] ?? 0);
|
||||
$endTimeTimestamp = (int) ($row['endTime'] ?? 0);
|
||||
|
||||
if ($addTime) {
|
||||
$startTimestamp = $startTimeTimestamp > 0 ? $startTimeTimestamp : $startDateTimestamp;
|
||||
$start = (new DateTimeImmutable())->setTimestamp($startTimestamp);
|
||||
$startText = $start->format('d.m.Y H:i');
|
||||
|
||||
if ($endDateTimestamp <= 0) {
|
||||
return ['startDateText' => $startText, 'endDateText' => null];
|
||||
}
|
||||
|
||||
$endTimestamp = $endTimeTimestamp > 0 ? $endTimeTimestamp : $endDateTimestamp;
|
||||
$end = (new DateTimeImmutable())->setTimestamp($endTimestamp);
|
||||
$sameDay = $start->format('Y-m-d') === $end->format('Y-m-d');
|
||||
$endText = $sameDay ? $end->format('H:i') : $end->format('d.m.Y H:i');
|
||||
|
||||
return ['startDateText' => $startText, 'endDateText' => $endText];
|
||||
}
|
||||
|
||||
$startTimestamp = $startDateTimestamp > 0 ? $startDateTimestamp : $startTimeTimestamp;
|
||||
$start = (new DateTimeImmutable())->setTimestamp($startTimestamp);
|
||||
$startText = $start->format('d.m.Y');
|
||||
|
||||
if ($endDateTimestamp <= 0) {
|
||||
return ['startDateText' => $startText, 'endDateText' => null];
|
||||
}
|
||||
|
||||
$end = (new DateTimeImmutable())->setTimestamp($endDateTimestamp);
|
||||
$endText = $end->format('d.m.Y');
|
||||
|
||||
return ['startDateText' => $startText, 'endDateText' => $endText !== $startText ? $endText : null];
|
||||
}
|
||||
|
||||
private function normalizePdfError(mixed $rawValue): ?string
|
||||
{
|
||||
if (!is_string($rawValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($rawValue) {
|
||||
'no_selection', 'not_found' => $rawValue,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
private function getConfiguredArchiveIds(ModuleModel $model): array
|
||||
{
|
||||
return array_values(array_unique(array_filter(array_map(
|
||||
static fn (mixed $value): int => (int) $value,
|
||||
StringUtil::deserialize($model->cal_calendar, true),
|
||||
))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $configuredArchiveIds
|
||||
*
|
||||
* @return list<array{id: int, title: string}>
|
||||
*/
|
||||
private function fetchAllowedArchives(array $configuredArchiveIds): array
|
||||
{
|
||||
if ([] === $configuredArchiveIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$user = $this->security->getUser();
|
||||
$memberGroupIds = [];
|
||||
|
||||
if ($user instanceof FrontendUser) {
|
||||
$memberGroupIds = array_values(array_unique(array_filter(array_map(
|
||||
static fn (mixed $value): int => (int) $value,
|
||||
StringUtil::deserialize($user->groups, true),
|
||||
))));
|
||||
}
|
||||
|
||||
$rows = $this->connection->createQueryBuilder()
|
||||
->select('c.id', 'c.title', 'c.protected', 'c.groups')
|
||||
->from('tl_calendar', 'c')
|
||||
->where('c.id IN (:ids)')
|
||||
->setParameter('ids', $configuredArchiveIds, ArrayParameterType::INTEGER)
|
||||
->orderBy('c.title', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative()
|
||||
;
|
||||
|
||||
$archives = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$isProtected = (int) ($row['protected'] ?? 0) === 1;
|
||||
|
||||
if ($isProtected) {
|
||||
if (!$user instanceof FrontendUser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$calendarGroups = array_values(array_unique(array_filter(array_map(
|
||||
static fn (mixed $value): int => (int) $value,
|
||||
StringUtil::deserialize($row['groups'] ?? null, true),
|
||||
))));
|
||||
|
||||
if ([] === $calendarGroups || [] === array_intersect($memberGroupIds, $calendarGroups)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$archives[] = [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'title' => $this->normalizeTitle((string) ($row['title'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
return $archives;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $rawValues
|
||||
* @param list<int> $configuredArchiveIds
|
||||
* @param bool $archiveFilterApplied
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
private function resolveSelectedArchiveIds(mixed $rawValues, array $configuredArchiveIds, bool $archiveFilterApplied): array
|
||||
{
|
||||
if ([] === $configuredArchiveIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!$archiveFilterApplied) {
|
||||
return $configuredArchiveIds;
|
||||
}
|
||||
|
||||
if (!is_array($rawValues) || [] === $rawValues) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$selected = array_values(array_unique(array_filter(array_map(
|
||||
static fn (mixed $value): int => (int) $value,
|
||||
$rawValues,
|
||||
))));
|
||||
|
||||
return array_values(array_intersect($configuredArchiveIds, $selected));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GymnasiumNossenBundle\Controller;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
#[Route('/events/pdf', name: 'gymnasium_event_pdf', methods: ['POST'])]
|
||||
class EventPdfController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Connection $connection,
|
||||
private readonly Environment $twig,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$eventIds = $this->normalizeEventIds($request->request->all('event_ids'));
|
||||
|
||||
if ([] === $eventIds) {
|
||||
return $this->redirectToListWithError($request, 'no_selection');
|
||||
}
|
||||
|
||||
$events = $this->fetchEvents($eventIds);
|
||||
|
||||
if ([] === $events) {
|
||||
return $this->redirectToListWithError($request, 'not_found');
|
||||
}
|
||||
|
||||
$from = $this->parseDateParameter($request->request->get('from'));
|
||||
$to = $this->parseDateParameter($request->request->get('to'));
|
||||
$heading = $this->normalizeOptionalText($request->request->get('pdf_heading')) ?? 'Termine';
|
||||
$introText = $this->normalizeOptionalText($request->request->get('pdf_intro'));
|
||||
|
||||
$html = $this->renderTemplate($events, $from, $to, $heading, $introText);
|
||||
|
||||
$options = new Options();
|
||||
$options->set('defaultFont', 'DejaVu Sans');
|
||||
$options->set('isRemoteEnabled', false);
|
||||
|
||||
$dompdf = new Dompdf($options);
|
||||
$dompdf->loadHtml($html, 'UTF-8');
|
||||
$dompdf->setPaper('A4', 'portrait');
|
||||
$dompdf->render();
|
||||
|
||||
return new Response(
|
||||
$dompdf->output(),
|
||||
Response::HTTP_OK,
|
||||
[
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="termine.pdf"',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $rawIds
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
private function normalizeEventIds(array $rawIds): array
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(array_map(
|
||||
static fn (mixed $value): int => (int) $value,
|
||||
$rawIds,
|
||||
))));
|
||||
|
||||
return array_values(array_filter($ids, static fn (int $id): bool => $id > 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $eventIds
|
||||
*
|
||||
* @return list<array{title: string, startText: string, endText: string|null}>
|
||||
*/
|
||||
private function fetchEvents(array $eventIds): array
|
||||
{
|
||||
$queryBuilder = $this->connection->createQueryBuilder();
|
||||
|
||||
$rows = $queryBuilder
|
||||
->select('e.title', 'e.addTime', 'e.startDate', 'e.endDate', 'e.startTime', 'e.endTime')
|
||||
->from('tl_calendar_events', 'e')
|
||||
->where('e.id IN (:ids)')
|
||||
->andWhere('e.published = :published')
|
||||
->setParameter('ids', $eventIds, ArrayParameterType::INTEGER)
|
||||
->setParameter('published', 1)
|
||||
->orderBy('e.startTime', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative()
|
||||
;
|
||||
|
||||
$events = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
['startText' => $startText, 'endText' => $endText] = $this->formatEventDateTimeTexts($row);
|
||||
|
||||
$events[] = [
|
||||
'title' => $this->normalizeTitle((string) ($row['title'] ?? '')),
|
||||
'startText' => $startText,
|
||||
'endText' => $endText,
|
||||
];
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
private function parseDateParameter(mixed $rawValue): ?DateTimeImmutable
|
||||
{
|
||||
if (!is_string($rawValue) || '' === $rawValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $rawValue);
|
||||
$errors = DateTimeImmutable::getLastErrors();
|
||||
|
||||
if (
|
||||
false === $date
|
||||
|| (is_array($errors) && (($errors['warning_count'] ?? 0) > 0 || ($errors['error_count'] ?? 0) > 0))
|
||||
|| $date->format('Y-m-d') !== $rawValue
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
private function normalizeTitle(string $title): string
|
||||
{
|
||||
$decoded = $title;
|
||||
|
||||
for ($index = 0; $index < 2; ++$index) {
|
||||
$next = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
if ($next === $decoded) {
|
||||
break;
|
||||
}
|
||||
|
||||
$decoded = $next;
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{title: string, startText: string, endText: string|null}> $events
|
||||
*/
|
||||
private function renderTemplate(array $events, ?DateTimeImmutable $from, ?DateTimeImmutable $to, string $heading, ?string $introText): string
|
||||
{
|
||||
$templatePath = __DIR__.'/../../Resources/views/pdf/events.html.twig';
|
||||
$templateContent = file_get_contents($templatePath);
|
||||
|
||||
if (false === $templateContent) {
|
||||
throw new \RuntimeException('The PDF template file could not be read: '.$templatePath);
|
||||
}
|
||||
|
||||
$dateRange = null;
|
||||
|
||||
if (null !== $from && null !== $to && $from <= $to) {
|
||||
$dateRange = sprintf('%s - %s', $this->formatGermanLongDate($from), $this->formatGermanLongDate($to));
|
||||
}
|
||||
|
||||
$logoDataUri = $this->getLogoDataUri();
|
||||
|
||||
return $this->twig->createTemplate($templateContent)->render([
|
||||
'events' => $events,
|
||||
'heading' => $heading,
|
||||
'introText' => $introText,
|
||||
'dateRange' => $dateRange,
|
||||
'logoDataUri' => $logoDataUri,
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeOptionalText(mixed $rawValue): ?string
|
||||
{
|
||||
if (!is_string($rawValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim($rawValue);
|
||||
|
||||
return '' !== $value ? $value : null;
|
||||
}
|
||||
|
||||
private function redirectToListWithError(Request $request, string $error): RedirectResponse
|
||||
{
|
||||
$target = (string) $request->headers->get('referer', '/');
|
||||
|
||||
$parts = parse_url($target);
|
||||
$query = [];
|
||||
|
||||
if (isset($parts['query']) && '' !== $parts['query']) {
|
||||
parse_str($parts['query'], $query);
|
||||
}
|
||||
|
||||
$query['pdf_error'] = $error;
|
||||
|
||||
$path = ($parts['path'] ?? '/').'?'.http_build_query($query);
|
||||
|
||||
return new RedirectResponse($path, Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
|
||||
private function formatGermanLongDate(DateTimeImmutable $date): string
|
||||
{
|
||||
$months = [
|
||||
1 => 'Januar',
|
||||
2 => 'Februar',
|
||||
3 => 'März',
|
||||
4 => 'April',
|
||||
5 => 'Mai',
|
||||
6 => 'Juni',
|
||||
7 => 'Juli',
|
||||
8 => 'August',
|
||||
9 => 'September',
|
||||
10 => 'Oktober',
|
||||
11 => 'November',
|
||||
12 => 'Dezember',
|
||||
];
|
||||
|
||||
$month = $months[(int) $date->format('n')] ?? $date->format('m');
|
||||
|
||||
return sprintf('%d. %s %d', (int) $date->format('j'), $month, (int) $date->format('Y'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*
|
||||
* @return array{startText: string, endText: string|null}
|
||||
*/
|
||||
private function formatEventDateTimeTexts(array $row): array
|
||||
{
|
||||
$addTime = (int) ($row['addTime'] ?? 0) === 1;
|
||||
$startDateTimestamp = (int) ($row['startDate'] ?? 0);
|
||||
$endDateTimestamp = (int) ($row['endDate'] ?? 0);
|
||||
$startTimeTimestamp = (int) ($row['startTime'] ?? 0);
|
||||
$endTimeTimestamp = (int) ($row['endTime'] ?? 0);
|
||||
|
||||
if ($addTime) {
|
||||
$startTimestamp = $startTimeTimestamp > 0 ? $startTimeTimestamp : $startDateTimestamp;
|
||||
$start = (new DateTimeImmutable())->setTimestamp($startTimestamp);
|
||||
$startText = $start->format('d.m.Y H:i').' Uhr';
|
||||
|
||||
if ($endDateTimestamp <= 0) {
|
||||
return ['startText' => $startText, 'endText' => null];
|
||||
}
|
||||
|
||||
$endTimestamp = $endTimeTimestamp > 0 ? $endTimeTimestamp : $endDateTimestamp;
|
||||
$end = (new DateTimeImmutable())->setTimestamp($endTimestamp);
|
||||
|
||||
$sameDay = $start->format('Y-m-d') === $end->format('Y-m-d');
|
||||
$endText = $sameDay ? $end->format('H:i').' Uhr' : $end->format('d.m.Y H:i').' Uhr';
|
||||
|
||||
return ['startText' => $startText, 'endText' => $endText];
|
||||
}
|
||||
|
||||
$startTimestamp = $startDateTimestamp > 0 ? $startDateTimestamp : $startTimeTimestamp;
|
||||
$start = (new DateTimeImmutable())->setTimestamp($startTimestamp);
|
||||
$startText = $start->format('d.m.Y');
|
||||
|
||||
if ($endDateTimestamp <= 0) {
|
||||
return ['startText' => $startText, 'endText' => null];
|
||||
}
|
||||
|
||||
$end = (new DateTimeImmutable())->setTimestamp($endDateTimestamp);
|
||||
$endText = $end->format('d.m.Y');
|
||||
|
||||
return ['startText' => $startText, 'endText' => $endText !== $startText ? $endText : null];
|
||||
}
|
||||
|
||||
private function getLogoDataUri(): ?string
|
||||
{
|
||||
$logoPath = __DIR__.'/../../Resources/img/2022_06_26_Gymnasium_Nossen_Siegel_schwarz.svg';
|
||||
$logoContent = file_get_contents($logoPath);
|
||||
|
||||
if (false === $logoContent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'data:image/svg+xml;base64,'.base64_encode($logoContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GymnasiumNossenBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
class GymnasiumNossenExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/contao/config'));
|
||||
$loader->load('services.yaml');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GymnasiumNossenBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class GymnasiumNossenBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return dirname(__DIR__);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
gymnasium_nossen_bundle_controllers:
|
||||
resource: ../../Controller/
|
||||
type: attribute
|
||||
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
GymnasiumNossenBundle\Controller\:
|
||||
resource: ../../../Controller/
|
||||
|
||||
GymnasiumNossenBundle\Controller\EventListModuleController:
|
||||
tags:
|
||||
- { name: contao.frontend_module, type: gymnasium_eventlist, category: events, template: frontend/module_event_list }
|
||||
Reference in New Issue
Block a user