src/Entity/Journal.php line 203

  1. <?php
  2. namespace App\Entity;
  3. use Doctrine\ORM\Mapping as ORM,
  4.     Doctrine\Common\Collections\Collection,
  5.     Doctrine\Common\Collections\ArrayCollection,
  6.     Doctrine\DBAL\Types\Types,
  7.     Doctrine\Common\Collections\Criteria;
  8. use Doctrine\ORM\Mapping\Index;
  9. use Gedmo\Mapping\Annotation as Gedmo;
  10. use Symfony\Component\Serializer\Annotation\Groups,
  11.     Symfony\Component\Validator\Constraints as Assert;
  12. use ApiPlatform\Metadata\ApiResource,
  13.     ApiPlatform\Metadata\ApiProperty,
  14.     ApiPlatform\Metadata\Get,
  15.     ApiPlatform\Metadata\GetCollection,
  16.     ApiPlatform\Metadata\Post,
  17.     ApiPlatform\Metadata\Put,
  18.     ApiPlatform\Metadata\Delete,
  19.     ApiPlatform\Metadata\ApiFilter,
  20.     ApiPlatform\Doctrine\Orm\Filter\SearchFilter,
  21.     ApiPlatform\Doctrine\Orm\Filter\RangeFilter,
  22.     ApiPlatform\Doctrine\Orm\Filter\DateFilter,
  23.     ApiPlatform\Doctrine\Orm\Filter\BooleanFilter,
  24.     ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
  25. use App\Entity\Trait\IdTrait,
  26.     App\Entity\Trait\UuidTrait,
  27.     App\Entity\Trait\OrdStatTrait,
  28.     App\Entity\Trait\TimestampableTrait,
  29.     App\Entity\Trait\TranslatableTrait,
  30.     App\Entity\Trait\Languages\JournalLanguagesTrait,
  31.     App\Entity\Trait\ArchivedTrait,
  32.     App\Entity\Trait\EditorialGroupableTrait,
  33.     App\Entity\Trait\ImportIdTrait,
  34.     App\Entity\Interface\TranslatableInterface,
  35.     App\Entity\Interface\OrdStatableInterface,
  36.     App\Entity\Interface\LanguageableInterface,
  37.     App\Entity\Interface\EditorialGroupableInterface,
  38.     App\Entity\Interface\ArchivableInterface,
  39.     App\Entity\Interface\OwnerableInterface,
  40.     App\Entity\Interface\CloneableInterface,
  41.     App\Repository\JournalRepository;
  42. use App\Enum\Language,
  43.     App\Enum\JournalCoverType,
  44.     App\Enum\JournalDefaultPage,
  45.     App\Entity\Admin,
  46.     App\Security\Voter\ArchivableVoter,
  47.     App\Lib\Actions,
  48.     App\DTO\CloneDTO,
  49.     App\StateProcessor\CloneProcessor,
  50.     App\Security\Voter\CloneVoter;
  51. use App\Attribute\DenormalizationInject,
  52.     App\Util\ClassUtils,
  53.     App\Filter\IriFilter;
  54. use App\Controller\Export\Journal\JournalCrossrefExporter,
  55.     App\Controller\Export\Journal\JournalSimcheckCrossrefExporter,
  56.     App\Controller\Export\Journal\JournalLicenceFulltextCrossrefExporter;
  57. use App\Controller\Stats\StatsJournalXlsxGenerator;
  58. use App\Filter\DateUnmapped;
  59. use App\Filter\OrderByUnmapped;
  60. use App\Filter\RangeUnmapped;
  61. use App\StateProvider\StatsJournalProvider;
  62. use App\StateProvider\StatsSystemProvider;
  63. #[ApiResource(
  64.     description'Journals',
  65.     normalizationContext: ['groups' => [
  66.         'read',
  67.         'read:' self::class,
  68.         'read:' self::class . 'Translation'
  69.     ]],
  70.     denormalizationContext: ['groups' => ['write']],
  71.     security'is_granted("' self::class . '")',
  72.     order: ['ord' => 'desc'],
  73.     operations: [
  74.         new GetCollection(),
  75.         new GetCollection(
  76.             uriTemplate'/journals/list',
  77.             normalizationContext: ['groups' => ['read''read:list']],
  78.         ),
  79.         new GetCollection(
  80.             name'get_journal_stats_system',
  81.             uriTemplate'/journals/stats/system',
  82.             providerStatsSystemProvider::class,
  83.             security'is_granted("ROLE_MODULE_STATS")',
  84.             normalizationContext: ['groups' => ['read:stats']],
  85.         ),
  86.         new GetCollection(
  87.             name'get_journal_stats',
  88.             uriTemplate'/journals/stats',
  89.             providerStatsJournalProvider::class,
  90.             security'is_granted("ROLE_MODULE_STATS")',
  91.             normalizationContext: ['groups' => ['read:stats']],
  92.             paginationMaximumItemsPerPage500
  93.         ),
  94.         new GetCollection(
  95.             name'get_journal_stats_xlsx',
  96.             uriTemplate'/journals/stats/xlsx',
  97.             providerStatsJournalProvider::class,
  98.             controllerStatsJournalXlsxGenerator::class,
  99.             security'is_granted("ROLE_MODULE_STATS")',
  100.         ),
  101.         new Post(
  102.             uriTemplate'/journals/clone',
  103.             inputCloneDTO::class,
  104.             processorCloneProcessor::class,
  105.             security'is_granted("' Actions::CLONE . '")',
  106.             securityMessageCloneVoter::MESSAGE
  107.         ),
  108.         new Post(),
  109.         new Get(),
  110.         new Get(
  111.             name'get_journal_crossref',
  112.             uriTemplate'/journals/{uuid}/export/crossref',
  113.             controllerJournalCrossrefExporter::class
  114.         ),
  115.         new Get(
  116.             name'get_journal_simcheck_crossref',
  117.             uriTemplate'/journals/{uuid}/export/simcheck_crossref',
  118.             controllerJournalSimcheckCrossrefExporter::class
  119.         ),
  120.         new Get(
  121.             name'get_journal_licence_fulltext_crossref',
  122.             uriTemplate'/journals/{uuid}/export/licence_fulltext_crossref',
  123.             controllerJournalLicenceFulltextCrossrefExporter::class
  124.         ),
  125.         new Put(),
  126.         new Delete(
  127.             securityPostDenormalize'is_granted("' Actions::DELETE '", object)',
  128.             securityPostDenormalizeMessageArchivableVoter::MESSAGE
  129.         ),
  130.     ],
  131.     extraProperties: ['standard_put' => false],
  132. )]
  133. #[ApiFilter(SearchFilter::class, properties: [
  134.     'translations.title' => 'partial',
  135.     'nativeTitle' => 'partial',
  136.     'data.workingTitle' => 'partial',
  137.     'data.issn' => 'exact',
  138.     'data.eIssn' => 'exact',
  139.     'data.state' => 'exact',
  140.     'data.type' => 'exact',
  141.     'domains.domain.nativeTitle' => 'partial',
  142.     'affiliations.affiliation.nativeTitle' => 'partial',
  143.     'affiliations.affiliation.type.title' => 'partial'
  144. ])]
  145. #[ApiFilter(RangeFilter::class, properties: ['meinScore'])]
  146. #[ApiFilter(DateFilter::class, properties: ['updatedAt'])]
  147. #[ApiFilter(IriFilter::class, properties: [
  148.     'nativeLanguage',
  149.     'domains.domain',
  150.     'affiliations.affiliation',
  151.     'data.periodicity',
  152.     'data.type',
  153.     'data.licence',
  154.     'data.state'
  155. ])]
  156. #[ApiFilter(BooleanFilter::class, properties: ['isArchived''stat'])]
  157. #[ApiFilter(OrderFilter::class, properties: [
  158.     'nativeTitle',
  159.     'nativeLanguage',
  160.     'data.type',
  161.     'data.state',
  162.     'affiliations.affiliation.nativeTitle',
  163.     'affiliations.affiliation.type.title',
  164.     'domains.domain.nativeTitle',
  165.     'data.workingTitle',
  166.     'data.periodicity',
  167.     'updatedAt',
  168.     'stat',
  169.     'ord'
  170. ])]
  171. #[ApiFilter(OrderByUnmapped::class, properties: [
  172.     'statsEarliestArticlePublicationDate',
  173.     'statsLatestArticlePublicationDate',
  174.     'statsAmountOfArticles',
  175.     'statsAmountOfArticles',
  176.     'statsAmountOfIssues',
  177.     'statsAmountOfVolumes',
  178.     'statsAmountOfViews',
  179.     'statsAmountOfDownloads'
  180. ])]
  181. #[ApiFilter(RangeUnmapped::class, properties: [
  182.     'statsAmountOfVolumes',
  183.     'statsAmountOfIssues',
  184.     'statsAmountOfArticles',
  185.     'statsAmountOfViews',
  186.     'statsAmountOfDownloads'
  187. ])]
  188. #[ApiFilter(DateUnmapped::class, properties: ['statsEarliestArticlePublicationDate','statsLatestArticlePublicationDate'])]
  189. #[Index(fields: ["nativeLanguage"], name"native_language_idx")]
  190. #[Index(fields: ["stat"], name"stat_idx")]
  191. #[ORM\Entity(repositoryClassJournalRepository::class)]
  192. class Journal implements
  193.     TranslatableInterface,
  194.     OrdStatableInterface,
  195.     LanguageableInterface,
  196.     EditorialGroupableInterface,
  197.     ArchivableInterface,
  198.     OwnerableInterface,
  199.     CloneableInterface
  200. {
  201.     use IdTrait,
  202.         UuidTrait,
  203.         OrdStatTrait,
  204.         TimestampableTrait,
  205.         TranslatableTrait,
  206.         JournalLanguagesTrait,
  207.         ArchivedTrait,
  208.         EditorialGroupableTrait,
  209.         ImportIdTrait;
  210.     #[ApiProperty(description'Native title sort key')]
  211.     #[Groups(['read:' self::class, 'read:stats'])]
  212.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  213.     private ?string $nativeTitleSortKey null;
  214.     #[ApiProperty(description'Data subresource'writabletrue)]
  215.     #[Groups(['read:' self::class, 'read:list''write'])]
  216.     #[Assert\Valid]
  217.     #[DenormalizationInject(method: ['name' => 'getData'])]
  218.     #[ORM\OneToOne(targetEntityJournalData::class, mappedBy'parent'cascade: ['persist''remove'])]
  219.     private JournalData $data;
  220.     #[ApiProperty(description'View subresource'writabletrue)]
  221.     #[Groups(['read:' self::class, 'write'])]
  222.     #[Assert\Valid]
  223.     #[DenormalizationInject(method: ['name' => 'getView'])]
  224.     #[ORM\OneToOne(targetEntityJournalView::class, mappedBy'parent'cascade: ['persist''remove'])]
  225.     private JournalView $view;
  226.     #[ApiProperty(description'Newest new issue'writabletrue)]
  227.     #[ORM\OneToOne(targetEntityJournalIssue::class)]
  228.     #[ORM\JoinColumn(onDelete'set null'nullabletrue)]
  229.     private ?JournalIssue $newestNewIssue null;
  230.     #[ApiProperty(description'Slug')]
  231.     #[Groups(['read:' self::class, 'write'])]
  232.     #[Gedmo\Slug(separator'-'style'default'updatabletruefields: ['nativeTitle'])]
  233.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  234.     private ?string $slug null;
  235.     #[ApiProperty(description'Slug')]
  236.     #[Groups(['read:' self::class, 'write'])]
  237.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  238.     private ?string $importSlug null;
  239.     #[ApiProperty(description'Slug')]
  240.     #[Groups(['read:' self::class, 'write'])]
  241.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  242.     private ?string $importSlugEn null;
  243.     #[ApiProperty(description'Created by')]
  244.     #[Groups(['read:' self::class])]
  245.     #[ORM\ManyToOne(targetEntityAdmin::class, inversedBy'journals')]
  246.     #[ORM\JoinColumn(onDelete'set null')]
  247.     private Admin $createdBy;
  248.     #[ApiProperty(description'Updated by')]
  249.     #[Groups(['read:' self::class])]
  250.     #[ORM\ManyToOne(targetEntityAdmin::class)]
  251.     #[ORM\JoinColumn(onDelete'set null')]
  252.     private Admin $updatedBy;
  253.     #[ApiProperty(description'Scientific council members'writableLinkfalse)]
  254.     #[ORM\OneToMany(targetEntityJournalScientificCouncilMember::class, mappedBy'parent'cascade: ['persist'])]
  255.     #[ORM\OrderBy(['ord' => 'desc'])]
  256.     private Collection $scientificCouncilMembers;
  257.     #[ApiProperty(description'Domains'fetchEagerfalse)]
  258.     #[Groups(['read''read:list'])]
  259.     #[ORM\OneToMany(targetEntityJournalDomain::class, mappedBy'parent'cascade: ['persist''remove'])]
  260.     #[ORM\OrderBy(['ord' => 'desc'])]
  261.     private Collection $domains;
  262.     #[ApiProperty(description'Indexation collections')]
  263.     #[ORM\OneToMany(targetEntityJournalIndexationCollection::class, mappedBy'parent'cascade: ['persist''remove'])]
  264.     #[ORM\OrderBy(['ord' => 'desc'])]
  265.     private Collection $indexationCollections;
  266.     #[ApiProperty(description'Indicators')]
  267.     #[ORM\OneToMany(targetEntityJournalIndicator::class, mappedBy'parent'cascade: ['persist''remove'])]
  268.     #[ORM\OrderBy(['ord' => 'desc'])]
  269.     private Collection $indicators;
  270.     #[ApiProperty(description'Affiliations'fetchEagerfalse)]
  271.     #[Groups(['read''read:list'])]
  272.     #[ORM\OneToMany(targetEntityJournalAffiliation::class, mappedBy'parent'cascade: ['persist''remove'])]
  273.     #[ORM\OrderBy(['ord' => 'desc'])]
  274.     private Collection $affiliations;
  275.     #[ApiProperty(description'Publishers')]
  276.     #[ORM\OneToMany(targetEntityJournalPublisher::class, mappedBy'parent'cascade: ['persist''remove'])]
  277.     #[ORM\OrderBy(['ord' => 'desc'])]
  278.     private Collection $publishers;
  279.     #[ApiProperty(description'Partners')]
  280.     #[ORM\OneToMany(targetEntityJournalPartner::class, mappedBy'parent'cascade: ['persist''remove'])]
  281.     #[ORM\OrderBy(['ord' => 'desc'])]
  282.     private Collection $partners;
  283.     #[ApiProperty(description'Slides')]
  284.     #[ORM\OneToMany(targetEntityJournalSlide::class, mappedBy'parent'cascade: ['persist''remove'])]
  285.     #[ORM\OrderBy(['ord' => 'desc'])]
  286.     private Collection $slides;
  287.     #[ApiProperty(description'Sections')]
  288.     #[ORM\OneToMany(targetEntityJournalSection::class, mappedBy'parent'cascade: ['persist''remove'])]
  289.     #[ORM\OrderBy(['ord' => 'desc'])]
  290.     private Collection $sections;
  291.     #[ApiProperty(description'Menu items')]
  292.     #[ORM\OneToMany(targetEntityJournalMenuItem::class, mappedBy'parent'cascade: ['persist''remove'])]
  293.     #[ORM\OrderBy(['ord' => 'desc'])]
  294.     private Collection $menuItems;
  295.     #[ApiProperty(description'Submenu groups')]
  296.     #[ORM\OneToMany(targetEntityJournalSubmenuGroup::class, mappedBy'parent'cascade: ['persist''remove'])]
  297.     #[ORM\OrderBy(['ord' => 'desc'])]
  298.     private Collection $submenuGroups;
  299.     #[ApiProperty(description'Sidemenu groups')]
  300.     #[ORM\OneToMany(targetEntityJournalSidemenuGroup::class, mappedBy'parent'cascade: ['persist''remove'])]
  301.     #[ORM\OrderBy(['ord' => 'desc'])]
  302.     private Collection $sidemenuGroups;
  303.     #[ApiProperty(description'Banners')]
  304.     #[ORM\OneToMany(targetEntityJournalBanner::class, mappedBy'parent'cascade: ['persist''remove'])]
  305.     #[ORM\OrderBy(['ord' => 'desc'])]
  306.     private Collection $banners;
  307.     #[ApiProperty(description'Volumes')]
  308.     #[ORM\OneToMany(targetEntityJournalVolume::class, mappedBy'parent'cascade: ['persist''remove'])]
  309.     #[ORM\OrderBy(['ord' => 'desc'])]
  310.     private Collection $volumes;
  311.     #[ApiProperty(description'Issues')]
  312.     #[ORM\OneToMany(targetEntityJournalIssue::class, mappedBy'parent'cascade: ['persist''remove'])]
  313.     #[ORM\OrderBy(['ord' => 'desc'])]
  314.     private Collection $issues;
  315.     #[ApiProperty(description'Articles')]
  316.     #[ORM\OneToMany(targetEntityJournalArticle::class, mappedBy'parent'cascade: ['persist''remove'])]
  317.     #[ORM\OrderBy(['ord' => 'desc'])]
  318.     private Collection $articles;
  319.     #[ApiProperty(description'Pages')]
  320.     #[ORM\OneToMany(targetEntityJournalPage::class, mappedBy'parent'cascade: ['persist''remove'])]
  321.     #[ORM\OrderBy(['ord' => 'desc'])]
  322.     private Collection $pages;
  323.     #[ApiProperty(description'News')]
  324.     #[ORM\OneToMany(targetEntityJournalNews::class, mappedBy'parent'cascade: ['persist''remove'])]
  325.     #[ORM\OrderBy(['ord' => 'desc'])]
  326.     private Collection $news;
  327.     #[ApiProperty(description'Collections')]
  328.     #[ORM\OneToMany(targetEntityJournalCollection::class, mappedBy'parent'cascade: ['persist''remove'])]
  329.     #[ORM\OrderBy(['ord' => 'desc'])]
  330.     private Collection $collections;
  331.     #[ApiProperty(description'Sort article by ord')]
  332.     #[Groups(['read:' self::class, 'write'])]
  333.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  334.     private bool $sortArticlesByOrd false;
  335.     #[ORM\OneToMany(mappedBy'parent'targetEntityJournalFooter::class, orphanRemovaltrue)]
  336.     private Collection $smallFooters;
  337.     #[ApiProperty(description'Statistics: earliest article publication date')]
  338.     #[Groups(['read:stats'])]
  339.     private ?\DateTimeImmutable $statsEarliestArticlePublicationDate null;
  340.     #[ApiProperty(description'Statistics: latest article publication date')]
  341.     #[Groups(['read:stats'])]
  342.     private ?\DateTimeImmutable $statsLatestArticlePublicationDate null;
  343.     #[ApiProperty(description'Statistics: amount of views')]
  344.     #[Groups(['read:stats'])]
  345.     private int $statsAmountOfArticles 0;
  346.     #[ApiProperty(description'Statistics: amount of views')]
  347.     #[Groups(['read:stats'])]
  348.     private int $statsAmountOfIssues 0;
  349.     #[ApiProperty(description'Statistics: amount of views')]
  350.     #[Groups(['read:stats'])]
  351.     private int $statsAmountOfVolumes 0;
  352.     #[ApiProperty(description'Statistics: amount of views')]
  353.     #[Groups(['read:stats'])]
  354.     private int $statsAmountOfViews 0;
  355.     #[ApiProperty(description'Statistics: amount of downloads')]
  356.     #[Groups(['read:stats'])]
  357.     private int $statsAmountOfDownloads 0;
  358.     public function __construct(Admin $createdBy, ?array $languages)
  359.     {
  360.         $this->setUuid();
  361.         $this->data = new JournalData($this);
  362.         $this->view = new JournalView($this);
  363.         $this->createdBy $createdBy;
  364.         $this->updatedBy $createdBy;
  365.         $this->translations = new ArrayCollection();
  366.         $this->domains = new ArrayCollection();
  367.         $this->indexationCollections = new ArrayCollection();
  368.         $this->indicators = new ArrayCollection();
  369.         $this->affiliations = new ArrayCollection();
  370.         $this->publishers = new ArrayCollection();
  371.         $this->partners = new ArrayCollection();
  372.         $this->slides = new ArrayCollection();
  373.         $this->editorialGroups = new ArrayCollection();
  374.         $this->scientificCouncilMembers = new ArrayCollection();
  375.         $this->sections = new ArrayCollection();
  376.         $this->menuItems = new ArrayCollection();
  377.         $this->submenuGroups = new ArrayCollection();
  378.         $this->sidemenuGroups = new ArrayCollection();
  379.         $this->banners = new ArrayCollection();
  380.         $this->volumes = new ArrayCollection();
  381.         $this->issues = new ArrayCollection();
  382.         $this->articles = new ArrayCollection();
  383.         $this->pages = new ArrayCollection();
  384.         $this->news = new ArrayCollection();
  385.         $this->collections = new ArrayCollection();
  386.         $this->setLanguages($languages ?? Language::DEFAULT_JOURNAL);
  387.         foreach (array_reverse(JournalDefaultPage::cases()) as $index => $idName) {
  388.             new JournalPage(
  389.                 parent$this,
  390.                 workingTitle$idName->value,
  391.                 idName$idName->value,
  392.                 ord$index 1,
  393.                 stattrue
  394.             );
  395.         }
  396.         $this->createdAt = new \DateTimeImmutable();
  397.         $this->updatedAt = new \DateTimeImmutable();
  398.         $this->smallFooters = new ArrayCollection();
  399.     }
  400.     public function getTitle(): string
  401.     {
  402.         return $this->data->getWorkingTitle();
  403.     }
  404.     public function setNativeTitleSortKey(string $nativeTitleSortKey): self
  405.     {
  406.         $this->nativeTitleSortKey $nativeTitleSortKey;
  407.         return $this;
  408.     }
  409.     public function getNativeTitleSortKey(): ?string
  410.     {
  411.         return $this->nativeTitleSortKey;
  412.     }
  413.     public function getData(): JournalData
  414.     {
  415.         return $this->data;
  416.     }
  417.     public function getView(): JournalView
  418.     {
  419.         return $this->view;
  420.     }
  421.     public function getCover(Language $lang): ?Media
  422.     {
  423.         if ($this->view->getCoverType() === JournalCoverType::CHOSEN) {
  424.             return $this->readAvailableTranslation($lang'cover');
  425.         }
  426.         if ($cover $this->getNewestIssue()?->readAvailableTranslation($lang'cover')) {
  427.             return $cover;
  428.         }
  429.         return $this->view->getCoverType() === JournalCoverType::CURRENT_ISSUE_OR_CHOSEN
  430.             $this->readAvailableTranslation($lang'cover')
  431.             : null;
  432.     }
  433.     public function getCoverAlt(Language $lang): ?string
  434.     {
  435.         if ($this->view->getCoverType() === JournalCoverType::CHOSEN) {
  436.             return $this->readAvailableTranslation($lang'coverAlt');
  437.         }
  438.         if ($this->getNewestIssue()?->readAvailableTranslation($lang'cover')) {
  439.             return $this->getNewestIssue()?->readAvailableTranslation($lang'coverAlt');
  440.         }
  441.         return $this->view->getCoverType() === JournalCoverType::CURRENT_ISSUE_OR_CHOSEN
  442.             $this->readAvailableTranslation($lang'coverAlt')
  443.             : null;
  444.     }
  445.     public function getNewestNewIssue(): ?JournalIssue
  446.     {
  447.         return $this->newestNewIssue;
  448.     }
  449.     public function setNewestNewIssue(?JournalIssue $newestNewIssue): self
  450.     {
  451.         $this->newestNewIssue $newestNewIssue;
  452.         return $this;
  453.     }
  454.     public function getNewestIssue(): ?JournalIssue
  455.     {
  456.         foreach ($this->getIssues() as $issue) {
  457.             if ($issue->getStat() && $issue->getIsNewestIssue()) {
  458.                 return $issue;
  459.             }
  460.         }
  461.         return null;
  462.     }
  463.     public function getSlug(): ?string
  464.     {
  465.         return $this->slug;
  466.     }
  467.     public function setSlug(?string $slug): self
  468.     {
  469.         $this->slug $slug;
  470.         return $this;
  471.     }
  472.     public function getImportSlug(): ?string
  473.     {
  474.         return $this->importSlug;
  475.     }
  476.     public function setImportSlug(?string $importSlug): self
  477.     {
  478.         $this->importSlug $importSlug;
  479.         return $this;
  480.     }
  481.     public function getImportSlugEn(): ?string
  482.     {
  483.         return $this->importSlugEn;
  484.     }
  485.     public function setImportSlugEn(?string $importSlugEn): self
  486.     {
  487.         $this->importSlugEn $importSlugEn;
  488.         return $this;
  489.     }
  490.     public function getCreatedBy(): Admin
  491.     {
  492.         return $this->createdBy;
  493.     }
  494.     public function getUpdatedBy(): Admin
  495.     {
  496.         return $this->updatedBy;
  497.     }
  498.     public function setUpdatedBy(Admin $updatedBy): self
  499.     {
  500.         $this->updatedBy $updatedBy;
  501.         return $this;
  502.     }
  503.     /**
  504.      * @return Collection|JournalScientificCouncilMember[]
  505.      */
  506.     public function getScientificCouncilMembers(): Collection
  507.     {
  508.         return $this->scientificCouncilMembers;
  509.     }
  510.     public function addScientificCouncilMember(JournalScientificCouncilMember $scientificCouncilMember): self
  511.     {
  512.         if ($this->scientificCouncilMembers->contains($scientificCouncilMember)) {
  513.             return $this;
  514.         }
  515.         $this->scientificCouncilMembers[] = $scientificCouncilMember;
  516.         return $this;
  517.     }
  518.     public function removeScientificCouncilMember(JournalScientificCouncilMember $scientificCouncilMember): self
  519.     {
  520.         $this->scientificCouncilMembers->removeElement($scientificCouncilMember);
  521.         return $this;
  522.     }
  523.     public function resetScientificCouncilMembers(): self
  524.     {
  525.         $this->scientificCouncilMembers = new ArrayCollection();
  526.         return $this;
  527.     }
  528.     /**
  529.      * @return Collection|JournalDomain[]
  530.      */
  531.     public function getDomains(): Collection
  532.     {
  533.         return $this->domains;
  534.     }
  535.     public function addDomain(JournalDomain $domain): self
  536.     {
  537.         if ($this->domains->contains($domain)) {
  538.             return $this;
  539.         }
  540.         $this->domains[] = $domain;
  541.         return $this;
  542.     }
  543.     public function removeDomain(JournalDomain $domain): self
  544.     {
  545.         $this->domains->removeElement($domain);
  546.         return $this;
  547.     }
  548.     public function resetDomains(): self
  549.     {
  550.         $this->domains = new ArrayCollection();
  551.         return $this;
  552.     }
  553.     /**
  554.      * @return Collection|JournalIndexationCollection[]
  555.      */
  556.     public function getIndexationCollections(): Collection
  557.     {
  558.         return $this->indexationCollections;
  559.     }
  560.     public function addIndexationCollection(JournalIndexationCollection $indexationCollection): self
  561.     {
  562.         if ($this->indexationCollections->contains($indexationCollection)) {
  563.             return $this;
  564.         }
  565.         $this->indexationCollections[] = $indexationCollection;
  566.         return $this;
  567.     }
  568.     public function removeIndexationCollection(JournalIndexationCollection $indexationCollection): self
  569.     {
  570.         $this->indexationCollections->removeElement($indexationCollection);
  571.         return $this;
  572.     }
  573.     public function resetIndexationCollections(): self
  574.     {
  575.         $this->indexationCollections = new ArrayCollection();
  576.         return $this;
  577.     }
  578.     /**
  579.      * @return Collection|JournalIndicator[]
  580.      */
  581.     public function getIndicators(): Collection
  582.     {
  583.         return $this->indicators;
  584.     }
  585.     public function addIndicator(JournalIndicator $indicator): self
  586.     {
  587.         if ($this->indicators->contains($indicator)) {
  588.             return $this;
  589.         }
  590.         $this->indicators[] = $indicator;
  591.         return $this;
  592.     }
  593.     public function removeIndicator(JournalIndicator $indicator): self
  594.     {
  595.         $this->indicators->removeElement($indicator);
  596.         return $this;
  597.     }
  598.     public function resetIndicators(): self
  599.     {
  600.         $this->indicators = new ArrayCollection();
  601.         return $this;
  602.     }
  603.     /**
  604.      * @return Collection|JournalAffiliation[]
  605.      */
  606.     public function getAffiliations(): Collection
  607.     {
  608.         return $this->affiliations;
  609.     }
  610.     public function addAffiliation(JournalAffiliation $affiliation): self
  611.     {
  612.         if ($this->affiliations->contains($affiliation)) {
  613.             return $this;
  614.         }
  615.         $this->affiliations[] = $affiliation;
  616.         return $this;
  617.     }
  618.     public function removeAffiliation(JournalAffiliation $affiliation): self
  619.     {
  620.         $this->affiliations->removeElement($affiliation);
  621.         return $this;
  622.     }
  623.     public function resetAffiliations(): self
  624.     {
  625.         $this->affiliations = new ArrayCollection();
  626.         return $this;
  627.     }
  628.     /**
  629.      * @return Collection|JournalPublisher[]
  630.      */
  631.     public function getPublishers(): Collection
  632.     {
  633.         return $this->publishers;
  634.     }
  635.     public function addPublisher(JournalPublisher $publisher): self
  636.     {
  637.         if ($this->publishers->contains($publisher)) {
  638.             return $this;
  639.         }
  640.         $this->publishers[] = $publisher;
  641.         return $this;
  642.     }
  643.     public function removePublisher(JournalPublisher $publisher): self
  644.     {
  645.         $this->publishers->removeElement($publisher);
  646.         return $this;
  647.     }
  648.     public function resetPublishers(): self
  649.     {
  650.         $this->publishers = new ArrayCollection();
  651.         return $this;
  652.     }
  653.     public function getVisiblePublishers(): Collection
  654.     {
  655.         return $this->publishers->filter(
  656.             fn (JournalPublisher $publisher) =>
  657.             $publisher->getStat() === true &&
  658.                 $publisher->getPublisher()->getStat() === true
  659.         );
  660.     }
  661.     /**
  662.      * @return Collection|JournalPartner[]
  663.      */
  664.     public function getPartners(): Collection
  665.     {
  666.         return $this->partners;
  667.     }
  668.     public function addPartner(JournalPartner $partner): self
  669.     {
  670.         if ($this->partners->contains($partner)) {
  671.             return $this;
  672.         }
  673.         $this->partners[] = $partner;
  674.         return $this;
  675.     }
  676.     public function removePartner(JournalPartner $partner): self
  677.     {
  678.         $this->partners->removeElement($partner);
  679.         return $this;
  680.     }
  681.     public function resetPartners(): self
  682.     {
  683.         $this->partners = new ArrayCollection();
  684.         return $this;
  685.     }
  686.     /**
  687.      * @return Collection|JournalSlide[]
  688.      */
  689.     public function getSlides(): Collection
  690.     {
  691.         return $this->slides;
  692.     }
  693.     public function addSlide(JournalSlide $slide): self
  694.     {
  695.         if ($this->slides->contains($slide)) {
  696.             return $this;
  697.         }
  698.         $this->slides[] = $slide;
  699.         return $this;
  700.     }
  701.     public function removeSlide(JournalSlide $slide): self
  702.     {
  703.         $this->slides->removeElement($slide);
  704.         return $this;
  705.     }
  706.     public function resetSlides(): self
  707.     {
  708.         $this->slides = new ArrayCollection();
  709.         return $this;
  710.     }
  711.     /**
  712.      * @return Collection|JournalSection[]
  713.      */
  714.     public function getSections(): Collection
  715.     {
  716.         return $this->sections;
  717.     }
  718.     public function addSection(JournalSection $section): self
  719.     {
  720.         if ($this->sections->contains($section)) {
  721.             return $this;
  722.         }
  723.         $this->sections[] = $section;
  724.         return $this;
  725.     }
  726.     public function removeSection(JournalSection $section): self
  727.     {
  728.         $this->sections->removeElement($section);
  729.         return $this;
  730.     }
  731.     public function resetSections(): self
  732.     {
  733.         $this->sections = new ArrayCollection();
  734.         return $this;
  735.     }
  736.     /**
  737.      * @return Collection|JournalMenuItem[]
  738.      */
  739.     public function getMenuItems(): Collection
  740.     {
  741.         return $this->menuItems;
  742.     }
  743.     public function addMenuItem(JournalMenuItem $menuItem): self
  744.     {
  745.         if ($this->menuItems->contains($menuItem)) {
  746.             return $this;
  747.         }
  748.         $this->menuItems[] = $menuItem;
  749.         return $this;
  750.     }
  751.     public function removeMenuItem(JournalMenuItem $menuItem): self
  752.     {
  753.         $this->menuItems->removeElement($menuItem);
  754.         return $this;
  755.     }
  756.     public function resetMenuItems(): self
  757.     {
  758.         $this->menuItems = new ArrayCollection();
  759.         return $this;
  760.     }
  761.     /**
  762.      * @return Collection|JournalSubmenuGroup[]
  763.      */
  764.     public function getSubmenuGroups(): Collection
  765.     {
  766.         return $this->submenuGroups;
  767.     }
  768.     public function addSubmenuGroup(JournalSubmenuGroup $submenuGroup): self
  769.     {
  770.         if ($this->submenuGroups->contains($submenuGroup)) {
  771.             return $this;
  772.         }
  773.         $this->submenuGroups[] = $submenuGroup;
  774.         return $this;
  775.     }
  776.     public function removeSubmenuGroup(JournalSubmenuGroup $submenuGroup): self
  777.     {
  778.         $this->submenuGroups->removeElement($submenuGroup);
  779.         return $this;
  780.     }
  781.     public function resetSubmenuGroups(): self
  782.     {
  783.         $this->submenuGroups = new ArrayCollection();
  784.         return $this;
  785.     }
  786.     /**
  787.      * @return Collection|JournalSidemenuGroup[]
  788.      */
  789.     public function getSidemenuGroups(): Collection
  790.     {
  791.         return $this->sidemenuGroups;
  792.     }
  793.     public function addSidemenuGroup(JournalSidemenuGroup $sidemenuGroup): self
  794.     {
  795.         if ($this->sidemenuGroups->contains($sidemenuGroup)) {
  796.             return $this;
  797.         }
  798.         $this->sidemenuGroups[] = $sidemenuGroup;
  799.         return $this;
  800.     }
  801.     public function removeSidemenuGroup(JournalSidemenuGroup $sidemenuGroup): self
  802.     {
  803.         $this->sidemenuGroups->removeElement($sidemenuGroup);
  804.         return $this;
  805.     }
  806.     public function resetSidemenuGroups(): self
  807.     {
  808.         $this->sidemenuGroups = new ArrayCollection();
  809.         return $this;
  810.     }
  811.     /**
  812.      * @return Collection|JournalBanner[]
  813.      */
  814.     public function getBanners(): Collection
  815.     {
  816.         return $this->banners;
  817.     }
  818.     public function addBanner(JournalBanner $banner): self
  819.     {
  820.         if ($this->banners->contains($banner)) {
  821.             return $this;
  822.         }
  823.         $this->banners[] = $banner;
  824.         return $this;
  825.     }
  826.     public function removeBanner(JournalBanner $banner): self
  827.     {
  828.         $this->banners->removeElement($banner);
  829.         return $this;
  830.     }
  831.     public function resetBanners(): self
  832.     {
  833.         $this->banners = new ArrayCollection();
  834.         return $this;
  835.     }
  836.     /**
  837.      * @return Collection|JournalVolume[]
  838.      */
  839.     public function getVolumes(): Collection
  840.     {
  841.         return $this->volumes;
  842.     }
  843.     public function addVolume(JournalVolume $volume): self
  844.     {
  845.         if ($this->volumes->contains($volume)) {
  846.             return $this;
  847.         }
  848.         $this->volumes[] = $volume;
  849.         return $this;
  850.     }
  851.     public function removeVolume(JournalVolume $volume): self
  852.     {
  853.         $this->volumes->removeElement($volume);
  854.         return $this;
  855.     }
  856.     public function resetVolumes(): self
  857.     {
  858.         $this->volumes = new ArrayCollection();
  859.         return $this;
  860.     }
  861.     /**
  862.      * @return Collection<int, JournalIssue>
  863.      */
  864.     public function getIssues(): Collection
  865.     {
  866.         return $this->issues;
  867.     }
  868.     /**
  869.      * @return Collection<int, JournalIssue>
  870.      */
  871.     public function getVisibleIssues(): Collection
  872.     {
  873.         return $this->issues->filter(fn (JournalIssue $issue) => $issue->getStat());
  874.     }
  875.     public function addIssue(JournalIssue $issue): self
  876.     {
  877.         if ($this->issues->contains($issue)) {
  878.             return $this;
  879.         }
  880.         $this->issues[] = $issue;
  881.         return $this;
  882.     }
  883.     public function removeIssue(JournalIssue $issue): self
  884.     {
  885.         $this->issues->removeElement($issue);
  886.         return $this;
  887.     }
  888.     public function resetIssues(): self
  889.     {
  890.         $this->issues = new ArrayCollection();
  891.         return $this;
  892.     }
  893.     public function getOldestIssue(): ?JournalIssue
  894.     {
  895.         $filtered = new ArrayCollection();
  896.         foreach ($this->getIssues() as $issue) {
  897.             if ($issue->getStat() && $issue->getPublishedAt()) {
  898.                 $filtered->add($issue);
  899.             }
  900.         }
  901.         if (count($filtered) === 0) {
  902.             return null;
  903.         }
  904.         $criteria Criteria::create()->orderBy(['publishedAt' => Criteria::ASC]);
  905.         return $filtered
  906.             ->matching($criteria)
  907.             ->first();
  908.     }
  909.     /**
  910.      * @return Collection|JournalArticle[]
  911.      */
  912.     public function getArticles(): Collection
  913.     {
  914.         return $this->articles;
  915.     }
  916.     /**
  917.      * @return Collection<int, JournalArticle>
  918.      */
  919.     public function getArticlesVisible(): Collection
  920.     {
  921.         return $this->articles->filter(fn (JournalArticle $article) => $article->getStat());
  922.     }
  923.     public function addArticle(JournalArticle $article): self
  924.     {
  925.         if ($this->articles->contains($article)) {
  926.             return $this;
  927.         }
  928.         $this->articles[] = $article;
  929.         return $this;
  930.     }
  931.     public function removeArticle(JournalArticle $article): self
  932.     {
  933.         $this->articles->removeElement($article);
  934.         return $this;
  935.     }
  936.     public function resetArticles(): self
  937.     {
  938.         $this->articles = new ArrayCollection();
  939.         return $this;
  940.     }
  941.     /**
  942.      * @return Collection|JournalPage[]
  943.      */
  944.     public function getPages(): Collection
  945.     {
  946.         return $this->pages;
  947.     }
  948.     public function addPage(JournalPage $page): self
  949.     {
  950.         if ($this->pages->contains($page)) {
  951.             return $this;
  952.         }
  953.         $this->pages[] = $page;
  954.         return $this;
  955.     }
  956.     public function removePage(JournalPage $page): self
  957.     {
  958.         $this->pages->removeElement($page);
  959.         return $this;
  960.     }
  961.     public function resetPages(): self
  962.     {
  963.         $this->pages = new ArrayCollection();
  964.         return $this;
  965.     }
  966.     /**
  967.      * @return Collection|JournalNews[]
  968.      */
  969.     public function getNews(): Collection
  970.     {
  971.         return $this->news;
  972.     }
  973.     public function addNews(JournalNews $news): self
  974.     {
  975.         if ($this->news->contains($news)) {
  976.             return $this;
  977.         }
  978.         $this->news[] = $news;
  979.         return $this;
  980.     }
  981.     public function removeNews(JournalNews $news): self
  982.     {
  983.         $this->news->removeElement($news);
  984.         return $this;
  985.     }
  986.     public function resetNews(): self
  987.     {
  988.         $this->news = new ArrayCollection();
  989.         return $this;
  990.     }
  991.     /**
  992.      * @return Collection|JournalCollection[]
  993.      */
  994.     public function getCollections(): Collection
  995.     {
  996.         return $this->collections;
  997.     }
  998.     public function addCollection(JournalCollection $collection): self
  999.     {
  1000.         if ($this->collections->contains($collection)) {
  1001.             return $this;
  1002.         }
  1003.         $this->collections[] = $collection;
  1004.         return $this;
  1005.     }
  1006.     public function removeCollection(JournalCollection $news): self
  1007.     {
  1008.         $this->news->removeElement($news);
  1009.         return $this;
  1010.     }
  1011.     public function resetCollections(): self
  1012.     {
  1013.         $this->collections = new ArrayCollection();
  1014.         return $this;
  1015.     }
  1016.     public function clone(Admin $createdBy null): self
  1017.     {
  1018.         $clone = clone $this;
  1019.         $clone->id null;
  1020.         $clone->setUuid();
  1021.         $clone->ord 0;
  1022.         $clone->stat false;
  1023.         $clone->importId null;
  1024.         $clone->data $this->data->clone($clone);
  1025.         $clone->view $this->view->clone($clone);
  1026.         $nativeLanguage $clone->getNativeLanguage();
  1027.         $nativeTranslation $clone->getTranslation($nativeLanguage);
  1028.         $nativeTranslation->setTitle($nativeTranslation->getTitle() . ' [KOPIA]');
  1029.         ClassUtils::cloneCollection($this$clone'translations');
  1030.         $clone
  1031.             ->resetDomains()
  1032.             ->resetIndexationCollections()
  1033.             ->resetIndicators()
  1034.             ->resetAffiliations()
  1035.             ->resetPublishers()
  1036.             ->resetPartners()
  1037.             ->resetSlides()
  1038.             ->resetSections()
  1039.             ->resetMenuItems()
  1040.             ->resetSubmenuGroups()
  1041.             ->resetBanners()
  1042.             ->resetVolumes()
  1043.             ->resetIssues()
  1044.             ->resetArticles()
  1045.             ->resetPages()
  1046.             ->resetNews()
  1047.             ->resetCollections()
  1048.             ->resetEditorialGroups()
  1049.             ->resetScientificCouncilMembers();
  1050.         $clone->createdBy $createdBy ?? $clone->createdBy;
  1051.         $clone->updatedBy $createdBy ?? $clone->updatedBy;
  1052.         $clone->createdAt = new \DateTimeImmutable();
  1053.         $clone->updatedAt = new \DateTimeImmutable();
  1054.         return $clone;
  1055.     }
  1056.     public function isSortArticlesByOrd(): bool
  1057.     {
  1058.         return $this->sortArticlesByOrd;
  1059.     }
  1060.     public function setSortArticlesByOrd(bool $sortArticlesByOrd): self
  1061.     {
  1062.         $this->sortArticlesByOrd $sortArticlesByOrd;
  1063.         return $this;
  1064.     }
  1065.     /**
  1066.      * @return Collection<int, JournalFooter>
  1067.      */
  1068.     public function getSmallFooters(): Collection
  1069.     {
  1070.         return $this->smallFooters;
  1071.     }
  1072.     public function addSmallFooter(JournalFooter $smallFooter): self
  1073.     {
  1074.         if (!$this->smallFooters->contains($smallFooter)) {
  1075.             $this->smallFooters->add($smallFooter);
  1076.             $smallFooter->setParent($this);
  1077.         }
  1078.         return $this;
  1079.     }
  1080.     public function removeSmallFooter(JournalFooter $smallFooter): self
  1081.     {
  1082.         if ($this->smallFooters->removeElement($smallFooter)) {
  1083.             // set the owning side to null (unless already changed)
  1084.             if ($smallFooter->getParent() === $this) {
  1085.                 $smallFooter->setParent(null);
  1086.             }
  1087.         }
  1088.         return $this;
  1089.     }
  1090.     public function getVisibleSmallFooters(): Collection
  1091.     {
  1092.         return $this->smallFooters->filter(fn (JournalFooter $smallFooter) => $smallFooter->getStat());
  1093.     }
  1094.     public function getStatsAmountOfArticles(): int
  1095.     {
  1096.         return $this->statsAmountOfArticles;
  1097.     }
  1098.     public function setStatsAmountOfArticles(int $statsAmountOfArticles): self
  1099.     {
  1100.         $this->statsAmountOfArticles $statsAmountOfArticles;
  1101.         return $this;
  1102.     }
  1103.     public function getStatsAmountOfIssues(): int
  1104.     {
  1105.         return $this->statsAmountOfIssues;
  1106.     }
  1107.     public function setStatsAmountOfIssues(int $statsAmountOfIssues): self
  1108.     {
  1109.         $this->statsAmountOfIssues $statsAmountOfIssues;
  1110.         return $this;
  1111.     }
  1112.     public function getStatsAmountOfVolumes(): int
  1113.     {
  1114.         return $this->statsAmountOfVolumes;
  1115.     }
  1116.     public function setStatsAmountOfVolumes(int $statsAmountOfVolumes): self
  1117.     {
  1118.         $this->statsAmountOfVolumes $statsAmountOfVolumes;
  1119.         return $this;
  1120.     }
  1121.     public function getStatsAmountOfViews(): int
  1122.     {
  1123.         return $this->statsAmountOfViews;
  1124.     }
  1125.     public function setStatsAmountOfViews(int $statsAmountOfViews): self
  1126.     {
  1127.         $this->statsAmountOfViews $statsAmountOfViews;
  1128.         return $this;
  1129.     }
  1130.     public function getStatsAmountOfDownloads(): int
  1131.     {
  1132.         return $this->statsAmountOfDownloads;
  1133.     }
  1134.     public function setStatsAmountOfDownloads(int $statsAmountOfDownloads): self
  1135.     {
  1136.         $this->statsAmountOfDownloads $statsAmountOfDownloads;
  1137.         return $this;
  1138.     }
  1139.     public function getStatsEarliestArticlePublicationDate(): ?\DateTimeImmutable
  1140.     {
  1141.         return $this->statsEarliestArticlePublicationDate;
  1142.     }
  1143.     public function setStatsEarliestArticlePublicationDate(?string $statsEarliestArticlePublicationDate): self
  1144.     {
  1145.         if (!$statsEarliestArticlePublicationDate) {
  1146.             return $this;
  1147.         }
  1148.         $this->statsEarliestArticlePublicationDate = new \DateTimeImmutable($statsEarliestArticlePublicationDate);
  1149.         return $this;
  1150.     }
  1151.     public function getStatsLatestArticlePublicationDate(): ?\DateTimeImmutable
  1152.     {
  1153.         return $this->statsLatestArticlePublicationDate;
  1154.     }
  1155.     public function setStatsLatestArticlePublicationDate(?string $statsLatestArticlePublicationDate): self
  1156.     {
  1157.         if (!$statsLatestArticlePublicationDate) {
  1158.             return $this;
  1159.         }
  1160.         $this->statsLatestArticlePublicationDate = new \DateTimeImmutable($statsLatestArticlePublicationDate);
  1161.         return $this;
  1162.     }
  1163.     #[Groups(['read:stats'])]
  1164.     public function getStatsAffiliations(): ?string
  1165.     {
  1166.         $affiliations = [];
  1167.         /** @var JournalAffiliation */
  1168.         foreach ($this->affiliations as $entry) {
  1169.             if (isset($affiliations[$entry->getAffiliation()->getUuid()->toString()])) {
  1170.                 continue;
  1171.             }
  1172.             $affiliations[$entry->getAffiliation()->getUuid()->toString()] =
  1173.                 $entry->getAffiliation()->readNativeLanguageTranslation('title');
  1174.         }
  1175.         sort($affiliations);
  1176.         return implode(', ',  $affiliations);
  1177.     }
  1178.     #[Groups(['read:stats'])]
  1179.     public function getStatsAffiliationTypes(): ?string
  1180.     {
  1181.         $types = [];
  1182.         /** @var JournalAffiliation */
  1183.         foreach ($this->affiliations as $entry) {
  1184.             if (!($type $entry->getAffiliation()?->getType())) {
  1185.                 continue;
  1186.             }
  1187.             if (isset($types[$type->getUuid()->toString()])) {
  1188.                 continue;
  1189.             }
  1190.             $types[$type->getUuid()->toString()] = $type->getTitle();
  1191.         }
  1192.         sort($types);
  1193.         return implode(', ',  $types);
  1194.     }
  1195.     #[Groups(['read:stats'])]
  1196.     public function getStatsDomains(): ?string
  1197.     {
  1198.         $domains = [];
  1199.         /** @var JournalDomain */
  1200.         foreach ($this->domains as $entry) {
  1201.             if (isset($domains[$entry->getDomain()->getUuid()->toString()])) {
  1202.                 continue;
  1203.             }
  1204.             $domains[$entry->getDomain()->getUuid()->toString()] =
  1205.                 $entry->getDomain()->readNativeLanguageTranslation('title');
  1206.         }
  1207.         sort($domains);
  1208.         return implode(', ',  $domains);
  1209.     }
  1210.     #[Groups(['read:stats'])]
  1211.     public function getStatsType(): ?string
  1212.     {
  1213.         return $this->data->getType()?->getTitle();
  1214.     }
  1215.     #[Groups(['read:stats'])]
  1216.     public function getStatsState(): ?string
  1217.     {
  1218.         return $this->data->getState()?->getTitle();
  1219.     }
  1220. }