twig = $twig; $this->tokenManager = $tokenManager; $this->request = $requestStack->getCurrentRequest(); $this->db = $db; $this->translator = $translator; $container = System::getContainer(); $objSession = $container->get('session'); $strKey = Input::get('popup') ? 'popupReferer' : 'referer'; $strRefererId = $this->request->attributes->get('_contao_referer_id'); $session = $objSession->get($strKey); $session[$strRefererId]['current'] = substr(Environment::get('requestUri'), \strlen(Environment::get('path')) + 1); $objSession->set($strKey, $session); Controller::loadDataContainer('tl_vr_wa_slot'); System::loadLanguageFile('tl_vr_wa_slot'); } public function __invoke(): Response { System::loadLanguageFile('default'); System::loadLanguageFile('tl_vr_wa_slotassistant'); Controller::loadDataContainer('tl_vr_wa_slot'); Controller::loadDataContainer('tl_vr_wa_slotassistant'); $GLOBALS['TL_CSS']['cirrus'] = 'bundles/vonrotenbergweinanlieferung/css/backend.css|static'; $arrData = [ 'request_token' => $this->tokenManager->getDefaultTokenValue(), 'ref' => $this->request->attributes->get('_contao_referer_id') ]; // Handle ajax request pre actions if ($_POST && Environment::get('isAjaxRequest')) { $this->objAjax = new Ajax(Input::post('action')); $this->objAjax->executePreActions(); } // Form submitted if (Input::post('FORM_SUBMIT') == 'wa_slotassistant') { $this->generateSlots(); } $arrData['fields'] = $this->getWidgets(); $arrData['messages'] = Message::generateUnwrapped() . Backend::getSystemMessages(); // Handle ajax request post actions if ($_POST && Environment::get('isAjaxRequest')) { $dc = new DC_File('tl_vr_wa_slotassistant'); $this->objAjax->executePostActions($dc); } return new Response( $this->twig->render( '@Contao_VonrotenbergWeinanlieferungBundle/be_wa_slotassistant.html.twig', $arrData ) ); } protected function getWidgets() { if (!empty($this->fields)) { return $this->fields; } $arrWidgets = [ 'base_legend' => [ [ 'class' => 'w50', 'help' => 'Legen Sie hier den Zeitslot-Typ/Annahmestelle fest.', 'widget' => new SelectMenu([ 'name' => 'slotType', 'mandatory' => true, 'options' => $this->getSlottypeOptions(), 'label' => 'Zeitslot-Typ', ]) ], [ 'class' => 'w50', 'help' => 'Die Pufferzeit zwischen den Slots.', 'widget' => new TextField([ 'name' => 'buffer', 'mandatory' => true, 'label' => 'Pufferzeit in min.', 'rgxp' => 'natural' ]) ] ], 'time_legend' => [ /*[ 'class' => 'w50 wizard', 'help' => 'Der Zeitpunkt ab wann der erste Zeitslot angelegt werden soll.', 'widget' => new TextField([ 'name' => 'date_start[1]', 'mandatory' => true, 'label' => 'Zeitraum Beginn', 'rgxp' => 'datim', 'datepicker' => true, ]) ], [ 'class' => 'w50 wizard', 'help' => 'Der Zeitpunkt bis wann der letzte Zeitslot abgeschlossen sein muss.', 'widget' => new TextField([ 'name' => 'date_end[1]', 'mandatory' => true, 'label' => 'Zeitraum Ende', 'rgxp' => 'datim', 'datepicker' => true, ]) ],*/ [ 'class' => 'clr', 'help' => 'Die Zeiträume, für die neue Slots angelegt werden sollen.', 'widget' => new MultiColumnWizard([ 'name' => 'timeframe', 'label' => 'Zeiträume', 'minCount' => 1, 'columnFields' => [ 'date_start' => [ 'label' => $GLOBALS['TL_LANG']['tl_vr_wa_slotassistant']['date_start'], 'inputType' => 'text', 'eval' => [ 'style' => 'width:150px', 'mandatory' => true, 'rgxp' =>'datim', 'datepicker' => true ] ], 'date_end' => [ 'label' => $GLOBALS['TL_LANG']['tl_vr_wa_slotassistant']['date_end'], 'inputType' => 'text', 'eval' => [ 'style' => 'width:150px', 'mandatory' => true, 'rgxp' =>'datim', 'datepicker' => true ] ] ] ]) ] ], 'slot_legend' => [ /*[ 'class' => 'clr', 'help' => 'Wählen Sie die Attribute aus, die für diesen Slot gelten sollen.', 'widget' => new CheckBox([ 'name' => 'attributes', 'mandatory' => false, 'label' => 'Attribute', 'options_callback' => array('vonRotenberg\WeinanlieferungBundle\EventListener\DataContainer\WeinanlieferungSlotContainerListener', 'getAttributeOptions'), ]) ],*/ [ 'class' => 'clr', 'help' => 'Wählen Sie die Attribute aus, die für diesen Slot gelten sollen.', 'widget' => new CheckBox(CheckBox::getAttributesFromDca($GLOBALS['TL_DCA']['tl_vr_wa_slot']['fields']['attributes'],'attributes',null,'Attribute','tl_vr_wa_slot',new DC_Table('tl_vr_wa_slot'))) ], [ 'class' => 'w50 wizard', 'help' => 'Der Beginn des Zeitraums, ab wann der Slot im Vorfeld gebucht werden kann.', 'widget' => new TextField([ 'name' => 'bookableFrom', 'mandatory' => true, 'label' => 'Buchbar ab', 'rgxp' => 'datim', 'datepicker' => true ]) ], [ 'class' => 'w50', 'help' => 'Das Ende des Zeitraums, bis wann der Slot im Vorfeld gebucht werden kann.', 'widget' => new TextField([ 'name' => 'bookableTill', 'mandatory' => true, 'label' => 'Buchbar bis in h', 'rgxp' => 'natural' ]) ], [ 'class' => 'clr', 'help' => 'Anmerkungen zum Zeitslot. Diese werden bei der Buchung angezeigt.', 'widget' => new TextArea([ 'name' => 'annotation', 'mandatory' => false, 'label' => 'Anmerkungen', 'rte' => 'tinyMCE' ]) ], [ 'class' => 'clr', 'help' => 'Anmerkungen zum Zeitslot. Diese werden bei der Buchung angezeigt.', 'widget' => new FileTree([ 'name' => 'enclosure', 'mandatory' => false, 'label' => 'Anmerkungen', 'multiple' => true, 'fieldType' => 'checkbox', 'filesOnly' => true, 'isDownloads' => true ]) ] ] ]; foreach ($arrWidgets as $group => $fields) { foreach ($fields as $field) { if (empty($field['widget']->id)) { $field['widget']->id = $field['widget']->name; } if (empty($field['widget']->table)) { $field['widget']->strTable = 'tl_vr_wa_slotassistant'; } $wizard = ''; // Date picker if ($field['widget']->datepicker ?? null) { $rgxp = $field['widget']->rgxp ?? 'date'; $format = Date::formatToJs(Config::get($rgxp . 'Format')); switch ($rgxp) { case 'datim': $time = ",\n timePicker: true"; break; case 'time': $time = ",\n pickOnly: \"time\""; break; default: $time = ''; break; } $strOnSelect = ''; // Trigger the auto-submit function (see #8603) if ($field['widget']->submitOnChange ?? null) { $strOnSelect = ",\n onSelect: function() { Backend.autoSubmit(\"wa_slotassistant\"); }"; } $wizard .= ' ' . Image::getHtml('assets/datepicker/images/icon.svg', '', 'title="' . StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['datepicker']) . '" id="toggle_' . $field['widget']->id . '" style="cursor:pointer"') . ' '; $field['widget']->wizard = $wizard; } // Replace the textarea with an RTE instance if (!empty($field['widget']->rte)) { list($file, $type) = explode('|', $field['widget']->rte, 2) + [null, null]; $fileBrowserTypes = []; $pickerBuilder = System::getContainer()->get('contao.picker.builder'); foreach (['file' => 'image', 'link' => 'file'] as $context => $fileBrowserType) { if ($pickerBuilder->supportsContext($context)) { $fileBrowserTypes[] = $fileBrowserType; } } $objTemplate = new BackendTemplate('be_' . $file); $objTemplate->selector = 'ctrl_' . $field['widget']->id; $objTemplate->type = $type; $objTemplate->fileBrowserTypes = $fileBrowserTypes; $objTemplate->source = 'wa_slotassistant.' . $field['widget']->id; $objTemplate->readonly = (bool)($field['widget']->readonly ?? false); // Deprecated since Contao 4.0, to be removed in Contao 5.0 $objTemplate->language = Backend::getTinyMceLanguage(); $field['widget']->updateMode = $objTemplate->parse(); unset($file, $type, $pickerBuilder, $fileBrowserTypes, $fileBrowserType); } } } $this->fields = $arrWidgets; return $this->fields; } protected function getSlottypeOptions() { $arrOptions = [['value'=>'','label'=>'-']]; $Slottypes = $this->db->prepare("SELECT t.id, t.title, t.containers, t.duration, t.`default`, s.title as 'standort' FROM tl_vr_wa_slottypes t INNER JOIN tl_vr_wa_standort s ON s.id = t.pid ORDER BY t.`default` DESC, t.title")->executeQuery(); $arrOptGroups = []; if ($Slottypes->rowCount() > 0) { foreach ($Slottypes->fetchAllAssociative() as $option) { $arrOptGroups[$option['standort']][] = [ 'value' => $option['id'], 'label' => $option['title'].' ['.$this->translator->trans('MSC.wa_container', [], 'contao_tl_vr_wa_units').': '.$option['containers'].'] ['.$this->translator->trans('MSC.wa_duration', [], 'contao_tl_vr_wa_units').': '.$option['duration'].' '.($option['duration'] == 1 ? $this->translator->trans('MSC.wa_duration_minute', [], 'contao_tl_vr_wa_units') : $this->translator->trans('MSC.wa_duration_minutes', [], 'contao_tl_vr_wa_units')).'] ['.$option['standort'].']' . ($option['default'] ? ' (Standard)' : '') ]; } } ksort($arrOptGroups); $arrOptions = array_merge($arrOptions, $arrOptGroups); return $arrOptions; } protected function validateForm() { $blnSubmit = false; if (Input::post('FORM_SUBMIT') == 'wa_slotassistant') { $blnSubmit = true; foreach ($this->getWidgets() as $group => $fields) { foreach ($fields as $field) { $field['widget']->validate(); if ($field['widget']->hasErrors()) { $blnSubmit = false; } } } } return $blnSubmit; } protected function generateSlots() { if (!Input::post('FORM_SUBMIT') == 'wa_slotassistant' || !$this->validateForm()) { return; } if ( ($SlotType = WeinanlieferungSlottypesModel::findByPk(Input::post('slotType'))) === null || ($Standort = $SlotType->getRelated('pid')) === null || !\is_array(Input::post('timeframe')) ) { return; } $now = time(); $intNewSlots = 0; $intBuffer = intval(Input::post('buffer')) * 60; $intDuration = (int) $SlotType->duration*60; // Get timespans foreach (Input::post('timeframe') as $timeframe) { if ( !isset($timeframe['date_start']) || !isset($timeframe['date_end']) ) { continue; } $StartDate = new Date($timeframe['date_start'], Date::getNumericDatimFormat()); $EndDate = new Date($timeframe['date_end'], Date::getNumericDatimFormat()); $intStart = $StartDate->tstamp; $intEnd = $EndDate->tstamp; $arrTimes = []; $arrStartTimes = []; $arrEndTimes = []; // Get existing slots in the timeframe $ExistingSlots = $this->db->prepare("SELECT id, date, time as 'start', (time+duration*60) as 'end', duration FROM tl_vr_wa_slot WHERE pid = ? AND (time >= ? OR time+$intDuration+$intBuffer >= ?) AND (time <= ? OR time+$intDuration+$intBuffer <= ?) ORDER BY time") ->executeQuery([$Standort->id, $intStart, $intStart, $intEnd, $intEnd]); if ($ExistingSlots->rowCount()) { foreach ($ExistingSlots->fetchAllAssociative() as $slot) { $arrTimes[$slot['start']] = [ 'start' => $slot['start'], 'end' => $slot['end'] ]; $arrStartTimes[] = $slot['start']; $arrEndTimes[] = $slot['end']; } } // Create all possible timeslots for given timeframe $intTime = $intStart; while ($intTime < $intEnd-$intDuration) { $arrConflicts = []; foreach ($arrStartTimes as $check_start) { $checkslot = $arrTimes[$check_start]; if ( ( $intTime >= $checkslot['start'] && $intTime <= $checkslot['end'] ) || ( $intTime + $intDuration + $intBuffer > $checkslot['start'] && $intTime + $intDuration + $intBuffer < $checkslot['end'] ) ) { $arrConflicts[] = $checkslot['end']; } } if (\count($arrConflicts) > 0) { $intTime = max($arrConflicts) + $intBuffer; continue; } $NewDateTime = new Date($intTime); $NewSlot = new WeinanlieferungSlotsModel(); $bookableFrom = new Date(Input::post('bookableFrom'), Date::getNumericDatimFormat()); $NewSlot->setRow([ 'pid' => $SlotType->pid, 'tstamp' => $now, 'date' => $NewDateTime->dayBegin, 'time' => $intTime, 'duration' => $intDuration/60, 'behaelter' => $SlotType->containers, 'overcapacity' => $SlotType->overcapacity, 'anmerkungen' => Input::post('annotation'), 'attributes' => \implode(',',\is_array(Input::post('attributes')) ? Input::post('attributes') : []), 'buchbar_ab' => (int) $bookableFrom->tstamp, 'buchbar_bis' => (int) $intTime-intval(Input::post('bookableTill'))*3600, 'published' => '1' ]); if (Input::post('enclosure')) { $NewSlot->addEnclosure = '1'; $NewSlot->enclosure = explode(',',Input::post('enclosure')); } $NewSlot->save(); $intNewSlots++; $intTime = $intTime + $intDuration + $intBuffer; } } Message::addConfirmation(sprintf('Es wurden %s neue Zeitslots angelegt.',$intNewSlots)); Backend::reload(); } }