<?php
namespace Products\NotificationsBundle\Entity;
use App\Entity\OAuth2\App\AbstractAppToken;
use App\Entity\Shared\AliasableInterface;
use App\Entity\Shared\AliasableTrait;
use App\Entity\Shared\DiscardableInterface;
use App\Entity\Shared\DiscardableTrait;
use App\Entity\Shared\FlagsInterface;
use App\Entity\Shared\FlagsTrait;
use App\Util\Locales;
use Cms\CoreBundle\Entity\OneRoster\OneRosterUser;
use Cms\CoreBundle\Model\Interfaces\OneRosterable\OneRosterableInterface;
use Cms\CoreBundle\Model\Interfaces\OneRosterable\OneRosterableTrait;
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 League\OAuth2\Server\Entities\UserEntityInterface;
use Products\NotificationsBundle\Entity\Recipients\AppRecipient;
use Products\NotificationsBundle\Entity\Recipients\EmailRecipient;
use Products\NotificationsBundle\Entity\Recipients\PhoneRecipient;
use Products\NotificationsBundle\Util\ReachabilityTrait;
use Reinder83\BinaryFlags\Bits;
use Serializable;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Class Profile
* @package Products\NotificationsBundle\Entity
*
* @ORM\Entity(
* repositoryClass = "Products\NotificationsBundle\Doctrine\Repository\ProfileRepository",
* )
* @ORM\Table(
* name = "notis__profile",
* indexes = {
* @ORM\Index(
* name = "idx__onerosterId",
* columns = {
* "onerosterId",
* },
* ),
* @ORM\Index(
* name = "idx__discarded",
* columns = {
* "discarded",
* },
* ),
* @ORM\Index(
* name = "idx__discardedAt",
* columns = {
* "discardedAt",
* },
* ),
* },
* )
*/
class Profile extends TenantedEntity implements
UserInterface,
Serializable,
EquatableInterface,
OneRosterableInterface,
DiscardableInterface,
UserEntityInterface,
PasswordAuthenticatedUserInterface,
FlagsInterface,
AliasableInterface
{
use ReachabilityTrait;
use OneRosterableTrait;
use DiscardableTrait;
use FlagsTrait;
use AliasableTrait;
public const METADATA__LANGUAGE = '_language';
const FLAGS = [
'fixed' => self::FLAGS__FIXED,
];
const FLAGS__FIXED = Bits::BIT_1;
/**
* @var Collection|ProfileContact[]
*
* @ORM\OneToMany(
* targetEntity = "Products\NotificationsBundle\Entity\ProfileContact",
* mappedBy = "profile",
* )
*/
protected Collection $contacts;
/**
* @var Collection|Checkup[]
*
* @ORM\OneToMany(
* targetEntity = "Products\NotificationsBundle\Entity\Checkup",
* mappedBy = "profile",
* )
* @ORM\OrderBy({
* "createdAt" = "DESC",
* })
*/
protected Collection $checkups;
/**
* @var Collection|AbstractAppToken[]
*
* @ORM\OneToMany(
* targetEntity = AbstractAppToken::class,
* mappedBy = "profile",
* )
* @ORM\OrderBy({
* "createdAt" = "DESC",
* })
*/
protected Collection $tokens;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = true,
* )
*/
protected ?string $firstName = null;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = true,
* )
*/
protected ?string $lastName = null;
/**
* This is always the language that comes over from the SIS, if any.
* This value should never be set by controllers for the portals, etc.
* Should only really ever be set in the syncing code.
*
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = true,
* length = 2,
* )
*/
protected ?string $language = null;
/**
* This is an optional language value to track any language setting the person has selected within our system.
* Things like portal controllers and such should modify this value.
*
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = true,
* length = 2,
* )
*/
protected ?string $languageOverride = null;
/**
* @var string|null
*
* @ORM\Column(
* type = "string",
* nullable = false,
* )
*/
protected ?string $role = null;
/**
* @var integer|null
*
* @ORM\Column(
* type = "integer",
* nullable = true,
* )
*/
protected ?int $code = null;
/**
* @var Collection|ProfileRelationship[]|null
*
* @ORM\OneToMany(
* targetEntity = "Products\NotificationsBundle\Entity\ProfileRelationship",
* mappedBy = "profile",
* )
*/
protected Collection $relationships;
/**
* @var array|null
*
* @ORM\Column(
* type = "json",
* nullable = true,
* )
*/
protected ?array $metadata = [];
/**
* @var Collection|AutomationRecord[]|
*
* @ORM\OneToMany(
* targetEntity = "Products\NotificationsBundle\Entity\AutomationRecord",
* mappedBy = "profile",
* )
*/
protected Collection $automationRecords;
/**
*
*/
public function __construct()
{
$this->contacts = new ArrayCollection();
$this->relationships = new ArrayCollection();
$this->tokens = new ArrayCollection();
$this->automationRecords = new ArrayCollection();
}
/**
* @return array
*/
public function getMetadata(): array
{
return $this->metadata ?? [];
}
/**
* @param array|null $metadata
* @return $this
*/
public function setMetadata(?array $metadata): self
{
$this->metadata = $metadata ?: null;
return $this;
}
/**
* @param string $key
* @param mixed $value
* @return $this
*/
public function setMetadataEntry(string $key, $value): self
{
$metadata = $this->getMetadata() ?: [];
$metadata[$key] = $value;
$this->setMetadata($metadata);
return $this;
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|ProfileContact[]
*/
public function getContacts($criteria = null): iterable
{
if ( ! $this->contacts instanceof Collection) {
$this->contacts = new ArrayCollection();
}
$criteria = ($criteria === true) ? new Criteria() : ($criteria ?: null);
if ( ! empty($criteria)) {
return $this->contacts->matching($criteria);
}
return $this->contacts;
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|ProfileContact[]
*/
public function getEmailContacts($criteria = null): iterable
{
return $this->getContacts($criteria)->filter(function (ProfileContact $contact) {
return $contact->getRecipient() instanceof EmailRecipient;
});
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|ProfileContact[]
*/
public function getPhoneContacts($criteria = null): iterable
{
return $this->getContacts($criteria)->filter(function (ProfileContact $contact) {
return $contact->getRecipient() instanceof PhoneRecipient;
});
}
/**
* @param PhoneRecipient $phoneRecipient
* @return ProfileContact|null
*/
public function getPhoneContactByPhoneRecipient(PhoneRecipient $phoneRecipient): ?ProfileContact
{
$expressionBuilder = Criteria::expr();
$criteria = new Criteria();
$criteria->andWhere($expressionBuilder->eq('recipient', $phoneRecipient));
return $this->getContacts($criteria)->first();
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|ProfileContact[]
*/
public function getAppContacts($criteria = null): iterable
{
return $this->getContacts($criteria)->filter(function (ProfileContact $contact) {
return $contact->getRecipient() instanceof AppRecipient;
});
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|AbstractRecipient[]
*/
public function getRecipients($criteria = null): iterable
{
return $this->getContacts($criteria)->map(function (ProfileContact $contact) {
return $contact->getRecipient();
});
}
/**
* @param Criteria|bool|null $criteria
* @return array|AbstractRecipient[]
*/
public function getRecipientsAsArray($criteria = null): array
{
return $this->getRecipients($criteria)->toArray();
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|EmailRecipient[]
*/
public function getEmailRecipients($criteria = null): iterable
{
return $this->getRecipients($criteria)->filter(function (AbstractRecipient $recipient) {
return $recipient instanceof EmailRecipient;
});
}
/**
* @param Criteria|bool|null $criteria
* @return array|EmailRecipient[]
*/
public function getEmailRecipientsAsArray($criteria = null): array
{
return $this->getEmailRecipients($criteria)->toArray();
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|PhoneRecipient[]
*/
public function getPhoneRecipients($criteria = null): iterable
{
return $this->getRecipients($criteria)->filter(function (AbstractRecipient $recipient) {
return $recipient instanceof PhoneRecipient;
});
}
/**
* @param Criteria|bool|null $criteria
* @return array|PhoneRecipient[]
*/
public function getPhoneRecipientsAsArray($criteria = null): array
{
return $this->getPhoneRecipients($criteria)->toArray();
}
/**
* @param Criteria|bool|null $criteria
* @return iterable|Collection|array|AppRecipient[]
*/
public function getAppRecipients($criteria = null): iterable
{
return $this->getRecipients($criteria)->filter(function (AbstractRecipient $recipient) {
return $recipient instanceof AppRecipient;
});
}
/**
* @param Criteria|bool|null $criteria
* @return array|AppRecipient[]
*/
public function getAppRecipientsAsArray($criteria = null): array
{
return $this->getAppRecipients($criteria)->toArray();
}
/**
* {@inheritDoc}
*/
public function getRoles(): array
{
return [
'DUMMY_ROLE',
];
}
/**
* {@inheritDoc}
*/
public function getPassword(): ?string
{
return $this->getCode();
}
/**
* {@inheritDoc}
*/
public function getSalt(): ?string
{
// TODO: this function is not needed once we are on Symfony6
return null;
}
/**
* {@inheritDoc}
*/
public function getUserIdentifier(): string
{
return $this->getUid()->toString();
}
/**
* {@inheritDoc}
*/
public function getUsername(): string
{
// TODO: remove this function once we are on Symfony6
return $this->getUserIdentifier();
}
/**
* {@inheritDoc}
*/
public function eraseCredentials()
{
// NOOP
}
/**
* {@inheritDoc}
*/
public function serialize(): string
{
return serialize([
$this->getId(),
]);
}
/**
* {@inheritDoc}
*/
public function unserialize($data): void
{
[$this->id] = unserialize($data, ['allowed_classes' => false]);
}
/**
* {@inheritDoc}
* @param Profile $user
*/
public function isEqualTo(UserInterface $user): bool
{
return $this->getId() === $user->getId();
}
/**
* @return string|null
*/
public function getFirstName(): ?string
{
return $this->firstName;
}
/**
* @param string|null $firstName
* @return $this
*/
public function setFirstName(?string $firstName): self
{
$this->firstName = $firstName ?: null;
return $this;
}
/**
* @return string|null
*/
public function getLastName(): ?string
{
return $this->lastName;
}
/**
* @param string|null $lastName
* @return $this
*/
public function setLastName(?string $lastName): self
{
$this->lastName = $lastName ?: null;
return $this;
}
/**
* @return string|null
*/
public function getFullName(): ?string
{
return trim(sprintf(
'%s %s',
$this->getFirstName() ?: '—',
$this->getLastName()
)) ?? null;
}
/**
* @return string|null
*/
public function getCensoredName(): ?string
{
return trim(sprintf(
'%s %s',
mb_strtoupper(mb_substr($this->getFirstName(), 0, 1)),
$this->getLastName()
)) ?? null;
}
/**
* @return string|null
*/
public function getSortName(): ?string
{
return trim(sprintf(
'%s, %s',
$this->getLastName(),
$this->getFirstName()
), ", \t\n\r\0\x0B") ?? null;
}
/**
* @return string|null
*/
public function getLanguage(): ?string
{
return $this->language;
}
/**
* @param string|null $language
* @return $this
*/
public function setLanguage(?string $language): self
{
if ($language && ! Locales::isLanguage($language)) {
throw new \RuntimeException(
sprintf(
'Language code "%s" is not supported, must be one of: %s.',
$language,
implode(', ', Locales::ISO6391),
),
);
}
$this->language = $language;
return $this->setMetadataEntry(self::METADATA__LANGUAGE, $this->getEffectiveLanguage());
}
/**
* @return string|null
*/
public function getLanguageOverride(): ?string
{
return $this->languageOverride;
}
/**
* @param string|null $languageOverride
* @return $this
*/
public function setLanguageOverride(?string $languageOverride): self
{
if ( ! Locales::isLanguage($languageOverride)) {
throw new \RuntimeException(
sprintf(
'Language override code "%s" is not supported, must be one of: %s.',
$languageOverride,
implode(', ', Locales::ISO6391),
),
);
}
$this->languageOverride = $languageOverride;
return $this->setMetadataEntry(self::METADATA__LANGUAGE, $this->getEffectiveLanguage());
}
/**
* This is the method that should generally be used when trying to determine a user's language preferences.
* If a user has set something within our system, that preference will be returned.
* If not, it will attempt to see if any language preference has come over from the SIS.
* If none of those are set, then it will return English as that is the default.
*
* @return string
*/
public function getEffectiveLanguage(): string
{
return $this->getLanguageOverride() ?? $this->getLanguage() ?? Locales::ISO6391__ENGLISH;
}
/**
* @return string
*/
public function getLocale(): string
{
return Locales::localeForLanguage(
$this->getEffectiveLanguage(),
);
}
/**
* @return string|null
*/
public function getRole(): ?string
{
return $this->role;
}
/**
* @param string $role
* @return $this
*/
public function setRole(string $role): self
{
$this->role = $role;
return $this;
}
/**
* @return int|null
*/
public function getRoleType(): ?int
{
return ($this->getRole()) ? OneRosterUser::ROLES_MAPPING[$this->getRole()] : null;
}
/**
* @return string|null
*/
public function getRoleTypeName(): ?string
{
return ($this->getRoleType()) ? OneRosterUser::TYPES_LOOKUP[$this->getRoleType()] : null;
}
/**
* @param int $type
* @return bool
*/
public function isRoleType(int $type): bool
{
return ($this->getRoleType() === $type);
}
/**
* TODO: rename and fix code...
*
* @return bool
*/
public function isRoleStaff(): bool
{
return $this->isRoleType(OneRosterUser::TYPES__STAFF);
}
/**
* TODO: rename and fix code...
*
* @return bool
*/
public function isRoleStudent(): bool
{
return $this->isRoleType(OneRosterUser::TYPES__STUDENT);
}
/**
* TODO: rename and fix code...
*
* @return bool
*/
public function isRoleFamily(): bool
{
return $this->isRoleType(OneRosterUser::TYPES__FAMILY);
}
/**
* @param Criteria|null $criteria
* @return Collection|ProfileRelationship[]
*/
public function getRelationships(?Criteria $criteria = null): Collection
{
if ( ! $this->relationships instanceof Collection) {
$this->relationships = new ArrayCollection();
}
if ( ! empty($criteria)) {
return $this->relationships->matching($criteria);
}
return $this->relationships;
}
/**
* @param Criteria|null $criteria
* @return Collection|Student[]
*/
public function getStudents(?Criteria $criteria = null): Collection
{
return $this->getRelationships($criteria)->map(function (ProfileRelationship $relationship) {
return $relationship->getStudent();
});
}
/**
* @return int|null
*/
public function getCode(): ?int
{
return $this->code;
}
/**
* @param int|null $code
* @return $this
*/
public function setCode(?int $code): self
{
$this->code = $code ?: null;
return $this;
}
/**
* @param Criteria|bool|null $criteria
* @return ArrayCollection|Checkup[]
*/
public function getCheckups($criteria = null): iterable
{
if ( ! $this->checkups instanceof Collection) {
$this->checkups = new ArrayCollection();
}
$criteria = ($criteria === true) ? new Criteria() : ($criteria ?: null);
if ( ! empty($criteria)) {
return $this->checkups->matching($criteria);
}
return $this->checkups;
}
/**
* @return Checkup|null
*/
public function getUnhandledCheckup(): ?Checkup
{
$results = $this->getCheckups(
(new Criteria())
->andWhere(Criteria::expr()->eq('ok', false))
->andWhere(Criteria::expr()->isNull('fixedAt'))
->orderBy([
'createdAt' => 'DESC',
])
);
return ($results->count()) ? $results->get(0) : null;
}
/**
* {@inheritDoc}
*/
public function getIdentifier(): string
{
return $this->getUidString();
}
/**
* @param Criteria|bool|null $criteria
* @return ArrayCollection|AbstractAppToken[]
*/
public function getTokens($criteria = null): iterable
{
if ( ! $this->tokens instanceof Collection) {
$this->tokens = new ArrayCollection();
}
$criteria = ($criteria === true) ? new Criteria() : ($criteria ?: null);
if ( ! empty($criteria)) {
return $this->tokens->matching($criteria);
}
return $this->tokens;
}
/**
* @return string|null
*/
public function getMetadataDistrict(): ?string
{
$metadata = $this->getMetadata();
return ($metadata && isset($metadata['_district'])) ? $metadata['_district'] : null;
}
/**
* @return array
*/
public function getMetadataSchools(): array
{
$metadata = $this->getMetadata();
return ($metadata && isset($metadata['_schools'])) ? $metadata['_schools'] : [];
}
/**
* @return Collection|AutomationRecord[]
*/
public function getAutomationRecords(): Collection
{
return $this->automationRecords;
}
/**
* @param Collection|AutomationRecord[] $automationRecords
* @return self
*/
public function setAutomationRecords(Collection $automationRecords): self
{
$this->automationRecords = $automationRecords;
return $this;
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
'metadata' => $this->getMetadata(),
];
}
}