<?php
namespace Cms\CoreBundle\Entity;
use App\Entity\Shared\FlagsInterface;
use App\Entity\Shared\FlagsTrait;
use App\Model\Async\StringSemaphoreInterface;
use App\Model\Async\StringSemaphoreTrait;
use App\Model\Schedule\ScheduleInterface;
use App\Model\Schedule\ScheduleTrait;
use Cms\CoreBundle\Entity\OneRoster\OneRosterOrg;
use Cms\CoreBundle\Service\OneRoster\AbstractOneRosterApi;
use Cms\TenantBundle\Entity\TenantedEntity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Reinder83\BinaryFlags\Bits;
/**
* Class OneRosterSync
* @package Cms\CoreBundle\Entity
*
* @ORM\Entity(
* repositoryClass = "Cms\CoreBundle\Doctrine\OneRosterSyncRepository"
* )
* @ORM\Table(
* name = "cms__one_roster__sync",
* )
*/
class OneRosterSync extends TenantedEntity implements FlagsInterface, StringSemaphoreInterface, ScheduleInterface
{
const STRATEGIES = [
'notifications__staff' => self::STRATEGIES__NOTIFICATIONS__STAFF,
'notifications__family' => self::STRATEGIES__NOTIFICATIONS__FAMILY,
'notifications__students' => self::STRATEGIES__NOTIFICATIONS__STUDENTS,
'notifications__community' => self::STRATEGIES__NOTIFICATIONS__COMMUNITY,
'sso' => self::STRATEGIES__SSO,
'directories' => self::STRATEGIES__DIRECTORIES,
'schools' => self::STRATEGIES__SCHOOLS,
];
const STRATEGIES__NOTIFICATIONS = [
'notifications__staff' => self::STRATEGIES__NOTIFICATIONS__STAFF,
'notifications__family' => self::STRATEGIES__NOTIFICATIONS__FAMILY,
'notifications__students' => self::STRATEGIES__NOTIFICATIONS__STUDENTS,
'notifications__community' => self::STRATEGIES__NOTIFICATIONS__COMMUNITY,
];
const STRATEGIES__NOTIFICATIONS__STAFF = Bits::BIT_1;
const STRATEGIES__NOTIFICATIONS__FAMILY = Bits::BIT_2;
const STRATEGIES__NOTIFICATIONS__STUDENTS = Bits::BIT_3;
const STRATEGIES__NOTIFICATIONS__COMMUNITY = Bits::BIT_4;
const STRATEGIES__SSO = Bits::BIT_17;
const STRATEGIES__DIRECTORIES = Bits::BIT_18;
const STRATEGIES__SCHOOLS = Bits::BIT_19;
// NOTE: order of arrays for each strategy matters!
const ONEROSTER_TYPES_MAPPINGS = [
self::STRATEGIES__NOTIFICATIONS__STAFF => AbstractOneRosterEntity::ONEROSTER_TYPES,
self::STRATEGIES__NOTIFICATIONS__FAMILY => AbstractOneRosterEntity::ONEROSTER_TYPES,
self::STRATEGIES__NOTIFICATIONS__STUDENTS => AbstractOneRosterEntity::ONEROSTER_TYPES,
self::STRATEGIES__NOTIFICATIONS__COMMUNITY => AbstractOneRosterEntity::ONEROSTER_TYPES,
self::STRATEGIES__SSO => AbstractOneRosterEntity::ONEROSTER_TYPES,
self::STRATEGIES__DIRECTORIES => AbstractOneRosterEntity::ONEROSTER_TYPES,
self::STRATEGIES__SCHOOLS => [
OneRosterOrg::ONEROSTER_TYPE => OneRosterOrg::class,
],
];
const FLAGS = [
'single_school' => self::FLAGS__SINGLE_SCHOOL,
'skip_twilio_lookups' => self::FLAGS__SKIP_TWILIO_LOOKUPS,
'ignore_enabled_user_property' => self::FLAGS__IGNORE_ENABLED_USER_PROPERTY,
'powerschool__skip_emergency_contacts' => self::FLAGS__POWERSCHOOL__SKIP_EMERGENCY_CONTACTS,
];
const FLAGS__SINGLE_SCHOOL = Bits::BIT_1;
const FLAGS__SKIP_TWILIO_LOOKUPS = Bits::BIT_2;
const FLAGS__IGNORE_ENABLED_USER_PROPERTY = Bits::BIT_3;
const FLAGS__POWERSCHOOL__SKIP_EMERGENCY_CONTACTS = Bits::BIT_17;
use FlagsTrait;
use StringSemaphoreTrait;
use ScheduleTrait;
/**
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "unsigned" = true,
* "default" = 0,
* },
* )
*/
protected int $strategies = 0;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = false,
* )
*/
protected ?string $vendor = null;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = false,
* )
*/
protected ?string $apiClientId = null;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = false,
* )
*/
protected ?string $apiClientSecret = null;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = true,
* )
*/
protected ?string $apiAppName = null;
/**
* @var array
*
* @ORM\Column(
* type = "json",
* nullable = true,
* )
*/
protected array $apiToken = [];
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = true,
* )
*/
protected ?string $districtId = null;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = true,
* )
*/
protected ?string $baseUri = null;
/**
* @var Collection|OneRosterJob[]
*
* @ORM\OneToMany(
* targetEntity = "Cms\CoreBundle\Entity\OneRosterJob",
* mappedBy = "sync",
* )
* @ORM\OrderBy({
* "createdAt" = "DESC",
* })
*/
protected Collection $jobs;
/**
* @ORM\Column(
* type = "boolean",
* nullable = false,
* options = {
* "default" = true,
* },
* )
*/
protected bool $active = true;
/**
* @return int
*/
public function getStrategies(): int
{
return $this->strategies;
}
/**
* @return array
*/
public function getStrategiesAsArray(): array
{
if ($this->strategies === 0) {
return [];
}
$result = [];
foreach (self::STRATEGIES as $name => $mask) {
if ($this->hasStrategy($mask)) {
$result[$name] = $mask;
}
}
return $result;
}
/**
* @param int $strategies
* @return $this
*/
public function setStrategies(int $strategies): self
{
$this->strategies = $strategies;
return $this;
}
/**
* @param int $strategy
* @return bool
*/
public function hasStrategy(int $strategy): bool
{
return (($this->strategies & $strategy) > 0);
}
/**
* @param int $strategy
* @param bool|null $toggle
* @return $this
*/
public function toggleStrategy(int $strategy, ?bool $toggle = null): self
{
if ($toggle === null) {
return ($this->hasStrategy($strategy)) ? $this->unmarkStrategy($strategy) : $this->markStrategy($strategy);
}
return ($toggle) ? $this->markStrategy($strategy) : $this->unmarkStrategy($strategy);
}
/**
* @param int $strategy
* @return $this
*/
public function markStrategy(int $strategy): self
{
$this->strategies |= $strategy;
return $this;
}
/**
* @param int $strategy
* @return $this
*/
public function unmarkStrategy(int $strategy): self
{
$this->strategies &= ~$strategy;
return $this;
}
/**
* @param Criteria|null $criteria
* @return Collection|OneRosterJob[]
*/
public function getJobs(?Criteria $criteria = null): Collection
{
if ( ! $this->jobs instanceof Collection) {
$this->jobs = new ArrayCollection();
}
if ( ! empty($criteria)) {
return $this->jobs->matching($criteria);
}
return $this->jobs;
}
/**
* @return OneRosterJob|null
*/
public function getLastJob(): ?OneRosterJob
{
if ($this->jobs->count() > 0) {
$jobs = $this->getJobs(new Criteria(null, ['createdAt' => 'DESC'], null, 1));
return $jobs->first();
}
return null;
}
/**
* @param int $limit
* @return Collection
*/
public function getLastJobs(int $limit = 10): Collection
{
return $this->getJobs(new Criteria(null, ['createdAt' => 'DESC'], null, $limit));
}
/**
* @return string|null
*/
public function getVendor(): ?string
{
return $this->vendor;
}
/**
* @param string $vendor
* @return $this
*/
public function setVendor(string $vendor): self
{
if ( ! in_array($vendor, AbstractOneRosterApi::VENDORS)) {
throw new \Exception(sprintf(
'Vendor "%s" is not valid.',
$vendor
));
}
$this->vendor = $vendor;
return $this;
}
/**
* @return string|null
*/
public function getApiClientId(): ?string
{
return $this->apiClientId;
}
/**
* @param string $apiClientId
* @return $this
*/
public function setApiClientId(string $apiClientId): self
{
$this->apiClientId = $apiClientId;
return $this;
}
/**
* @return string|null
*/
public function getApiClientSecret(): ?string
{
return $this->apiClientSecret;
}
/**
* @param string $apiClientSecret
* @return $this
*/
public function setApiClientSecret(string $apiClientSecret): self
{
$this->apiClientSecret = $apiClientSecret;
return $this;
}
/**
* @return string|null
*/
public function getApiAppName(): ?string
{
return $this->apiAppName;
}
/**
* @param string|null $apiAppName
* @return $this
*/
public function setApiAppName(?string $apiAppName): self
{
$this->apiAppName = $apiAppName ?: null;
return $this;
}
/**
* @return array
*/
public function getApiToken(): array
{
return $this->apiToken;
}
/**
* @return string|null
*/
public function getApiTokenScope(): ?string
{
$token = $this->getApiToken();
if ($token && array_key_exists('scope', $token) && ! empty($token['scope'])) {
return $token['scope'];
}
return null;
}
/**
* @return int|null
*/
public function getApiTokenExpiresIn(): ?int
{
$token = $this->getApiToken();
if ($token && array_key_exists('expires_in', $token) && ! empty($token['expires_in'])) {
return $token['expires_in'];
}
return null;
}
/**
* @return string|null
*/
public function getApiTokenTokenType(): ?string
{
$token = $this->getApiToken();
if ($token && array_key_exists('token_type', $token) && ! empty($token['token_type'])) {
return $token['token_type'];
}
return null;
}
/**
* @return string|null
*/
public function getApiTokenAccessToken(): ?string
{
$token = $this->getApiToken();
if ($token && array_key_exists('access_token', $token) && ! empty($token['access_token'])) {
return $token['access_token'];
}
return null;
}
/**
* @param array $apiToken
* @return $this
*/
public function setApiToken(array $apiToken): self
{
$this->apiToken = $apiToken;
return $this;
}
/**
* @return string|null
*/
public function getDistrictId(): ?string
{
return $this->districtId;
}
/**
* @param string|null $districtId
* @return $this
*/
public function setDistrictId(?string $districtId): self
{
if ($districtId === '') {
$districtId = null;
}
$this->districtId = $districtId;
return $this;
}
/**
* @return string|null
*/
public function getBaseUri(): ?string
{
return $this->baseUri;
}
/**
* @param string|null $baseUri
* @return $this
*/
public function setBaseUri(?string $baseUri): self
{
$this->baseUri = $baseUri;
return $this;
}
/**
* @return bool
*/
public function isOptimized(): bool
{
return $this->getVendor() && in_array(
$this->getVendor(),
[
AbstractOneRosterApi::VENDORS__SCHOOLNOW,
AbstractOneRosterApi::VENDORS__SCHOOLNOW_DEV,
AbstractOneRosterApi::VENDORS__SCHOOLNOW_TEST,
AbstractOneRosterApi::VENDORS__SCHOOLNOW_NGROK,
],
true,
);
}
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
}