src/Entity/Journal.php line 146

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