<?php declare(strict_types=1); /* * This file is part of contao-weinanlieferung-bundle. * * (c) vonRotenberg * * @license commercial */ namespace vonRotenberg\WeinanlieferungBundle\Controller\Frontend\Ajax; use Contao\Controller; use Contao\CoreBundle\Controller\AbstractController; use Contao\CoreBundle\Exception\PageNotFoundException; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\CoreBundle\Security\Authentication\Token\TokenChecker; use Contao\Environment; use Contao\File; use Contao\FilesModel; use Contao\FormFileUpload; use Contao\Frontend; use Contao\FrontendUser; use Contao\Input; use Contao\StringUtil; use Contao\System; use Doctrine\DBAL\Connection; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLageModel; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungLeseartModel; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungRebsorteModel; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungReservationModel; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungSlotsModel; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungUnitsModel; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeGroupModel; use vonRotenberg\WeinanlieferungBundle\Model\WeinanlieferungAttributeModel; /** * @Route("/_ajax/vr_wa/v1/slot", name="vr_wa_slot_ajax", defaults={"_scope" = "frontend", "_token_check" = false}) */ class SlotAjaxController extends AbstractController { private $tokenChecker; private $translator; private $framework; public function __construct(ContaoFramework $framework, TokenChecker $tokenChecker, TranslatorInterface $translator) { $this->framework = $framework; $this->tokenChecker = $tokenChecker; $this->translator = $translator; } public function __invoke(Request $request) { System::loadLanguageFile('default'); if (!$this->tokenChecker->hasFrontendUser()) { return $this->renderUnauthorized(); } if (empty($_REQUEST['do'])) { return new Response('Required parameter missing',412); } $blnModal = true; if (!empty($_REQUEST['modal'])) { $blnModal = !(strtolower($_REQUEST['modal']) == 'false'); } switch ($_REQUEST['do']) { case 'details': return $this->renderDetails($blnModal); case 'annotation': return $this->renderAnnotation($blnModal); case 'booking': return $this->renderBooking($blnModal); case 'reservate': return $this->reservate(); case 'updateReservation': return $this->updateReservation(); case 'availableUnitAmount': return $this->renderAvailableUnitAmount(); case 'availableBookingUnitAmount': return $this->renderAvailableBookingUnitAmount(); case 'delete': return $this->deleteReservation(); } return new Response('',500); } protected function renderDetails(bool $blnModal=true,string $error=null) { $insertTagService = Controller::getContainer()->get('contao.insert_tag.parser'); if (empty($_REQUEST['id'])) { return new Response('Required parameter missing',412); } if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null) { return new Response('Could not load slot data',500); } // Get slot reservations from user $arrReservations = []; if (($Reservations = WeinanlieferungReservationModel::findBy(["uid = ?","pid = ?"],[FrontendUser::getInstance()->id,$Slot->id])) !== null) { foreach ($Reservations as $reservation) { $Unit = WeinanlieferungUnitsModel::findByPk($reservation->unit); $arrReservations[] = array_merge($reservation->row(),[ 'unitLabel' => $Unit->title ?? $this->translator->trans('tl_vr_wa_units.containers.0', [], 'contao_tl_vr_wa_units') ]); } } // Build data $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter()); $intBookableBehaelter = $Slot->getAvailableBehaelter() + $Slot->getOvercapacityBehaelter(); $arrUnits = []; if (($Site = $Slot->getRelated('pid')) !== null) { if (($Units = $Site->getRelated('units')) !== null) { foreach ($Units as $unit) { // $arrUnits[$unit->id] = $unit->title . ' (' . $unit->containers . ' '.$this->translator->trans('tl_vr_wa_units.containers.0', [], 'contao_tl_vr_wa_units').')'; $arrUnits[$unit->id] = $unit->title; } } } // Load attribute groups and attributes $arrAttributeGroups = []; // Get selected attributes $selectedAttributeIds = []; if ($Slot->attributes) { $selectedAttributeIds = array_map('trim', explode(',', $Slot->attributes)); } // If no attributes are selected, don't show any if (empty($selectedAttributeIds)) { // Keep the attribute_groups array empty } else { // Fetch the selected attributes $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); if ($selectedAttributes !== null) { // Group the attributes by their parent group $attributesByGroup = []; $groupIds = []; // First pass: collect all group IDs and organize attributes by group foreach ($selectedAttributes as $attribute) { $groupId = $attribute->pid; $groupIds[] = $groupId; if (!isset($attributesByGroup[$groupId])) { $attributesByGroup[$groupId] = []; } $attributesByGroup[$groupId][] = [ 'id' => $attribute->id, 'title' => $attribute->title, 'description' => $attribute->description ]; } // Get unique group IDs $groupIds = array_unique($groupIds); // Fetch the groups if (!empty($groupIds)) { $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); if ($groups !== null) { // Create the attribute groups array foreach ($groups as $group) { if (isset($attributesByGroup[$group->id])) { $arrAttributeGroups[] = [ 'id' => $group->id, 'title' => $group->title, 'description' => $group->description, 'type' => $group->type, 'multiple' => (bool)$group->multiple, 'required' => (bool)$group->required, 'attributes' => $attributesByGroup[$group->id] ]; } } } } } } $standort = $Slot->getRelated('pid'); $disable_base_unit = !empty($standort->disable_base_unit); // Determine default selected unit $defaultUnitId = ''; if ($disable_base_unit && !empty($arrUnits)) { // If base unit is disabled, select the first available unit reset($arrUnits); $defaultUnitId = key($arrUnits); } // Adjust quantity options based on default unit $behaelterOptions = range(min($intBookableBehaelter,1), $intBookableBehaelter); if ($defaultUnitId && ($Unit = WeinanlieferungUnitsModel::findByPk($defaultUnitId)) !== null) { $intDefault = floor($intAvailableBehaelter / max(1, $Unit->containers)); $intAmount = floor($intBookableBehaelter / max(1, $Unit->containers)); $behaelterOptions = $intAmount ? range(1, $intAmount) : []; } $arrData = [ 'modal' => $blnModal, 'id' => $Slot->id, 'slot' => array_merge($Slot->row(),[ 'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''), 'behaelterAvailable' => $intAvailableBehaelter ]), 'standort' => $standort, 'buchen' => [ 'buchbar' => (boolean) $intAvailableBehaelter, 'behaelter' => $behaelterOptions, 'units' => $arrUnits, 'disable_base_unit' => $disable_base_unit, 'default_unit' => $defaultUnitId ], 'reservations' => $arrReservations, 'attribute_groups' => $arrAttributeGroups ]; if (!empty($error)) { $arrData['toast'] = $error; } return $this->render('@Contao/modal_slot_details.html.twig',$arrData); } protected function renderAnnotation(bool $blnModal=true,string $error=null) { $insertTagService = Controller::getContainer()->get('contao.insert_tag.parser'); if (empty($_REQUEST['id'])) { return new Response('Required parameter missing',412); } if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null) { return new Response('Could not load slot data',500); } // Build data $arrSorten = []; $Sorten = StringUtil::deserialize($Slot->sorten,true); foreach($Sorten as $sorte) { $objSorte = WeinanlieferungRebsorteModel::findByPk($sorte['sorte']); $objLeseart = WeinanlieferungLeseartModel::findByPk($sorte['leseart']); $arrSorten[$objSorte->id.','.$objLeseart->id] = ($objSorte !== null ? $objSorte->title : '') . ' ' . ($objLeseart !== null ? $objLeseart->title : ''); } $arrErnteart = []; if ($Slot->ernteart !== null) { foreach (explode(',', $Slot->ernteart) as $ernteart) { $arrErnteart[] = $GLOBALS['TL_LANG']['REF']['wa_ernteart'][$ernteart] ?? $ernteart; } } $arrLage = []; if (($Lage = $Slot->getRelated('lage')) !== null) { $arrLage = $Lage->fetchEach('title'); } $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter()); $arrData = [ 'modal' => $blnModal, 'id' => $Slot->id, 'slot' => array_merge($Slot->row(),[ 'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''), 'sorte' => $arrSorten, 'behaelterAvailable' => $intAvailableBehaelter ]), 'standort' => $Slot->getRelated('pid'), 'lage' => $arrLage, 'ernteart' => $arrErnteart, 'buchen' => [ 'buchbar' => (boolean) $intAvailableBehaelter, 'behaelter' => range(min($intAvailableBehaelter,1),$intAvailableBehaelter), 'sorten' => $arrSorten ], ]; return $this->render('@Contao/modal_slot_annotation.html.twig',$arrData); } protected function renderBooking(bool $blnModal=true,string $error=null) { $insertTagService = Controller::getContainer()->get('contao.insert_tag.parser'); $arrData = []; if (empty($_REQUEST['id'])) { return new Response('Required parameter missing',412); } /** @var WeinanlieferungSlotsModel $Slot */ if (($Booking = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null || ($Slot = $Booking->getRelated('pid')) === null) { return new Response('Could not load booking data',500); } $objFile = FilesModel::findByUuid($Booking->upload); if (!empty($_REQUEST['deleteFile']) && $_REQUEST['deleteFile'] && $objFile !== null) { $File = new File($objFile->path); if ($File->delete()) { $objFile->delete(); $objFile = null; } } if (!empty($Booking->upload) && $objFile !== null) { $File = new File($objFile->path); $strHref = Environment::get('request'); // Remove an existing file parameter (see #5683) if (isset($_GET['file'])) { $strHref = preg_replace('/(&(amp;)?|\?)file=[^&]+/', '', $strHref); } $strHref .= (strpos($strHref, '?') !== false ? '&' : '?') . 'file=' . System::urlEncode($File->value); $arrData['file'] = [ 'link' => $strHref, 'filename' => $File->filename, 'extension' => $File->extension, 'name' => $File->name, 'path' => $File->dirname ]; } // Send the file to the browser (see #4632 and #8375) if ($objFile !== null && ($file = Input::get('file', true))) { if ($file == $objFile->path) { Controller::sendFileToBrowser($file); } } $intAvailableBehaelter = max(0,$Slot->getAvailableBehaelter()); $intBookableBehaelter = $Slot->getAvailableBehaelter() + $Slot->getOvercapacityBehaelter(); $intDefaultAmount = $intAvailableBehaelter+$Booking->behaelter; $intUnitAmount = $intBookableBehaelter+$Booking->behaelter; $arrUnits = []; if (($Site = $Slot->getRelated('pid')) !== null) { if (($Units = $Site->getRelated('units')) !== null) { foreach ($Units as $unit) { //$arrUnits[$unit->id] = $unit->title . ' (' . $unit->containers . ' '.$this->translator->trans('tl_vr_wa_units.containers.0', [], 'contao_tl_vr_wa_units').')'; $arrUnits[$unit->id] = $unit->title; } } if ($Booking->unit > 0 && \in_array($Booking->unit, array_keys($arrUnits))) { if (($Unit = WeinanlieferungUnitsModel::findByPk($Booking->unit)) === null) { return new Response('Could not load unit data', 500); } $intDefaultAmount = floor($intDefaultAmount / max(1, $Unit->containers)); $intUnitAmount = floor($intUnitAmount / max(1, $Unit->containers)); } } // Load attribute groups and attributes $arrAttributeGroups = []; $attributeValues = []; // Parse existing attribute values if available if (!empty($Booking->attribute_values)) { try { $attributeValues = json_decode($Booking->attribute_values, true); if (!is_array($attributeValues)) { $attributeValues = []; } } catch (\Exception $e) { $attributeValues = []; } } // Get selected attributes $selectedAttributeIds = []; if ($Slot->attributes) { $selectedAttributeIds = array_map('trim', explode(',', $Slot->attributes)); } // If no attributes are selected, don't show any if (empty($selectedAttributeIds)) { // Keep the attribute_groups array empty } else { // Fetch the selected attributes $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); if ($selectedAttributes !== null) { // Group the attributes by their parent group $attributesByGroup = []; $groupIds = []; // First pass: collect all group IDs and organize attributes by group foreach ($selectedAttributes as $attribute) { $groupId = $attribute->pid; $groupIds[] = $groupId; if (!isset($attributesByGroup[$groupId])) { $attributesByGroup[$groupId] = []; } $attributesByGroup[$groupId][] = [ 'id' => $attribute->id, 'title' => $attribute->title, 'description' => $attribute->description, 'value' => $attributeValues[$attribute->id] ?? null ]; } // Get unique group IDs $groupIds = array_unique($groupIds); // Fetch the groups if (!empty($groupIds)) { $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); if ($groups !== null) { // Create the attribute groups array foreach ($groups as $group) { if (isset($attributesByGroup[$group->id])) { $arrAttributeGroups[] = [ 'id' => $group->id, 'title' => $group->title, 'description' => $group->description, 'type' => $group->type, 'multiple' => (bool)$group->multiple, 'required' => (bool)$group->required, 'attributes' => $attributesByGroup[$group->id] ]; } } } } } } $standort = $Slot->getRelated('pid'); $disable_base_unit = !empty($standort->disable_base_unit); // Determine default selected unit $defaultUnitId = $Booking->unit ?: ''; if ($defaultUnitId === '' && $disable_base_unit && !empty($arrUnits)) { // If base unit is disabled and no unit is selected, select the first available unit reset($arrUnits); $defaultUnitId = key($arrUnits); } // Adjust quantity options based on default unit if needed $behaelterOptions = $intUnitAmount ? range(1, $intUnitAmount) : []; $arrData = array_merge($arrData,[ 'modal' => $blnModal, 'id' => $Booking->id, 'slot' => array_merge($Slot->row(),[ 'anmerkungen' => $insertTagService->replace($Slot->anmerkungen ?? ''), 'behaelterAvailable' => $intAvailableBehaelter + $Booking->behaelter, ]), 'buchung' => $Booking->row(), 'standort' => $standort, 'buchen' => [ 'buchbar' => (boolean) $intBookableBehaelter, 'default' => $intDefaultAmount, 'behaelter' => $behaelterOptions, 'units' => $arrUnits, 'disable_base_unit' => $disable_base_unit, 'default_unit' => $defaultUnitId ], 'attribute_groups' => $arrAttributeGroups ]); if (!empty($error)) { $arrData['toast'] = $error; } return $this->render('@Contao/modal_booking_details.html.twig',$arrData); } protected function reservate() { Controller::loadDataContainer('tl_vr_wa_reservation'); $arrData = []; if (($Slot = WeinanlieferungSlotsModel::findByPk($_REQUEST['id'])) === null) { return $this->renderDetails(false,'<div class="toast toast--danger mx-0">Fehler: der Zeitslot ist nicht verfügbar.</div>'); } if (($rootPage = Frontend::getRootPageFromUrl()) !== null && !empty($rootPage->vr_wa_uploadFolderSRC)) { $File = new FormFileUpload(\Contao\Widget::getAttributesFromDca($GLOBALS['TL_DCA']['tl_vr_wa_reservation']['fields']['upload'], 'upload')); $File->storeFile = true; $File->doNotOverwrite = true; $File->uploadFolder = $rootPage->vr_wa_uploadFolderSRC; $File->validate(); if ($File->hasErrors()) { return $this->renderDetails(false,'<div class="toast toast--danger mx-0">' . $File->getErrorAsHTML() . '</div>'); } if (!empty($_SESSION['FILES'][$File->name])) { $arrData['filename'] = $_SESSION['FILES'][$File->name]['name'] ?? ''; $arrData['upload'] = $_SESSION['FILES'][$File->name]['uuid'] ? StringUtil::uuidToBin($_SESSION['FILES'][$File->name]['uuid']) : null; } } if (empty($_REQUEST['id'])) { return new Response('Missing parameter',412); } // Form validation $intBehaelter = Input::post('behaelter'); if (Input::post('behaelter') > $Slot->getAvailableBehaelter(true)) { return $this->renderDetails(false,sprintf('<div class="toast toast--danger mx-0">Fehler: Es sind mittlerweile nur noch %s Bottiche verfügbar.</div>',$Slot->getAvailableBehaelter(true))); } $arrError = []; foreach (['behaelter'] as $field) { if (empty(Input::post($field))) { $arrError = [$field]; } } // Get allowed units if (Input::post('unit')) { if (($SelectedUnit = WeinanlieferungUnitsModel::findByPk(intval(Input::post('unit')))) === null) { return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht zur Verfügung. Bitte versuchen Sie es noch einmal.</div>'); } if (($Site = $Slot->getRelated('pid')) !== null) { $unitIds = StringUtil::deserialize($Site->units, true); $Units = WeinanlieferungUnitsModel::findMultipleByIds($unitIds); if (!\in_array(intval(Input::post('unit')), $Units->fetchEach('id'))) { return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht mehr zur Verfügung. Bitte versuchen Sie es noch einmal.</div>'); } } $intBehaelter = intval($intBehaelter) * intval($SelectedUnit->containers); } // Process attribute values $attributeValues = []; // Get selected attributes $selectedAttributeIds = []; if ($Slot->attributes) { $selectedAttributeIds = array_map('trim', explode(',', $Slot->attributes)); } // If no attributes are selected, skip attribute processing if (!empty($selectedAttributeIds)) { // Fetch the selected attributes $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); if ($selectedAttributes !== null) { // Group the attributes by their parent group $attributesByGroup = []; $groupIds = []; // First pass: collect all group IDs foreach ($selectedAttributes as $attribute) { $groupId = $attribute->pid; $groupIds[] = $groupId; } // Get unique group IDs $groupIds = array_unique($groupIds); // Fetch the groups if (!empty($groupIds)) { $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); if ($groups !== null) { // Create maps for attribute-to-group, group-required status, and group-type $attributeToGroupMap = []; $groupRequiredMap = []; $groupTypeMap = []; foreach ($groups as $group) { $groupRequiredMap[$group->id] = (bool)$group->required; $groupTypeMap[$group->id] = $group->type; } foreach ($selectedAttributes as $attribute) { $attributeToGroupMap[$attribute->id] = $attribute->pid; } // Track which groups have values $groupHasValue = []; // Process the selected attributes foreach ($selectedAttributes as $attribute) { $attributeKey = 'attribute_' . $attribute->id; $groupId = $attributeToGroupMap[$attribute->id]; $groupType = $groupTypeMap[$groupId]; $groupKey = 'group_' . $groupId; if ($groupType === 'text') { // For text attributes, store the text value if (!empty(Input::post($attributeKey))) { $attributeValues[$attribute->id] = Input::post($attributeKey); $groupHasValue[$groupId] = true; } } else if ($groupType === 'option') { // For option attributes with multiple values (checkboxes) if (Input::post($attributeKey)) { $attributeValues[$attribute->id] = true; $groupHasValue[$groupId] = true; } // For option attributes with single value (radio buttons) else if (Input::post($groupKey) == $attribute->id) { $attributeValues[$attribute->id] = true; $groupHasValue[$groupId] = true; } } } // Check if required groups have values foreach ($groupRequiredMap as $groupId => $isRequired) { if ($isRequired && empty($groupHasValue[$groupId])) { $arrError[] = 'attribute_group_' . $groupId; } } } } } } if (count($arrError)) { return $this->renderDetails(false,'<div class="toast toast--danger mx-0">Bitte geben Sie alle Pflichtangaben (mit * markierte Felder) an</div>'); } $Reservation = new WeinanlieferungReservationModel(); $arrData = array_merge($arrData,[ 'pid' => $_REQUEST['id'], 'tstamp' => time(), 'uid' => FrontendUser::getInstance()->id, 'unit' => intval(Input::post('unit')), 'behaelter' => $intBehaelter, 'amount' => Input::post('behaelter'), 'annotation' => Input::post('annotation'), 'attribute_values' => !empty($attributeValues) ? json_encode($attributeValues) : null, ]); $Reservation->setRow($arrData); $Reservation->save(); return new Response('<div class="toast toast--success mx-0"><p>Reservierung erfolgreich</p></div>',200,['HX-Trigger'=> 'updateWaList']); } protected function updateReservation() { Controller::loadDataContainer('tl_vr_wa_reservation'); if (empty($_REQUEST['id'])) { return new Response('Missing parameter',412); } if (($Reservation = WeinanlieferungReservationModel::findById($_REQUEST['id'])) === null) { return $this->renderBooking(false,'<div class="toast toast--danger mx-0">Fehler: Buchung kann nicht geladen werden.</div>'); } if (($Slot = $Reservation->getRelated('pid')) === null) { return $this->renderBooking(false,'<div class="toast toast--danger mx-0">Fehler: der Zeitslot ist nicht verfügbar.</div>'); } if (($rootPage = Frontend::getRootPageFromUrl()) !== null && !empty($rootPage->vr_wa_uploadFolderSRC)) { $File = new FormFileUpload(\Contao\Widget::getAttributesFromDca($GLOBALS['TL_DCA']['tl_vr_wa_reservation']['fields']['upload'], 'upload')); $File->storeFile = true; $File->doNotOverwrite = true; $File->uploadFolder = $rootPage->vr_wa_uploadFolderSRC; $File->validate(); if ($File->hasErrors()) { return $this->renderBooking(false,'<div class="toast toast--danger mx-0">' . $File->getErrorAsHTML() . '</div>'); } if (!empty($_SESSION['FILES'][$File->name])) { $Reservation->filename = $_SESSION['FILES'][$File->name]['name'] ?? ''; $Reservation->upload = $_SESSION['FILES'][$File->name]['uuid'] ? StringUtil::uuidToBin($_SESSION['FILES'][$File->name]['uuid']) : null; } } // Form validation $intBehaelter = Input::post('behaelter'); if (Input::post('behaelter') > $Slot->getAvailableBehaelter(true)+$Reservation->behaelter) { return $this->renderBooking(false,sprintf('<div class="toast toast--danger mx-0">Fehler: Es sind mittlerweile nur noch %s Bottiche verfügbar.</div>',$Slot->getAvailableBehaelter(true)+$Reservation->behaelter)); } $arrError = []; foreach (['behaelter'] as $field) { if (empty(Input::post($field))) { $arrError = [$field]; } } // Get allowed units if (Input::post('unit')) { if (($SelectedUnit = WeinanlieferungUnitsModel::findByPk(intval(Input::post('unit')))) === null) { return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht zur Verfügung. Bitte versuchen Sie es noch einmal.</div>'); } if (($Site = $Slot->getRelated('pid')) !== null) { $unitIds = StringUtil::deserialize($Site->units, true); $Units = WeinanlieferungUnitsModel::findMultipleByIds($unitIds); if (!\in_array(intval(Input::post('unit')), $Units->fetchEach('id'))) { return $this->renderDetails(false, '<div class="toast toast--danger mx-0">Die gewünschte Botticheinheit steht nicht mehr zur Verfügung. Bitte versuchen Sie es noch einmal.</div>'); } } $intBehaelter = intval($intBehaelter) * intval($SelectedUnit->containers); } // Process attribute values $attributeValues = []; // Get selected attributes $selectedAttributeIds = []; if ($Slot->attributes) { $selectedAttributeIds = array_map('trim', explode(',', $Slot->attributes)); } // If no attributes are selected, skip attribute processing if (!empty($selectedAttributeIds)) { // Fetch the selected attributes $selectedAttributes = WeinanlieferungAttributeModel::findMultipleByIds($selectedAttributeIds); if ($selectedAttributes !== null) { // Group the attributes by their parent group $attributesByGroup = []; $groupIds = []; // First pass: collect all group IDs foreach ($selectedAttributes as $attribute) { $groupId = $attribute->pid; $groupIds[] = $groupId; } // Get unique group IDs $groupIds = array_unique($groupIds); // Fetch the groups if (!empty($groupIds)) { $groups = WeinanlieferungAttributeGroupModel::findMultipleByIds($groupIds); if ($groups !== null) { // Create maps for attribute-to-group, group-required status, and group-type $attributeToGroupMap = []; $groupRequiredMap = []; $groupTypeMap = []; foreach ($groups as $group) { $groupRequiredMap[$group->id] = (bool)$group->required; $groupTypeMap[$group->id] = $group->type; } foreach ($selectedAttributes as $attribute) { $attributeToGroupMap[$attribute->id] = $attribute->pid; } // Track which groups have values $groupHasValue = []; // Process the selected attributes foreach ($selectedAttributes as $attribute) { $attributeKey = 'attribute_' . $attribute->id; $groupId = $attributeToGroupMap[$attribute->id]; $groupType = $groupTypeMap[$groupId]; $groupKey = 'group_' . $groupId; if ($groupType === 'text') { // For text attributes, store the text value if (!empty(Input::post($attributeKey))) { $attributeValues[$attribute->id] = Input::post($attributeKey); $groupHasValue[$groupId] = true; } } else if ($groupType === 'option') { // For option attributes with multiple values (checkboxes) if (Input::post($attributeKey)) { $attributeValues[$attribute->id] = true; $groupHasValue[$groupId] = true; } // For option attributes with single value (radio buttons) else if (Input::post($groupKey) == $attribute->id) { $attributeValues[$attribute->id] = true; $groupHasValue[$groupId] = true; } } } // Check if required groups have values foreach ($groupRequiredMap as $groupId => $isRequired) { if ($isRequired && empty($groupHasValue[$groupId])) { $arrError[] = 'attribute_group_' . $groupId; } } } } } } if (count($arrError)) { return $this->renderBooking(false,'<div class="toast toast--danger mx-0">Bitte geben Sie alle Pflichtangaben (mit * markierte Felder) an</div>'); } $Reservation->tstamp = time(); $Reservation->unit = intval(Input::post('unit')); $Reservation->behaelter = $intBehaelter; $Reservation->amount = Input::post('behaelter'); $Reservation->annotation = Input::post('annotation'); $Reservation->attribute_values = !empty($attributeValues) ? json_encode($attributeValues) : null; $Reservation->save(); return new Response('<div class="toast toast--success mx-0"><p>Reservierung erfolgreich geändert</p></div>',200,['HX-Trigger'=> 'updateWaBooking']); } protected function deleteReservation() { if (empty($_REQUEST['id'])) { return new Response('Missing parameter',412); } /** @var Connection $db */ $db = Controller::getContainer()->get('database_connection'); $arrCriteria = [ 'uid' => FrontendUser::getInstance()->id, 'id' => $_REQUEST['id'] ]; if ($db->delete('tl_vr_wa_reservation',$arrCriteria)) { return new Response(null,203,['HX-Trigger'=> 'updateWaBooking']); } return new Response('Could not delete',500); } protected function renderUnauthorized() { return $this->render('@Contao/modal_unauthorized.html.twig'); } protected function renderAvailableUnitAmount(): Response { if (empty($_REQUEST['id'])) { return new Response('Required parameter missing',412); } if (($Slot = WeinanlieferungSlotsModel::findPublishedById($_REQUEST['id'])) === null) { return new Response('Could not load slot data',500); } $intDefault = max(0,$Slot->getAvailableBehaelter()); $intAmount = $intDefault; if ($intDefault > 0) { $intOvercapacityBehaelter = $Slot->getOvercapacityBehaelter(); $intAmount += $intOvercapacityBehaelter; } if (!empty($_REQUEST['unit']) && intval($_REQUEST['unit']) > 0) { if (($Site = $Slot->getRelated('pid')) === null || ($Unit = WeinanlieferungUnitsModel::findByPk($_REQUEST['unit'])) === null) { return new Response('Could not load unit data', 500); } $intDefault = floor($intDefault / max(1, $Unit->containers)); $intAmount = floor($intAmount / max(1, $Unit->containers)); } $strOutput = "<select id=\"res-behaelter\" name=\"behaelter\" required> <option value=\"\">-</option>\n"; for ($i = 1; $i <= $intAmount; $i++) { if ($intDefault < $i) { $strOutput .= "<option value=\"$i\">$i (".$this->translator->trans('MSC.wa_overbooking',[], 'contao_default').")</option>\n"; } else { $strOutput .= "<option value=\"$i\">$i</option>\n"; } } $strOutput .= "</select>\n"; return new Response($strOutput); } protected function renderAvailableBookingUnitAmount(): Response { if (empty($_REQUEST['id'])) { return new Response('Required parameter missing',412); } if (($Reservation = WeinanlieferungReservationModel::findByPk($_REQUEST['id'])) === null || ($Slot = $Reservation->getRelated('pid')) === null) { return new Response('Could not load slot data',500); } $intDefault = max(0,$Slot->getAvailableBehaelter())+$Reservation->behaelter; $intAmount = $intDefault; if ($intDefault > 0) { $intOvercapacityBehaelter = $Slot->getOvercapacityBehaelter(); $intAmount += $intOvercapacityBehaelter; } if (!empty($_REQUEST['unit']) && intval($_REQUEST['unit']) > 0) { if (($Site = $Slot->getRelated('pid')) === null || ($Unit = WeinanlieferungUnitsModel::findByPk($_REQUEST['unit'])) === null) { return new Response('Could not load unit data', 500); } $intDefault = floor($intDefault / max(1, $Unit->containers)); $intAmount = floor($intAmount / max(1, $Unit->containers)); } $strOutput = "<select id=\"res-behaelter\" name=\"behaelter\" required> <option value=\"\">-</option>\n"; for ($i = 1; $i <= $intAmount; $i++) { if ($intDefault < $i) { $strOutput .= "<option value=\"$i\">$i (".$this->translator->trans('MSC.wa_overbooking',[], 'contao_default').")</option>\n"; } else { $strOutput .= "<option value=\"$i\">$i</option>\n"; } } $strOutput .= "</select>\n"; return new Response($strOutput); } }