shell bypass 403
<?php
namespace AmeliaBooking\Domain\Services\TimeSlot;
use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
use AmeliaBooking\Domain\Entity\Booking\Appointment\Appointment;
use AmeliaBooking\Domain\Entity\Booking\Appointment\CustomerBooking;
use AmeliaBooking\Domain\Entity\Booking\SlotsEntities;
use AmeliaBooking\Domain\Entity\Schedule\DayOff;
use AmeliaBooking\Domain\Entity\Bookable\Service\Service;
use AmeliaBooking\Domain\Entity\User\Provider;
use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
use AmeliaBooking\Domain\Collection\Collection;
use AmeliaBooking\Domain\Services\Entity\EntityService;
use AmeliaBooking\Domain\Services\Interval\IntervalService;
use AmeliaBooking\Domain\Services\Resource\AbstractResourceService;
use AmeliaBooking\Domain\Services\Schedule\ScheduleService;
use AmeliaBooking\Domain\Services\User\ProviderService;
use AmeliaBooking\Domain\ValueObjects\String\BookingStatus;
use AmeliaBooking\Domain\ValueObjects\String\Status;
use DateInterval;
use DatePeriod;
use DateTime;
use DateTimeZone;
use Exception;
/**
* Class TimeSlotService
*
* @package AmeliaBooking\Domain\Services\TimeSlot
*/
class TimeSlotService
{
/** @var IntervalService */
private $intervalService;
/** @var ScheduleService */
private $scheduleService;
/** @var ProviderService */
private $providerService;
/** @var AbstractResourceService */
private $resourceService;
/** @var EntityService */
private $entityService;
/**
* TimeSlotService constructor.
*
* @param IntervalService $intervalService
* @param ScheduleService $scheduleService
* @param ProviderService $providerService
* @param AbstractResourceService $resourceService
* @param EntityService $entityService
*/
public function __construct(
IntervalService $intervalService,
ScheduleService $scheduleService,
ProviderService $providerService,
AbstractResourceService $resourceService,
EntityService $entityService
) {
$this->intervalService = $intervalService;
$this->scheduleService = $scheduleService;
$this->providerService = $providerService;
$this->resourceService = $resourceService;
$this->entityService = $entityService;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* get appointment intervals for provider.
*
* @param array $weekDaysIntervals
* @param array $intervals
* @param string $dateString
* @param int $start
* @param int $end
* @return array
*/
private function getModifiedEndInterval($weekDaysIntervals, &$intervals, $dateString, $start, $end)
{
$dayIndex = DateTimeService::getDayIndex($dateString);
if (
isset($weekDaysIntervals[$dayIndex]['busy'][$start]) &&
$weekDaysIntervals[$dayIndex]['busy'][$start][1] > $end
) {
$end = $weekDaysIntervals[$dayIndex]['busy'][$start][1];
}
if (
isset($intervals[$dateString]['occupied'][$start]) &&
$intervals[$dateString]['occupied'][$start][1] > $end
) {
$end = $intervals[$dateString]['occupied'][$start][1];
}
return $end;
}
/**
* Split start and end in array of dates.
*
* @param DateTime $start
* @param DateTime $end
*
* @return array
*/
private function getPeriodDates($start, $end)
{
/** @var DatePeriod $period */
$period = new DatePeriod(
$start->setTime(0, 0, 0),
new DateInterval('P1D'),
$end
);
$periodDates = [];
/** @var DateTime $date */
foreach ($period as $index => $date) {
$periodDates[] = $date->format('Y-m-d');
}
return $periodDates;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* get appointment intervals for provider.
*
* @param Provider $provider
* @param Collection $locations
* @param int $serviceId
* @param int $locationId
* @param int $personsCount
* @param boolean $bookIfPending
* @param boolean $bookOverApp
* @param array $weekDaysIntervals
* @param array $specialDaysIntervals
* @return array
* @throws InvalidArgumentException
*/
private function getProviderAppointmentIntervals(
$provider,
$locations,
$serviceId,
$locationId,
$personsCount,
$bookIfPending,
$bookOverApp,
&$weekDaysIntervals,
&$specialDaysIntervals
) {
$intervals = [];
$specialDays = [];
foreach ($specialDaysIntervals as $specialDay) {
$specialDays = array_merge($specialDays, $specialDay['dates']);
}
/** @var Appointment $app */
foreach ($provider->getAppointmentList()->getItems() as $app) {
$occupiedStart = $provider->getTimeZone() ?
DateTimeService::getDateTimeObjectInTimeZone(
$app->getBookingStart()->getValue()->format('Y-m-d H:i'),
$provider->getTimeZone()->getValue()
) : DateTimeService::getCustomDateTimeObject($app->getBookingStart()->getValue()->format('Y-m-d H:i'));
$occupiedEnd = $provider->getTimeZone() ?
DateTimeService::getDateTimeObjectInTimeZone(
$app->getBookingEnd()->getValue()->format('Y-m-d H:i'),
$provider->getTimeZone()->getValue()
) : DateTimeService::getCustomDateTimeObject($app->getBookingEnd()->getValue()->format('Y-m-d H:i'));
if ($app->getServiceId()->getValue()) {
$occupiedStart->modify('-' . ($app->getService()->getTimeBefore() ? $app->getService()->getTimeBefore()->getValue() : 0) . ' seconds');
$occupiedEnd->modify('+' . ($app->getService()->getTimeAfter() ? $app->getService()->getTimeAfter()->getValue() : 0) . ' seconds');
}
$occupiedDateStart = $occupiedStart->format('Y-m-d');
$occupiedSecondsStart = $this->intervalService->getSeconds($occupiedStart->format('H:i') . ':00');
$occupiedSecondsEnd = $this->intervalService->getSeconds($occupiedEnd->format('H:i:s'));
if (
$occupiedDateStart === $occupiedEnd->format('Y-m-d') &&
(!$bookOverApp || !$app->getServiceId()->getValue())
) {
$intervals[$occupiedDateStart]['occupied'][$occupiedSecondsStart] = [
$occupiedSecondsStart,
$this->getModifiedEndInterval(
!array_key_exists($occupiedDateStart, $specialDays) ? $weekDaysIntervals : [],
$intervals,
$occupiedDateStart,
$occupiedSecondsStart,
$occupiedSecondsEnd
)
];
} elseif (!$bookOverApp || !$app->getServiceId()->getValue()) {
$dates = $this->getPeriodDates($occupiedStart, $occupiedEnd);
$datesCount = sizeof($dates);
if ($datesCount === 1) {
$intervals[$dates[0]]['occupied'][$occupiedSecondsStart] = [
$occupiedSecondsStart,
$occupiedSecondsEnd === 0 ? 86400 : $occupiedSecondsEnd
];
} else {
foreach ($dates as $index => $date) {
if ($index === 0) {
$intervals[$date]['occupied'][$occupiedSecondsStart] = [$occupiedSecondsStart, 86400];
} elseif ($index === $datesCount - 1) {
$modifiedEnd = $this->getModifiedEndInterval(
!array_key_exists($occupiedDateStart, $specialDays) ?
$weekDaysIntervals :
[],
$intervals,
$date,
0,
$occupiedSecondsEnd
);
$intervals[$date]['occupied'][0] = [
0,
$modifiedEnd === 0 ? 86400 : $modifiedEnd
];
} else {
$intervals[$date]['occupied'][0] = [0, 86400];
}
}
}
}
$providerLocationId = $provider->getLocationId() ? $provider->getLocationId()->getValue() : null;
if ($app->getServiceId()->getValue() === $serviceId) {
$persons = 0;
/** @var CustomerBooking $booking */
foreach ($app->getBookings()->getItems() as $booking) {
$persons += $booking->getPersons()->getValue();
}
$status = $app->getStatus()->getValue();
$appLocationId = $app->getLocationId() ? $app->getLocationId()->getValue() : null;
$hasCapacity =
$personsCount !== null &&
($persons + $personsCount) <= $app->getService()->getMaxCapacity()->getValue() &&
!($app->isFull() ? $app->isFull()->getValue() : false);
$hasLocation =
!$locationId ||
($app->getLocationId() && $app->getLocationId()->getValue() === $locationId) ||
(!$app->getLocationId() && $providerLocationId === $locationId) ||
($appLocationId &&
$appLocationId === $locationId &&
$locations->getItem($appLocationId)->getStatus()->getValue() === Status::VISIBLE) ||
(!$appLocationId && $providerLocationId &&
$locations->getItem($providerLocationId)->getStatus()->getValue() === Status::VISIBLE);
$duration = $app->getBookingStart()->getValue()->diff($app->getBookingEnd()->getValue());
if (
($hasLocation && $status === BookingStatus::APPROVED && $hasCapacity) ||
($hasLocation && $status === BookingStatus::PENDING && ($bookIfPending || $hasCapacity))
) {
$endDateTime = $app->getBookingEnd()->getValue()->format('Y-m-d H:i:s');
$endDateTimeParts = explode(' ', $endDateTime);
$intervals[$occupiedDateStart]['available'][$app->getBookingStart()->getValue()->format('H:i')] =
[
'locationId' => $app->getLocationId() ?
$app->getLocationId()->getValue() : $providerLocationId,
'places' => $app->getService()->getMaxCapacity()->getValue() - $persons,
'endDate' => $endDateTimeParts[0],
'endTime' => $endDateTimeParts[1],
'serviceId' => $serviceId,
'duration' => ($duration->days * 24 * 60) + ($duration->h * 60) + $duration->i,
];
} else {
$intervals[$occupiedDateStart]['full'][$app->getBookingStart()->getValue()->format('H:i')] =
[
'locationId' => $app->getLocationId() ?
$app->getLocationId()->getValue() : $providerLocationId,
'places' => $app->getService()->getMaxCapacity()->getValue() - $persons,
'end' => $app->getBookingEnd()->getValue()->format('Y-m-d H:i:s'),
'serviceId' => $app->getServiceId()->getValue(),
'duration' => ($duration->days * 24 * 60) + ($duration->h * 60) + $duration->i,
];
}
} elseif ($app->getServiceId()->getValue()) {
$duration = $app->getBookingStart()->getValue()->diff($app->getBookingEnd()->getValue());
$intervals[$occupiedDateStart]['full'][$app->getBookingStart()->getValue()->format('H:i')] =
[
'locationId' => $app->getLocationId() ?
$app->getLocationId()->getValue() : $providerLocationId,
'places' => 0,
'end' => $app->getBookingEnd()->getValue()->format('Y-m-d H:i:s'),
'serviceId' => $app->getServiceId()->getValue(),
'duration' => ($duration->days * 24 * 60) + ($duration->h * 60) + $duration->i,
];
}
}
return $intervals;
}
/**
* get provider day off dates.
*
* @param Provider $provider
*
* @return array
* @throws Exception
*/
private function getProviderDayOffDates($provider)
{
$dates = [];
/** @var DayOff $dayOff */
foreach ($provider->getDayOffList()->getItems() as $dayOff) {
$endDateCopy = clone $dayOff->getEndDate()->getValue();
$dayOffPeriod = new DatePeriod(
$dayOff->getStartDate()->getValue(),
new DateInterval('P1D'),
$endDateCopy->modify('+1 day')
);
/** @var DateTime $date */
foreach ($dayOffPeriod as $date) {
$dateFormatted = $dayOff->getRepeat()->getValue() ?
$date->format('m-d') :
$date->format('Y-m-d');
$dates[$dateFormatted] = $dateFormatted;
}
}
return $dates;
}
/**
* get available appointment intervals.
*
* @param array $availableIntervals
* @param array $unavailableIntervals
*
* @return array
*/
private function getAvailableIntervals(&$availableIntervals, $unavailableIntervals)
{
$parsedAvailablePeriod = [];
ksort($availableIntervals);
ksort($unavailableIntervals);
foreach ($availableIntervals as $available) {
$parsedAvailablePeriod[] = $available;
foreach ($unavailableIntervals as $unavailable) {
if ($parsedAvailablePeriod) {
$lastAvailablePeriod = $parsedAvailablePeriod[sizeof($parsedAvailablePeriod) - 1];
if ($unavailable[0] >= $lastAvailablePeriod[0] && $unavailable[1] <= $lastAvailablePeriod[1]) {
// unavailable interval is inside available interval
$fixedPeriod = array_pop($parsedAvailablePeriod);
if ($fixedPeriod[0] !== $unavailable[0]) {
$parsedAvailablePeriod[] = [$fixedPeriod[0], $unavailable[0], $fixedPeriod[2]];
}
if ($unavailable[1] !== $fixedPeriod[1]) {
$parsedAvailablePeriod[] = [$unavailable[1], $fixedPeriod[1], $fixedPeriod[2]];
}
} elseif (
$unavailable[0] <= $lastAvailablePeriod[0] &&
$unavailable[1] >= $lastAvailablePeriod[1]
) {
// available interval is inside unavailable interval
array_pop($parsedAvailablePeriod);
} elseif (
$unavailable[0] <= $lastAvailablePeriod[0] &&
$unavailable[1] >= $lastAvailablePeriod[0] &&
$unavailable[1] <= $lastAvailablePeriod[1]
) {
// unavailable interval intersect start of available interval
$fixedPeriod = array_pop($parsedAvailablePeriod);
if ($unavailable[1] !== $fixedPeriod[1]) {
$parsedAvailablePeriod[] = [$unavailable[1], $fixedPeriod[1], $fixedPeriod[2]];
}
} elseif (
$unavailable[0] >= $lastAvailablePeriod[0] &&
$unavailable[0] <= $lastAvailablePeriod[1] &&
$unavailable[1] >= $lastAvailablePeriod[1]
) {
// unavailable interval intersect end of available interval
$fixedPeriod = array_pop($parsedAvailablePeriod);
if ($fixedPeriod[0] !== $unavailable[0]) {
$parsedAvailablePeriod[] = [$fixedPeriod[0], $unavailable[0], $fixedPeriod[2]];
}
}
}
}
}
return $parsedAvailablePeriod;
}
/**
* @param Service $service
* @param Provider $provider
* @param int $personsCount
*
* @return bool
*
* @throws Exception
*/
private function getOnlyAppointmentsSlots($service, $provider, $personsCount)
{
$getOnlyAppointmentsSlots = false;
if ($provider->getServiceList()->keyExists($service->getId()->getValue())) {
/** @var Service $providerService */
$providerService = $provider->getServiceList()->getItem($service->getId()->getValue());
if ($personsCount < $providerService->getMinCapacity()->getValue()) {
$getOnlyAppointmentsSlots = true;
}
}
return $getOnlyAppointmentsSlots;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Service $service
* @param int $locationId
* @param Collection $providers
* @param Collection $locations
* @param array $globalDaysOffDates
* @param DateTime $startDateTime
* @param DateTime $endDateTime
* @param int $personsCount
* @param boolean $bookIfPending
* @param boolean $bookIfNotMin
* @param boolean $bookAfterMin
* @param boolean $bookOverApp
* @param array $appointmentsCount
* @param boolean $allowAdminBookAtAnytime
*
* @return array
* @throws Exception
*/
private function getFreeTime(
Service $service,
$locationId,
Collection $locations,
Collection $providers,
array $globalDaysOffDates,
DateTime $startDateTime,
DateTime $endDateTime,
$personsCount,
$bookIfPending,
$bookIfNotMin,
$bookAfterMin,
$bookOverApp,
$appointmentsCount,
$allowAdminBookAtAnytime
) {
$weekDayIntervals = [];
$appointmentIntervals = [];
$daysOffDates = [];
$specialDayIntervals = [];
$getOnlyAppointmentsSlots = [];
$serviceId = $service->getId()->getValue();
/** @var Provider $provider */
foreach ($providers->getItems() as $provider) {
$providerId = $provider->getId()->getValue();
$getOnlyAppointmentsSlots[$providerId] = $bookIfNotMin && $bookAfterMin ? $this->getOnlyAppointmentsSlots(
$service,
$provider,
$personsCount
) : false;
$daysOffDates[$providerId] = $this->getProviderDayOffDates($provider);
$weekDayIntervals[$providerId] = $this->scheduleService->getProviderWeekDaysIntervals(
$provider,
$locations,
$locationId,
$serviceId
);
$specialDayIntervals[$providerId] = $this->scheduleService->getProviderSpecialDayIntervals(
$provider,
$locations,
$locationId,
$serviceId
);
$appointmentIntervals[$providerId] = $this->getProviderAppointmentIntervals(
$provider,
$locations,
$serviceId,
$locationId,
$personsCount,
$bookIfPending,
$bookOverApp,
$weekDayIntervals[$providerId],
$specialDayIntervals[$providerId]
);
}
$freeDateIntervals = [];
foreach ($appointmentIntervals as $providerKey => $providerDates) {
foreach ((array)$providerDates as $dateKey => $dateIntervals) {
$dayIndex = DateTimeService::getDayIndex($dateKey);
$specialDayDateKey = null;
foreach ((array)$specialDayIntervals[$providerKey] as $specialDayKey => $specialDays) {
if (array_key_exists($dateKey, $specialDays['dates'])) {
$specialDayDateKey = $specialDayKey;
break;
}
}
if (
$specialDayDateKey !== null &&
isset($specialDayIntervals[$providerKey][$specialDayDateKey]['intervals']['free'])
) {
// get free intervals if it is special day
$freeDateIntervals[$providerKey][$dateKey] = $this->getAvailableIntervals(
$specialDayIntervals[$providerKey][$specialDayDateKey]['intervals']['free'],
!empty($dateIntervals['occupied']) ? $dateIntervals['occupied'] : []
);
} elseif (
isset($weekDayIntervals[$providerKey][$dayIndex]['free']) &&
!isset($specialDayIntervals[$providerKey][$specialDayDateKey]['intervals'])
) {
// get free intervals if it is working day
$unavailableIntervals =
$weekDayIntervals[$providerKey][$dayIndex]['busy'] + (!empty($dateIntervals['occupied']) ? $dateIntervals['occupied'] : []);
$intersectedTimes = array_intersect(
array_keys($weekDayIntervals[$providerKey][$dayIndex]['busy']),
array_keys(!empty($dateIntervals['occupied']) ? $dateIntervals['occupied'] : [])
);
foreach ($intersectedTimes as $time) {
$unavailableIntervals[$time] =
$weekDayIntervals[$providerKey][$dayIndex]['busy'][$time] >
$dateIntervals['occupied'][$time] ?
$weekDayIntervals[$providerKey][$dayIndex]['busy'][$time] :
$dateIntervals['occupied'][$time];
}
$freeDateIntervals[$providerKey][$dateKey] = $this->getAvailableIntervals(
$weekDayIntervals[$providerKey][$dayIndex]['free'],
$unavailableIntervals ?: []
);
}
}
}
$startDateTime = clone $startDateTime;
$startDateTime->setTime(0, 0);
$endDateTime = clone $endDateTime;
$endDateTime->modify('+1 day')->setTime(0, 0);
// create calendar
$period = new DatePeriod(
$startDateTime,
new DateInterval('P1D'),
$endDateTime
);
$calendar = [];
/** @var DateTime $day */
foreach ($period as $day) {
$currentDate = $day->format('Y-m-d');
$dayIndex = (int)$day->format('N');
$isGlobalDayOff = array_key_exists($currentDate, $globalDaysOffDates) ||
array_key_exists($day->format('m-d'), $globalDaysOffDates);
if (!$isGlobalDayOff) {
foreach ($weekDayIntervals as $providerKey => $providerWorkingHours) {
$isProviderDayOff = array_key_exists($currentDate, $daysOffDates[$providerKey]) ||
array_key_exists($day->format('m-d'), $daysOffDates[$providerKey]);
$specialDayDateKey = null;
foreach ((array)$specialDayIntervals[$providerKey] as $specialDayKey => $specialDays) {
if (array_key_exists($currentDate, $specialDays['dates'])) {
$specialDayDateKey = $specialDayKey;
break;
}
}
if (!$isProviderDayOff) {
// daily limit per employee
if (
!$allowAdminBookAtAnytime &&
!empty($appointmentsCount['limitCount']) &&
!empty($appointmentsCount['appCount'][$providerKey][$currentDate]) &&
$appointmentsCount['appCount'][$providerKey][$currentDate] >= $appointmentsCount['limitCount']
) {
continue;
}
if ($freeDateIntervals && isset($freeDateIntervals[$providerKey][$currentDate])) {
// get date intervals if there are appointments (special or working day)
$calendar[$currentDate][$providerKey] = [
'slots' => $personsCount && $bookIfNotMin && isset($appointmentIntervals[$providerKey][$currentDate]['available']) ?
$appointmentIntervals[$providerKey][$currentDate]['available'] : [],
'full' => isset($appointmentIntervals[$providerKey][$currentDate]['full']) ?
$appointmentIntervals[$providerKey][$currentDate]['full'] : [],
'intervals' => $getOnlyAppointmentsSlots[$providerKey] ? [] : $freeDateIntervals[$providerKey][$currentDate],
'count' => !empty($appointmentsCount[$providerKey][$currentDate]) ? $appointmentsCount[$providerKey][$currentDate] : 0
];
} else {
if ($specialDayDateKey !== null && isset($specialDayIntervals[$providerKey][$specialDayDateKey]['intervals']['free'])) {
// get date intervals if it is special day with out appointments
$calendar[$currentDate][$providerKey] = [
'slots' => [],
'full' => [],
'intervals' => $getOnlyAppointmentsSlots[$providerKey] ?
[] :
$specialDayIntervals[$providerKey][$specialDayDateKey]['intervals']['free'],
'count' => !empty($appointmentsCount[$providerKey][$currentDate]) ?
$appointmentsCount[$providerKey][$currentDate] :
0
];
} elseif (
isset($weekDayIntervals[$providerKey][$dayIndex]) &&
!isset($specialDayIntervals[$providerKey][$specialDayDateKey]['intervals'])
) {
// get date intervals if it is working day without appointments
$calendar[$currentDate][$providerKey] = [
'slots' => [],
'full' => [],
'intervals' => $getOnlyAppointmentsSlots[$providerKey] ?
[] :
$weekDayIntervals[$providerKey][$dayIndex]['free'],
'count' => !empty($appointmentsCount[$providerKey][$currentDate]) ?
$appointmentsCount[$providerKey][$currentDate] :
0
];
}
}
}
}
}
}
return $calendar;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Service $service
* @param int $requiredTime
* @param array $freeIntervals
* @param array $resourcedIntervals
* @param int $slotLength
* @param DateTime $startDateTime
* @param bool $serviceDurationAsSlot
* @param bool $bufferTimeInSlot
* @param bool $isFrontEndBooking
* @param String $timeZone
*
* @return array
*/
private function getAppointmentFreeSlots(
$service,
$requiredTime,
&$freeIntervals,
$resourcedIntervals,
$slotLength,
$startDateTime,
$serviceDurationAsSlot,
$bufferTimeInSlot,
$isFrontEndBooking,
$timeZone
) {
$availableResult = [];
$occupiedResult = [];
$realRequiredTime = $requiredTime -
$service->getTimeBefore()->getValue() -
$service->getTimeAfter()->getValue();
if ($serviceDurationAsSlot && !$bufferTimeInSlot) {
$requiredTime = $requiredTime -
$service->getTimeBefore()->getValue() -
$service->getTimeAfter()->getValue();
}
$currentDateTime = DateTimeService::getNowDateTimeObject();
$currentDateString = $currentDateTime->format('Y-m-d');
$currentTimeStringInSeconds = $this->intervalService->getSeconds($currentDateTime->format('H:i:s'));
$currentTimeInSeconds = $this->intervalService->getSeconds($currentDateTime->format('H:i:s'));
$currentDateFormatted = $currentDateTime->format('Y-m-d');
$startTimeInSeconds = $this->intervalService->getSeconds($startDateTime->format('H:i:s'));
$startDateFormatted = $startDateTime->format('Y-m-d');
$bookingLength = $serviceDurationAsSlot && $isFrontEndBooking ? $requiredTime : $slotLength;
$appCount = [];
$isContinuousTime = false;
$continuousTimeSlot = null;
foreach ($freeIntervals as $dateKey => $dateProviders) {
foreach ((array)$dateProviders as $providerKey => $provider) {
foreach ((array)$provider['intervals'] as $timePeriod) {
$moveStart = false;
if ($timePeriod[0] === 0 && $isContinuousTime && $continuousTimeSlot !== null) {
$isContinuousTime = false;
$moveStart = true;
}
if ($timePeriod[1] === 86400) {
$nextDateString = DateTimeService::getDateTimeObjectInTimeZone(
$dateKey . ' 00:00:00',
$timeZone
)->modify('+1 days')->format('Y-m-d');
if (
isset($freeIntervals[$nextDateString][$providerKey]['intervals'][0]) &&
$freeIntervals[$nextDateString][$providerKey]['intervals'][0][0] === 0
) {
$isContinuousTime = true;
$nextDayInterval = $freeIntervals[$nextDateString][$providerKey]['intervals'][0][1];
$timePeriod[1] += (
$realRequiredTime + $service->getTimeAfter()->getValue() <= $nextDayInterval
? $realRequiredTime + $service->getTimeAfter()->getValue()
: $nextDayInterval);
}
}
if ($serviceDurationAsSlot && !$bufferTimeInSlot) {
$timePeriod[1] = $timePeriod[1] - $service->getTimeAfter()->getValue();
}
$customerTimeStart = $timePeriod[0] + (!$moveStart ? $service->getTimeBefore()->getValue() : 0);
$providerTimeStart = $customerTimeStart - (!$moveStart ? $service->getTimeBefore()->getValue() : 0);
$numberOfSlots = (int)(
floor(
(
$timePeriod[1] -
$providerTimeStart -
($requiredTime - ($moveStart ? $service->getTimeBefore()->getValue() : 0))
) / $bookingLength
) + 1
);
$inspectResourceIndexes = [];
if (isset($resourcedIntervals[$dateKey])) {
foreach ($resourcedIntervals[$dateKey] as $resourceIndex => $resourceData) {
if (
array_intersect(
$timePeriod[2],
$resourcedIntervals[$dateKey][$resourceIndex]['locationsIds']
)
) {
$inspectResourceIndexes[] = $resourceIndex;
}
}
}
$providerPeriodSlots = [];
$achievedLength = 0;
if ($moveStart && $continuousTimeSlot !== 86400 && ($bookingLength - (86400 - $continuousTimeSlot)) >= 0) {
$customerTimeStart += $bookingLength - (86400 - $continuousTimeSlot);
$providerTimeStart += $bookingLength - (86400 - $continuousTimeSlot);
$numberOfSlots = (int)(floor(($timePeriod[1] - $providerTimeStart - $requiredTime) / $bookingLength) + 1);
}
if ($moveStart) {
$continuousTimeSlot = null;
}
for ($i = 0; $i < $numberOfSlots; $i++) {
$achievedLength += $bookingLength;
$timeSlot = $customerTimeStart + $i * $bookingLength;
if (
(
$startDateFormatted !== $dateKey &&
($serviceDurationAsSlot &&
!$bufferTimeInSlot ? $timeSlot <= $timePeriod[1] - $requiredTime : true)
) ||
($startDateFormatted === $dateKey && $startTimeInSeconds < $timeSlot) ||
($startDateFormatted ===
$currentDateFormatted &&
$startDateFormatted === $dateKey &&
$startTimeInSeconds < $timeSlot &&
$currentTimeInSeconds < $timeSlot)
) {
$timeSlotEnd = $timeSlot + $bookingLength;
$filteredLocationsIds = $timePeriod[2];
foreach ($inspectResourceIndexes as $resourceIndex) {
foreach ($resourcedIntervals[$dateKey][$resourceIndex]['intervals'] as $start => $end) {
if (
($start >= $timeSlot && $start < $timeSlotEnd) ||
($end > $timeSlot && $end <= $timeSlotEnd) ||
($start <= $timeSlot && $end >= $timeSlotEnd) ||
($start >= $timeSlot && $start < $timeSlot + $requiredTime)
) {
$filteredLocationsIds = array_diff(
$filteredLocationsIds,
$resourcedIntervals[$dateKey][$resourceIndex]['locationsIds']
);
if (!$filteredLocationsIds) {
if ($achievedLength < $requiredTime) {
$providerPeriodSlots = [];
$achievedLength = 0;
}
continue 3;
}
$removedLocationsIds = array_diff(
$resourcedIntervals[$dateKey][$resourceIndex]['locationsIds'],
$filteredLocationsIds
);
if ($removedLocationsIds && $achievedLength < $requiredTime) {
$parsedPeriodSlots = [];
foreach ($providerPeriodSlots as $previousTimeSlot => $periodSlotData) {
if (
$start >= $previousTimeSlot &&
$start < $previousTimeSlot + $requiredTime
) {
foreach ($periodSlotData as $data) {
if (!in_array($data[1], $removedLocationsIds)) {
$parsedPeriodSlots[$previousTimeSlot][] = $data;
}
}
} else {
$parsedPeriodSlots[$previousTimeSlot] = $periodSlotData;
}
}
$providerPeriodSlots = $parsedPeriodSlots;
}
}
}
}
if (!$timePeriod[2]) {
$providerPeriodSlots[$timeSlot][] = [$providerKey, null];
} elseif ($filteredLocationsIds) {
foreach ($filteredLocationsIds as $locationId) {
$providerPeriodSlots[$timeSlot][] = [$providerKey, $locationId];
}
}
}
}
foreach ($providerPeriodSlots as $timeSlot => $data) {
$time = sprintf('%02d', floor($timeSlot / 3600)) . ':'
. sprintf('%02d', floor(($timeSlot / 60) % 60));
if ($time !== '24:00') {
$availableResult[$dateKey][$time] = $data;
if ($isContinuousTime) {
$continuousTimeSlot = $timeSlot;
}
}
}
}
foreach ($provider['slots'] as $appointmentTime => $appointmentData) {
$startInSeconds = $this->intervalService->getSeconds($appointmentTime . ':00');
if (
$currentDateString === $dateKey &&
($currentTimeStringInSeconds > $startInSeconds || $startTimeInSeconds > $startInSeconds)
) {
continue;
}
$endInSeconds = $this->intervalService->getSeconds($appointmentData['endTime']) + $service->getTimeAfter()->getValue();
$newEndInSeconds = $startInSeconds + $realRequiredTime;
if (
$newEndInSeconds !== 86400 &&
($newEndInSeconds > 86400 ? $newEndInSeconds - 86400 > $endInSeconds : $newEndInSeconds > $endInSeconds)
) {
if ($dateKey !== $appointmentData['endDate']) {
$nextDateString = DateTimeService::getDateTimeObjectInTimeZone(
$dateKey . ' 00:00:00',
$timeZone
)->modify('+1 days')->format('Y-m-d');
if (
!isset($freeIntervals[$nextDateString][$providerKey]['intervals'][0]) ||
$freeIntervals[$nextDateString][$providerKey]['intervals'][0][0] != $endInSeconds ||
$freeIntervals[$nextDateString][$providerKey]['intervals'][0][1] < $newEndInSeconds - 86400
) {
continue;
}
} elseif ($newEndInSeconds > 86400) {
$nextIntervalIsValid = false;
foreach ($freeIntervals[$dateKey][$providerKey]['intervals'] as $interval) {
if ($interval[0] === $endInSeconds && $interval[1] === 86400) {
$nextIntervalIsValid = true;
break;
}
}
if (!$nextIntervalIsValid) {
continue;
}
$nextDateString = DateTimeService::getDateTimeObjectInTimeZone(
$dateKey . ' 00:00:00',
$timeZone
)->modify('+1 days')->format('Y-m-d');
if (
!isset($freeIntervals[$nextDateString][$providerKey]['intervals'][0]) ||
$freeIntervals[$nextDateString][$providerKey]['intervals'][0][0] != 0 ||
$freeIntervals[$nextDateString][$providerKey]['intervals'][0][1] < $newEndInSeconds - 86400
) {
continue;
}
} else {
$nextIntervalIsValid = false;
foreach ($freeIntervals[$dateKey][$providerKey]['intervals'] as $interval) {
if ($interval[0] === $endInSeconds && $interval[1] >= $newEndInSeconds) {
$nextIntervalIsValid = true;
break;
}
}
if (!$nextIntervalIsValid) {
continue;
}
}
}
$availableResult[$dateKey][$appointmentTime] = [
[
$providerKey,
$appointmentData['locationId'],
$appointmentData['places'],
$appointmentData['serviceId'],
$appointmentData['duration'],
]
];
}
foreach ($provider['full'] as $appointmentTime => $appointmentData) {
$occupiedResult[$dateKey][$appointmentTime][] = [
$providerKey,
$appointmentData['locationId'],
$appointmentData['places'],
$appointmentData['serviceId'],
$appointmentData['duration'],
];
}
$appCount[$dateKey] = $freeIntervals[$dateKey][$providerKey]['count'];
}
}
return [
'available' => $availableResult,
'occupied' => $occupiedResult,
'appCount' => $appCount
];
}
/**
* @param array $slots
* @param string $timeZone
*
* @return array
* @throws Exception
*/
private function getSlotsInMainTimeZoneFromTimeZone($slots, $timeZone)
{
$convertedProviderSlots = [];
foreach ($slots as $slotDate => $slotTimes) {
foreach ($slots[$slotDate] as $slotTime => $slotTimesProviders) {
$convertedSlotParts = explode(
' ',
DateTimeService::getDateTimeObjectInTimeZone(
$slotDate . ' ' . $slotTime,
$timeZone
)->setTimezone(new DateTimeZone(DateTimeService::getTimeZone()->getName()))->format('Y-m-d H:i')
);
$convertedProviderSlots[$convertedSlotParts[0]][$convertedSlotParts[1]] = $slotTimesProviders;
}
}
return $convertedProviderSlots;
}
/**
* @param Collection $appointments
* @param int $excludeAppointmentId
*
* @return array
* @throws Exception
*/
public function getAppointmentCount($appointments, $excludeAppointmentId)
{
$appCount = [];
/** @var Appointment $appointment */
foreach ($appointments->getItems() as $appointment) {
if (!$excludeAppointmentId || empty($appointment->getId()) || $appointment->getId()->getValue() !== $excludeAppointmentId) {
if (!empty($appCount[$appointment->getProviderId()->getValue()][$appointment->getBookingStart()->getValue()->format('Y-m-d')])) {
$appCount[$appointment->getProviderId()->getValue()][$appointment->getBookingStart()->getValue()->format('Y-m-d')]++;
} else {
$appCount[$appointment->getProviderId()->getValue()][$appointment->getBookingStart()->getValue()->format('Y-m-d')] = 1;
}
}
}
return $appCount;
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param array $settings
* @param array $props
* @param SlotsEntities $slotsEntities
* @param Collection $appointments
*
* @return array
* @throws Exception
*/
public function getSlots($settings, $props, $slotsEntities, $appointments)
{
$appointmentsCount = $this->getAppointmentCount($appointments, $props['excludeAppointmentId']);
$resourcedLocationsIntervals = $slotsEntities->getResources()->length() ?
$this->resourceService->manageResources(
$slotsEntities->getResources(),
$appointments,
$slotsEntities->getLocations(),
$slotsEntities->getServices()->getItem($props['serviceId']),
$slotsEntities->getProviders(),
$props['locationId'],
$props['excludeAppointmentId'],
array_key_exists('totalPersons', $props) ? $props['totalPersons'] : $props['personsCount']
) : [];
$this->entityService->filterSlotsAppointments($slotsEntities, $appointments, $props);
$this->providerService->addAppointmentsToAppointmentList(
$slotsEntities->getProviders(),
$appointments,
$settings['isGloballyBusySlot']
);
return $this->getCalculatedFreeSlots(
$settings,
$props,
$slotsEntities,
$resourcedLocationsIntervals,
$appointmentsCount
);
}
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param array $settings
* @param array $props
* @param SlotsEntities $slotsEntities
* @param array $resourcedLocationsIntervals
* @param array $appointmentsCount
*
* @return array
* @throws Exception
*/
private function getCalculatedFreeSlots(
$settings,
$props,
$slotsEntities,
$resourcedLocationsIntervals,
$appointmentsCount
) {
$freeProvidersSlots = [];
/** @var DateTime $startDateTime */
$startDateTime = $props['startDateTime'];
/** @var DateTime $endDateTime */
$endDateTime = $props['endDateTime'];
/** @var Service $service */
$service = $slotsEntities->getServices()->getItem($props['serviceId']);
/** @var Collection $providers */
$providers = $slotsEntities->getProviders();
/** @var Collection $locations */
$locations = $slotsEntities->getLocations();
$requiredTime = $this->entityService->getAppointmentRequiredTime(
$service,
$props['extras']
);
/** @var Provider $provider */
foreach ($providers->getItems() as $provider) {
if ($provider->getServiceList()->keyExists($service->getId()->getValue())) {
/** @var Service $providerService */
$providerService = $provider->getServiceList()->getItem($service->getId()->getValue());
if ($providerService && $props['personsCount'] > $providerService->getMaxCapacity()->getValue()) {
continue;
}
}
$providerContainer = new Collection();
if ($provider->getTimeZone()) {
$this->providerService->modifyProviderTimeZone(
$provider,
$settings['allowAdminBookAtAnyTime'] ? [] : $settings['globalDaysOff'],
$startDateTime,
$endDateTime
);
}
$start = $provider->getTimeZone() ?
DateTimeService::getCustomDateTimeObjectInTimeZone(
$startDateTime->format('Y-m-d H:i'),
$provider->getTimeZone()->getValue()
) : DateTimeService::getCustomDateTimeObject($startDateTime->format('Y-m-d H:i'));
$end = $provider->getTimeZone() ?
DateTimeService::getCustomDateTimeObjectInTimeZone(
$endDateTime->format('Y-m-d H:i'),
$provider->getTimeZone()->getValue()
) : DateTimeService::getCustomDateTimeObject($endDateTime->format('Y-m-d H:i'));
$providerContainer->addItem($provider, $provider->getId()->getValue());
$limitPerEmployee = !empty($settings['limitPerEmployee']) && !empty($settings['limitPerEmployee']['enabled']) ?
$settings['limitPerEmployee']['numberOfApp'] : null;
$freeIntervals = $this->getFreeTime(
$service,
$props['locationId'],
$locations,
$providerContainer,
$settings['allowAdminBookAtAnyTime'] || $provider->getTimeZone() ?
[] : $settings['globalDaysOff'],
$start,
$end,
$props['personsCount'],
$props['isFrontEndBooking'] && $settings['allowBookingIfPending'] && $settings['defaultAppointmentStatus'] === BookingStatus::PENDING,
$settings['allowBookingIfNotMin'],
$props['isFrontEndBooking'] ? $settings['openedBookingAfterMin'] : false,
!empty($settings['allowAdminBookOverApp']),
['limitCount' => $limitPerEmployee, 'appCount' => $appointmentsCount],
!empty($settings['allowAdminBookAtAnyTime']),
);
$freeProvidersSlots[$provider->getId()->getValue()] = $this->getAppointmentFreeSlots(
$service,
$requiredTime,
$freeIntervals,
!empty($resourcedLocationsIntervals[$provider->getId()->getValue()])
? $resourcedLocationsIntervals[$provider->getId()->getValue()] : [],
$settings['timeSlotLength'] ?: $requiredTime,
$start,
$settings['allowAdminBookAtAnyTime'] ? $settings['adminServiceDurationAsSlot'] :
$settings['serviceDurationAsSlot'],
$settings['bufferTimeInSlot'],
true,
$provider->getTimeZone() ?
$provider->getTimeZone()->getValue() : DateTimeService::getTimeZone()->getName()
);
}
$freeSlots = [
'available' => [],
'occupied' => [],
'appCount' => [],
'duration' => $requiredTime / 60,
];
foreach ($freeProvidersSlots as $providerKey => $providerSlots) {
/** @var Provider $provider */
$provider = $providers->getItem($providerKey);
$freeSlots['appCount'][$providerKey] = $providerSlots['appCount'];
foreach (['available', 'occupied'] as $type) {
if ($provider->getTimeZone()) {
$providerSlots[$type] = $this->getSlotsInMainTimeZoneFromTimeZone(
$providerSlots[$type],
$provider->getTimeZone()->getValue()
);
}
foreach ($providerSlots[$type] as $dateKey => $dateSlots) {
foreach ($dateSlots as $timeKey => $slotData) {
if (empty($freeSlots[$type][$dateKey][$timeKey])) {
$freeSlots[$type][$dateKey][$timeKey] = [];
}
foreach ($slotData as $item) {
$freeSlots[$type][$dateKey][$timeKey][] = $item;
}
if (isset($freeSlots[$type][$dateKey])) {
if (!$freeSlots[$type][$dateKey]) {
unset($freeSlots[$type][$dateKey]);
} else {
ksort($freeSlots[$type][$dateKey]);
}
}
}
}
}
}
return $freeSlots;
}
}