<?php declare(strict_types=1);
namespace Cogi\CogiAdvancedDeliveryDate\Subscriber;
use Exception;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Validation\BuildValidationEvent;
use Shopware\Core\Framework\Validation\DataValidationDefinition;
use Shopware\Core\Framework\Validation\DataValidator;
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
use Shopware\Core\PlatformRequest;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
class CheckoutSubscriber implements EventSubscriberInterface {
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* @var Session
*/
private $session;
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var DataValidator
*/
private $validator;
/**
* CheckoutSubscriber constructor.
* @param SystemConfigService $systemConfigService
* @param Session $session
* @param RequestStack $requestStack
* @param DataValidator $validator
*/
public function __construct(SystemConfigService $systemConfigService, Session $session, RequestStack $requestStack, DataValidator $validator) {
$this->systemConfigService = $systemConfigService;
$this->session = $session;
$this->requestStack = $requestStack;
$this->validator = $validator;
}
public static function getSubscribedEvents(): array {
return [
CheckoutConfirmPageLoadedEvent::class => 'onConfirmPageLoaded',
ProductPageLoadedEvent::class => 'onConfirmPageLoaded',
'framework.validation.order.create' => 'onValidate'
];
}
/**
* Event für Confirm Seite
*
* @param CheckoutConfirmPageLoadedEvent|ProductPageLoadedEvent $event
* @throws Exception
*/
public function onConfirmPageLoaded($event) {
/** @var CheckoutConfirmPageLoadedEvent|ProductPageLoadedEvent $page */
$page = $event->getPage();
$config = $this->systemConfigService->get('CogiAdvancedDeliveryDate.config', $event->getSalesChannelContext()->getSalesChannel()->getId());
$output['salesChannelUrl'] = $event->getSalesChannelContext()->getSalesChannel()->getDomains()->first()->getUrl();
$output['savedDate'] = $this->session->get('cogiAdvancedDeliveryDate', null);
$output['showCalendarWeeks'] = $config['showCalendarWeeks'] ? 'true' : 'false';
$output['daysOfWeekDisabled'] = $config['daysOfWeekDisabled'] ?? [];
$startTime = time() + ($config['minDays'] * 86400);
// skip to next day if hour is exceeded
if (isset($config['timeExceeded']) && strtotime(date('H:i')) > strtotime(date($config['timeExceeded']))) {
$startTime += 86400;
}
// check if the start day collisions with any disabled day of the week
$currentStartDay = date("w", $startTime);
$modifiedDate = false;
while (in_array($currentStartDay, $output['daysOfWeekDisabled'])) {
$startTime += 86400;
$currentStartDay = date("w", $startTime);
$modifiedDate = true;
}
// skip to next day if hour is exceeded
if ($modifiedDate && isset($config['timeExceeded']) && strtotime(date('H:i')) > strtotime(date($config['timeExceeded']))) {
$startTime += 86400;
}
// if pickup is enabled, set different settings
if (method_exists($page, "getCart") && $this->isPickupActive($config, $page->getCart()->getDeliveries()->getElements()[0]->getShippingMethod()->getId())) {
// pickup datepicker
$output['endDate'] = date('d.m.Y', time() + ($config['pickupMaxDays'] * 86400));
$output['startDate'] = date('d.m.Y', time());
$output['daysOfWeekDisabled'] = $config['pickupDaysOfWeekDisabled'] ?? [];
$output['savedHour'] = $this->session->get('cogiAdvancedDeliveryPickupHour', null);
// check already saved hour (in session) if it fits current settings
if ($output['savedHour']) {
if (date("H:i", time() + $config['pickupPreparationTime'] * 60) > $output['savedHour']) {
$output['savedHour'] = null;
$this->session->set('cogiAdvancedDeliveryPickupHour', null);
}
}
$output['pickupEnabled'] = true;
$output['pickupHourMin'] = date("H:i", strtotime($config['pickupHourMin']));
$output['pickupHourMax'] = date("H:i", strtotime($config['pickupHourMax']));
$output['pickupHours'] = array();
$output['disabledDates'] = $this->getDisabledDays($config, true);
$pickupIsToday = false;
// check if pickup date is today
if ($output['savedDate'] === date("d.m.Y"))
$pickupIsToday = true;
// iterate hours for the pickup hourpicker
for ($hour = (int)date("H", strtotime($config['pickupHourMin'])); $hour < (int)date("H", strtotime($config['pickupHourMax'])); $hour++) {
if ($hour < 10) {
$hour = "0{$hour}";
}
// iterate minutes
for ($minute = 0; $minute < 59; $minute++) {
// continue current number if item isnt a step
if ($minute % $config['pickupHourStep'] !== 0) {
continue;
}
// add prepending zero
if ($minute < 10) {
$minute = "0{$minute}";
}
if ($pickupIsToday && date("H:i", time() + $config['pickupPreparationTime'] * 60) > "{$hour}:{$minute}") {
continue;
}
$output['pickupHours'][] = "{$hour}:{$minute}";
}
}
} else {
// normal datepicker
$output['pickupEnabled'] = false;
$output['endDate'] = date('d.m.Y', time() + ($config['maxDays'] * 86400));
$output['startDate'] = date('d.m.Y', $startTime);
$output['disabledDates'] = $this->getDisabledDays($config);
}
$page->addExtension('CogiAdvancedDelivery', new ArrayEntity($output));
}
/**
* @param array $config
* @param $currentShippingMethodId
* @return bool
*/
private function isPickupActive(array $config, $currentShippingMethodId) {
return $config['pickupActive'] === true && $currentShippingMethodId === $config['pickupShippingMethod'];
}
private function getDisabledDays(array $config, bool $pickup = false) {
$disabledHolidayDates = [];
$haystack = $config['holidayDatesDisabled'] ?? [];
if ($pickup) {
$haystack = $config['pickupHolidayDatesDisabled'] ?? [];
}
foreach (json_decode(file_get_contents(__DIR__ . '/../Resources/holidays.json')) as $key => $item) {
if (in_array($item, $haystack)) {
$disabledHolidayDates[] = date('d.m.Y', strtotime($key));
}
}
$datesDisabled = explode("\n", $config['datesDisabled'] ?? "");
if ($pickup)
$datesDisabled = explode("\n", $config['pickupDatesDisabled'] ?? "");
return array_merge($disabledHolidayDates, $datesDisabled);
}
public function onValidate(BuildValidationEvent $event) {
$salesChannelId = $this->requestStack->getCurrentRequest()->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
$config = $this->systemConfigService->get('CogiAdvancedDeliveryDate.config', $salesChannelId);
$currentShippingMethodId = $this->requestStack->getCurrentRequest()->attributes->get('sw-sales-channel-context')->getShippingMethod()->get('id');
// return if config option is not existant or false
if (!isset($config['isRequired']) || $config['isRequired'] === false) return;
// return if the current shipping method is a disabled one
if (in_array($currentShippingMethodId, $config['disabledShippingMethods'] ?? [])) return;
$data['cogiAdvancedDeliveryDate'] = $this->session->get('cogiAdvancedDeliveryDate', null);
$data['cogiAdvancedDeliveryPickupHour'] = $this->session->get('cogiAdvancedDeliveryPickupHour', null);
// validate
$definition = new DataValidationDefinition('checkout.cogiAdvancedDeliveryDate');
$definition->add('cogiAdvancedDeliveryDate', new NotBlank());
// pickup is active, add another validation rule
if ($this->isPickupActive($config, $currentShippingMethodId)) {
$definition->add('cogiAdvancedDeliveryPickupHour', new NotBlank());
}
$violations = $this->validator->getViolations($data, $definition);
if (!$violations->count()) return;
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'Delivery Date is missing',
'Delivery Date is missing',
[],
'',
'cogiAdvancedDeliveryDate',
$data['cogiAdvancedDeliveryDate'],
null,
'cogiAdvancedDeliveryDate-missing'
));
throw new ConstraintViolationException($violations, $data);
}
}