<?php
namespace Sq\Entity\Schema\ORM;
use Carbon\Carbon;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Sq\Entity\Enum\DayOfWeek;
/**
* QueueSchedule.
*/
#[ORM\Entity(repositoryClass: \Sq\Service\Repository\ORM\TimeslotRepository::class)]
#[ORM\Table(name: 'queue_schedule')]
#[ORM\Index(name: 'qs_qpc_id', columns: ['qs_qpc_id'])]
class QueueSchedule implements BelongsToWorkspaceInterface, OnboardingContentInterface
{
use WorkspaceValidationTrait;
/**
* @var int
*/
#[ORM\Column(name: 'qs_id', type: 'integer', nullable: false, options: ['unsigned' => true])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
private $id;
/**
* @var PostCategory
*/
#[ORM\ManyToOne(targetEntity: PostCategory::class, fetch: 'EAGER', inversedBy: 'timeslots')]
#[ORM\JoinColumn(name: 'qs_qpc_id', referencedColumnName: 'qpc_id', nullable: false)]
private $category;
/**
* @var string|null
*/
#[ORM\Column(name: 'qs_local_days', type: 'string', length: 13, nullable: true)]
private $localDay;
/**
* @deprecated
*
* @var string
*/
#[ORM\Column(name: 'qs_timezone', type: 'string', length: 42, nullable: false, options: ['default' => 'Europe/London'])]
private $deprecatedTimezone = 'Europe/London';
#[ORM\OneToOne(targetEntity: QueueScheduleTime::class, mappedBy: 'queueSchedule', fetch: 'EAGER', cascade: ['persist', 'remove'])]
private $localTime;
#[ORM\ManyToMany(targetEntity: SocialProfile::class)]
#[ORM\JoinTable(name: 'queue_schedule_account', joinColumns: [new ORM\JoinColumn(name: 'qsa_qs_id', referencedColumnName: 'qs_id')], inverseJoinColumns: [new ORM\JoinColumn(name: 'qsa_sn_id', referencedColumnName: 'sn_id')])]
private $profiles;
/**
*
* @var Workspace|null
*/
#[ORM\ManyToOne(targetEntity: Workspace::class, inversedBy: 'timeslots')]
#[ORM\JoinColumn(name: 'qs_ws_id', referencedColumnName: 'ws_id', nullable: true)]
private $workspace;
/**
* @var bool
*/
#[ORM\Column(name: 'qs_onboarding_content', type: 'boolean', nullable: false)]
protected $onboardingContent = false;
public function __construct(
?Workspace $workspace,
PostCategory $category,
int $localDay,
\DateTimeInterface $localTime,
array $socialProfiles,
bool $isOnboardingContent = false,
) {
if ($workspace !== null)
{
$this->validateSameWorkspace($workspace, [$category, ...$socialProfiles]);
}
$this->category = $category;
$this->localDay = $localDay;
$this->localTime = new QueueScheduleTime($this, $localTime);
$this->profiles = new ArrayCollection($socialProfiles);
$this->workspace = $workspace;
$this->onboardingContent = $isOnboardingContent;
}
public function getId(): ?int
{
return $this->id;
}
public function getCategory(): PostCategory
{
return $this->category;
}
public function setCategory(PostCategory $category): self
{
if ($this->workspace !== null)
{
$this->validateSameWorkspace($this->workspace, [$category]);
}
$this->category = $category;
return $this;
}
public function getLocalDayOfWeek(): DayOfWeek
{
$day = (int) $this->localDay;
$day === 0 && $day = 1;
return DayOfWeek::from($day);
}
public function setLocalDayOfWeek(DayOfWeek $dayOfWeek): self
{
$this->localDay = $dayOfWeek->getValue();
return $this;
}
public function getLocalTime(): \DateTimeInterface
{
return $this->localTime->getLocalTime();
}
public function setLocalTime(\DateTimeInterface $time): self
{
$this->localTime->setLocalTime($time);
return $this;
}
/**
* @return Collection|SocialProfile[]
*/
public function getSocialProfiles(): Collection
{
return $this->profiles->filter(function (SocialProfile $profile): bool
{
return !$profile->isDeleted();
});
}
public function addProfile(SocialProfile $profile): self
{
if (!$this->profiles->contains($profile))
{
if ($this->workspace !== null)
{
$this->validateSameWorkspace($this->workspace, [$profile]);
}
$this->profiles->add($profile);
}
return $this;
}
public function removeProfile(SocialProfile $profile): self
{
if ($this->profiles->contains($profile))
{
$this->profiles->removeElement($profile);
}
return $this;
}
public function getWorkspace(): Workspace
{
return $this->workspace;
}
public function isLegacyWithoutWorkspace(): bool
{
return $this->workspace === null;
}
/**
* @param \DateTimeZone|string $timezone
* The Member or Workspace timezone.
*
* Can return null if a next posting date cannot be calculated.
* (e.g. a Timeslot exists on a day that a seasonal category doesn't include.)
* Note: This does not check if an entire queue or subqueue is paused, it only checks for seasonal category ranges.
*/
public function calculateNextPublishingDateTime(\DateTimeZone|string $timezone, int $addWeeks = 0): ?\DateTimeInterface
{
$localDayName = $this->getLocalDayOfWeek()->getDayName();
$time = $this->getLocalTime()->format("H:i:s");
$oneMinuteFromNow = Carbon::now('UTC')->addMinute();
$nextPostingDay = Carbon::createFromFormat("D H:i:s", $localDayName . " " . $time, $timezone);
while ($nextPostingDay < $oneMinuteFromNow)
{
$nextPostingDay->addWeek();
}
$nextPostingDay->addWeeks($addWeeks);
if ($this->category->isSeasonal())
{
$startDate = $this->category->getStartDate();
$endDate = $this->category->getEndDate();
$seasonalStart = Carbon::createFromFormat("Y-m-d H:i:s", $startDate->format("Y-m-d") . "00:00:00", $timezone);
$seasonalEnd = Carbon::createFromFormat("Y-m-d H:i:s", $endDate->format("Y-m-d") . "23:59:59", $timezone);
$years = 0;
while (true)
{
while ($nextPostingDay < $seasonalStart)
{
// Keep adding weeks until the next posting day is after or equals the seasonal start.
$nextPostingDay->addWeek();
}
if ($nextPostingDay <= $seasonalEnd)
{
// We know the next posting day is after the seasonal start, so if it's also less than the seasonal end,
// we have a valid posting time.
return $nextPostingDay;
}
// Since we couldn't find a valid posting time for this year, we add a year to the seasonal start/end and
// try again. If we go past 11 years, return null as it's not possible to calculate a time.
$seasonalStart->addYear();
$seasonalEnd->addYear();
$years++;
if ($years > 11)
{
// For some reason we couldn't find a calculated time, so this sub-queue will be un-queued.
return null;
}
}
}
return $nextPostingDay;
}
public function isOnboardingContent(): bool
{
return $this->onboardingContent;
}
}