src/App/Entity/Content/AbstractObject.php line 61

Open in your IDE?
  1. <?php
  2. namespace App\Entity\Content;
  3. use App\Entity\Content\Common\DiscriminatorInterface;
  4. use App\Entity\Content\Common\DiscriminatorTrait;
  5. use App\Entity\Content\Common\Props\HeadlineTrait;
  6. use App\Entity\Content\Common\Props\TimestampTrait;
  7. use App\Entity\Feed\AbstractEntry;
  8. use App\Entity\Feed\Entry\AbstractContentEntry;
  9. use App\Entity\Shared\UlidIdentifiableInterface;
  10. use App\Entity\Shared\UlidIdentifiableTrait;
  11. use App\Entity\System\School;
  12. use App\Entity\System\SocialAccount;
  13. use App\Enum\ChannelEnum;
  14. use App\Model\Async\StringSemaphoreInterface;
  15. use App\Model\Async\StringSemaphoreTrait;
  16. use App\Model\Content\ContentInterface;
  17. use App\Model\Content\ObjectInterface;
  18. use App\Util\Bitwise;
  19. use App\Util\Errors;
  20. use Cms\ContainerBundle\Entity\Container;
  21. use Cms\CoreBundle\Model\Interfaces\Blameable;
  22. use Cms\CoreBundle\Model\Interfaces\Loggable\LoggableInterface;
  23. use Cms\CoreBundle\Model\Interfaces\Timestampable;
  24. use Cms\Modules\AlertBundle\Model\Alert\AlertData;
  25. use Cms\TenantBundle\Model as Tenantable;
  26. use DateTimeInterface;
  27. use Doctrine\Common\Collections\ArrayCollection;
  28. use Doctrine\Common\Collections\Collection;
  29. use Doctrine\ORM\Mapping as ORM;
  30. use Ramsey\Uuid\Uuid;
  31. use Ramsey\Uuid\UuidInterface;
  32. use Symfony\Component\Serializer\Annotation\Context;
  33. use Symfony\Component\Serializer\Annotation\Groups;
  34. use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
  35. /**
  36.  * @ORM\Entity(
  37.  *     repositoryClass = "App\Doctrine\Repository\Content\ObjectRepository",
  38.  * )
  39.  * @ORM\InheritanceType("SINGLE_TABLE")
  40.  * @ORM\DiscriminatorColumn(
  41.  *     name = DiscriminatorInterface::COLUMN__NAME,
  42.  *     type = DiscriminatorInterface::COLUMN__TYPE,
  43.  *     length = 64,
  44.  * )
  45.  * @ORM\DiscriminatorMap(AbstractObject::DISCRS)
  46.  * @ORM\Table(
  47.  *     name = "sn__content__object",
  48.  *     indexes = {
  49.  *         @ORM\Index(
  50.  *             name = "idx__discr",
  51.  *             columns = {
  52.  *                 "discr",
  53.  *             },
  54.  *         ),
  55.  *     },
  56.  * )
  57.  */
  58. abstract class AbstractObject
  59.     implements
  60.         Tenantable\TenantableInterface,
  61.         UlidIdentifiableInterface,
  62.         Timestampable\TimestampableInterface,
  63.         Blameable\BlameableInterface,
  64.         DiscriminatorInterface,
  65.         ContentInterface,
  66.         ObjectInterface,
  67.         LoggableInterface,
  68.         StringSemaphoreInterface
  69. {
  70.     const DISCR null;
  71.     const DISCRS = [
  72.         Alerts\Alert\AlertObject::DISCR => Alerts\Alert\AlertObject::class,
  73.         Events\Event\EventObject::DISCR => Events\Event\EventObject::class,
  74.         Exhibits\Gallery\GalleryObject::DISCR => Exhibits\Gallery\GalleryObject::class,
  75.         Exhibits\Video\VideoObject::DISCR => Exhibits\Video\VideoObject::class,
  76.         Posts\Post\PostObject::DISCR => Posts\Post\PostObject::class,
  77.     ];
  78.     const CLASSES__ALTERATION null;
  79.     const ROUTING_SLUG null;
  80.     public const HEADLINE_LIMIT 100;
  81.     use Tenantable\TenantableTrait;
  82.     use UlidIdentifiableTrait;
  83.     use Timestampable\TimestampableTrait;
  84.     use Blameable\BlameableTrait;
  85.     use DiscriminatorTrait;
  86.     use HeadlineTrait;
  87.     use TimestampTrait;
  88.     use StringSemaphoreTrait;
  89.     /**
  90.      * @var School|null
  91.      *
  92.      * @ORM\ManyToOne(
  93.      *     targetEntity = School::class,
  94.      * )
  95.      * @ORM\JoinColumn(
  96.      *     fieldName = "school",
  97.      *     referencedColumnName = "id",
  98.      *     nullable = true,
  99.      *     onDelete = "CASCADE",
  100.      * )
  101.      *
  102.      *  @Groups({"school", "school_minimal"})
  103.      *
  104.      */
  105.     protected ?School $school null;
  106.     /**
  107.      * @var AbstractContentEntry|null
  108.      *
  109.      * @ORM\OneToOne(
  110.      *     targetEntity = AbstractContentEntry::class,
  111.      *     mappedBy = "object",
  112.      *     cascade = {"remove"}
  113.      * )
  114.      */
  115.     protected ?AbstractContentEntry $entry null;
  116.     /**
  117.      * @var Container|null
  118.      *
  119.      * @ORM\ManyToOne(
  120.      *     targetEntity = Container::class,
  121.      * )
  122.      * @ORM\JoinColumn(
  123.      *     fieldName = "department",
  124.      *     referencedColumnName = "id",
  125.      *     nullable = true,
  126.      *     onDelete = "CASCADE",
  127.      * )
  128.      *
  129.      * @Groups({"department", "department_minimal"})
  130.      *
  131.      */
  132.     protected ?Container $department null;
  133.     /**
  134.      * @var int
  135.      *
  136.      * @ORM\Column(
  137.      *     type = "bigint",
  138.      *     nullable = false,
  139.      *     options = {
  140.      *         "default" = 0,
  141.      *     },
  142.      * )
  143.      *
  144.      * @Groups("object")
  145.      *
  146.      * https://github.com/symfony/symfony/issues/54418
  147.      * @Context(denormalizationContext = {AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT = true})
  148.      *
  149.      */
  150.     protected int $schoolTypes 0;
  151.     /**
  152.      * @var int
  153.      *
  154.      * @ORM\Column(
  155.      *     type = "integer",
  156.      *     nullable = false,
  157.      *     options = {
  158.      *         "default" = ObjectInterface::VISIBILITIES__PUBLISHED,
  159.      *     },
  160.      * )
  161.      *
  162.      * @Groups("object_visibility")
  163.      *
  164.      */
  165.     protected int $visibility ObjectInterface::VISIBILITIES__PUBLISHED;
  166.     /**
  167.      * @var Collection|null
  168.      */
  169.     protected Collection $alterations;
  170.     /**
  171.      * @var Collection|null
  172.      */
  173.     protected Collection $drafts;
  174.     /**
  175.      * @var int|null
  176.      *
  177.      * @ORM\Column(
  178.      *     type = "integer",
  179.      *     nullable = true,
  180.      * )
  181.      */
  182.     protected ?int $migrationId null;
  183.     /**
  184.      * @var UuidInterface|null
  185.      *
  186.      * @ORM\Column(
  187.      *     type = "uuid",
  188.      *     nullable = true,
  189.      * )
  190.      */
  191.     protected ?UuidInterface $migrationUid null;
  192.     /**
  193.      * @var DateTimeInterface|null
  194.      *
  195.      * @ORM\Column(
  196.      *     type = "datetime",
  197.      *     nullable = true,
  198.      * )
  199.      *
  200.      * @Groups("object_scheduled_at")
  201.      *
  202.      */
  203.     protected ?DateTimeInterface $scheduledAt null;
  204.     /**
  205.      * @var int
  206.      *
  207.      * @ORM\Column(
  208.      *     type = "integer",
  209.      *     nullable = false,
  210.      *     options = {
  211.      *         "default" = ChannelEnum::NONE,
  212.      *         "unsigned" = true,
  213.      *     },
  214.      * )
  215.      *
  216.      * @Groups("object")
  217.      *
  218.      */
  219.     protected int $channels ChannelEnum::WEBSITE ChannelEnum::APP;
  220.     /**
  221.      * @var int
  222.      *
  223.      * @ORM\Column(
  224.      *     type = "integer",
  225.      *     nullable = false,
  226.      *     options = {
  227.      *         "default" = AlertData::BEHAVIORS__NONE,
  228.      *     },
  229.      * )
  230.      *
  231.      * @Groups("object")
  232.      *
  233.      */
  234.     protected int $websiteBehavior AlertData::BEHAVIORS__NONE;
  235.     /**
  236.      * @var int|null
  237.      *
  238.      * @ORM\Column(
  239.      *     type = "integer",
  240.      *     nullable = false,
  241.      *     options = {
  242.      *         "default" = AlertData::LEVELS__INFORMATIVE,
  243.      *     },
  244.      * )
  245.      *
  246.      * @Groups("object")
  247.      *
  248.      */
  249.     protected int $websiteLevel AlertData::LEVELS__INFORMATIVE;
  250.     /**
  251.      * @var DateTimeInterface|null
  252.      *
  253.      * @ORM\Column(
  254.      *     type = "datetime",
  255.      *     nullable = true,
  256.      * )
  257.      *
  258.      * @Groups("object")
  259.      *
  260.      */
  261.     protected ?DateTimeInterface $websiteEndDateTime null;
  262.     /**
  263.      * @var array|null
  264.      *
  265.      * @ORM\Column(
  266.      *     type = "json",
  267.      *     nullable = true,
  268.      * )
  269.      */
  270.     protected ?array $socialPosts = [];
  271.     /**
  272.      * @param ContentInterface|null $content
  273.      */
  274.     public function __construct(?ContentInterface $content null)
  275.     {
  276.         $this->alterations = new ArrayCollection();
  277.         $this->drafts = new ArrayCollection();
  278.         if ($content) {
  279.             $this->copy($content);
  280.         }
  281.     }
  282.     /**
  283.      * @return int|null
  284.      */
  285.     public function getMigrationId(): ?int
  286.     {
  287.         return $this->migrationId;
  288.     }
  289.     /**
  290.      * @param int|null $migrationId
  291.      * @return $this
  292.      */
  293.     public function setMigrationId(?int $migrationId): self
  294.     {
  295.         $this->migrationId $migrationId;
  296.         return $this;
  297.     }
  298.     /**
  299.      * @return UuidInterface|null
  300.      */
  301.     public function getMigrationUid(): ?UuidInterface
  302.     {
  303.         return $this->migrationUid;
  304.     }
  305.     /**
  306.      * @param string|UuidInterface|null $migrationUid
  307.      * @return $this
  308.      */
  309.     public function setMigrationUid($migrationUid): self
  310.     {
  311.         if ($migrationUid) {
  312.             if (is_string($migrationUid)) {
  313.                 $migrationUid Uuid::fromString($migrationUid);
  314.             }
  315.             if ( ! $migrationUid instanceof UuidInterface) {
  316.                 throw new \Exception();
  317.             }
  318.         }
  319.         $this->migrationUid $migrationUid ?: null;
  320.         return $this;
  321.     }
  322.     /**
  323.      * {@inheritDoc}
  324.      */
  325.     public static function getAlterationClass(): ?string
  326.     {
  327.         return static::CLASSES__ALTERATION;
  328.     }
  329.     /**
  330.      * {@inheritDoc}
  331.      * @return Collection|AbstractDraft[]
  332.      */
  333.     public function getDrafts(): iterable
  334.     {
  335.         return $this->drafts;
  336.     }
  337.     /**
  338.      * {@inheritDoc}
  339.      * @return Collection|AbstractAlteration[]
  340.      */
  341.     public function getAlterations(): iterable
  342.     {
  343.         return $this->alterations;
  344.     }
  345.     /**
  346.      * @return School|null
  347.      */
  348.     public function getSchool(): ?School
  349.     {
  350.         return $this->school;
  351.     }
  352.     /**
  353.      * @param School|null $school
  354.      * @return $this
  355.      */
  356.     public function setSchool(?School $school): self
  357.     {
  358.         $this->school $school;
  359.         return $this;
  360.     }
  361.     /**
  362.      * @return Container|null
  363.      */
  364.     public function getDepartment(): ?Container
  365.     {
  366.         return $this->department;
  367.     }
  368.     /**
  369.      * @param Container|null $department
  370.      * @return $this
  371.      */
  372.     public function setDepartment(?Container $department): self
  373.     {
  374.         $this->department $department;
  375.         return $this;
  376.     }
  377.     /**
  378.      * @return int
  379.      */
  380.     public function getSchoolTypes(): int
  381.     {
  382.         return $this->schoolTypes;
  383.     }
  384.     /**
  385.      * @return string[]
  386.      */
  387.     public function getSchoolTypesName(): array
  388.     {
  389.         return array_filter(
  390.             array_map(static function ($type) {
  391.                 return array_search($typeSchool::TYPES);
  392.             }, Bitwise::explode($this->schoolTypes))
  393.         );
  394.     }
  395.     /**
  396.      * @param int|null $schoolTypes
  397.      * @return $this
  398.      */
  399.     public function setSchoolTypes(?int $schoolTypes): self
  400.     {
  401.         $this->schoolTypes $schoolTypes ?: 0;
  402.         return $this;
  403.     }
  404.     /**
  405.      * @return int
  406.      */
  407.     public function getVisibility(): int
  408.     {
  409.         return $this->visibility;
  410.     }
  411.     /**
  412.      * @return string
  413.      */
  414.     public function getVisibilityName(): string
  415.     {
  416.         return array_search($this->getVisibility(), ObjectInterface::VISIBILITIES);
  417.     }
  418.     /**
  419.      * @param int $visibility
  420.      * @return $this
  421.      */
  422.     public function setVisibility(int $visibility): self
  423.     {
  424.         if ( ! in_array($visibilityObjectInterface::VISIBILITIES)) {
  425.             throw new \LogicException();
  426.         }
  427.         $this->visibility $visibility;
  428.         return $this;
  429.     }
  430.     /**
  431.      * @return bool
  432.      */
  433.     public function isUnpublished(): bool
  434.     {
  435.         return $this->getVisibility() === ObjectInterface::VISIBILITIES__UNPUBLISHED;
  436.     }
  437.     /**
  438.      * @return bool
  439.      */
  440.     public function isHidden(): bool
  441.     {
  442.         return $this->getVisibility() === ObjectInterface::VISIBILITIES__HIDDEN;
  443.     }
  444.     /**
  445.      * @return bool
  446.      */
  447.     public function isPublished(): bool
  448.     {
  449.         return $this->getVisibility() === ObjectInterface::VISIBILITIES__PUBLISHED;
  450.     }
  451.     /**
  452.      * @return AbstractEntry|null
  453.      */
  454.     public function getEntry(): ?AbstractEntry
  455.     {
  456.         return $this->entry;
  457.     }
  458.     /**
  459.      * @param AbstractEntry|null $entry
  460.      * @return $this
  461.      */
  462.     public function setEntry(?AbstractEntry $entry): self
  463.     {
  464.         $this->entry $entry;
  465.         return $this;
  466.     }
  467.     /**
  468.      * @return DateTimeInterface|null
  469.      */
  470.     public function getScheduledAt(): ?DateTimeInterface
  471.     {
  472.         return $this->scheduledAt;
  473.     }
  474.     /**
  475.      * @param DateTimeInterface|null $scheduledAt
  476.      * @return self
  477.      */
  478.     public function setScheduledAt(?DateTimeInterface $scheduledAt): self
  479.     {
  480.         $this->scheduledAt $scheduledAt;
  481.         return $this;
  482.     }
  483.     /**
  484.      * @return int
  485.      */
  486.     public function getChannels(): int
  487.     {
  488.         return $this->channels;
  489.     }
  490.     /**
  491.      * @return string[]
  492.      */
  493.     public function getChannelsName(): array
  494.     {
  495.         return array_filter(
  496.             array_map(static function ($channel) {
  497.                 return array_search($channelChannelEnum::ALL_CHANNELS);
  498.             }, Bitwise::explode($this->channels))
  499.         );
  500.     }
  501.     /**
  502.      * @param int $channels
  503.      * @return self
  504.      */
  505.     public function setChannels(int $channels): self
  506.     {
  507.         $this->channels $channels;
  508.         return $this;
  509.     }
  510.     /**
  511.      * @param int $channel
  512.      * @return bool
  513.      */
  514.     public function hasChannel(int $channel): bool
  515.     {
  516.         return (($this->getChannels() & $channel) === $channel);
  517.     }
  518.     /**
  519.      * @return int
  520.      */
  521.     public function getWebsiteBehavior(): int
  522.     {
  523.         return $this->websiteBehavior;
  524.     }
  525.     /**
  526.      * @return string
  527.      */
  528.     public function getWebsiteBehaviorName(): string
  529.     {
  530.         return array_search($this->getWebsiteBehavior(), AlertData::BEHAVIORS);
  531.     }
  532.     /**
  533.      * @param int $websiteBehavior
  534.      * @return self
  535.      */
  536.     public function setWebsiteBehavior(int $websiteBehavior): self
  537.     {
  538.         $this->websiteBehavior $websiteBehavior;
  539.         return $this;
  540.     }
  541.     /**
  542.      * @return int
  543.      */
  544.     public function getWebsiteLevel(): int
  545.     {
  546.         return $this->websiteLevel;
  547.     }
  548.     /**
  549.      * @return string
  550.      */
  551.     public function getWebsiteLevelName(): string
  552.     {
  553.         return array_search($this->getWebsiteLevel(), AlertData::LEVELS);
  554.     }
  555.     /**
  556.      * @param int $websiteLevel
  557.      * @return self
  558.      */
  559.     public function setWebsiteLevel(int $websiteLevel): self
  560.     {
  561.         $this->websiteLevel $websiteLevel;
  562.         return $this;
  563.     }
  564.     /**
  565.      * @return DateTimeInterface|null
  566.      */
  567.     public function getWebsiteEndDateTime(): ?DateTimeInterface
  568.     {
  569.         return $this->websiteEndDateTime;
  570.     }
  571.     /**
  572.      * @param DateTimeInterface|null $websiteEndDateTime
  573.      * @return self
  574.      */
  575.     public function setWebsiteEndDateTime(?DateTimeInterface $websiteEndDateTime): self
  576.     {
  577.         $this->websiteEndDateTime $websiteEndDateTime;
  578.         return $this;
  579.     }
  580.     public function getReview(): array
  581.     {
  582.         return [
  583.             'channels' => $this->getChannels(),
  584.             'scheduledAt' => $this->getScheduledAt(),
  585.             'scheduled' => (bool)$this->getScheduledAt(),
  586.             'websiteEndDateTime' => $this->getWebsiteEndDateTime(),
  587.             'websiteLevel' => $this->getWebsiteLevel(),
  588.             'websiteBehavior' => $this->getWebsiteBehavior(),
  589.             'schoolTypes' => Bitwise::explode($this->getSchoolTypes()),
  590.         ];
  591.     }
  592.     public function setReview(array $review): self
  593.     {
  594.         if (empty($review)) {
  595.             return $this;
  596.         }
  597.         if (isset($review['scheduled']) && $review['scheduled'] === false) {
  598.             $review['scheduledAt'] = null;
  599.         }
  600.         if (isset($review['scheduledAt']) && $review['scheduledAt'] instanceof DateTimeInterface) {
  601.             $this->setVisibility(ObjectInterface::VISIBILITIES__UNPUBLISHED);
  602.         }
  603.         return $this
  604.             ->setScheduledAt($review['scheduledAt'] ?? null)
  605.             ->setChannels($review['channels'] ?? ChannelEnum::NONE)
  606.             ->setWebsiteEndDateTime($review['websiteEndDateTime'] ?? null)
  607.             ->setWebsiteLevel($review['websiteLevel'] ?? AlertData::LEVELS__INFORMATIVE)
  608.             ->setWebsiteBehavior($review['websiteBehavior'] ?? AlertData::BEHAVIORS__NONE)
  609.             ->setSchoolTypes(array_sum($review['schoolTypes'] ?? []))
  610.         ;
  611.     }
  612.     /**
  613.      * @return string
  614.      */
  615.     abstract public function __toString(): string;
  616.     /**
  617.      * {@inheritDoc}
  618.      */
  619.     public function getLoggableDetails(): array
  620.     {
  621.         return [
  622.             'id' => (string) $this->getId(),
  623.             'title' => $this->__toString(),
  624.             'type' => static::ROUTING_SLUG,
  625.         ];
  626.     }
  627.     /**
  628.      * @param string $socialType
  629.      * @param string $accountId
  630.      * @param string|null $postId
  631.      * @param \Throwable|string $result
  632.      * @return $this
  633.      */
  634.     public function addSocialPost(
  635.         string $socialType,
  636.         string $accountId,
  637.         ?string $postId,
  638.         $result
  639.     ): self
  640.     {
  641.         // make sure that the social type is legit
  642.         if ( ! array_key_exists($socialTypeSocialAccount::DISCRS)) {
  643.             throw new \LogicException();
  644.         }
  645.         // for backwards compatibility, make sure we have an array to work with
  646.         if ($this->socialPosts === null) {
  647.             $this->socialPosts = [];
  648.         }
  649.         // make sure this social type exists
  650.         if ( ! array_key_exists($socialType$this->socialPosts)) {
  651.             $this->socialPosts[$socialType] = [];
  652.         }
  653.         // set the specific account details
  654.         $this->socialPosts[$socialType][$accountId] = [
  655.             'id' => $postId,
  656.         ];
  657.         switch (true) {
  658.             case $result && is_string($result):
  659.                 $this->socialPosts[$socialType][$accountId]['url'] = $result;
  660.                 break;
  661.             case $result instanceof \Throwable:
  662.                 $this->socialPosts[$socialType][$accountId]['error'] = Errors::jsonSerialize(
  663.                     $result,
  664.                     false,
  665.                 );
  666.                 break;
  667.         }
  668.         return $this;
  669.     }
  670.     /**
  671.      * @return array
  672.      */
  673.     public function getSocialPosts(): array
  674.     {
  675.         return $this->socialPosts ?? [];
  676.     }
  677. }