src/App/Entity/Feed/AbstractEntry.php line 47

Open in your IDE?
  1. <?php
  2. namespace App\Entity\Feed;
  3. use App\Entity\Content\Common\DiscriminatorInterface;
  4. use App\Entity\Content\Common\DiscriminatorTrait;
  5. use App\Entity\Content\Common\Props\SlugInterface;
  6. use App\Entity\Feed\Entry;
  7. use App\Entity\Shared\UlidIdentifiableInterface;
  8. use App\Entity\Shared\UlidIdentifiableTrait;
  9. use App\Entity\System\School;
  10. use App\Model\Content\Media\AbstractMedia;
  11. use App\Model\Content\Media\MediaCollection;
  12. use App\Util\Bitwise;
  13. use App\Util\Html;
  14. use Cms\ContainerBundle\Entity\Container;
  15. use Cms\CoreBundle\Model\Interfaces\Blameable\BlameableInterface;
  16. use Cms\CoreBundle\Model\Interfaces\Blameable\BlameableTrait;
  17. use Cms\CoreBundle\Model\Interfaces\Timestampable\TimestampableInterface;
  18. use Cms\CoreBundle\Model\Interfaces\Timestampable\TimestampableTrait;
  19. use Cms\CoreBundle\Service\Slugger;
  20. use Cms\TenantBundle\Model\TenantableInterface;
  21. use Cms\TenantBundle\Model\TenantableTrait;
  22. use DateTimeInterface;
  23. use Doctrine\ORM\Mapping as ORM;
  24. use Html2Text\Html2Text;
  25. use Reinder83\BinaryFlags\Bits;
  26. use Spatie\Url\Url;
  27. use Symfony\Component\Serializer\Annotation\Context;
  28. use Symfony\Component\Serializer\Annotation\Groups;
  29. use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
  30. /**
  31.  * @ORM\Entity(
  32.  *     repositoryClass = "App\Doctrine\Repository\Feed\EntryRepository",
  33.  * )
  34.  * @ORM\InheritanceType("SINGLE_TABLE")
  35.  * @ORM\DiscriminatorColumn(
  36.  *     name = DiscriminatorInterface::COLUMN__NAME,
  37.  *     type = DiscriminatorInterface::COLUMN__TYPE,
  38.  * )
  39.  * @ORM\DiscriminatorMap(AbstractEntry::DISCRS)
  40.  * @ORM\Table(
  41.  *     name = "sys__feed__entry",
  42.  * )
  43.  */
  44. abstract class AbstractEntry
  45.     implements
  46.         UlidIdentifiableInterface,
  47.         TenantableInterface,
  48.         TimestampableInterface,
  49.         BlameableInterface,
  50.         DiscriminatorInterface,
  51.         SlugInterface
  52. {
  53.     const DISCRS = [
  54.         Entry\AbstractContentEntry::DISCR => Entry\AbstractContentEntry::class,
  55.         Entry\ContentEventEntry::DISCR => Entry\ContentEventEntry::class,
  56.         Entry\ContentGalleryEntry::DISCR => Entry\ContentGalleryEntry::class,
  57.         Entry\ContentPostEntry::DISCR => Entry\ContentPostEntry::class,
  58.         Entry\ContentVideoEntry::DISCR => Entry\ContentVideoEntry::class,
  59.     ];
  60.     const DISCR null;
  61.     const TYPE_CLASSES = [
  62.         self::TYPES__EVENT => Entry\ContentEventEntry::class,
  63.         self::TYPES__GALLERY => Entry\ContentGalleryEntry::class,
  64.         self::TYPES__POST => Entry\ContentPostEntry::class,
  65.         self::TYPES__VIDEO => Entry\ContentVideoEntry::class,
  66.     ];
  67.     const TYPES__EVENT 'event';
  68.     const TYPES__GALLERY 'gallery';
  69.     const TYPES__POST 'post';
  70.     const TYPES__VIDEO 'video';
  71.     public const PERMISSION_MAP = [
  72.         self::TYPES__POST => 'campussuite.cms.modules.news.manage',
  73.         self::TYPES__EVENT => 'campussuite.cms.modules.calendar.manage',
  74.         self::TYPES__GALLERY => 'campussuite.cms.modules.gallery.manage',
  75.         self::TYPES__VIDEO => 'campussuite.cms.modules.gallery.manage',
  76.     ];
  77.     const ROUTING_SLUG 'feed';
  78.     const VISIBILITIES = [
  79.         'unpublished' => self::VISIBILITIES__UNPUBLISHED,
  80.         'hidden' => self::VISIBILITIES__HIDDEN,
  81.         'published' => self::VISIBILITIES__PUBLISHED,
  82.     ];
  83.     const VISIBILITIES__UNPUBLISHED Bits::BIT_1;
  84.     const VISIBILITIES__HIDDEN Bits::BIT_2;
  85.     const VISIBILITIES__PUBLISHED Bits::BIT_3;
  86.     const DEFAULT_PREVIEW_LENGTH 50;
  87.     use UlidIdentifiableTrait;
  88.     use TenantableTrait;
  89.     use TimestampableTrait;
  90.     use BlameableTrait;
  91.     use DiscriminatorTrait;
  92.     /**
  93.      * @var School|null
  94.      *
  95.      * @ORM\ManyToOne(
  96.      *     targetEntity = School::class,
  97.      * )
  98.      * @ORM\JoinColumn(
  99.      *     name = "school",
  100.      *     referencedColumnName = "id",
  101.      *     onDelete = "CASCADE",
  102.      * )
  103.      *
  104.      * @Groups({"school", "school_minimal"})
  105.      *
  106.      */
  107.     protected ?School $school null;
  108.     /**
  109.      * @var int
  110.      *
  111.      * @ORM\Column(
  112.      *     type = "bigint",
  113.      *     nullable = false,
  114.      *     options = {
  115.      *         "default" = 0,
  116.      *     },
  117.      * )
  118.      *
  119.      * @Groups("entry")
  120.      * https://github.com/symfony/symfony/issues/54418
  121.      * @Context(denormalizationContext = {AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT = true})
  122.      */
  123.     protected int $schoolType 0;
  124.     /**
  125.      * @var Container|null
  126.      *
  127.      * @ORM\ManyToOne(
  128.      *     targetEntity = Container::class,
  129.      * )
  130.      * @ORM\JoinColumn(
  131.      *     name = "department",
  132.      *     referencedColumnName = "id",
  133.      *     onDelete = "CASCADE",
  134.      * )
  135.      *
  136.      * @Groups({"department", "department_minimal"})
  137.      *
  138.      */
  139.     protected ?Container $department null;
  140.     /**
  141.      * @var DateTimeInterface|null
  142.      *
  143.      * @ORM\Column(
  144.      *     type = "datetime",
  145.      *     nullable = false,
  146.      * )
  147.      *
  148.      * @Groups("entry_timestamp")
  149.      *
  150.      */
  151.     protected ?DateTimeInterface $timestamp null;
  152.     /**
  153.      * @var string|null
  154.      *
  155.      * @ORM\Column(
  156.      *     type = "string",
  157.      *     nullable = false,
  158.      * )
  159.      *
  160.      * @Groups("entry")
  161.      *
  162.      */
  163.     protected ?string $label null;
  164.     /**
  165.      * @var string|null
  166.      *
  167.      * @ORM\Column(
  168.      *     type = "text",
  169.      *     nullable = true,
  170.      * )
  171.      *
  172.      * @Groups("entry")
  173.      */
  174.     protected ?string $preview null;
  175.     /**
  176.      * @var array|string[]
  177.      */
  178.     protected array $previewChunks = [];
  179.     /**
  180.      * @var MediaCollection|AbstractMedia[]|null
  181.      *
  182.      * @ORM\Column(
  183.      *     type = "media_array",
  184.      *     nullable = false,
  185.      * )
  186.      *
  187.      * @Groups("media")
  188.      *
  189.      */
  190.     protected ?MediaCollection $media null;
  191.     /**
  192.      * @var int
  193.      *
  194.      * @ORM\Column(
  195.      *     type = "integer",
  196.      *     nullable = false,
  197.      *     options = {
  198.      *         "default" = AbstractEntry::VISIBILITIES__PUBLISHED,
  199.      *     },
  200.      * )
  201.      *
  202.      * @Groups("entry_visibility")
  203.      *
  204.      */
  205.     protected int $visibility self::VISIBILITIES__PUBLISHED;
  206.     /**
  207.      * @var bool
  208.      *
  209.      * @ORM\Column(
  210.      *     type = "boolean",
  211.      *     nullable = false,
  212.      *     options = {
  213.      *         "default" = true,
  214.      *     },
  215.      * )
  216.      */
  217.     protected bool $boosted true;
  218.     /**
  219.      * @var bool
  220.      *
  221.      * @ORM\Column(
  222.      *     type = "boolean",
  223.      *     nullable = false,
  224.      *     options = {
  225.      *         "default" = false,
  226.      *     },
  227.      * )
  228.      *
  229.      * @Groups("entry")
  230.      *
  231.      */
  232.     protected bool $pinned false;
  233.     /**
  234.      * @var DateTimeInterface|null
  235.      *
  236.      * @ORM\Column(
  237.      *     type = "datetime_immutable",
  238.      *     nullable = true,
  239.      * )
  240.      *
  241.      * @Groups("entry")
  242.      *
  243.      */
  244.     protected ?DateTimeInterface $pinnedAt null;
  245.     /**
  246.      * @var string|null
  247.      *
  248.      * @ORM\Column(
  249.      *     type = "string",
  250.      *     nullable = true,
  251.      * )
  252.      */
  253.     protected ?string $link null;
  254.     /**
  255.      * @return string|null
  256.      */
  257.     public function getLink(): ?string
  258.     {
  259.         return $this->link;
  260.     }
  261.     /**
  262.      * @param string|null $link
  263.      * @return $this
  264.      */
  265.     public function setLink(?string $link): self
  266.     {
  267.         $this->link trim($link) ?: null;
  268.         return $this;
  269.     }
  270.     /**
  271.      * @return bool
  272.      */
  273.     public function isLinkInternal(): bool
  274.     {
  275.         return ($this->getLink() === '/' || preg_match('/^\\/[^\\/]/'$this->getLink()));
  276.     }
  277.     /**
  278.      * @return bool
  279.      */
  280.     public function isLinkExternal(): bool
  281.     {
  282.         return ( ! $this->isLinkInternal());
  283.     }
  284.     /**
  285.      * @return Url|null
  286.      */
  287.     public function getLinkAsUrl(): ?Url
  288.     {
  289.         return $this->getLink() ? Url::fromString($this->getLink()) : null;
  290.     }
  291.     /**
  292.      * @return School|null
  293.      */
  294.     public function getSchool(): ?School
  295.     {
  296.         return $this->school;
  297.     }
  298.     /**
  299.      * @param School|null $school
  300.      * @return $this
  301.      */
  302.     public function setSchool(?School $school): self
  303.     {
  304.         $this->school $school;
  305.         return $this;
  306.     }
  307.     /**
  308.      * @return int
  309.      */
  310.     public function getSchoolType(): int
  311.     {
  312.         return $this->schoolType;
  313.     }
  314.     /**
  315.      * @return string[]
  316.      */
  317.     public function getSchoolTypeName(): array
  318.     {
  319.         return array_filter(
  320.             array_map(static function ($type) {
  321.                 return array_search($typeSchool::TYPES);
  322.             }, Bitwise::explode($this->schoolType))
  323.         );
  324.     }
  325.     /**
  326.      * @param int $schoolType
  327.      * @return $this
  328.      */
  329.     public function setSchoolType(int $schoolType): self
  330.     {
  331.         $this->schoolType max(0$schoolType);
  332.         return $this;
  333.     }
  334.     /**
  335.      * @return Container|null
  336.      */
  337.     public function getDepartment(): ?Container
  338.     {
  339.         return $this->department;
  340.     }
  341.     /**
  342.      * @param Container|null $department
  343.      * @return $this
  344.      */
  345.     public function setDepartment(?Container $department): self
  346.     {
  347.         $this->department $department;
  348.         return $this;
  349.     }
  350.     /**
  351.      * @return DateTimeInterface|null
  352.      */
  353.     public function getTimestamp(): ?DateTimeInterface
  354.     {
  355.         return $this->timestamp;
  356.     }
  357.     /**
  358.      * @param DateTimeInterface $timestamp
  359.      * @return $this
  360.      */
  361.     public function setTimestamp(DateTimeInterface $timestamp): self
  362.     {
  363.         $this->timestamp $timestamp;
  364.         return $this;
  365.     }
  366.     /**
  367.      * @return string|null
  368.      */
  369.     public function getLabel(): ?string
  370.     {
  371.         return $this->label;
  372.     }
  373.     /**
  374.      * @param string $label
  375.      * @return $this
  376.      */
  377.     public function setLabel(string $label): self
  378.     {
  379.         $this->label $label;
  380.         return $this;
  381.     }
  382.     /**
  383.      * @param int|null $max
  384.      * @return string|null
  385.      */
  386.     public function getPreview(?int $max null): ?string
  387.     {
  388.         return ($max !== null) ? implode(' '$this->getPreviewChunks($max)) : $this->preview;
  389.     }
  390.     /**
  391.      * @param string|null $preview
  392.      * @return $this
  393.      */
  394.     public function setPreview(?string $preview): self
  395.     {
  396.         $this->preview = (new Html2Text($preview, ['do_links' => 'none']))->getText() ?: null;
  397.         $this->previewChunks = [];
  398.         return $this;
  399.     }
  400.     /**
  401.      * @param int|null $max
  402.      * @return array|string[]
  403.      */
  404.     public function getPreviewChunks(?int $max null): array
  405.     {
  406.         $preview $this->getPreview();
  407.         if ( ! $preview) {
  408.             return [];
  409.         }
  410.         if ( ! $this->previewChunks) {
  411.             $this->previewChunks explode(' '$preview);
  412.         }
  413.         return ($max !== null) ? array_slice($this->previewChunks0$max ?: self::DEFAULT_PREVIEW_LENGTH) : $this->previewChunks;
  414.     }
  415.     /**
  416.      * @return AbstractMedia|null
  417.      */
  418.     public function peekMedia(): ?AbstractMedia
  419.     {
  420.         if ($this->getMedia() && $this->getMedia()[0] instanceof AbstractMedia) {
  421.             return $this->getMedia()[0];
  422.         }
  423.         return null;
  424.     }
  425.     /**
  426.      * @return MediaCollection|AbstractMedia[]
  427.      */
  428.     public function getMedia(): MediaCollection
  429.     {
  430.         if ( ! $this->media) {
  431.             $this->media = new MediaCollection();
  432.         }
  433.         return $this->media;
  434.     }
  435.     /**
  436.      * @param MediaCollection|AbstractMedia[]|null $media
  437.      * @return $this
  438.      */
  439.     public function setMedia(?MediaCollection $media): self
  440.     {
  441.         $this->media $media ?: new MediaCollection();
  442.         return $this;
  443.     }
  444.     /**
  445.      * @param $entity
  446.      * @return $this
  447.      */
  448.     abstract public function populate($entity): self;
  449.     /**
  450.      * @return string|null
  451.      */
  452.     abstract public function getType(): string;
  453.     /**
  454.      * @return int
  455.      */
  456.     public function getVisibility(): int
  457.     {
  458.         return $this->visibility;
  459.     }
  460.     /**
  461.      * @return string
  462.      */
  463.     public function getVisibilityName(): string
  464.     {
  465.         return array_search($this->getVisibility(), self::VISIBILITIES);
  466.     }
  467.     /**
  468.      * @param int $visibility
  469.      * @return $this
  470.      */
  471.     public function setVisibility(int $visibility): self
  472.     {
  473.         if ( ! in_array($visibilityself::VISIBILITIES)) {
  474.             throw new \LogicException();
  475.         }
  476.         $this->visibility $visibility;
  477.         return $this;
  478.     }
  479.     /**
  480.      * @return bool
  481.      */
  482.     public function isUnpublished(): bool
  483.     {
  484.         return $this->getVisibility() === self::VISIBILITIES__UNPUBLISHED;
  485.     }
  486.     /**
  487.      * @return bool
  488.      */
  489.     public function isHidden(): bool
  490.     {
  491.         return $this->getVisibility() === self::VISIBILITIES__HIDDEN;
  492.     }
  493.     /**
  494.      * @return bool
  495.      */
  496.     public function isPublished(): bool
  497.     {
  498.         return $this->getVisibility() === self::VISIBILITIES__PUBLISHED;
  499.     }
  500.     /**
  501.      * @return bool
  502.      */
  503.     public function isBoosted(): bool
  504.     {
  505.         return $this->boosted;
  506.     }
  507.     /**
  508.      * @param bool $boosted
  509.      * @return $this
  510.      */
  511.     public function setBoosted(bool $boosted): self
  512.     {
  513.         $this->boosted $boosted;
  514.         return $this;
  515.     }
  516.     /**
  517.      * @return bool
  518.      */
  519.     public function isPinned(): bool
  520.     {
  521.         return $this->pinned;
  522.     }
  523.     /**
  524.      * @return DateTimeInterface|null
  525.      */
  526.     public function getPinnedAt(): ?DateTimeInterface
  527.     {
  528.         return $this->pinnedAt;
  529.     }
  530.     /**
  531.      * @param DateTimeInterface|null $pinnedAt
  532.      * @return $this
  533.      */
  534.     public function setPinnedAt(?DateTimeInterface $pinnedAt): self
  535.     {
  536.         $this->pinnedAt $pinnedAt;
  537.         $this->pinned = (bool) $pinnedAt;
  538.         return $this;
  539.     }
  540.     /**
  541.      * @return string
  542.      */
  543.     public function __toString(): string
  544.     {
  545.         return $this->getId();
  546.     }
  547.     /**
  548.      * {@inheritDoc}
  549.      */
  550.     public function getSlug(): ?string
  551.     {
  552.         return Slugger::slug($this->getLabel(), true) ?: null;
  553.     }
  554.     /**
  555.      * {@inheritDoc}
  556.      */
  557.     public function setSlug(?string $slug): SlugInterface
  558.     {
  559.         return $this;
  560.     }
  561.     /**
  562.      * @return AbstractMedia|null
  563.      */
  564.     public function getFeature(): ?AbstractMedia
  565.     {
  566.         return $this->getMedia()->getFeature();
  567.     }
  568. }