Initial release
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\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 MummertMedia\EventManagerBundle\MummertMediaEventManagerBundle;
|
||||
|
||||
class Plugin implements BundlePluginInterface
|
||||
{
|
||||
public function getBundles(ParserInterface $parser): iterable
|
||||
{
|
||||
return [
|
||||
BundleConfig::create(MummertMediaEventManagerBundle::class)
|
||||
->setLoadAfter([ContaoCoreBundle::class, ContaoCalendarBundle::class]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
||||
|
||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||
use Contao\CoreBundle\Twig\FragmentTemplate;
|
||||
use Contao\Dbafs;
|
||||
use Contao\FilesModel;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\ModuleModel;
|
||||
use Contao\PageModel;
|
||||
use Contao\StringUtil;
|
||||
use MummertMedia\EventManagerBundle\Form\EventType;
|
||||
use MummertMedia\EventManagerBundle\Service\EventRepository;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[AsFrontendModule(type: 'event_edit', category: 'eventmanager', template: 'frontend/event_edit')]
|
||||
class EventEditController extends AbstractFrontendModuleController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EventRepository $eventRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$backUrl = $this->resolveBackUrl($request, $model);
|
||||
$eventParam = $request->query->get('event');
|
||||
$isCreateMode = '1' === (string) $request->query->get('create', '0') && (null === $eventParam || (int) $eventParam <= 0);
|
||||
|
||||
if (!$user instanceof FrontendUser) {
|
||||
$template->set('error', 'Bitte zuerst als Mitglied einloggen.');
|
||||
$template->set('backUrl', $backUrl);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$eventId = $isCreateMode ? 0 : (int) $eventParam;
|
||||
$event = null;
|
||||
|
||||
if (!$isCreateMode) {
|
||||
if ($eventId <= 0 || !$this->eventRepository->memberHasEvent((int) $user->id, $eventId)) {
|
||||
$template->set('error', 'Keine Berechtigung für diese Veranstaltung oder ungültige ID.');
|
||||
$template->set('backUrl', $backUrl);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$event = $this->eventRepository->findById($eventId);
|
||||
|
||||
if (null === $event) {
|
||||
$template->set('error', 'Veranstaltung nicht gefunden.');
|
||||
$template->set('backUrl', $backUrl);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
}
|
||||
|
||||
if ($isCreateMode) {
|
||||
$event = [
|
||||
'title' => '',
|
||||
'startDate' => null,
|
||||
'endDate' => null,
|
||||
'addTime' => '',
|
||||
'startTime' => null,
|
||||
'endTime' => null,
|
||||
'location_id' => 0,
|
||||
'type' => null,
|
||||
'teaser' => '',
|
||||
'description' => '',
|
||||
'url' => '',
|
||||
'photographer' => '',
|
||||
'addImage' => '',
|
||||
'termsAccepted' => '',
|
||||
'isSoldOut' => '',
|
||||
'isCanceled' => '',
|
||||
'published' => '',
|
||||
'singleSRC' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$memberOrganizationIds = $this->eventRepository->getOrganizationIdsForMember((int) $user->id);
|
||||
$organizationChoices = $this->eventRepository->getOrganizationChoicesForMember((int) $user->id);
|
||||
$showOrganization = count($memberOrganizationIds) > 1;
|
||||
$currentOrganizationIds = $this->eventRepository->getOrganizationIdsForEvent($eventId);
|
||||
|
||||
$formData = [
|
||||
'title' => (string) ($event['title'] ?? ''),
|
||||
'startDate' => !empty($event['startDate']) ? date('Y-m-d', (int) $event['startDate']) : '',
|
||||
'endDate' => !empty($event['endDate']) ? date('Y-m-d', (int) $event['endDate']) : '',
|
||||
'addTime' => '1' === (string) ($event['addTime'] ?? ''),
|
||||
'startTime' => ('1' === (string) ($event['addTime'] ?? '') && !empty($event['startTime'])) ? date('H:i', (int) $event['startTime']) : '',
|
||||
'endTime' => $this->resolveFormEndTime($event),
|
||||
'location_id' => (int) ($event['location_id'] ?? 0),
|
||||
'type' => StringUtil::deserialize($event['type'] ?? null, true),
|
||||
'teaser' => (string) ($event['teaser'] ?? ''),
|
||||
'description' => (string) ($event['description'] ?? ''),
|
||||
'url' => (string) ($event['url'] ?? ''),
|
||||
'photographer' => (string) ($event['photographer'] ?? ''),
|
||||
'addImage' => '1' === (string) ($event['addImage'] ?? ''),
|
||||
'termsAccepted' => '1' === (string) ($event['termsAccepted'] ?? ''),
|
||||
'isSoldOut' => '1' === (string) ($event['isSoldOut'] ?? ''),
|
||||
'isCanceled' => '1' === (string) ($event['isCanceled'] ?? ''),
|
||||
'published' => '1' === (string) ($event['published'] ?? ''),
|
||||
];
|
||||
|
||||
if ($showOrganization) {
|
||||
$allowedOrganizationIds = array_values(array_unique(array_map('intval', $memberOrganizationIds)));
|
||||
$selectedOrganizationIds = array_values(array_intersect($currentOrganizationIds, $allowedOrganizationIds));
|
||||
|
||||
if ([] === $selectedOrganizationIds && [] !== $allowedOrganizationIds) {
|
||||
$selectedOrganizationIds = [(int) $allowedOrganizationIds[0]];
|
||||
}
|
||||
|
||||
$formData['organization_ids'] = $selectedOrganizationIds;
|
||||
}
|
||||
|
||||
$currentImagePath = null;
|
||||
|
||||
if (!empty($event['singleSRC'])) {
|
||||
$imageModel = FilesModel::findByUuid((string) $event['singleSRC']);
|
||||
|
||||
if (null !== $imageModel) {
|
||||
$currentImagePath = $imageModel->path;
|
||||
}
|
||||
}
|
||||
|
||||
$form = $this->createForm(EventType::class, $formData, [
|
||||
'location_choices' => $this->eventRepository->getLocationChoices(),
|
||||
'organization_choices' => $organizationChoices,
|
||||
'selected_organization_ids' => $showOrganization ? ($formData['organization_ids'] ?? []) : [],
|
||||
'show_organization' => $showOrganization,
|
||||
'terms_page_url' => $this->resolveTermsPageUrl($model),
|
||||
]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted()) {
|
||||
$submittedData = (array) $form->getData();
|
||||
|
||||
if (!empty($submittedData['addImage']) && '' === trim((string) ($submittedData['photographer'] ?? ''))) {
|
||||
$form->get('photographer')->addError(new FormError('Die Angabe des Urhebers ist notwendig. Ihnen muss eine Genehmigung zur Verwendung des Bildes vorliegen.'));
|
||||
}
|
||||
|
||||
if (!empty($submittedData['addTime']) && '' === trim((string) ($submittedData['startTime'] ?? ''))) {
|
||||
$form->get('startTime')->addError(new FormError('Bitte geben Sie eine Startzeit an.'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$submittedData = (array) $form->getData();
|
||||
|
||||
$selectedOrganizationIds = [];
|
||||
|
||||
if ($showOrganization) {
|
||||
$selectedOrganizationIds = array_map('intval', (array) $form->get('organization_ids')->getData());
|
||||
} elseif ([] !== $memberOrganizationIds) {
|
||||
$selectedOrganizationIds = [(int) $memberOrganizationIds[0]];
|
||||
}
|
||||
|
||||
$allowedOrganizationIds = array_values(array_unique(array_map('intval', $memberOrganizationIds)));
|
||||
$selectedOrganizationIds = array_values(array_intersect($selectedOrganizationIds, $allowedOrganizationIds));
|
||||
|
||||
if ([] === $selectedOrganizationIds) {
|
||||
if ($showOrganization) {
|
||||
$form->get('organization_ids')->addError(new FormError('Bitte mindestens eine Organisation auswählen.'));
|
||||
} else {
|
||||
$form->addError(new FormError('Keine gültige Veranstalter-Zuordnung vorhanden.'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && !$form->isValid()) {
|
||||
$template->set('form', $form->createView());
|
||||
$template->set('backUrl', $backUrl);
|
||||
$template->set('requestToken', $this->container->get('contao.csrf.token_manager')->getDefaultTokenValue());
|
||||
$template->set('currentImagePath', $currentImagePath);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
if ($isCreateMode) {
|
||||
$createdEventId = $this->eventRepository->createForMember(
|
||||
(int) $user->id,
|
||||
(int) ($model->frontendAuthorId ?? 0),
|
||||
(int) ($model->frontendArchiveId ?? 0),
|
||||
$submittedData,
|
||||
$selectedOrganizationIds,
|
||||
);
|
||||
|
||||
if (null === $createdEventId) {
|
||||
$form->addError(new FormError('Die Veranstaltung konnte nicht erstellt werden.'));
|
||||
$template->set('form', $form->createView());
|
||||
$template->set('backUrl', $backUrl);
|
||||
$template->set('requestToken', $this->container->get('contao.csrf.token_manager')->getDefaultTokenValue());
|
||||
$template->set('currentImagePath', $currentImagePath);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$eventId = $createdEventId;
|
||||
} else {
|
||||
if ($showOrganization) {
|
||||
$this->eventRepository->assignEventToOrganizations($eventId, $selectedOrganizationIds);
|
||||
}
|
||||
|
||||
$this->eventRepository->update($eventId, $submittedData);
|
||||
}
|
||||
|
||||
$useImage = !empty($submittedData['addImage']);
|
||||
$removeImage = '1' === (string) $request->request->get('remove_image', '0');
|
||||
$uploadedImage = $form->get('eventUpload')->getData();
|
||||
|
||||
if (!$useImage) {
|
||||
$this->eventRepository->updateImageFields($eventId, false, null);
|
||||
} elseif ($uploadedImage instanceof UploadedFile) {
|
||||
$newImageUuid = $this->storeEventImage($uploadedImage, $model, $eventId);
|
||||
|
||||
if (null !== $newImageUuid) {
|
||||
$this->eventRepository->updateImageFields($eventId, true, $newImageUuid);
|
||||
}
|
||||
} elseif ($removeImage) {
|
||||
$this->eventRepository->updateImageFields($eventId, true, null);
|
||||
}
|
||||
|
||||
if ($request->request->has('save_back')) {
|
||||
return new RedirectResponse($backUrl);
|
||||
}
|
||||
|
||||
if ($isCreateMode) {
|
||||
return new RedirectResponse($this->buildEventEditUrl($eventId, $backUrl));
|
||||
}
|
||||
|
||||
return new RedirectResponse($request->getUri());
|
||||
}
|
||||
|
||||
$template->set('form', $form->createView());
|
||||
$template->set('backUrl', $backUrl);
|
||||
$template->set('requestToken', $this->container->get('contao.csrf.token_manager')->getDefaultTokenValue());
|
||||
$template->set('currentImagePath', $currentImagePath);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
private function storeEventImage(UploadedFile $uploadedFile, ModuleModel $model, int $eventId): ?string
|
||||
{
|
||||
if (empty($model->eventFolder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$folderModel = FilesModel::findByUuid((string) $model->eventFolder);
|
||||
|
||||
if (null === $folderModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$organization = $this->eventRepository->findPrimaryOrganizationForEvent($eventId);
|
||||
|
||||
if (null === $organization) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$titleSlug = strtolower(StringUtil::generateAlias((string) $organization['title']));
|
||||
$titleSlug = preg_replace('/[^a-z0-9-]+/', '-', $titleSlug ?? '') ?? '';
|
||||
$titleSlug = trim($titleSlug, '-');
|
||||
$titleSlug = substr($titleSlug, 0, 12);
|
||||
|
||||
if ('' === $titleSlug) {
|
||||
$titleSlug = 'organization';
|
||||
}
|
||||
|
||||
$uploadBasePath = trim((string) $folderModel->path, '/');
|
||||
|
||||
if ('' === $uploadBasePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$targetRelativeDir = sprintf('%s/org-%d-%s', $uploadBasePath, (int) $organization['id'], $titleSlug);
|
||||
$projectDir = (string) $this->getParameter('kernel.project_dir');
|
||||
$targetAbsoluteDir = rtrim($projectDir, '/').'/'.$targetRelativeDir;
|
||||
|
||||
if (!is_dir($targetAbsoluteDir) && !mkdir($targetAbsoluteDir, 0775, true) && !is_dir($targetAbsoluteDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$originalName = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
|
||||
$safeName = preg_replace('/[^a-zA-Z0-9_-]+/', '-', (string) $originalName);
|
||||
$safeName = trim((string) $safeName, '-');
|
||||
|
||||
if ('' === $safeName) {
|
||||
$safeName = 'event-image';
|
||||
}
|
||||
|
||||
$extension = $uploadedFile->guessExtension() ?: $uploadedFile->getClientOriginalExtension() ?: 'bin';
|
||||
$filename = sprintf('%s-%s.%s', $safeName, uniqid('', true), strtolower((string) $extension));
|
||||
|
||||
$uploadedFile->move($targetAbsoluteDir, $filename);
|
||||
|
||||
$relativePath = $targetRelativeDir.'/'.$filename;
|
||||
$fileModel = Dbafs::addResource($relativePath);
|
||||
|
||||
return null !== $fileModel ? (string) $fileModel->uuid : null;
|
||||
}
|
||||
|
||||
private function resolveBackUrl(Request $request, ModuleModel $model): string
|
||||
{
|
||||
if ((int) ($model->listPage ?? 0) > 0) {
|
||||
$listPage = $this->getContaoAdapter(PageModel::class)->findById((int) $model->listPage);
|
||||
|
||||
if ($listPage instanceof PageModel) {
|
||||
return $this->generateContentUrl($listPage);
|
||||
}
|
||||
}
|
||||
|
||||
$ref = (string) $request->query->get('ref', '');
|
||||
|
||||
if ('' !== $ref) {
|
||||
$decoded = base64_decode($ref, true);
|
||||
|
||||
if (false !== $decoded && str_starts_with($decoded, '/')) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$page = $this->getPageModel();
|
||||
|
||||
return $page instanceof PageModel ? $this->generateContentUrl($page) : '/';
|
||||
}
|
||||
|
||||
private function resolveTermsPageUrl(ModuleModel $model): ?string
|
||||
{
|
||||
if ((int) ($model->termsPage ?? 0) <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$termsPage = $this->getContaoAdapter(PageModel::class)->findById((int) $model->termsPage);
|
||||
|
||||
if (!$termsPage instanceof PageModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->generateContentUrl($termsPage);
|
||||
}
|
||||
|
||||
private function buildEventEditUrl(int $eventId, string $backUrl): string
|
||||
{
|
||||
$page = $this->getPageModel();
|
||||
|
||||
if (!$page instanceof PageModel) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
return $this->generateContentUrl($page, [
|
||||
'event' => (string) $eventId,
|
||||
'ref' => base64_encode($backUrl),
|
||||
]);
|
||||
}
|
||||
|
||||
private function resolveFormEndTime(array $event): string
|
||||
{
|
||||
if ('1' !== (string) ($event['addTime'] ?? '')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$startTime = (int) ($event['startTime'] ?? 0);
|
||||
$endTime = (int) ($event['endTime'] ?? 0);
|
||||
|
||||
if ($endTime <= 0 || $startTime <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($endTime === $startTime || $endTime - $startTime === 86399) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return date('H:i', $endTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
||||
|
||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||
use Contao\CoreBundle\Twig\FragmentTemplate;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\ModuleModel;
|
||||
use Contao\PageModel;
|
||||
use Contao\StringUtil;
|
||||
use MummertMedia\EventManagerBundle\Service\EventRepository;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[AsFrontendModule(type: 'member_events', category: 'eventmanager', template: 'frontend/member_events')]
|
||||
class MemberEventsController extends AbstractFrontendModuleController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EventRepository $eventRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user instanceof FrontendUser) {
|
||||
$template->set('upcomingEvents', []);
|
||||
$template->set('pastEvents', []);
|
||||
$template->set('isEditor', false);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$isEditor = $this->isEditor($user);
|
||||
$editPage = null;
|
||||
|
||||
if ((int) ($model->editPage ?? 0) > 0) {
|
||||
$editPage = $this->getContaoAdapter(PageModel::class)->findById((int) $model->editPage);
|
||||
}
|
||||
|
||||
$currentPage = $this->getPageModel();
|
||||
$backUrl = $currentPage instanceof PageModel ? $this->generateContentUrl($currentPage) : '/';
|
||||
|
||||
if ($isEditor && 'POST' === $request->getMethod() && $request->request->has('action')) {
|
||||
$action = (string) $request->request->get('action', '');
|
||||
$eventId = (int) $request->request->get('event_id', 0);
|
||||
|
||||
if (!in_array($action, ['create', 'toggle_published', 'duplicate', 'delete'], true)) {
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
if ('create' === $action && $editPage instanceof PageModel) {
|
||||
return new RedirectResponse($this->generateContentUrl($editPage, [
|
||||
'create' => '1',
|
||||
'ref' => base64_encode($backUrl),
|
||||
]));
|
||||
}
|
||||
|
||||
if ($eventId > 0 && $this->eventRepository->memberHasEvent((int) $user->id, $eventId)) {
|
||||
if ('toggle_published' === $action) {
|
||||
$this->eventRepository->togglePublished($eventId);
|
||||
} elseif ('duplicate' === $action) {
|
||||
$this->eventRepository->duplicate($eventId);
|
||||
} elseif ('delete' === $action) {
|
||||
$this->eventRepository->delete($eventId);
|
||||
}
|
||||
}
|
||||
|
||||
return new RedirectResponse($request->getUri());
|
||||
}
|
||||
|
||||
$events = $this->eventRepository->findByMemberId((int) $user->id);
|
||||
|
||||
$upcomingItems = [];
|
||||
$pastItems = [];
|
||||
$today = strtotime('today');
|
||||
|
||||
foreach ($events as $event) {
|
||||
$editUrl = null;
|
||||
|
||||
if ($isEditor && $editPage instanceof PageModel) {
|
||||
$editUrl = $this->generateContentUrl($editPage, [
|
||||
'event' => (string) $event['id'],
|
||||
'ref' => base64_encode($backUrl),
|
||||
]);
|
||||
}
|
||||
|
||||
$item = [
|
||||
'id' => (int) $event['id'],
|
||||
'title' => (string) ($event['title'] ?? ''),
|
||||
'startDate' => (int) ($event['startDate'] ?? 0),
|
||||
'published' => '1' === (string) ($event['published'] ?? ''),
|
||||
'editUrl' => $editUrl,
|
||||
];
|
||||
|
||||
if ((int) $item['startDate'] >= $today) {
|
||||
$upcomingItems[] = $item;
|
||||
} else {
|
||||
$pastItems[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
usort(
|
||||
$upcomingItems,
|
||||
static fn (array $a, array $b): int => ((int) $b['startDate']) <=> ((int) $a['startDate']),
|
||||
);
|
||||
|
||||
usort(
|
||||
$pastItems,
|
||||
static fn (array $a, array $b): int => ((int) $b['startDate']) <=> ((int) $a['startDate']),
|
||||
);
|
||||
|
||||
$template->set('upcomingEvents', $upcomingItems);
|
||||
$template->set('pastEvents', $pastItems);
|
||||
$template->set('isEditor', $isEditor);
|
||||
$template->set('canCreateEvent', $isEditor && $editPage instanceof PageModel);
|
||||
$template->set('requestToken', $this->container->get('contao.csrf.token_manager')->getDefaultTokenValue());
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
private function isEditor(FrontendUser $user): bool
|
||||
{
|
||||
$groups = is_array($user->groups) ? $user->groups : StringUtil::deserialize($user->groups, true);
|
||||
|
||||
return in_array(1, array_map('intval', $groups), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
||||
|
||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||
use Contao\CoreBundle\Twig\FragmentTemplate;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\ModuleModel;
|
||||
use Contao\PageModel;
|
||||
use Contao\StringUtil;
|
||||
use MummertMedia\EventManagerBundle\Service\OrganizationRepository;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[AsFrontendModule(type: 'member_organizations', category: 'eventmanager', template: 'frontend/member_organizations')]
|
||||
class MemberOrganizationsController extends AbstractFrontendModuleController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly OrganizationRepository $organizationRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user instanceof FrontendUser) {
|
||||
$template->set('organizations', []);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$organizations = $this->organizationRepository->findByMemberId((int) $user->id);
|
||||
$isEditor = $this->isEditor($user);
|
||||
$editPage = null;
|
||||
|
||||
if ((int) ($model->editPage ?? 0) > 0) {
|
||||
$editPage = $this->getContaoAdapter(PageModel::class)->findById((int) $model->editPage);
|
||||
}
|
||||
|
||||
$currentPage = $this->getPageModel();
|
||||
$backUrl = $currentPage instanceof PageModel ? $this->generateContentUrl($currentPage) : '/';
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($organizations as $organization) {
|
||||
$editUrl = null;
|
||||
|
||||
if ($isEditor && $editPage instanceof PageModel) {
|
||||
$editUrl = $this->generateContentUrl($editPage, [
|
||||
'organization' => (string) $organization['id'],
|
||||
'ref' => base64_encode($backUrl),
|
||||
]);
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'id' => (int) $organization['id'],
|
||||
'title' => (string) ($organization['title'] ?? ''),
|
||||
'editUrl' => $editUrl,
|
||||
];
|
||||
}
|
||||
|
||||
$template->set('organizations', $items);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
private function isEditor(FrontendUser $user): bool
|
||||
{
|
||||
$groups = is_array($user->groups) ? $user->groups : StringUtil::deserialize($user->groups, true);
|
||||
|
||||
return in_array(1, array_map('intval', $groups), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
||||
|
||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||
use Contao\CoreBundle\Twig\FragmentTemplate;
|
||||
use Contao\Dbafs;
|
||||
use Contao\FilesModel;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\ModuleModel;
|
||||
use Contao\PageModel;
|
||||
use Contao\StringUtil;
|
||||
use MummertMedia\EventManagerBundle\Form\OrganizationType;
|
||||
use MummertMedia\EventManagerBundle\Service\OrganizationRepository;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[AsFrontendModule(type: 'organization_edit', category: 'eventmanager', template: 'frontend/organization_edit')]
|
||||
class OrganizationEditController extends AbstractFrontendModuleController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly OrganizationRepository $organizationRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$backUrl = $this->resolveBackUrl($request, $model);
|
||||
$organizationParam = $request->query->get('organization');
|
||||
|
||||
if (null === $organizationParam) {
|
||||
$template->set('error', 'Ungültiger Aufruf: Parameter "organization" fehlt.');
|
||||
$template->set('backUrl', $backUrl);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
if (!$user instanceof FrontendUser) {
|
||||
$template->set('error', 'Bitte zuerst als Mitglied einloggen.');
|
||||
$template->set('backUrl', $backUrl);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$organizationId = (int) $organizationParam;
|
||||
|
||||
if ($organizationId <= 0 || !$this->organizationRepository->memberHasOrganization((int) $user->id, $organizationId)) {
|
||||
$template->set('error', 'Keine Berechtigung für diese Organisation oder ungültige ID.');
|
||||
$template->set('backUrl', $backUrl);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$organization = $this->organizationRepository->findById($organizationId);
|
||||
|
||||
if (null === $organization) {
|
||||
$template->set('error', 'Organisation nicht gefunden.');
|
||||
$template->set('backUrl', $backUrl);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$formData = [
|
||||
'title' => (string) ($organization['title'] ?? ''),
|
||||
'street' => (string) ($organization['street'] ?? ''),
|
||||
'postal' => (string) ($organization['postal'] ?? ''),
|
||||
'city' => (string) ($organization['city'] ?? ''),
|
||||
'state' => (string) ($organization['state'] ?? ''),
|
||||
'country' => (string) ($organization['country'] ?? ''),
|
||||
'phone' => (string) ($organization['phone'] ?? ''),
|
||||
'email' => (string) ($organization['email'] ?? ''),
|
||||
'website' => (string) ($organization['website'] ?? ''),
|
||||
'description' => (string) ($organization['description'] ?? ''),
|
||||
'type' => StringUtil::deserialize($organization['type'] ?? null, true),
|
||||
];
|
||||
|
||||
$currentLogoPath = null;
|
||||
|
||||
if (!empty($organization['logo'])) {
|
||||
$logoModel = FilesModel::findByUuid((string) $organization['logo']);
|
||||
|
||||
if (null !== $logoModel) {
|
||||
$currentLogoPath = $logoModel->path;
|
||||
}
|
||||
}
|
||||
|
||||
$form = $this->createForm(OrganizationType::class, $formData);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$submittedData = (array) $form->getData();
|
||||
|
||||
$this->organizationRepository->update($organizationId, $submittedData);
|
||||
|
||||
$deleteLogo = '1' === (string) $request->request->get('remove_logo', '0');
|
||||
$uploadedLogo = $form->get('logoUpload')->getData();
|
||||
|
||||
if ($uploadedLogo instanceof UploadedFile) {
|
||||
$newLogoUuid = $this->storeOrganizationLogo(
|
||||
$uploadedLogo,
|
||||
$model,
|
||||
$organizationId,
|
||||
(string) ($submittedData['title'] ?? ''),
|
||||
);
|
||||
|
||||
if (null !== $newLogoUuid) {
|
||||
$this->organizationRepository->updateLogo($organizationId, $newLogoUuid);
|
||||
}
|
||||
} elseif ($deleteLogo) {
|
||||
$this->organizationRepository->updateLogo($organizationId, null);
|
||||
}
|
||||
|
||||
if ($request->request->has('save_back')) {
|
||||
return new RedirectResponse($backUrl);
|
||||
}
|
||||
|
||||
return new RedirectResponse($request->getUri());
|
||||
}
|
||||
|
||||
$template->set('form', $form->createView());
|
||||
$template->set('backUrl', $backUrl);
|
||||
$template->set('requestToken', $this->container->get('contao.csrf.token_manager')->getDefaultTokenValue());
|
||||
$template->set('currentLogoPath', $currentLogoPath);
|
||||
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
private function storeOrganizationLogo(UploadedFile $uploadedFile, ModuleModel $model, int $organizationId, string $organizationTitle): ?string
|
||||
{
|
||||
if (empty($model->logoFolder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$folderModel = FilesModel::findByUuid((string) $model->logoFolder);
|
||||
|
||||
if (null === $folderModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$uploadBasePath = trim((string) $folderModel->path, '/');
|
||||
|
||||
if ('' === $uploadBasePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$titleSlug = strtolower(StringUtil::generateAlias($organizationTitle));
|
||||
$titleSlug = preg_replace('/[^a-z0-9-]+/', '-', $titleSlug ?? '') ?? '';
|
||||
$titleSlug = trim($titleSlug, '-');
|
||||
$titleSlug = substr($titleSlug, 0, 12);
|
||||
|
||||
if ('' === $titleSlug) {
|
||||
$titleSlug = 'organization';
|
||||
}
|
||||
|
||||
$targetRelativeDir = sprintf('%s/org-%d-%s', $uploadBasePath, $organizationId, $titleSlug);
|
||||
$projectDir = (string) $this->getParameter('kernel.project_dir');
|
||||
$targetAbsoluteDir = rtrim($projectDir, '/').'/'.$targetRelativeDir;
|
||||
|
||||
if (!is_dir($targetAbsoluteDir) && !mkdir($targetAbsoluteDir, 0775, true) && !is_dir($targetAbsoluteDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$originalName = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
|
||||
$safeName = preg_replace('/[^a-zA-Z0-9_-]+/', '-', (string) $originalName);
|
||||
$safeName = trim((string) $safeName, '-');
|
||||
|
||||
if ('' === $safeName) {
|
||||
$safeName = 'logo';
|
||||
}
|
||||
|
||||
$extension = $uploadedFile->guessExtension() ?: $uploadedFile->getClientOriginalExtension() ?: 'bin';
|
||||
$filename = sprintf('%s-%s.%s', $safeName, uniqid('', true), strtolower((string) $extension));
|
||||
|
||||
$uploadedFile->move($targetAbsoluteDir, $filename);
|
||||
|
||||
$relativePath = $targetRelativeDir.'/'.$filename;
|
||||
$fileModel = Dbafs::addResource($relativePath);
|
||||
|
||||
return null !== $fileModel ? (string) $fileModel->uuid : null;
|
||||
}
|
||||
|
||||
private function resolveBackUrl(Request $request, ModuleModel $model): string
|
||||
{
|
||||
if ((int) ($model->listPage ?? 0) > 0) {
|
||||
$listPage = $this->getContaoAdapter(PageModel::class)->findById((int) $model->listPage);
|
||||
|
||||
if ($listPage instanceof PageModel) {
|
||||
return $this->generateContentUrl($listPage);
|
||||
}
|
||||
}
|
||||
|
||||
$ref = (string) $request->query->get('ref', '');
|
||||
|
||||
if ('' !== $ref) {
|
||||
$decoded = base64_decode($ref, true);
|
||||
|
||||
if (false !== $decoded && str_starts_with($decoded, '/')) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$page = $this->getPageModel();
|
||||
|
||||
return $page instanceof PageModel ? $this->generateContentUrl($page) : '/';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
class MummertMediaEventManagerExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
|
||||
$loader->load('services.yaml');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\EventListener\DataContainer;
|
||||
|
||||
use Contao\DataContainer;
|
||||
|
||||
class SetDateAddedCallback
|
||||
{
|
||||
public function onBeforeSubmit(array $values, DataContainer $dc): array
|
||||
{
|
||||
if (null !== $dc->activeRecord && (int) ($dc->activeRecord->dateAdded ?? 0) > 0) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
if (!isset($values['dateAdded']) || 0 === (int) $values['dateAdded']) {
|
||||
$values['dateAdded'] = time();
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TimeType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Url;
|
||||
|
||||
class EventType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$termsLabel = 'Ich stimme den Nutzungsbedingungen zu.';
|
||||
|
||||
if (null !== $options['terms_page_url']) {
|
||||
$termsLabel = sprintf(
|
||||
'Ich stimme den <a href="%s" target="_blank" rel="noopener noreferrer">Nutzungsbedingungen</a> zu.',
|
||||
htmlspecialchars($options['terms_page_url'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
|
||||
);
|
||||
}
|
||||
|
||||
$showOrganizationSelect = true === $options['show_organization'];
|
||||
|
||||
$builder
|
||||
->add('title', TextType::class, [
|
||||
'label' => 'Titel',
|
||||
'required' => true,
|
||||
])
|
||||
->add('startDate', DateType::class, [
|
||||
'label' => 'Startdatum',
|
||||
'required' => false,
|
||||
'widget' => 'single_text',
|
||||
'input' => 'string',
|
||||
'html5' => false,
|
||||
'format' => 'yyyy-MM-dd',
|
||||
'attr' => [
|
||||
'class' => 'js-flatpickr-date',
|
||||
],
|
||||
])
|
||||
->add('endDate', DateType::class, [
|
||||
'label' => 'Enddatum',
|
||||
'required' => false,
|
||||
'widget' => 'single_text',
|
||||
'input' => 'string',
|
||||
'html5' => false,
|
||||
'format' => 'yyyy-MM-dd',
|
||||
'help' => 'Lassen Sie das Feld leer, um ein eintägiges Event zu erstellen.',
|
||||
'attr' => [
|
||||
'class' => 'js-flatpickr-date',
|
||||
],
|
||||
])
|
||||
->add('addTime', CheckboxType::class, [
|
||||
'label' => 'Dem Event eine Start- und Endzeit hinzufügen.',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'js-add-time-toggle',
|
||||
],
|
||||
])
|
||||
->add('startTime', TimeType::class, [
|
||||
'label' => 'Startzeit',
|
||||
'required' => false,
|
||||
'input' => 'string',
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'input_format' => 'H:i',
|
||||
'attr' => [
|
||||
'class' => 'js-flatpickr-time',
|
||||
],
|
||||
])
|
||||
->add('endTime', TimeType::class, [
|
||||
'label' => 'Endzeit',
|
||||
'required' => false,
|
||||
'input' => 'string',
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'input_format' => 'H:i',
|
||||
'help' => 'Lassen Sie das Feld leer, um ein Event mit offenem Ende zu erstellen.',
|
||||
'attr' => [
|
||||
'class' => 'js-flatpickr-time',
|
||||
],
|
||||
]);
|
||||
|
||||
if ($showOrganizationSelect) {
|
||||
$builder->add('organization_ids', ChoiceType::class, [
|
||||
'label' => 'Veranstalter',
|
||||
'choices' => $options['organization_choices'],
|
||||
'required' => true,
|
||||
'mapped' => false,
|
||||
'multiple' => true,
|
||||
'data' => $options['selected_organization_ids'],
|
||||
'attr' => [
|
||||
'class' => 'js-organization-choice',
|
||||
'data-placeholder' => 'Veranstalter suchen …',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('location_id', ChoiceType::class, [
|
||||
'label' => 'Veranstaltungsort',
|
||||
'choices' => $options['location_choices'],
|
||||
'required' => false,
|
||||
'choice_value' => static fn ($value) => null !== $value ? (string) $value : '',
|
||||
'placeholder' => 'Bitte auswählen',
|
||||
'attr' => [
|
||||
'class' => 'js-location-choice',
|
||||
'data-placeholder' => 'Veranstaltungsort suchen …',
|
||||
],
|
||||
])
|
||||
->add('type', ChoiceType::class, [
|
||||
'label' => 'Typ',
|
||||
'choices' => [
|
||||
'Unterkunft' => 'accommodation',
|
||||
'Einkaufen' => 'shopping',
|
||||
'Kultur' => 'culture',
|
||||
],
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'js-event-type-choice',
|
||||
'data-placeholder' => 'Typen suchen …',
|
||||
],
|
||||
])
|
||||
->add('teaser', TextareaType::class, ['label' => 'Teaser', 'required' => false])
|
||||
->add('description', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
|
||||
->add('url', UrlType::class, [
|
||||
'label' => 'Link (extern)',
|
||||
'required' => false,
|
||||
'default_protocol' => 'https',
|
||||
'invalid_message' => 'Bitte eine gültige URL mit http:// oder https:// eingeben (z. B. https://example.org).',
|
||||
'constraints' => [
|
||||
new Url([
|
||||
'protocols' => ['http', 'https'],
|
||||
'message' => 'Bitte eine gültige URL mit http:// oder https:// eingeben (z. B. https://example.org).',
|
||||
]),
|
||||
],
|
||||
])
|
||||
->add('addImage', CheckboxType::class, [
|
||||
'label' => 'Dem Event ein Bild hinzufügen',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'js-add-image-toggle',
|
||||
],
|
||||
])
|
||||
->add('eventUpload', FileType::class, [
|
||||
'label' => 'Bild hochladen',
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
'attr' => [
|
||||
'class' => 'js-event-image-upload',
|
||||
'accept' => 'image/*',
|
||||
],
|
||||
])
|
||||
->add('photographer', TextType::class, [
|
||||
'label' => 'Urheber/Fotograf',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'js-photographer',
|
||||
'maxlength' => '255',
|
||||
],
|
||||
])
|
||||
->add('termsAccepted', CheckboxType::class, [
|
||||
'label' => $termsLabel,
|
||||
'label_html' => true,
|
||||
'required' => true,
|
||||
])
|
||||
->add('isSoldOut', CheckboxType::class, [
|
||||
'label' => 'Diese Veranstaltung ist ausverkauft.',
|
||||
'required' => false,
|
||||
])
|
||||
->add('isCanceled', CheckboxType::class, [
|
||||
'label' => 'Diese Veranstaltung wurde abgesagt.',
|
||||
'required' => false,
|
||||
])
|
||||
->add('published', CheckboxType::class, ['label' => 'Veröffentlicht', 'required' => false]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => true,
|
||||
'location_choices' => [],
|
||||
'organization_choices' => [],
|
||||
'selected_organization_ids' => [],
|
||||
'show_organization' => false,
|
||||
'terms_page_url' => null,
|
||||
]);
|
||||
|
||||
$resolver->setAllowedTypes('location_choices', 'array');
|
||||
$resolver->setAllowedTypes('organization_choices', 'array');
|
||||
$resolver->setAllowedTypes('selected_organization_ids', 'array');
|
||||
$resolver->setAllowedTypes('show_organization', 'bool');
|
||||
$resolver->setAllowedTypes('terms_page_url', ['null', 'string']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class OrganizationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('title', TextType::class, ['label' => 'Titel', 'required' => true])
|
||||
->add('street', TextType::class, ['label' => 'Straße', 'required' => false])
|
||||
->add('postal', TextType::class, ['label' => 'PLZ', 'required' => false])
|
||||
->add('city', TextType::class, ['label' => 'Ort', 'required' => false])
|
||||
->add('state', TextType::class, ['label' => 'Bundesland', 'required' => false])
|
||||
->add('country', TextType::class, ['label' => 'Land', 'required' => false])
|
||||
->add('phone', TextType::class, ['label' => 'Telefon', 'required' => false])
|
||||
->add('email', EmailType::class, ['label' => 'E-Mail', 'required' => false])
|
||||
->add('website', TextType::class, ['label' => 'Webseite', 'required' => false])
|
||||
->add('description', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
|
||||
->add('type', ChoiceType::class, [
|
||||
'label' => 'Typ',
|
||||
'choices' => [
|
||||
'Unterkunft' => 'accommodation',
|
||||
'Einkaufen' => 'shopping',
|
||||
'Kultur' => 'culture',
|
||||
],
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'js-organization-type-choice',
|
||||
'data-placeholder' => 'Typ auswählen …',
|
||||
],
|
||||
])
|
||||
->add('logoUpload', FileType::class, [
|
||||
'label' => 'Logo hochladen',
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
'attr' => [
|
||||
'class' => 'js-logo-upload',
|
||||
'accept' => 'image/*',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Model;
|
||||
|
||||
use Contao\Model;
|
||||
|
||||
class LocationModel extends Model
|
||||
{
|
||||
protected static $strTable = 'tl_location';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Model;
|
||||
|
||||
use Contao\Model;
|
||||
|
||||
class OrganizationModel extends Model
|
||||
{
|
||||
protected static $strTable = 'tl_organization';
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class MummertMediaEventManagerBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return dirname(__DIR__);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Service;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
class EventRepository
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Connection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function findByMemberId(int $memberId): array
|
||||
{
|
||||
return $this->connection->createQueryBuilder()
|
||||
->select('DISTINCT e.*')
|
||||
->from('tl_calendar_events', 'e')
|
||||
->innerJoin('e', 'tl_calendar_events_organization', 'ceo', 'ceo.event_id = e.id')
|
||||
->innerJoin('ceo', 'tl_member_organization', 'mo', 'mo.organization_id = ceo.organization_id')
|
||||
->where('mo.member_id = :memberId')
|
||||
->setParameter('memberId', $memberId, ParameterType::INTEGER)
|
||||
->orderBy('e.startDate', 'DESC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
}
|
||||
|
||||
public function memberHasEvent(int $memberId, int $eventId): bool
|
||||
{
|
||||
$exists = $this->connection->createQueryBuilder()
|
||||
->select('1')
|
||||
->from('tl_calendar_events', 'e')
|
||||
->innerJoin('e', 'tl_calendar_events_organization', 'ceo', 'ceo.event_id = e.id')
|
||||
->innerJoin('ceo', 'tl_member_organization', 'mo', 'mo.organization_id = ceo.organization_id')
|
||||
->where('e.id = :eventId')
|
||||
->andWhere('mo.member_id = :memberId')
|
||||
->setParameter('eventId', $eventId, ParameterType::INTEGER)
|
||||
->setParameter('memberId', $memberId, ParameterType::INTEGER)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
return false !== $exists;
|
||||
}
|
||||
|
||||
public function togglePublished(int $eventId): void
|
||||
{
|
||||
$published = $this->connection->createQueryBuilder()
|
||||
->select('published')
|
||||
->from('tl_calendar_events')
|
||||
->where('id = :id')
|
||||
->setParameter('id', $eventId, ParameterType::INTEGER)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
$isPublished = '1' === (string) $published;
|
||||
|
||||
$this->connection->update(
|
||||
'tl_calendar_events',
|
||||
[
|
||||
'published' => $isPublished ? 0 : 1,
|
||||
'tstamp' => time(),
|
||||
],
|
||||
['id' => $eventId],
|
||||
['id' => ParameterType::INTEGER],
|
||||
);
|
||||
}
|
||||
|
||||
public function duplicate(int $eventId): ?int
|
||||
{
|
||||
$row = $this->findById($eventId);
|
||||
|
||||
if (null === $row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
unset($row['id']);
|
||||
|
||||
if (array_key_exists('title', $row)) {
|
||||
$row['title'] = trim((string) $row['title'].' (Kopie)');
|
||||
}
|
||||
|
||||
if (array_key_exists('alias', $row)) {
|
||||
$row['alias'] = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('published', $row)) {
|
||||
$row['published'] = 0;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
if (array_key_exists('dateAdded', $row)) {
|
||||
$row['dateAdded'] = $now;
|
||||
}
|
||||
|
||||
if (array_key_exists('tstamp', $row)) {
|
||||
$row['tstamp'] = $now;
|
||||
}
|
||||
|
||||
$this->connection->insert('tl_calendar_events', $row);
|
||||
|
||||
$newEventId = (int) $this->connection->lastInsertId();
|
||||
|
||||
$organizationIds = $this->connection->createQueryBuilder()
|
||||
->select('organization_id')
|
||||
->from('tl_calendar_events_organization')
|
||||
->where('event_id = :eventId')
|
||||
->setParameter('eventId', $eventId, ParameterType::INTEGER)
|
||||
->executeQuery()
|
||||
->fetchFirstColumn();
|
||||
|
||||
foreach ($organizationIds as $organizationId) {
|
||||
$this->connection->insert(
|
||||
'tl_calendar_events_organization',
|
||||
[
|
||||
'event_id' => $newEventId,
|
||||
'organization_id' => (int) $organizationId,
|
||||
],
|
||||
[
|
||||
'event_id' => ParameterType::INTEGER,
|
||||
'organization_id' => ParameterType::INTEGER,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return $newEventId;
|
||||
}
|
||||
|
||||
public function createDraftForMember(int $memberId, int $authorId, int $archiveId): ?int
|
||||
{
|
||||
$resolvedArchiveId = $this->resolveArchiveId($archiveId);
|
||||
|
||||
if ($resolvedArchiveId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$organizationId = $this->connection->createQueryBuilder()
|
||||
->select('organization_id')
|
||||
->from('tl_member_organization')
|
||||
->where('member_id = :memberId')
|
||||
->setParameter('memberId', $memberId, ParameterType::INTEGER)
|
||||
->orderBy('organization_id', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
if (false === $organizationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
$this->connection->insert(
|
||||
'tl_calendar_events',
|
||||
[
|
||||
'pid' => $resolvedArchiveId,
|
||||
'author' => max(0, $authorId),
|
||||
'title' => '',
|
||||
'alias' => '',
|
||||
'startDate' => 0,
|
||||
'endDate' => null,
|
||||
'published' => 0,
|
||||
'tstamp' => $now,
|
||||
],
|
||||
[
|
||||
'pid' => ParameterType::INTEGER,
|
||||
'author' => ParameterType::INTEGER,
|
||||
],
|
||||
);
|
||||
|
||||
$eventId = (int) $this->connection->lastInsertId();
|
||||
|
||||
if ($eventId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->connection->insert(
|
||||
'tl_calendar_events_organization',
|
||||
[
|
||||
'tstamp' => $now,
|
||||
'event_id' => $eventId,
|
||||
'organization_id' => (int) $organizationId,
|
||||
],
|
||||
[
|
||||
'event_id' => ParameterType::INTEGER,
|
||||
'organization_id' => ParameterType::INTEGER,
|
||||
],
|
||||
);
|
||||
|
||||
return $eventId;
|
||||
}
|
||||
|
||||
/** @return array{authorId:int,archiveId:int} */
|
||||
public function findCreationDefaultsByListPage(int $listPageId): array
|
||||
{
|
||||
if ($listPageId <= 0) {
|
||||
return ['authorId' => 0, 'archiveId' => 0];
|
||||
}
|
||||
|
||||
$row = $this->connection->createQueryBuilder()
|
||||
->select('frontendAuthorId', 'frontendArchiveId')
|
||||
->from('tl_module')
|
||||
->where('type = :type')
|
||||
->andWhere('listPage = :listPage')
|
||||
->setParameter('type', 'event_edit')
|
||||
->setParameter('listPage', $listPageId, ParameterType::INTEGER)
|
||||
->orderBy('id', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
if (false === $row || null === $row) {
|
||||
return ['authorId' => 0, 'archiveId' => 0];
|
||||
}
|
||||
|
||||
return [
|
||||
'authorId' => (int) ($row['frontendAuthorId'] ?? 0),
|
||||
'archiveId' => (int) ($row['frontendArchiveId'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveArchiveId(int $archiveId): int
|
||||
{
|
||||
if ($archiveId > 0) {
|
||||
$exists = $this->connection->createQueryBuilder()
|
||||
->select('id')
|
||||
->from('tl_calendar')
|
||||
->where('id = :id')
|
||||
->setParameter('id', $archiveId, ParameterType::INTEGER)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
if (false !== $exists) {
|
||||
return $archiveId;
|
||||
}
|
||||
}
|
||||
|
||||
$fallback = $this->connection->createQueryBuilder()
|
||||
->select('id')
|
||||
->from('tl_calendar')
|
||||
->orderBy('id', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
return false !== $fallback ? (int) $fallback : 0;
|
||||
}
|
||||
|
||||
public function delete(int $eventId): void
|
||||
{
|
||||
$this->connection->delete(
|
||||
'tl_calendar_events_organization',
|
||||
['event_id' => $eventId],
|
||||
['event_id' => ParameterType::INTEGER],
|
||||
);
|
||||
|
||||
$this->connection->delete(
|
||||
'tl_calendar_events',
|
||||
['id' => $eventId],
|
||||
['id' => ParameterType::INTEGER],
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed>|null */
|
||||
public function findById(int $eventId): ?array
|
||||
{
|
||||
$row = $this->connection->createQueryBuilder()
|
||||
->select('*')
|
||||
->from('tl_calendar_events')
|
||||
->where('id = :id')
|
||||
->setParameter('id', $eventId, ParameterType::INTEGER)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
return false === $row ? null : $row;
|
||||
}
|
||||
|
||||
/** @return array{id:int,title:string}|null */
|
||||
public function findPrimaryOrganizationForEvent(int $eventId): ?array
|
||||
{
|
||||
$row = $this->connection->createQueryBuilder()
|
||||
->select('o.id', 'o.title')
|
||||
->from('tl_calendar_events_organization', 'ceo')
|
||||
->innerJoin('ceo', 'tl_organization', 'o', 'o.id = ceo.organization_id')
|
||||
->where('ceo.event_id = :eventId')
|
||||
->setParameter('eventId', $eventId, ParameterType::INTEGER)
|
||||
->orderBy('ceo.organization_id', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
if (false === $row || null === $row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) $row['id'],
|
||||
'title' => (string) ($row['title'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<int> */
|
||||
public function getOrganizationIdsForEvent(int $eventId): array
|
||||
{
|
||||
$rows = $this->connection->createQueryBuilder()
|
||||
->select('organization_id')
|
||||
->from('tl_calendar_events_organization')
|
||||
->where('event_id = :eventId')
|
||||
->setParameter('eventId', $eventId, ParameterType::INTEGER)
|
||||
->orderBy('organization_id', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchFirstColumn();
|
||||
|
||||
return array_values(array_unique(array_map('intval', $rows)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getLocationChoices(): array
|
||||
{
|
||||
$rows = $this->connection->createQueryBuilder()
|
||||
->select('id', 'title')
|
||||
->from('tl_location')
|
||||
->where('published = :published')
|
||||
->setParameter('published', '1')
|
||||
->orderBy('title', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$choices = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$choices[(string) $row['title']] = (int) $row['id'];
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getOrganizationChoicesForMember(int $memberId): array
|
||||
{
|
||||
$rows = $this->connection->createQueryBuilder()
|
||||
->select('o.id', 'o.title')
|
||||
->from('tl_member_organization', 'mo')
|
||||
->innerJoin('mo', 'tl_organization', 'o', 'o.id = mo.organization_id')
|
||||
->where('mo.member_id = :memberId')
|
||||
->setParameter('memberId', $memberId, ParameterType::INTEGER)
|
||||
->orderBy('o.title', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$choices = [];
|
||||
$seenLabels = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$id = (int) $row['id'];
|
||||
$label = trim((string) ($row['title'] ?? ''));
|
||||
|
||||
if ('' === $label) {
|
||||
$label = 'Organisation '.$id;
|
||||
}
|
||||
|
||||
if (isset($seenLabels[$label])) {
|
||||
$label .= ' (#'.$id.')';
|
||||
}
|
||||
|
||||
$seenLabels[$label] = true;
|
||||
$choices[$label] = $id;
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
/** @return array<int> */
|
||||
public function getOrganizationIdsForMember(int $memberId): array
|
||||
{
|
||||
$ids = $this->connection->createQueryBuilder()
|
||||
->select('organization_id')
|
||||
->from('tl_member_organization')
|
||||
->where('member_id = :memberId')
|
||||
->setParameter('memberId', $memberId, ParameterType::INTEGER)
|
||||
->orderBy('organization_id', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchFirstColumn();
|
||||
|
||||
return array_values(array_unique(array_map('intval', $ids)));
|
||||
}
|
||||
|
||||
/** @param array<int> $organizationIds */
|
||||
public function assignEventToOrganizations(int $eventId, array $organizationIds): void
|
||||
{
|
||||
$this->connection->delete(
|
||||
'tl_calendar_events_organization',
|
||||
['event_id' => $eventId],
|
||||
['event_id' => ParameterType::INTEGER],
|
||||
);
|
||||
|
||||
$organizationIds = array_values(array_unique(array_map('intval', $organizationIds)));
|
||||
|
||||
foreach ($organizationIds as $organizationId) {
|
||||
if ($organizationId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->connection->insert(
|
||||
'tl_calendar_events_organization',
|
||||
[
|
||||
'tstamp' => time(),
|
||||
'event_id' => $eventId,
|
||||
'organization_id' => $organizationId,
|
||||
],
|
||||
[
|
||||
'event_id' => ParameterType::INTEGER,
|
||||
'organization_id' => ParameterType::INTEGER,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function update(int $eventId, array $data): void
|
||||
{
|
||||
$eventData = $this->buildEventData($data);
|
||||
|
||||
$this->connection->update(
|
||||
'tl_calendar_events',
|
||||
$eventData,
|
||||
['id' => $eventId],
|
||||
[
|
||||
'id' => ParameterType::INTEGER,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @param array<int> $organizationIds
|
||||
*/
|
||||
public function createForMember(int $memberId, int $authorId, int $archiveId, array $data, array $organizationIds): ?int
|
||||
{
|
||||
if ($memberId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$resolvedArchiveId = $this->resolveArchiveId($archiveId);
|
||||
|
||||
if ($resolvedArchiveId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$eventData = $this->buildEventData($data);
|
||||
$eventData['pid'] = $resolvedArchiveId;
|
||||
$eventData['author'] = max(0, $authorId);
|
||||
|
||||
$this->connection->insert(
|
||||
'tl_calendar_events',
|
||||
$eventData,
|
||||
[
|
||||
'pid' => ParameterType::INTEGER,
|
||||
'author' => ParameterType::INTEGER,
|
||||
],
|
||||
);
|
||||
|
||||
$eventId = (int) $this->connection->lastInsertId();
|
||||
|
||||
if ($eventId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$memberOrganizationIds = $this->getOrganizationIdsForMember($memberId);
|
||||
$allowedOrganizationIds = array_values(array_intersect(array_map('intval', $organizationIds), array_map('intval', $memberOrganizationIds)));
|
||||
|
||||
if ([] === $allowedOrganizationIds) {
|
||||
$this->connection->delete('tl_calendar_events', ['id' => $eventId], ['id' => ParameterType::INTEGER]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->assignEventToOrganizations($eventId, $allowedOrganizationIds);
|
||||
|
||||
return $eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildEventData(array $data): array
|
||||
{
|
||||
$startDate = !empty($data['startDate']) ? strtotime((string) $data['startDate']) : false;
|
||||
$endDate = !empty($data['endDate']) ? strtotime((string) $data['endDate']) : false;
|
||||
$url = trim((string) ($data['url'] ?? ''));
|
||||
$addTime = !empty($data['addTime']);
|
||||
$alias = $this->buildAlias($startDate, (string) ($data['title'] ?? ''));
|
||||
|
||||
$startDateTimestamp = false !== $startDate ? (int) $startDate : 0;
|
||||
$endDateTimestamp = false !== $endDate ? (int) $endDate : 0;
|
||||
$timeBaseTimestamp = $endDateTimestamp > 0 ? $endDateTimestamp : $startDateTimestamp;
|
||||
|
||||
$startTimeTimestamp = $startDateTimestamp;
|
||||
$endTimeTimestamp = $timeBaseTimestamp > 0 ? $timeBaseTimestamp + 86399 : 0;
|
||||
|
||||
if ($addTime) {
|
||||
$startTimeCandidate = $this->combineDateAndTime($startDateTimestamp, (string) ($data['startTime'] ?? ''));
|
||||
$startTimeTimestamp = null !== $startTimeCandidate ? $startTimeCandidate : $startDateTimestamp;
|
||||
|
||||
$endTimeCandidate = $this->combineDateAndTime($timeBaseTimestamp, (string) ($data['endTime'] ?? ''));
|
||||
$endTimeTimestamp = null !== $endTimeCandidate ? $endTimeCandidate : $startTimeTimestamp;
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $data['title'] ?? '',
|
||||
'alias' => $alias,
|
||||
'startDate' => false !== $startDate ? $startDate : 0,
|
||||
'endDate' => false !== $endDate ? $endDate : null,
|
||||
'location_id' => (int) ($data['location_id'] ?? 0),
|
||||
'addTime' => $addTime ? 1 : 0,
|
||||
'startTime' => $startTimeTimestamp,
|
||||
'endTime' => $endTimeTimestamp,
|
||||
'type' => serialize($data['type'] ?? []),
|
||||
'teaser' => $data['teaser'] ?? null,
|
||||
'description' => $data['description'] ?? null,
|
||||
'url' => $url,
|
||||
'source' => '' !== $url ? 'external' : 'default',
|
||||
'photographer' => $data['photographer'] ?? '',
|
||||
'addImage' => !empty($data['addImage']) ? 1 : 0,
|
||||
'termsAccepted' => !empty($data['termsAccepted']) ? '1' : '',
|
||||
'isSoldOut' => !empty($data['isSoldOut']) ? '1' : '',
|
||||
'isCanceled' => !empty($data['isCanceled']) ? '1' : '',
|
||||
'published' => !empty($data['published']) ? 1 : 0,
|
||||
'tstamp' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
private function buildAlias(false|int $startDate, string $title): string
|
||||
{
|
||||
if (false === $startDate || $startDate <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$normalizedTitle = trim($title);
|
||||
|
||||
if ('' === $normalizedTitle) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$normalizedTitle = strtolower($normalizedTitle);
|
||||
$transliterated = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $normalizedTitle);
|
||||
|
||||
if (false !== $transliterated) {
|
||||
$normalizedTitle = strtolower($transliterated);
|
||||
}
|
||||
|
||||
$normalizedTitle = preg_replace('/[^a-z0-9]+/', '', $normalizedTitle) ?? '';
|
||||
$normalizedTitle = substr($normalizedTitle, 0, 20);
|
||||
|
||||
if ('' === $normalizedTitle) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return date('Y-m-d', $startDate).'_'.$normalizedTitle;
|
||||
}
|
||||
|
||||
private function combineDateAndTime(int $dateTimestamp, string $time): ?int
|
||||
{
|
||||
if ($dateTimestamp <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalizedTime = trim($time);
|
||||
|
||||
if (!preg_match('/^([01]\d|2[0-3]):([0-5]\d)$/', $normalizedTime, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hours = (int) $matches[1];
|
||||
$minutes = (int) $matches[2];
|
||||
|
||||
return $dateTimestamp + ($hours * 3600) + ($minutes * 60);
|
||||
}
|
||||
|
||||
public function updateImageFields(int $eventId, bool $addImage, ?string $singleSrcUuid): void
|
||||
{
|
||||
$types = [
|
||||
'id' => ParameterType::INTEGER,
|
||||
];
|
||||
|
||||
if (null !== $singleSrcUuid) {
|
||||
$types['singleSRC'] = ParameterType::BINARY;
|
||||
}
|
||||
|
||||
$this->connection->update(
|
||||
'tl_calendar_events',
|
||||
[
|
||||
'addImage' => $addImage ? 1 : 0,
|
||||
'singleSRC' => $singleSrcUuid,
|
||||
'tstamp' => time(),
|
||||
],
|
||||
['id' => $eventId],
|
||||
$types,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MummertMedia\EventManagerBundle\Service;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
class OrganizationRepository
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Connection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function findByMemberId(int $memberId): array
|
||||
{
|
||||
return $this->connection->createQueryBuilder()
|
||||
->select('o.*')
|
||||
->from('tl_organization', 'o')
|
||||
->innerJoin('o', 'tl_member_organization', 'mo', 'mo.organization_id = o.id')
|
||||
->where('mo.member_id = :memberId')
|
||||
->setParameter('memberId', $memberId, ParameterType::INTEGER)
|
||||
->orderBy('o.title', 'ASC')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
}
|
||||
|
||||
public function memberHasOrganization(int $memberId, int $organizationId): bool
|
||||
{
|
||||
$exists = $this->connection->createQueryBuilder()
|
||||
->select('1')
|
||||
->from('tl_member_organization', 'mo')
|
||||
->where('mo.member_id = :memberId')
|
||||
->andWhere('mo.organization_id = :organizationId')
|
||||
->setParameter('memberId', $memberId, ParameterType::INTEGER)
|
||||
->setParameter('organizationId', $organizationId, ParameterType::INTEGER)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
return false !== $exists;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed>|null */
|
||||
public function findById(int $organizationId): ?array
|
||||
{
|
||||
$row = $this->connection->createQueryBuilder()
|
||||
->select('*')
|
||||
->from('tl_organization')
|
||||
->where('id = :id')
|
||||
->setParameter('id', $organizationId, ParameterType::INTEGER)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
|
||||
return false === $row ? null : $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function update(int $organizationId, array $data): void
|
||||
{
|
||||
$this->connection->update(
|
||||
'tl_organization',
|
||||
[
|
||||
'title' => $data['title'] ?? '',
|
||||
'street' => $data['street'] ?? '',
|
||||
'postal' => $data['postal'] ?? '',
|
||||
'city' => $data['city'] ?? '',
|
||||
'state' => $data['state'] ?? '',
|
||||
'country' => $data['country'] ?? '',
|
||||
'phone' => $data['phone'] ?? '',
|
||||
'email' => $data['email'] ?? '',
|
||||
'website' => $data['website'] ?? '',
|
||||
'description' => $data['description'] ?? null,
|
||||
'type' => serialize($data['type'] ?? []),
|
||||
'tstamp' => time(),
|
||||
],
|
||||
['id' => $organizationId],
|
||||
[
|
||||
'id' => ParameterType::INTEGER,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function updateLogo(int $organizationId, ?string $logoUuid): void
|
||||
{
|
||||
$types = [
|
||||
'id' => ParameterType::INTEGER,
|
||||
];
|
||||
|
||||
if (null !== $logoUuid) {
|
||||
$types['logo'] = ParameterType::BINARY;
|
||||
}
|
||||
|
||||
$this->connection->update(
|
||||
'tl_organization',
|
||||
[
|
||||
'logo' => $logoUuid,
|
||||
'tstamp' => time(),
|
||||
],
|
||||
['id' => $organizationId],
|
||||
$types,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user