<?php
namespace App\Controller\Frontend;
use App\Application\Content\ContentFrontendData;
use App\Application\Content\ContentService;
use App\Application\Mail\MailerService;
use App\Application\Vs\VsService;
use App\Entity\Vs\Vs;
use App\Entity\Vs\VsAbo;
use App\Entity\Vs\VsImage;
use App\Entity\Vs\VsStatus;
use App\Entity\Vs\VsTyp;
use App\Form\Vs\VsType;
use App\Util\FileTool;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Validator\Constraints\Image;
use Symfony\Component\Validator\Validator\ValidatorInterface;
#[Route(path: '/veranstaltungen')]
class VeranstaltungenController extends AbstractController
{
#[Route(path: '/', name: 'fe.veranstaltungen')]
public function liste(
Request $request,
VsService $vsService,
ContentService $contentService
): Response {
$rubriken = $vsService->findRubriken();
$searchFilter = [
's' => $request->query->get('s', ''),
'd' => $request->query->get('d', ''),
'r' => $request->query->get('r', null),
'd_bis' => '', // wird unten gesetzt
'd_next' => '', // wird unten gesetzt
'd_prev' => '', // wird unten gesetzt
];
// Date validate and set default
$dateNow = new \DateTimeImmutable('now');
if ($searchFilter['d']) {
$fromDateInput = \DateTimeImmutable::createFromFormat('d.m.Y', $searchFilter['d']);
if ($fromDateInput === false || (int) $fromDateInput->format('Ymd') < (int) $dateNow->format('Ymd')) {
$searchFilter['d'] = '';
} else {
$searchFilter['d'] = $fromDateInput;
}
}
if (!$searchFilter['d']) {
$searchFilter['d'] = $dateNow; // default heute setzen
}
// Bis datum (bis zum letzten Tag vom nächsten Monat)
// $dateBis = $searchFilter['d']->add(new \DateInterval('P1M'));
// $searchFilter['d_bis'] = $dateBis->modify('last day of this month');
// Previous / Bis / Next Datum rechnen (falls KEINE Suche abgesetzt wurde da dieser ueber ganzen Zeitraum gehen soll)
if (!$searchFilter['s']) {
$searchFilter['d_bis'] = $searchFilter['d']->add(new \DateInterval('P4D'));
$searchFilter['d_next'] = $searchFilter['d_bis']->add(new \DateInterval('P1D'));
// Previous Berechnen
$prevDateTmp = $searchFilter['d']->sub(new \DateInterval('P15D'));
if ((int) $searchFilter['d']->format('Ymd') === (int) $dateNow->format('Ymd')) {
$searchFilter['d_prev'] = null; // kein, da heutiges Datum gesetzt
} elseif ((int) $prevDateTmp->format('Ymd') <= (int) $dateNow->format('Ymd')) {
$searchFilter['d_prev'] = $dateNow; // frühestes datum ist heute
} else {
$searchFilter['d_prev'] = $prevDateTmp;
}
}
// Ende
// --- Veranstaltungen finden
// Filter Parameter zusammenstellen
$filter['status_ids'] = [VsStatus::STAT_ANZEIGEN];
$filter['from'] = $searchFilter['d'];
$filter['to'] = $searchFilter['d_bis'] ?: null;
$filter['title_likequery'] = strlen($searchFilter['s']) > 2 ? $searchFilter['s'] : '';
$filter['rubrik_ids'] = $searchFilter['r'] != '' && is_numeric($searchFilter['r']) ? [(int) $searchFilter['r']] : [];
// Einzellveranstaltungen finden
$filter['typ_ids'] = [VsTyp::TYP_EINZEL_VS];
$einzellVs = $vsService->findVeranstaltungen($filter, ['startdatum' => 'asc']);
// Restliche Veranstaltungen finden
$filter['typ_ids'] = [VsTyp::TYP_DAUER_VS, VsTyp::TYP_OHNE_FIXDATUM_VS];
$restVs = $vsService->findVeranstaltungen($filter, ['startdatum' => 'desc']);
// --- Ende
$contentSeoVeranstaltungen = $contentService->getContentByCode('ACCORDION_VERANSTALTUNGEN', new ContentFrontendData(), true);
return $this->render('frontend/veranstaltungen/liste.html.twig', [
'rubriken' => $rubriken,
'filter' => $searchFilter,
'einzellVs' => $einzellVs,
'restVs' => $restVs,
'contentSeoVeranstaltungen' => $contentSeoVeranstaltungen,
]);
}
/**
* JSON-Datenexport aktuellster Veranstaltungen für Integration seitens UriOnline.ch.
*/
#[Route(path: '/urionline-export', name: 'fe.veranstaltungen_urionline')]
public function urionlineExport(VsService $vsService): JsonResponse
{
$maxReturn = 10;
// Datum von bis
$dateNow = new \DateTimeImmutable('now');
$dateBis = $dateNow->add(new \DateInterval('P14D'));
// Ende
// --- Veranstaltungen finden
$filter['status_ids'] = [VsStatus::STAT_ANZEIGEN];
$filter['from'] = $dateNow;
$filter['to'] = $dateBis;
$filter['typ_ids'] = [VsTyp::TYP_EINZEL_VS]; // Einzellveranstaltungen finden
$einzellVs = $vsService->findVeranstaltungen($filter, ['startdatum' => 'asc'], [$maxReturn, 1]);
// --- Ende
// JSON Datenarray bilden
$data = [
'veranstaltungskalender_url' => $this->generateUrl('fe.veranstaltungen', [], UrlGeneratorInterface::ABSOLUTE_URL),
'veranstaltungen_einzel' => [],
'veranstaltungen_dauer' => 'not implemented yet',
'veranstaltungen_ohnefixdatum' => 'not implemented yet',
];
foreach ($einzellVs['data'] as $vs) {
/*
* @var Vs $vs
*/
$data['veranstaltungen_einzel'][] = [
'titel' => $vs->getTitel(),
'datum' => $vs->getStartdatum()->format('Y-m-d'),
'adresse' => $vs->getAdresse(),
'ort' => $vs->getRegion()?->getRegionname(),
];
}
$response = $this->json($data);
$response->setPublic();
$response->setMaxAge(3600);
$response->setSharedMaxAge(3600);
$response->headers->set('Access-Control-Allow-Origin', '*');
return $response;
}
#[Route(path: '/{id}/{slug}', name: 'fe.veranstaltungen_detail', requirements: ['id' => '\d+'])]
public function detail(int $id, $slug, VsService $vsService): RedirectResponse|Response
{
$vs = $vsService->findVsById($id);
if (!$vs || !$vs->getAbo()->isPremium()) {
throw $this->createNotFoundException();
}
// Falls Slug nicht mehr passend zur id, redirect einleiten!
if ($vs->getSlug() !== $slug) {
return $this->redirectToRoute('fe.veranstaltungen_detail', ['id' => $id, 'slug' => $vs->getSlug()], 301);
}
return $this->render('frontend/veranstaltungen/detail.html.twig', [
'vs' => $vs,
]);
}
#[Route(path: '/produkt', name: 'fe.veranstaltungen_produkt')]
public function produkt(): Response
{
return $this->render('frontend/veranstaltungen/produkt.html.twig');
}
#[Route(path: '/melden-ok', name: 'fe.veranstaltungen_melden_ok')]
public function meldenOk(): Response
{
return $this->render('frontend/veranstaltungen/melden-ok.html.twig');
}
#[Route(path: '/melden/std-online', name: 'fe.veranstaltungen_melden_std_online', defaults: ['aboId' => VsAbo::ABO_STD_MIT_INSERAT])]
#[Route(path: '/melden/premium', name: 'fe.veranstaltungen_melden_premium_ohne_inserat', defaults: ['aboId' => VsAbo::ABO_PREM_OHNE_INSERAT])]
#[Route(path: '/melden/premium-mit-inserat', name: 'fe.veranstaltungen_melden_premium_mit_inserat', defaults: ['aboId' => VsAbo::ABO_PREM_MIT_INSERAT])]
public function melden(Request $request, $aboId, VsService $vsService, MailerService $mailerService): Response
{
$abo = $vsService->getAboById($aboId);
if (!$abo) {
throw $this->createNotFoundException();
}
$vs = new Vs();
$vs->setStatus($vsService->getStatusById(VsStatus::STAT_NONE)); // weise "Nicht zugewiesen" dem Objekt Status, wenn nichts angegeben wurde
$vs->setAbo($abo);
$vs->setTyp($vsService->getTypById(VsTyp::TYP_EINZEL_VS)); // weise "Einzelveranstaltung" als Standard zu, weil dies zu 90 % zutrifft
$form = $this->createForm(VsType::class, $vs); // referenziere auf gleiches Formular wie im Backend, damit ich nur ein grosses Formular anpassen muss!
// Entferne Felder aus Formular, da ich diesen oben fixe Werte zugewiesen werden
$form->remove('status');
$form->remove('abo');
$form->remove('typ');
// Ende
// Entferne nicht mehr erwünschte Felder
$form->remove('kkontaktieren');
$form->remove('kbemerkung');
// Ende
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// dump($form->getData());die;
/**
* @var Vs $vs
*/
$vs = $form->getData();
// Bild Objekt erzeugen, falls vorhanden
$vsImageFilename = basename((string) $form->get('vsImageFilepath')->getData());
$vsImageAlt = $form->get('vsImageAlt')->getData();
$vsImageCaption = $form->get('vsImageCaption')->getData();
$vs->setVsImage($vsImageFilename, $vsImageAlt, $vsImageCaption);
// Ende
// Standardmässig Export Uristier / Export UW
$vs->setExportUristier(true);
$vs->setExportUw(true);
// End
$vsService->saveVs($vs);
// Kopiere File von Temp Verzeichnis ins Finale Verzeichnis
if ($vsImageFilename !== '' && $vsImageFilename !== '0') {
$tmpSaveDirPath = __DIR__.'/../../../var/data/public/vs/images/tmp';
$vsImageFilenameThumb = VsImage::PREFIX_THUMBNAME.$vsImageFilename;
$fs = new Filesystem();
$fs->copy($tmpSaveDirPath.'/'.$vsImageFilename, $vs::baseObjectServerDirPathStatic($vs->getUuid()).'/'.$vsImageFilename);
$fs->copy($tmpSaveDirPath.'/'.$vsImageFilenameThumb, $vs::baseObjectServerDirPathStatic($vs->getUuid()).'/'.$vsImageFilenameThumb);
$fs->remove($tmpSaveDirPath.'/'.$vsImageFilename);
$fs->remove($tmpSaveDirPath.'/'.$vsImageFilenameThumb);
}
// Mail an Inserate versenden
$emailTo = $this->getParameter('veranstaltungen_email');
$emailFrom = $this->getParameter('mailer_from');
$emailFromName = $this->getParameter('mailer_from_name');
$subject = 'Neue Veranstaltung "'.$vs->getTitel().'" erfasst';
$body = '<p>Auf www.urnerwochenblatt.ch wurde eine neue Veranstaltung erfasst.<br><br>';
$body .= 'Weitere Infos zur Veranstaltung und dessen Aufschaltung erfolgt über die VK-Administration oder falls bereits angemeldet über Direktlink ';
$body .= $this->generateUrl('app.vs_edit', ['id' => $vs->getId()], UrlGeneratorInterface::ABSOLUTE_URL).' </p>';
$body .= '<br>';
$body .= '<b>Veranstaltung</b><br>';
$body .= '<p>'.$vs->getTitel().'<br>';
$body .= $vs->getAdresse().'<br>';
$body .= ''.$vs->getRegion().'</p>';
$body .= '<br>';
$body .= '<b>Kunde</b><br><br>';
$body .= '<table cellpadding="0", cellspacing="0">';
$body .= '<tr><td>Firma/Verein </td><td>'.$vs->getKveranstalter().'</td></tr>';
$body .= '<tr><td>Vor-/Nachname </td><td>'.$vs->getKvorname().' '.$vs->getKname().'</td></tr>';
$body .= '<tr><td>Adresse</td><td>'.$vs->getKstrasse().'</td></tr>';
$body .= '<tr><td>PLZ/Ort</td><td>'.$vs->getKplz().' '.$vs->getKort().'</td></tr>';
$body .= '<tr><td>E-Mail</td><td>'.$vs->getKemail().'</td></tr>';
$body .= '<tr><td>Telefon</td><td>'.$vs->getKtel().'</td></tr>';
$body .= '<tr><td>Bemerkung</td><td>'.$vs->getKbemerkung().'</td></tr>';
$body .= $vs->isKkontaktieren() ? '<tr><td><br>HINWEIS:</td><td><br>Kunde wünscht Kontaktaufnahme!</td></tr>' : '';
$body .= '</table>';
$body .= '<br><br>';
$mailerService->setFrom($emailFrom, $emailFromName);
$mailerService->setTo($emailTo);
$mailerService->setSubject($subject);
$mailerService->setBody($body, 'text/html');
$mailerService->send();
// Ende
return $this->redirectToRoute('fe.veranstaltungen_melden_ok');
}
return $this->render('frontend/veranstaltungen/melden.html.twig', [
'abo' => $abo,
'vs' => $vs,
'form' => $form->createView(),
]);
}
#[Route(path: '/fileupload/{action}', name: 'fe.veranstaltungen_jqueryfileupload')]
public function jqueryFileUpload(Request $request, $action, LoggerInterface $logger, ValidatorInterface $validator): JsonResponse
{
if (!$action) {
throw new \InvalidArgumentException('Action nicht definiert! Benötigt zur korrekten Ablage des hochgeladenen Files auf dem Server.');
}
$return_ = [
'success' => null,
'msg' => '',
'uploadedFileWebPath' => null,
'requestQueryParams' => $request->query->all(), // Alle URL Übergabeparameter zurückgeben zur möglichen Hilfe und Weiterverwendung in Frontend
];
try {
/**
* @var UploadedFile $uploadedFile
*/
foreach ($request->files as $uploadedFile) {
switch ($action) {
case 'VSIMAGE_FRONTEND':
$uuid = $request->query->get('vsUuid');
if (!$uuid) {
throw new \InvalidArgumentException('URL Parameter "uuid" leer oder nicht vorhanden!');
}
// Validierung Bildupload
$violations = $validator->validate($uploadedFile, [
new Image(['maxWidth' => 5120, 'mimeTypes' => ['image/jpeg', 'image/png', 'image/gif']]), // 5120 als Schutz, Bild wird unten runtergerechnet
]);
if (0 !== count($violations)) {
$return_['success'] = false;
foreach ($violations as $violation) {
$return_['msg'] .= $violation->getMessage();
}
} else {
$saveDirPath = __DIR__.'/../../../var/data/public/vs/images/tmp';
// directory protection/security for overloading: delete all images bevor
$fs = new Filesystem();
if ($fs->exists($saveDirPath)) {
$finder = new Finder();
$finder->files()->in($saveDirPath)->name('*'.$uuid.'*');
foreach ($finder as $file) {
$fs->remove($file->getPathname());
}
$finder->files()->in($saveDirPath)->date('before 1 hour ago');
foreach ($finder as $file) {
$fs->remove($file->getPathname());
}
}
// end
// Erstelle neuen "sauberen", fixen Filename
$newFileName = $uuid.'-'.mt_rand(1000, 9999).'.'.$uploadedFile->getClientOriginalExtension();
$newFileNameThumb = VsImage::PREFIX_THUMBNAME.$newFileName;
$serverPathToFile = $saveDirPath.'/'.$newFileName;
$serverPathToThumbFile = $saveDirPath.'/'.$newFileNameThumb;
$webPathToFile = $request->getBasePath().'/data/vs/images/tmp/'.$newFileName;
// Datei in Projektverzeichnis speichern
$uploadedFile->move($saveDirPath, $newFileName);
// Erstellt Thumbnail Bild (falls grösser max Image size), sonst einfach eine Kopie als Thumbnailbild
$resizedFilepath = FileTool::createImageWithWidth($serverPathToFile, Vs::MAX_WIDTH_VSIMAGE, true);
copy($resizedFilepath, $serverPathToThumbFile);
// copy file to new Pfad und Filename
// if ($serverPathToFile !== $resizedFilepath) {
// rename($resizedFilepath, $serverPathToFile);
// }
// Webpath zum hochgeladenen File
$return_['filename'] = $newFileName;
$return_['success'] = true;
$return_['uploadedFileWebPath'] = $webPathToFile;
}
break;
default:
throw new \InvalidArgumentException('File-Action "'.$action.'" nicht definiert oder ungültig !');
}
}
} catch (\InvalidArgumentException $e) {
$return_['success'] = false;
$return_['msg'] = $e->getMessage();
} catch (\Exception $e) {
$logger->critical($e->getMessage());
$return_['success'] = false;
$return_['msg'] = 'Es ist ein Fehler beim Dateiupload passiert. Bitte versuchen Sie es noch einmal.';
}
return new JsonResponse($return_);
}
}