src/Entity/JournalArticle.php line 207

  1. <?php
  2. namespace App\Entity;
  3. use App\StateProvider\JournalArticleAutocompleteProvider;
  4. use Doctrine\ORM\Mapping as ORM,
  5.     Doctrine\DBAL\Types\Types,
  6.     Doctrine\Common\Collections\Collection,
  7.     Doctrine\Common\Collections\ArrayCollection;
  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\DateFilter,
  22.     ApiPlatform\Doctrine\Orm\Filter\ExistsFilter,
  23.     ApiPlatform\Doctrine\Orm\Filter\OrderFilter,
  24.     ApiPlatform\Doctrine\Orm\Filter\BooleanFilter,
  25.     ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
  26. use App\Entity\Trait\IdTrait,
  27.     App\Entity\Trait\UuidTrait,
  28.     App\Entity\Trait\OrdStatTrait,
  29.     App\Entity\Trait\TimestampableTrait,
  30.     App\Entity\Trait\TranslatableTrait,
  31.     App\Entity\Trait\Languages\LanguageableChildTrait,
  32.     App\Entity\Trait\ArchivedTrait,
  33.     App\Entity\Trait\ImportIdTrait,
  34.     App\Entity\Interface\TranslatableInterface,
  35.     App\Entity\Interface\OrdStatableInterface,
  36.     App\Entity\Interface\LanguageableChildInterface,
  37.     App\Entity\Interface\ArchivableInterface,
  38.     App\Entity\Interface\CloneableInterface;
  39. use App\Enum\JournalDateFormat,
  40.     App\Enum\JournalArticleType,
  41.     App\Enum\JournalLicence,
  42.     App\Enum\JournalArticleFileType,
  43.     App\Enum\AuthorProperty,
  44.     App\Enum\JournalArticleAuthorRole,
  45.     App\Doctrine\DBAL\Types\AuthorPropertiesType,
  46.     App\Repository\JournalArticleRepository,
  47.     App\Security\Voter\ArchivableVoter,
  48.     App\Lib\Actions,
  49.     App\DTO\CloneDTO,
  50.     App\StateProcessor\CloneProcessor,
  51.     App\Security\Voter\CloneVoter,
  52.     App\Filter\IriFilter,
  53.     App\Doctrine\DBAL\Types\LanguagesType;
  54. use App\Util\ClassUtils,
  55.     App\Enum\Language,
  56.     App\Attribute\DenormalizationInject;
  57. use App\Controller\Export\JournalArticle\JournalArticleCrossrefExporter,
  58.     App\Controller\Export\JournalArticle\JournalArticleSimcheckCrossrefExporter,
  59.     App\Controller\Export\JournalArticle\JournalArticleFundingCrossrefExporter,
  60.     App\Controller\Export\JournalArticle\JournalArticleLicenceFulltextCrossrefExporter,
  61.     App\Controller\Export\JournalArticle\JournalArticleReferencesCrossrefExporter,
  62.     App\Controller\Export\JournalArticle\JournalArticleJATSExporter;
  63. use App\Controller\Stats\StatsJournalArticleXlsxGenerator;
  64. use App\Filter\OrderByUnmapped;
  65. use App\StateProvider\StatsJournalArticleProvider;
  66. use Symfony\Component\Serializer\Annotation\MaxDepth;
  67. #[ApiResource(
  68.     description'Journal articles',
  69.     normalizationContext: ['groups' => [
  70.         'read',
  71.         'read:' self::class,
  72.         'read:' self::class . 'Translation',
  73.         'read:' self::class . 'MetadataTranslation'
  74.     ]],
  75.     denormalizationContext: ['groups' => ['write']],
  76.     security'is_granted("' Journal::class . '")',
  77.     order: ['ord' => 'desc'],
  78.     operations: [
  79.         new GetCollection(paginationMaximumItemsPerPage500normalizationContext: ['groups' => ['read:minimal']]),
  80.         new GetCollection(
  81.             uriTemplate'/journal_articles/autocomplete',
  82.             providerJournalArticleAutocompleteProvider::class,
  83.             normalizationContext: ['groups' => ['read:autocomplete']],
  84.         ),
  85.         new GetCollection(
  86.             name'get_journal_article_stats',
  87.             uriTemplate'/journal_articles/stats',
  88.             providerStatsJournalArticleProvider::class,
  89.             security'is_granted("ROLE_MODULE_STATS")',
  90.             normalizationContext: ['groups' => ['read:stats']],
  91.             paginationMaximumItemsPerPage500
  92.         ),
  93.         new GetCollection(
  94.             name'get_journal_article_stats_xlsx',
  95.             uriTemplate'/journal_articles/stats/xlsx',
  96.             providerStatsJournalArticleProvider::class,
  97.             controllerStatsJournalArticleXlsxGenerator::class,
  98.             security'is_granted("ROLE_MODULE_STATS")',
  99.         ),
  100.         new Post(
  101.             uriTemplate'/journal_articles/clone',
  102.             inputCloneDTO::class,
  103.             processorCloneProcessor::class,
  104.             security'is_granted("' Actions::CLONE . '")',
  105.             securityMessageCloneVoter::MESSAGE
  106.         ),
  107.         new Post(denormalizationContext: ['groups' => ['write',  'post']]),
  108.         new Get(),
  109.         new Get(
  110.             name'get_journal_article_crossref',
  111.             uriTemplate'/journal_articles/{uuid}/export/crossref',
  112.             controllerJournalArticleCrossrefExporter::class
  113.         ),
  114.         new Get(
  115.             name'get_journal_article_jats',
  116.             uriTemplate'/journal_articles/{uuid}/export/jats',
  117.             controllerJournalArticleJATSExporter::class
  118.         ),
  119.         new Get(
  120.             name'get_journal_article_simcheck_crossref',
  121.             uriTemplate'/journal_articles/{uuid}/export/simcheck_crossref',
  122.             controllerJournalArticleSimcheckCrossrefExporter::class
  123.         ),
  124.         new Get(
  125.             name'get_journal_article_funding_crossref',
  126.             uriTemplate'/journal_articles/{uuid}/export/funding_crossref',
  127.             controllerJournalArticleFundingCrossrefExporter::class
  128.         ),
  129.         new Get(
  130.             name'get_journal_article_licence_fulltext_crossref',
  131.             uriTemplate'/journal_articles/{uuid}/export/licence_fulltext_crossref',
  132.             controllerJournalArticleLicenceFulltextCrossrefExporter::class
  133.         ),
  134.         new Get(
  135.             name'get_journal_article_references_crossref',
  136.             uriTemplate'/journal_articles/{uuid}/export/references_crossref',
  137.             controllerJournalArticleReferencesCrossrefExporter::class
  138.         ),
  139.         new Put(),
  140.         new Delete(
  141.             securityPostDenormalize'is_granted("' Actions::DELETE '", object)',
  142.             securityPostDenormalizeMessageArchivableVoter::MESSAGE
  143.         ),
  144.     ],
  145.     extraProperties: ['standard_put' => false],
  146. )]
  147. #[ApiFilter(SearchFilter::class, properties: [
  148.     'translations.title' => 'partial',
  149.     'nativeTitle' => 'partial',
  150.     'workingTitle' => 'partial',
  151.     'uuid' => 'exact',
  152.     'doi' => 'partial',
  153.     'parent.affiliations.affiliation.nativeTitle' => 'partial',
  154.     'parent.nativeTitle' => 'partial',
  155.     'issue.volume.translations.title' => 'partial',
  156.     'issue.translations.title' => 'partial',
  157.     'section.translations.title' => 'partial',
  158.     'licence' => 'exact',
  159.     'nativeMetadataLanguage' => 'exact',
  160.     // 'authors.author.affiliations.nativeTitle' => 'partial', //not used see api/src/StateProvider/StatsJournalArticleProvider.php
  161.     'authors.author.affiliations.country' => 'partial',
  162.     'translations.metaKeywords' => 'partial'
  163. ])]
  164. #[ApiFilter(ExistsFilter::class, properties: ['doi'])]
  165. #[ApiFilter(IriFilter::class, properties: [
  166.     'parent',
  167.     'issue',
  168.     'type',
  169.     'section'
  170. ])]
  171. #[ApiFilter(OrderByUnmapped::class, properties: ['statsIssue'])]
  172. #[ApiFilter(DateFilter::class, properties: ['updatedAt''onlinePublishedAt''acceptedAt''receivedAt'])]
  173. #[ApiFilter(BooleanFilter::class, properties: ['isArchived''stat'])]
  174. #[ApiFilter(RangeFilter::class, properties: ['statsAmountOfViews''statsAmountOfDownloads'])]
  175. #[ApiFilter(OrderFilter::class, properties: [
  176.     'nativeTitle',
  177.     'doi',
  178.     'pageFrom',
  179.     'isOpen',
  180.     'onlinePublishedAt',
  181.     'updatedAt',
  182.     'receivedAt',
  183.     'acceptedAt',
  184.     'nativeMetadataLanguage',
  185.     'parent.nativeTitle',
  186.     // 'issue.nativeTitle',?? nie ma takiego pola
  187.     // 'issue.volume.nativeTitle', ?? nie ma takiego pola
  188.     // 'section.nativeTitle', ?? nie ma
  189.     'ord',
  190.     'statsAmountOfViews',
  191.     'statsAmountOfDownloads'
  192. ])]
  193. #[Index(fields: ["importId"], name"import_id_idx")]
  194. #[Index(fields: ["stat"], name"stat_idx")]
  195. #[ORM\Entity(repositoryClassJournalArticleRepository::class)]
  196. class JournalArticle implements
  197.     TranslatableInterface,
  198.     OrdStatableInterface,
  199.     LanguageableChildInterface,
  200.     ArchivableInterface,
  201.     CloneableInterface
  202. {
  203.     use IdTrait,
  204.         UuidTrait,
  205.         OrdStatTrait,
  206.         TimestampableTrait,
  207.         TranslatableTrait,
  208.         LanguageableChildTrait,
  209.         ArchivedTrait,
  210.         ImportIdTrait;
  211.     const DEFAULT_FILES = [
  212.         JournalArticleFileType::BIBLIOGRAPHY,
  213.         JournalArticleFileType::ABSTRACT_TEXT,
  214.         JournalArticleFileType::FULL_TEXT
  215.     ];
  216.     #[ApiProperty(description'Parent'writableLinkfalse)]
  217.     #[Groups(['read''post'])]
  218.     #[ORM\ManyToOne(targetEntityJournal::class, inversedBy'articles')]
  219.     #[ORM\JoinColumn(onDelete'cascade'nullablefalse)]
  220.     private Journal $parent;
  221.     #[ApiProperty(description'Native title')]
  222.     #[Groups(['read''read:stats''read:autocomplete'])]
  223.     #[ORM\Column(typeTypes::TEXTnullabletrue)]
  224.     private ?string $nativeTitle null;
  225.     #[ApiProperty(description'Native title raw')]
  226.     #[Groups(['read'])]
  227.     #[ORM\Column(typeTypes::TEXTnullabletrue)]
  228.     private ?string $nativeTitleRaw null;
  229.     #[ApiProperty(description'Working title')]
  230.     #[Groups(['read''read:minimal''write'])]
  231.     #[Assert\NotBlank]
  232.     #[ORM\Column(typeTypes::STRINGlength511)]
  233.     private string $workingTitle;
  234.     #[ApiProperty(description'Licence'writableLinkfalse)]
  235.     #[Groups(['read:' self::class, 'read:minimal''read:stats''write'])]
  236.     #[ORM\Column(
  237.         typeTypes::STRING,
  238.         enumTypeJournalLicence::class,
  239.         length255,
  240.         options: ['default' => JournalLicence::CC_BY]
  241.     )]
  242.     private JournalLicence $licence JournalLicence::CC_BY;
  243.     #[ApiProperty(description'Issue'writableLinkfalsefetchEagerfalse)]
  244.     #[Groups(['read''write'])]
  245.     #[Assert\NotNull]
  246.     #[Assert\Expression(
  247.         expression'!this || value.getParent() === this.getParent()',
  248.         message'Cannot assign issue from the outside of current journal.'
  249.     )]
  250.     #[ORM\ManyToOne(targetEntityJournalIssue::class, inversedBy'articles')]
  251.     #[ORM\JoinColumn(onDelete'cascade'nullablefalse)]
  252.     private JournalIssue $issue;
  253.     #[ApiProperty(description'Funding number')]
  254.     #[Groups(['read:' self::class, 'write'])]
  255.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  256.     private ?string $fundingNumber null;
  257.     #[ApiProperty(description'Type'writableLinkfalse)]
  258.     #[Groups(['read''read:minimal''read:stats''write'])]
  259.     #[ORM\Column(
  260.         typeTypes::STRING,
  261.         enumTypeJournalArticleType::class,
  262.         length255,
  263.         options: [
  264.             'default' => JournalArticleType::ORIGINAL_RESEARCH_ARTICLE
  265.         ]
  266.     )]
  267.     private JournalArticleType $type JournalArticleType::ORIGINAL_RESEARCH_ARTICLE;
  268.     #[ApiProperty(description'Section'writableLinkfalsefetchEagerfalse)]
  269.     #[Groups(['read:' self::class, 'read:minimal''write'])]
  270.     #[MaxDepth(1)]
  271.     #[ORM\ManyToOne(targetEntityJournalSection::class)]
  272.     #[ORM\JoinColumn(onDelete'set null')]
  273.     private ?JournalSection $section null;
  274.     #[ApiProperty(description'DOI')]
  275.     #[Groups(['read:' self::class, 'read:minimal''read:stats''write'])]
  276.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  277.     private ?string $doi null;
  278.     #[ApiProperty(description'Page from')]
  279.     #[Groups(['read:' self::class, 'read:minimal''read:stats''write'])]
  280.     // #[Assert\Positive]
  281.     #[ORM\Column(typeTypes::STRINGnullabletrue)] //, options: ['default' => 1])]
  282.     private ?string $pageFrom null;
  283.     #[ApiProperty(description'Page to')]
  284.     #[Groups(['read:' self::class, 'read:minimal''read:stats''write'])]
  285.     // #[Assert\Expression(
  286.     //     expression: 'value >= this.getPageFrom()',
  287.     //     message: 'Page to must be greater than page from.'
  288.     // )]
  289.     #[ORM\Column(typeTypes::STRINGnullabletrue)] //INTEGER, options: ['default' => 1])]
  290.     private ?string $pageTo null;
  291.     #[ApiProperty(description'Citation type'writableLinkfalse)]
  292.     #[Groups(['read:' self::class, 'write'])]
  293.     #[ORM\ManyToOne(targetEntityCitationType::class)]
  294.     #[ORM\JoinColumn(onDelete'set null')]
  295.     private ?CitationType $citationType null;
  296.     #[ApiProperty(description'Citation')]
  297.     #[Groups(['read:' self::class, 'write'])]
  298.     #[ORM\Column(typeTypes::TEXTnullabletrue)]
  299.     private ?string $citation null;
  300.     #[ApiProperty(description'Received at')]
  301.     #[Groups(['read:' self::class, 'read:stats''write'])]
  302.     #[ORM\Column(typeTypes::DATE_IMMUTABLEnullabletrue)]
  303.     private ?\DateTimeImmutable $receivedAt null;
  304.     #[ApiProperty(description'Received at format'writableLinkfalse)]
  305.     #[Groups(['read:' self::class, 'write'])]
  306.     #[ORM\Column(
  307.         typeTypes::STRING,
  308.         enumTypeJournalDateFormat::class,
  309.         length255,
  310.         options: ['default' => JournalDateFormat::COMPLETE]
  311.     )]
  312.     private JournalDateFormat $receivedAtFormat JournalDateFormat::COMPLETE;
  313.     #[ApiProperty(description'Accepted at')]
  314.     #[Groups(['read:' self::class, 'read:stats''write'])]
  315.     #[ORM\Column(typeTypes::DATE_IMMUTABLEnullabletrue)]
  316.     private ?\DateTimeImmutable $acceptedAt null;
  317.     #[ApiProperty(description'Accepted at format'writableLinkfalse)]
  318.     #[Groups(['read:' self::class, 'write'])]
  319.     #[ORM\Column(
  320.         typeTypes::STRING,
  321.         enumTypeJournalDateFormat::class,
  322.         length255,
  323.         options: ['default' => JournalDateFormat::COMPLETE]
  324.     )]
  325.     private JournalDateFormat $acceptedAtFormat JournalDateFormat::COMPLETE;
  326.     #[ApiProperty(description'Corrected at')]
  327.     #[Groups(['read:' self::class, 'write'])]
  328.     #[ORM\Column(typeTypes::DATE_IMMUTABLEnullabletrue)]
  329.     private ?\DateTimeImmutable $correctedAt null;
  330.     #[ApiProperty(description'Corrected at format'writableLinkfalse)]
  331.     #[Groups(['read:' self::class, 'write'])]
  332.     #[ORM\Column(
  333.         typeTypes::STRING,
  334.         enumTypeJournalDateFormat::class,
  335.         length255,
  336.         options: ['default' => JournalDateFormat::COMPLETE]
  337.     )]
  338.     private JournalDateFormat $correctedAtFormat JournalDateFormat::COMPLETE;
  339.     #[ApiProperty(description'Online published at')]
  340.     #[Groups(['read:' self::class, 'read:stats''write'])]
  341.     #[ORM\Column(typeTypes::DATE_IMMUTABLEnullabletrue)]
  342.     private ?\DateTimeImmutable $onlinePublishedAt null;
  343.     #[ApiProperty(description'Online published at format'writableLinkfalse)]
  344.     #[Groups(['read:' self::class, 'write'])]
  345.     #[ORM\Column(
  346.         typeTypes::STRING,
  347.         enumTypeJournalDateFormat::class,
  348.         length255,
  349.         options: ['default' => JournalDateFormat::COMPLETE]
  350.     )]
  351.     private JournalDateFormat $onlinePublishedAtFormat JournalDateFormat::COMPLETE;
  352.     #[ApiProperty(description'Is open')]
  353.     #[Groups(['read:' self::class, 'read:minimal''read:stats''write'])]
  354.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => true])]
  355.     private bool $isOpen true;
  356.     #[ApiProperty(description'Price')]
  357.     #[Groups(['read:' self::class])]
  358.     #[Assert\PositiveOrZero]
  359.     #[ORM\Column(typeTypes::INTEGERnullabletrue)]
  360.     private ?int $price null;
  361.     #[ApiProperty(description'Grace date')]
  362.     #[Groups(['read:' self::class, 'write'])]
  363.     #[ORM\Column(typeTypes::DATE_IMMUTABLEnullabletrue)]
  364.     private ?\DateTimeImmutable $graceDate null;
  365.     #[ApiProperty(description'Meta link')]
  366.     #[Groups(['read:' self::class, 'write'])]
  367.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  368.     private ?string $metaLink null;
  369.     /** @deprecated */
  370.     #[ApiProperty(description'Language'writableLinkfalse)]
  371.     #[Groups(['read:' self::class, 'write'])]
  372.     #[ORM\Column(
  373.         typeTypes::STRING,
  374.         enumTypeLanguage::class,
  375.         length255,
  376.         options: ['default' => Language::PL]
  377.     )]
  378.     private Language $language Language::PL;
  379.     #[ApiProperty(description'Visible author properties'writableLinkfalse)]
  380.     #[Groups(['read:' self::class, 'write'])]
  381.     #[Assert\Choice(callback: [AuthorProperty::class, 'cases'], multipletrue)]
  382.     #[Assert\Unique]
  383.     #[Assert\Count(min1)]
  384.     #[ORM\Column(typeAuthorPropertiesType::NAME)]
  385.     private array $visibleAuthorProperties = [
  386.         AuthorProperty::PREFIX,
  387.         AuthorProperty::NAME,
  388.         AuthorProperty::SURNAME,
  389.         AuthorProperty::AFFILIATION
  390.     ];
  391.     #[ApiProperty(description'Publication languages'writableLinkfalse)]
  392.     #[Groups(['read:' self::class, 'write'])]
  393.     #[Assert\Choice(callback: [Language::class, 'cases'], multipletrue)]
  394.     #[Assert\Unique]
  395.     #[Assert\Count(min1)]
  396.     #[ORM\Column(typeLanguagesType::NAME)]
  397.     private array $publicationLanguages = [Language::EN];
  398.     #[ApiProperty(description'Metadata languages'writableLinkfalse)]
  399.     #[Groups(['read:' self::class, 'write''read:minimal'])]
  400.     #[Assert\Choice(callback: [Language::class, 'cases'], multipletrue)]
  401.     #[Assert\Unique]
  402.     // #[Assert\Count(min: 1)]
  403.     #[ORM\Column(typeLanguagesType::NAME)]
  404.     private array $metadataLanguages = [Language::ENLanguage::PL];
  405.     #[ApiProperty(description'Native metadata language')]
  406.     #[Groups(['read:' self::class, 'read:stats''read:minimal'])]
  407.     #[ORM\Column(
  408.         typeTypes::STRING,
  409.         enumTypeLanguage::class,
  410.         length255,
  411.         nullabletrue,
  412.     )]
  413.     private ?Language $nativeMetadataLanguage null;
  414.     #[ApiProperty(description'Slug')]
  415.     #[Groups(['read:' self::class, 'write''read:minimal'])]
  416.     #[Gedmo\Slug(separator'-'style'default'updatabletruefields: ['nativeTitleRaw'])]
  417.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  418.     private ?string $slug null;
  419.     #[ApiProperty(description'Statistics: amount of views')]
  420.     #[Groups(['read:stats'])]
  421.     #[ORM\Column(typeTypes::INTEGER)]
  422.     private int $statsAmountOfViews 0;
  423.     #[ApiProperty(description'Statistics: amount of downloads')]
  424.     #[Groups(['read:stats'])]
  425.     #[ORM\Column(typeTypes::INTEGER)]
  426.     private int $statsAmountOfDownloads 0;
  427.     #[ApiProperty(description'Metadata translations')]
  428.     #[Groups(['read''write''read:minimal'])]
  429.     #[MaxDepth(2)]
  430.     #[Assert\Type(Collection::class)]
  431.     #[Assert\Valid]
  432.     #[DenormalizationInject(method: ['name' => 'getMetadataTranslation''key' => 'lang'])]
  433.     #[ORM\OneToMany(
  434.         targetEntityJournalArticleMetadataTranslation::class,
  435.         mappedBy'parent',
  436.         cascade: ['persist''remove'],
  437.         orphanRemovaltrue,
  438.         indexBy'lang'
  439.     )]
  440.     private Collection $metadataTranslations;
  441.     #[ApiProperty(description'Classifications')]
  442.     #[ORM\OneToMany(targetEntityJournalArticleClassification::class, mappedBy'parent'cascade: ['persist'])]
  443.     #[ORM\OrderBy(['ord' => 'desc'])]
  444.     private Collection $classifications;
  445.     #[ApiProperty(description'Research datas')]
  446.     #[ORM\OneToMany(targetEntityJournalArticleResearchData::class, mappedBy'parent'cascade: ['persist'])]
  447.     #[ORM\OrderBy(['ord' => 'desc'])]
  448.     private Collection $researchDatas;
  449.     #[ApiProperty(description'Relationships')]
  450.     #[ORM\OneToMany(targetEntityJournalArticleRelationship::class, mappedBy'parent'cascade: ['persist'])]
  451.     #[ORM\OrderBy(['ord' => 'desc'])]
  452.     private Collection $relationships;
  453.     #[ApiProperty(description'Content blocks')]
  454.     #[ORM\OneToMany(targetEntityJournalArticleContentBlock::class, mappedBy'parent'cascade: ['persist'])]
  455.     #[ORM\OrderBy(['ord' => 'desc'])]
  456.     private Collection $contentBlocks;
  457.     #[ApiProperty(description'Additional blocks')]
  458.     #[ORM\OneToMany(targetEntityJournalArticleAdditionalBlock::class, mappedBy'parent')]
  459.     #[ORM\OrderBy(['ord' => 'desc'])]
  460.     private Collection $additionalBlocks;
  461.     #[ApiProperty(description'Bibliography entries')]
  462.     #[ORM\OneToMany(
  463.         targetEntityJournalArticleBibliographyEntry::class,
  464.         mappedBy'parent',
  465.         cascade: ['persist''remove'],
  466.         orphanRemovaltrue
  467.     )]
  468.     #[ORM\OrderBy(['ord' => 'desc'])]
  469.     private Collection $bibliographyEntries;
  470.     #[ApiProperty(description'Files')]
  471.     #[ORM\OneToMany(targetEntityJournalArticleFile::class, mappedBy'parent'cascade: ['persist'])]
  472.     #[ORM\OrderBy(['ord' => 'desc'])]
  473.     private Collection $files;
  474.     #[ApiProperty(description'Authors')]
  475.     #[Groups(['read:' self::class, 'read:minimal'])]
  476.     #[MaxDepth(1)]
  477.     #[ORM\OneToMany(targetEntityJournalArticleAuthor::class, mappedBy'parent')]
  478.     #[ORM\OrderBy(['ord' => 'desc'])]
  479.     private Collection $authors;
  480.     private ?array $redactors null;
  481.     #[ApiProperty(description'View entries')]
  482.     #[ORM\OneToMany(targetEntityJournalArticleViewEntry::class, mappedBy'parent'cascade: ['persist'])]
  483.     #[ORM\OrderBy(['createdAt' => 'desc'])]
  484.     private Collection $viewEntries;
  485.     #[ApiProperty(description'Download entries')]
  486.     #[ORM\OneToMany(targetEntityJournalArticleDownloadEntry::class, mappedBy'parent'cascade: ['persist'])]
  487.     #[ORM\OrderBy(['createdAt' => 'desc'])]
  488.     private Collection $downloadEntries;
  489.     #[ApiProperty(description'Slug title language'writableLinkfalse)]
  490.     #[Groups(['read:' self::class, 'read:minimal''write'])]
  491.     #[Assert\Choice(callback'getMetadataLanguages'message'Można wybrać tylko jeden z jÄ™zyków tekstu.')]
  492.     #[ORM\Column(
  493.         typeTypes::STRING,
  494.         enumTypeLanguage::class,
  495.         length255,
  496.         nullabletrue,
  497.     )]
  498.     private ?Language $slugTitleLanguage null;
  499.     #[ORM\OneToMany(mappedBy'parent'targetEntityJournalArticleSlugHistory::class, cascade: ['persist'], orphanRemovaltrue,)]
  500.     private Collection $slugHistory;
  501.     #[ApiProperty(description'Is crossmark button visible')]
  502.     #[Groups(['read:' self::class, 'write'])]
  503.     #[ORM\Column(typeTypes::BOOLEANnullabletrue)]
  504.     private ?bool $isCrossmarkButtonVisible null;
  505.     #[ORM\OneToMany(mappedBy'article'targetEntityCartProduct::class)]
  506.     private Collection $cartProducts;
  507.     #[ApiProperty(description'Statistics: full text url')]
  508.     #[Groups(['read:stats'])]
  509.     private ?string $statsFullTextUrl null;
  510.     #[ApiProperty(description'Statistics: article url')]
  511.     #[Groups(['read:stats'])]
  512.     private ?string $statsArticleUrl null;
  513.     public function __construct(Journal $parentJournalIssue $issue)
  514.     {
  515.         $this->setUuid();
  516.         $this->parent $parent;
  517.         $this->issue $issue;
  518.         $this->translations = new ArrayCollection();
  519.         $this->metadataTranslations = new ArrayCollection();
  520.         $this->classifications = new ArrayCollection();
  521.         $this->researchDatas = new ArrayCollection();
  522.         $this->relationships = new ArrayCollection();
  523.         $this->contentBlocks = new ArrayCollection();
  524.         $this->additionalBlocks = new ArrayCollection();
  525.         $this->bibliographyEntries = new ArrayCollection();
  526.         $this->files = new ArrayCollection();
  527.         $this->authors = new ArrayCollection();
  528.         $this->viewEntries = new ArrayCollection();
  529.         $this->downloadEntries = new ArrayCollection();
  530.         $this->slugHistory = new ArrayCollection();
  531.         foreach ($this->parent->getLanguages() as $lang) {
  532.             new JournalArticleTranslation($this$lang);
  533.         }
  534.         foreach ($this->metadataLanguages as $lang) {
  535.             new JournalArticleMetadataTranslation($this$lang);
  536.         }
  537.         foreach (self::DEFAULT_FILES as $type) {
  538.             new JournalArticleFile($this$type);
  539.         }
  540.         $this->isOpen $issue->getIsOpen();
  541.         $this->price $parent->getData()->getArticlePrice() ?? $parent->getData()->getIssuePrice();
  542.         $this->citationType $issue->getSuggestedCitationType();
  543.         $this->licence $issue->getLicence();
  544.         $this->isCrossmarkButtonVisible $issue->getIsCrossmarkButtonVisible();
  545.         $this->createdAt = new \DateTimeImmutable();
  546.         $this->updatedAt = new \DateTimeImmutable();
  547.         $parent->addArticle($this);
  548.         $this->cartProducts = new ArrayCollection();
  549.     }
  550.     public function getParent(): Journal
  551.     {
  552.         return $this->parent;
  553.     }
  554.     public function getNativeMetadataLanguage(): ?Language
  555.     {
  556.         return $this->nativeMetadataLanguage;
  557.     }
  558.     public function getNativeTitle(): ?string
  559.     {
  560.         return $this->nativeTitle;
  561.     }
  562.     public function setNativeTitle(?string $nativeTitle): self
  563.     {
  564.         $this->nativeTitle $nativeTitle;
  565.         return $this;
  566.     }
  567.     public function getNativeTitleRaw(): ?string
  568.     {
  569.         return $this->nativeTitleRaw;
  570.     }
  571.     public function setNativeTitleRaw(?string $nativeTitleRaw): self
  572.     {
  573.         $this->nativeTitleRaw $nativeTitleRaw;
  574.         $this->addCurrentSlugToHistory();
  575.         return $this;
  576.     }
  577.     public function getWorkingTitle(): ?string
  578.     {
  579.         return $this->workingTitle;
  580.     }
  581.     public function setWorkingTitle(?string $workingTitle): self
  582.     {
  583.         $this->workingTitle $workingTitle;
  584.         return $this;
  585.     }
  586.     public function getLicence(): JournalLicence
  587.     {
  588.         return $this->licence;
  589.     }
  590.     public function setLicence(JournalLicence $licence): self
  591.     {
  592.         $this->licence $licence;
  593.         return $this;
  594.     }
  595.     public function getIssue(): JournalIssue
  596.     {
  597.         return $this->issue;
  598.     }
  599.     public function setIssue(JournalIssue $issue): self
  600.     {
  601.         $this->issue $issue;
  602.         return $this;
  603.     }
  604.     public function getFundingNumber(): ?string
  605.     {
  606.         return $this->fundingNumber;
  607.     }
  608.     public function setFundingNumber(?string $fundingNumber): self
  609.     {
  610.         $this->fundingNumber $fundingNumber;
  611.         return $this;
  612.     }
  613.     public function getType(): JournalArticleType
  614.     {
  615.         return $this->type;
  616.     }
  617.     public function setType(JournalArticleType $type): self
  618.     {
  619.         $this->type $type;
  620.         return $this;
  621.     }
  622.     public function getSection(): ?JournalSection
  623.     {
  624.         return $this->section;
  625.     }
  626.     public function setSection(?JournalSection $section): self
  627.     {
  628.         $this->section $section;
  629.         return $this;
  630.     }
  631.     public function getDoi(): ?string
  632.     {
  633.         return $this->doi;
  634.     }
  635.     public function setDoi(?string $doi): self
  636.     {
  637.         $this->doi $doi;
  638.         return $this;
  639.     }
  640.     public function getPageFrom(): ?string
  641.     {
  642.         return $this->pageFrom;
  643.     }
  644.     public function setPageFrom(?string $pageFrom): self
  645.     {
  646.         $this->pageFrom $pageFrom;
  647.         return $this;
  648.     }
  649.     public function getPageTo(): ?string
  650.     {
  651.         return $this->pageTo;
  652.     }
  653.     public function setPageTo(?string $pageTo): self
  654.     {
  655.         $this->pageTo $pageTo;
  656.         return $this;
  657.     }
  658.     public function getCitationType(): ?CitationType
  659.     {
  660.         return $this->citationType;
  661.     }
  662.     public function setCitationType(?CitationType $citationType): self
  663.     {
  664.         $this->citationType $citationType;
  665.         return $this;
  666.     }
  667.     public function getCitation(): ?string
  668.     {
  669.         return $this->citation;
  670.     }
  671.     public function setCitation(?string $citation): self
  672.     {
  673.         $this->citation $citation;
  674.         return $this;
  675.     }
  676.     public function getReceivedAt(): ?\DateTimeImmutable
  677.     {
  678.         return $this->receivedAt;
  679.     }
  680.     public function setReceivedAt(?\DateTimeImmutable $receivedAt): self
  681.     {
  682.         $this->receivedAt $receivedAt;
  683.         return $this;
  684.     }
  685.     public function getReceivedAtFormat(): JournalDateFormat
  686.     {
  687.         return $this->receivedAtFormat;
  688.     }
  689.     public function setReceivedAtFormat(JournalDateFormat $receivedAtFormat): self
  690.     {
  691.         $this->receivedAtFormat $receivedAtFormat;
  692.         return $this;
  693.     }
  694.     public function getAcceptedAt(): ?\DateTimeImmutable
  695.     {
  696.         return $this->acceptedAt;
  697.     }
  698.     public function setAcceptedAt(?\DateTimeImmutable $acceptedAt): self
  699.     {
  700.         $this->acceptedAt $acceptedAt;
  701.         return $this;
  702.     }
  703.     public function getAcceptedAtFormat(): JournalDateFormat
  704.     {
  705.         return $this->acceptedAtFormat;
  706.     }
  707.     public function setAcceptedAtFormat(JournalDateFormat $acceptedAtFormat): self
  708.     {
  709.         $this->acceptedAtFormat $acceptedAtFormat;
  710.         return $this;
  711.     }
  712.     public function getCorrectedAt(): ?\DateTimeImmutable
  713.     {
  714.         return $this->correctedAt;
  715.     }
  716.     public function setCorrectedAt(?\DateTimeImmutable $correctedAt): self
  717.     {
  718.         $this->correctedAt $correctedAt;
  719.         return $this;
  720.     }
  721.     public function getCorrectedAtFormat(): JournalDateFormat
  722.     {
  723.         return $this->correctedAtFormat;
  724.     }
  725.     public function setCorrectedAtFormat(JournalDateFormat $correctedAtFormat): self
  726.     {
  727.         $this->correctedAtFormat $correctedAtFormat;
  728.         return $this;
  729.     }
  730.     public function getOnlinePublishedAt(): ?\DateTimeImmutable
  731.     {
  732.         return $this->onlinePublishedAt;
  733.     }
  734.     public function setOnlinePublishedAt(?\DateTimeImmutable $onlinePublishedAt): self
  735.     {
  736.         if ($this->onlinePublishedAt === $onlinePublishedAt) {
  737.             return $this;
  738.         }
  739.         $this->onlinePublishedAt $onlinePublishedAt;
  740.         if (!$onlinePublishedAt) {
  741.             return $this;
  742.         }
  743.         $period $this->parent->getData()->getOpenAccessGrantedAfterPeriod();
  744.         if (!$period) {
  745.             return $this;
  746.         }
  747.         $graceDate = new \DateTime("@{$this->onlinePublishedAt->getTimestamp()}");
  748.         $graceDate->modify($period);
  749.         $this->graceDate = new \DateTimeImmutable("@{$graceDate->getTimestamp()}");
  750.         return $this;
  751.     }
  752.     public function getOnlinePublishedAtFormat(): JournalDateFormat
  753.     {
  754.         return $this->onlinePublishedAtFormat;
  755.     }
  756.     public function setOnlinePublishedAtFormat(JournalDateFormat $onlinePublishedAtFormat): self
  757.     {
  758.         $this->onlinePublishedAtFormat $onlinePublishedAtFormat;
  759.         return $this;
  760.     }
  761.     public function getAllDatesFormatted(): array
  762.     {
  763.         $properties = ['ReceivedAt''AcceptedAt''CorrectedAt''OnlinePublishedAt'];
  764.         $dates = [];
  765.         foreach ($properties as $property) {
  766.             $date $this->{'get' $property}();
  767.             $dates[$property] = null;
  768.             if (!$date) {
  769.                 continue;
  770.             }
  771.             $dates[$property] = match ($this->{'get' $property 'Format'}()) {
  772.                 JournalDateFormat::YEAR => $date->format('Y'),
  773.                 JournalDateFormat::YEAR_AND_MONTH => $date->format('m.Y'),
  774.                 JournalDateFormat::COMPLETE => $date->format('d.m.Y'),
  775.                 default => null,
  776.             };
  777.         }
  778.         return $dates;
  779.     }
  780.     public function getIsOpen(): bool
  781.     {
  782.         return $this->isOpen;
  783.     }
  784.     public function setIsOpen(bool $isOpen): self
  785.     {
  786.         $this->isOpen $isOpen;
  787.         return $this;
  788.     }
  789.     public function getPrice(): ?int
  790.     {
  791.         return $this->price;
  792.     }
  793.     #[Groups(['read:' self::class, 'read:' Journal::class])]
  794.     public function getPriceReal(): ?float
  795.     {
  796.         return $this->price round($this->price 10002) : null;
  797.     }
  798.     public function setPrice(?int $price): self
  799.     {
  800.         $this->price $price;
  801.         return $this;
  802.     }
  803.     #[Groups(['write'])]
  804.     public function setPriceReal(?float $priceReal): self
  805.     {
  806.         $this->price = (int) ($priceReal 1000);
  807.         return $this;
  808.     }
  809.     public function getGraceDate(): ?\DateTimeImmutable
  810.     {
  811.         return $this->graceDate;
  812.     }
  813.     public function setGraceDate(?\DateTimeImmutable $graceDate): self
  814.     {
  815.         $this->graceDate $graceDate;
  816.         return $this;
  817.     }
  818.     public function getMetaLink(): ?string
  819.     {
  820.         return $this->metaLink;
  821.     }
  822.     public function setMetaLink(?string $metaLink): self
  823.     {
  824.         $this->metaLink $metaLink;
  825.         return $this;
  826.     }
  827.     /** @deprecated */
  828.     public function getLanguage(): Language
  829.     {
  830.         return $this->language;
  831.     }
  832.     /** @deprecated */
  833.     public function setLanguage(Language $language): self
  834.     {
  835.         $this->language $language;
  836.         return $this;
  837.     }
  838.     public function getVisibleAuthorProperties(): array
  839.     {
  840.         return $this->visibleAuthorProperties;
  841.     }
  842.     public function setVisibleAuthorProperties(array $visibleAuthorProperties): self
  843.     {
  844.         $this->visibleAuthorProperties $visibleAuthorProperties;
  845.         return $this;
  846.     }
  847.     public function getPublicationLanguages(): array
  848.     {
  849.         return $this->publicationLanguages;
  850.     }
  851.     public function setPublicationLanguages(array $publicationLanguages): self
  852.     {
  853.         $this->publicationLanguages $publicationLanguages;
  854.         return $this;
  855.     }
  856.     public function getMetadataLanguages(): array
  857.     {
  858.         return $this->metadataLanguages;
  859.     }
  860.     public function setMetadataLanguages(array $metadataLanguages): self
  861.     {
  862.         $this->metadataLanguages $metadataLanguages;
  863.         /** @var JournalArticleMetadataTranslation */
  864.         foreach ($this->metadataTranslations as $metadataTranslation) {
  865.             if (in_array($metadataTranslation->getLang(), $this->metadataLanguages)) {
  866.                 continue;
  867.             }
  868.             $this->deleteMetadataTranslation($metadataTranslation);
  869.         }
  870.         foreach ($this->metadataLanguages as $lang) {
  871.             if (!$lang instanceof Language || isset($this->metadataTranslations[$lang->value])) {
  872.                 continue;
  873.             }
  874.             $class JournalArticleMetadataTranslation::class;
  875.             new $class($this$lang);
  876.         }
  877.         if (count($metadataLanguages) < 1) {
  878.             $this->nativeMetadataLanguage null;
  879.             return $this;
  880.         }
  881.         $this->nativeMetadataLanguage $this->metadataLanguages[array_key_first($this->metadataLanguages)];
  882.         return $this;
  883.     }
  884.     public function getAvailableMetadataLanguages(string $property): array
  885.     {
  886.         $langs = [];
  887.         foreach ($this->metadataLanguages as $lang) {
  888.             /** @var JournalArticleMetadataTranslation */
  889.             $metadataTranslation $this->getMetadataTranslation($lang);
  890.             switch ($property) {
  891.                 case 'abstract':
  892.                     if (!empty($metadataTranslation->getAbstract())) {
  893.                         $langs[] = $lang;
  894.                     }
  895.                     break;
  896.                 case 'keywords':
  897.                     if (!empty($metadataTranslation->getKeywords())) {
  898.                         $langs[] = $lang;
  899.                     }
  900.                     break;
  901.                 case 'title':
  902.                     if (!empty($metadataTranslation->getTitle())) {
  903.                         $langs[] = $lang;
  904.                     }
  905.                 default:
  906.                     break;
  907.             }
  908.         }
  909.         return $langs;
  910.     }
  911.     public function getSlug(): ?string
  912.     {
  913.         return $this->slug;
  914.     }
  915.     public function setSlug(?string $slug): self
  916.     {
  917.         $this->slug $slug;
  918.         return $this;
  919.     }
  920.     public function getStatsAmountOfViews(): int
  921.     {
  922.         return $this->statsAmountOfViews;
  923.     }
  924.     public function hitStatsView(): int
  925.     {
  926.         return $this->statsAmountOfViews++;
  927.     }
  928.     /*
  929.     public function updateStatsView(int $amount): self
  930.     {
  931.         $this->statsAmountOfViews += $amount;
  932.         return $this;
  933.     }*/
  934.     public function getStatsAmountOfDownloads(): int
  935.     {
  936.         return $this->statsAmountOfDownloads;
  937.     }
  938.     public function hitStatsDownload(): int
  939.     {
  940.         return $this->statsAmountOfDownloads++;
  941.     }
  942.     /*
  943.     public function updateStatsDownload(int $amount): self
  944.     {
  945.         $this->statsAmountOfDownloads += $amount;
  946.         return $this;
  947.     }*/
  948.     /**
  949.      * @return Collection|JournalArticleMetadataTranslation[]
  950.      */
  951.     public function getMetadataTranslations(): Collection
  952.     {
  953.         return $this->metadataTranslations;
  954.     }
  955.     public function getMetadataTranslation(Language $lang): mixed
  956.     {
  957.         return $this->metadataTranslations[$lang->value] ?? null;
  958.     }
  959.     public function addMetadataTranslation(JournalArticleMetadataTranslation $metadataTranslation): self
  960.     {
  961.         if ($this->metadataTranslations->contains($metadataTranslation)) {
  962.             return $this;
  963.         }
  964.         $this->metadataTranslations[$metadataTranslation->getLang()->value] = $metadataTranslation;
  965.         return $this;
  966.     }
  967.     public function deleteMetadataTranslation(JournalArticleMetadataTranslation $metadataTranslation): self
  968.     {
  969.         $this->metadataTranslations->removeElement($metadataTranslation);
  970.         return $this;
  971.     }
  972.     public function resetMetadataTranslations(): self
  973.     {
  974.         $this->metadataTranslations = new ArrayCollection();
  975.         return $this;
  976.     }
  977.     public function readMetadataTranslation(null|string|Language $langstring $property): mixed
  978.     {
  979.         if (!$lang) {
  980.             return null;
  981.         }
  982.         $lang is_string($lang)
  983.             ? Language::from($lang)
  984.             : $lang;
  985.         if (!isset($this->metadataTranslations[$lang->value])) {
  986.             return null;
  987.         }
  988.         if (!str_contains($property'.')) {
  989.             return $this->metadataTranslations[$lang->value]->{'get' ucfirst($property)}();
  990.         }
  991.         $path explode('.'$property);
  992.         $current array_shift($path);
  993.         return $this->readProperty(
  994.             object$this->metadataTranslations[$lang->value]->{'get' ucfirst($current)}(),
  995.             path$path
  996.         );
  997.     }
  998.     public function readAvailableMetadataTranslation(
  999.         string|Language $lang,
  1000.         string $property,
  1001.         bool $isNotEmpty true
  1002.     ): mixed {
  1003.         $lang is_string($lang)
  1004.             ? Language::from($lang)
  1005.             : $lang;
  1006.         if (!$this->isLanguageAvailable($lang$this->metadataLanguages)) {
  1007.             return $this->readMetadataTranslation($this->nativeMetadataLanguage$property);
  1008.         }
  1009.         $value $this->readMetadataTranslation($lang$property);
  1010.         if ($isNotEmpty && empty($value)) {
  1011.             return $this->readMetadataTranslation($this->nativeMetadataLanguage$property);
  1012.         }
  1013.         return $value;
  1014.     }
  1015.     private function isLanguageAvailable(Language $lang, ?array $languages): bool
  1016.     {
  1017.         return !$languages || in_array($lang$languages);
  1018.     }
  1019.     /**
  1020.      * @return Collection|JournalArticleClassification[]
  1021.      */
  1022.     public function getClassifications(): Collection
  1023.     {
  1024.         return $this->classifications;
  1025.     }
  1026.     public function getVisibleClassifications(): Collection
  1027.     {
  1028.         return $this->getClassifications()->filter(
  1029.             fn (JournalArticleClassification $classification) => $classification->getType()->getStat() === true
  1030.         );
  1031.     }
  1032.     public function addClassification(JournalArticleClassification $classification): self
  1033.     {
  1034.         if ($this->classifications->contains($classification)) {
  1035.             return $this;
  1036.         }
  1037.         $this->classifications[] = $classification;
  1038.         return $this;
  1039.     }
  1040.     public function removeClassification(JournalArticleClassification $classification): self
  1041.     {
  1042.         $this->classifications->removeElement($classification);
  1043.         return $this;
  1044.     }
  1045.     public function resetClassifications(): self
  1046.     {
  1047.         $this->classifications = new ArrayCollection();
  1048.         return $this;
  1049.     }
  1050.     /**
  1051.      * @return Collection|JournalArticleResearchData[]
  1052.      */
  1053.     public function getResearchDatas(): Collection
  1054.     {
  1055.         return $this->researchDatas;
  1056.     }
  1057.     public function addResearchData(JournalArticleResearchData $researchData): self
  1058.     {
  1059.         if ($this->researchDatas->contains($researchData)) {
  1060.             return $this;
  1061.         }
  1062.         $this->researchDatas[] = $researchData;
  1063.         return $this;
  1064.     }
  1065.     public function removeResearchData(JournalArticleResearchData $researchData): self
  1066.     {
  1067.         $this->researchDatas->removeElement($researchData);
  1068.         return $this;
  1069.     }
  1070.     public function resetResearchDatas(): self
  1071.     {
  1072.         $this->researchDatas = new ArrayCollection();
  1073.         return $this;
  1074.     }
  1075.     /**
  1076.      * @return Collection|JournalArticleRelationship[]
  1077.      */
  1078.     public function getRelationships(): Collection
  1079.     {
  1080.         return $this->relationships;
  1081.     }
  1082.     /**
  1083.      * @return JournalArticleRelationship[]
  1084.      */
  1085.     public function getRelationshipsVisible(): array
  1086.     {
  1087.         $relations = [];
  1088.         foreach ($this->getRelationships() as $relationship) {
  1089.             if (!$relationship->getTargetArticle()?->getStat()) {
  1090.                 continue;
  1091.             }
  1092.             $relations[] = $relationship;
  1093.         }
  1094.         return $relations;
  1095.     }
  1096.     public function addRelationship(JournalArticleRelationship $relationship): self
  1097.     {
  1098.         if ($this->relationships->contains($relationship)) {
  1099.             return $this;
  1100.         }
  1101.         $this->relationships[] = $relationship;
  1102.         return $this;
  1103.     }
  1104.     public function removeRelationship(JournalArticleRelationship $relationship): self
  1105.     {
  1106.         $this->relationships->removeElement($relationship);
  1107.         return $this;
  1108.     }
  1109.     public function resetRelationships(): self
  1110.     {
  1111.         $this->relationships = new ArrayCollection();
  1112.         return $this;
  1113.     }
  1114.     /**
  1115.      * @return Collection|JournalArticleContentBlock[]
  1116.      */
  1117.     public function getContentBlocks(): Collection
  1118.     {
  1119.         return $this->contentBlocks;
  1120.     }
  1121.     public function getVisibleContentBlocks(): Collection
  1122.     {
  1123.         return $this->contentBlocks->filter(
  1124.             fn (JournalArticleContentBlock $block) => $block->getStat() === true
  1125.         );
  1126.     }
  1127.     public function addContentBlock(JournalArticleContentBlock $contentBlock): self
  1128.     {
  1129.         if ($this->contentBlocks->contains($contentBlock)) {
  1130.             return $this;
  1131.         }
  1132.         $this->contentBlocks[] = $contentBlock;
  1133.         return $this;
  1134.     }
  1135.     public function removeContentBlock(JournalArticleContentBlock $contentBlock): self
  1136.     {
  1137.         $this->contentBlocks->removeElement($contentBlock);
  1138.         return $this;
  1139.     }
  1140.     public function resetContentBlocks(): self
  1141.     {
  1142.         $this->contentBlocks = new ArrayCollection();
  1143.         return $this;
  1144.     }
  1145.     /**
  1146.      * @return Collection|JournalArticleAddditionalBlock[]
  1147.      */
  1148.     public function getAdditionalBlocks(): Collection
  1149.     {
  1150.         return $this->additionalBlocks;
  1151.     }
  1152.     public function getVisibleAdditionalBlocks(): Collection
  1153.     {
  1154.         return $this->additionalBlocks->filter(
  1155.             fn (JournalArticleAdditionalBlock $block) => $block->getStat() === true
  1156.         );
  1157.     }
  1158.     public function addAdditionalBlock(JournalArticleAdditionalBlock $additionalBlock): self
  1159.     {
  1160.         if ($this->additionalBlocks->contains($additionalBlock)) {
  1161.             return $this;
  1162.         }
  1163.         $this->additionalBlocks[] = $additionalBlock;
  1164.         return $this;
  1165.     }
  1166.     public function removeAdditionalBlock(JournalArticleAdditionalBlock $additionalBlock): self
  1167.     {
  1168.         $this->additionalBlocks->removeElement($additionalBlock);
  1169.         return $this;
  1170.     }
  1171.     public function resetAdditionalBlocks(): self
  1172.     {
  1173.         $this->additionalBlocks = new ArrayCollection();
  1174.         return $this;
  1175.     }
  1176.     /**
  1177.      * @return Collection|JournalArticleBibliographyEntry[]
  1178.      */
  1179.     public function getBibliographyEntries(): Collection
  1180.     {
  1181.         return $this->bibliographyEntries;
  1182.     }
  1183.     public function addBibliographyEntry(JournalArticleBibliographyEntry $bibliographyEntry): self
  1184.     {
  1185.         if ($this->bibliographyEntries->contains($bibliographyEntry)) {
  1186.             return $this;
  1187.         }
  1188.         $this->bibliographyEntries[] = $bibliographyEntry;
  1189.         return $this;
  1190.     }
  1191.     public function removeBibliographyEntry(JournalArticleBibliographyEntry $bibliographyEntry): self
  1192.     {
  1193.         $this->bibliographyEntries->removeElement($bibliographyEntry);
  1194.         return $this;
  1195.     }
  1196.     public function resetBibliographyEntries(): self
  1197.     {
  1198.         $this->bibliographyEntries = new ArrayCollection();
  1199.         return $this;
  1200.     }
  1201.     public function importBibliography(): self
  1202.     {
  1203.         $nativeLanguage $this->getParentNativeLanguage();
  1204.         $nativeTranslation $this->getTranslation($nativeLanguage);
  1205.         if (!$nativeTranslation?->getImportedBibliography()) {
  1206.             return $this;
  1207.         }
  1208.         $otherLanguages array_filter(
  1209.             $this->getParentLanguages(),
  1210.             fn (Language $lang) => $nativeLanguage !== $lang
  1211.         );
  1212.         $imported = [];
  1213.         foreach ([$nativeLanguage] + $otherLanguages as $lang) {
  1214.             $imported[$lang->value] = $this->getImportedBibliographyEntries($lang);
  1215.         }
  1216.         $this->resetBibliographyEntries();
  1217.         $ord count($imported[$nativeLanguage->value]);
  1218.         foreach ($imported[$nativeLanguage->value] as $key => $title) {
  1219.             preg_match('/10.\d{4,9}\/[-._;()\/:A-Z0-9]+$/i'$title$doi);
  1220.             $entry = new JournalArticleBibliographyEntry($this);
  1221.             $entry
  1222.                 ->setDoi($doi[0] ?? null)
  1223.                 ->setOrd($ord--);
  1224.             $translation $entry->getTranslation($nativeLanguage);
  1225.             $translation->setTitle($title);
  1226.             $this->fillBibliographyEntryTranslations(
  1227.                 entry$entry,
  1228.                 languages$otherLanguages,
  1229.                 imported$imported,
  1230.                 key$key
  1231.             );
  1232.         }
  1233.         return $this;
  1234.     }
  1235.     private function getImportedBibliographyEntries(Language $lang): array
  1236.     {
  1237.         $importedBibliography $this->getTranslation($lang)?->getImportedBibliography() ?? '';
  1238.         $bibliographyEntries array_map(
  1239.             fn (string $bibliograpyEntry) => trim($bibliograpyEntry),
  1240.             preg_split('/\r\n|\r|\n|<br>|<br \/>|<br\/>/'$importedBibliography)
  1241.         );
  1242.         $bibliographyEntries array_filter(
  1243.             $bibliographyEntries,
  1244.             fn (string $bibliograpyEntry) => !empty($bibliograpyEntry)
  1245.         );
  1246.         return $bibliographyEntries;
  1247.     }
  1248.     private function fillBibliographyEntryTranslations(
  1249.         JournalArticleBibliographyEntry $entry,
  1250.         array $languages,
  1251.         array $imported,
  1252.         int $key
  1253.     ): self {
  1254.         foreach ($languages as $language) {
  1255.             $translation $entry->getTranslation($language);
  1256.             $translation->setTitle($imported[$language->value][$key] ?? null);
  1257.         }
  1258.         return $this;
  1259.     }
  1260.     /**
  1261.      * @return Collection|JournalArticleFile[]
  1262.      */
  1263.     public function getFiles(): Collection
  1264.     {
  1265.         return $this->files;
  1266.     }
  1267.     public function addFile(JournalArticleFile $file): self
  1268.     {
  1269.         if ($this->files->contains($file)) {
  1270.             return $this;
  1271.         }
  1272.         $this->files[] = $file;
  1273.         return $this;
  1274.     }
  1275.     public function removeFile(JournalArticleFile $file): self
  1276.     {
  1277.         $this->files->removeElement($file);
  1278.         return $this;
  1279.     }
  1280.     public function resetFiles(): self
  1281.     {
  1282.         $this->files = new ArrayCollection();
  1283.         return $this;
  1284.     }
  1285.     #[Groups(['read:' self::class, 'read:minimal'])]
  1286.     public function getHasFiles(): bool
  1287.     {
  1288.         foreach ($this->files as $file) {
  1289.             if (!$file->getHasFiles()) {
  1290.                 continue;
  1291.             }
  1292.             return true;
  1293.         }
  1294.         return false;
  1295.     }
  1296.     public function getFullTextFile(): ?JournalArticleFile
  1297.     {
  1298.         foreach ($this->files as $file) {
  1299.             if (!$file->getStat() || $file->getType() !== JournalArticleFileType::FULL_TEXT) {
  1300.                 continue;
  1301.             }
  1302.             return $file;
  1303.         }
  1304.         return null;
  1305.     }
  1306.     public function getFullTextTranslationFile(string|Language $lang): ?JournalArticleFileTranslationFile
  1307.     {
  1308.         $fullText $this->getFullTextFile();
  1309.         if (!$fullText) {
  1310.             return null;
  1311.         }
  1312.         $lang is_string($lang)
  1313.             ? Language::from($lang)
  1314.             : $lang;
  1315.         $files $fullText->readAvailableTranslation($lang'files');
  1316.         if (!$files) {
  1317.             return null;
  1318.         }
  1319.         return ($first $files->first()) === false null $first;
  1320.     }
  1321.     public function getFullTextExtensions(): array
  1322.     {
  1323.         $fullText $this->getFullTextFile();
  1324.         if (!$fullText) {
  1325.             return [];
  1326.         }
  1327.         $extensions = [];
  1328.         /** @var JournalArticleFileTranslation $translation */
  1329.         foreach ($fullText->getTranslations() as $translation) {
  1330.             /** @var JournalArticleFileTranslationFile $file */
  1331.             foreach ($translation->getVisibleFiles() as $file) {
  1332.                 if ($file->getMedia()?->getExtension()) {
  1333.                     $extensions[] = $file->getMedia()->getExtension();
  1334.                 }
  1335.             }
  1336.         }
  1337.         return array_unique($extensions);
  1338.     }
  1339.     /** @return <int, JournalArticleFileTranslationFile> */
  1340.     public function getAdditionalFiles(string|Language $lang): array
  1341.     {
  1342.         $lang is_string($lang)
  1343.             ? Language::from($lang)
  1344.             : $lang;
  1345.         $files = [];
  1346.         foreach ($this->files as $file) {
  1347.             if (!$file->getStat() || $file->getType() === JournalArticleFileType::FULL_TEXT) {
  1348.                 continue;
  1349.             }
  1350.             $files[] = $file;
  1351.         }
  1352.         $finalFiles = [];
  1353.         /** @var JournalArticleFile $file */
  1354.         foreach ($files as $file) {
  1355.             /** @var JournalArticleFileTranslation $trans */
  1356.             $files $file->readAvailableTranslation($lang'files');
  1357.             if (!$files) {
  1358.                 continue;
  1359.             }
  1360.             foreach ($files as $finalFile) {
  1361.                 if (!$finalFile->getMedia() || !$finalFile->getTitle()) {
  1362.                     continue;
  1363.                 }
  1364.                 $finalFiles[] = $finalFile;
  1365.             }
  1366.         }
  1367.         return $finalFiles;
  1368.     }
  1369.     public function getFileByType(JournalArticleFileType $type): ?JournalArticleFile
  1370.     {
  1371.         return $this->files->findFirst(
  1372.             fn (int $keyJournalArticleFile $file) => $file->getType() === $type
  1373.         );
  1374.     }
  1375.     /**
  1376.      * @return Collection|JournalArticleAuthor[]
  1377.      */
  1378.     public function getAuthors(): Collection
  1379.     {
  1380.         return $this->authors;
  1381.     }
  1382.     public function setAuthors(array $authors): self
  1383.     {
  1384.         foreach ($authors as $author) {
  1385.             $this->authors[] = $author;
  1386.         }
  1387.         return $this;
  1388.     }
  1389.     public function addAuthor(JournalArticleAuthor $author): self
  1390.     {
  1391.         if ($this->authors->contains($author)) {
  1392.             return $this;
  1393.         }
  1394.         $this->authors[] = $author;
  1395.         return $this;
  1396.     }
  1397.     public function removeAuthor(JournalArticleAuthor $author): self
  1398.     {
  1399.         $this->authors->removeElement($author);
  1400.         return $this;
  1401.     }
  1402.     public function resetAuthors(): self
  1403.     {
  1404.         $this->authors = new ArrayCollection();
  1405.         return $this;
  1406.     }
  1407.     public function getVisibleAuthors(string|Language $lang, ?Author $highlightAuthor null): array
  1408.     {
  1409.         $lang = !is_string($lang)
  1410.             ? $lang
  1411.             Language::from($lang);
  1412.         $authors $this->getVisibleAuthorsByRole($langJournalArticleAuthorRole::AUTHOR);
  1413.         if (null !== $highlightAuthor) {
  1414.             $authors = [
  1415.                 $highlightAuthor,
  1416.                 ...array_filter(
  1417.                     $authors,
  1418.                     fn (JournalArticleAuthor $author) => $author->getAuthor()->getUuid() === $highlightAuthor->getUuid()
  1419.                 )
  1420.             ];
  1421.         }
  1422.         return $authors;
  1423.     }
  1424.     public function getVisibleAuthorsByRole(string|Language $langJournalArticleAuthorRole|string $type): array
  1425.     {
  1426.         $lang = !is_string($lang)
  1427.             ? $lang
  1428.             Language::from($lang);
  1429.         $type is_string($type)
  1430.             ? JournalArticleAuthorRole::from($type)
  1431.             : $type;
  1432.         $isNameVisible in_array(AuthorProperty::NAME$this->visibleAuthorProperties);
  1433.         $isSurnameVisible in_array(AuthorProperty::SURNAME$this->visibleAuthorProperties);
  1434.         if (!$isNameVisible && !$isSurnameVisible) {
  1435.             return [];
  1436.         }
  1437.         return $this->getAuthors()->filter(
  1438.             fn (JournalArticleAuthor $author) => $author->getRole() === $type
  1439.         )->toArray();
  1440.     }
  1441.     public function getIssueRedactors(): array
  1442.     {
  1443.         if ($this->redactors) {
  1444.             return $this->redactors;
  1445.         }
  1446.         $this->redactors = [];
  1447.         foreach ($this->issue?->getEditorialGroups() as $group) {
  1448.             foreach ($group->getMembers() as $member) {
  1449.                 if (in_array($member$this->redactors)) {
  1450.                     continue;
  1451.                 }
  1452.                 $this->redactors[] = $member;
  1453.             }
  1454.         }
  1455.         return $this->redactors;
  1456.     }
  1457.     // -----
  1458.     // STATS
  1459.     // -----
  1460.     /**
  1461.      * @return Collection|JournalArticleViewEntry[]
  1462.      */
  1463.     public function getViewEntries(): Collection
  1464.     {
  1465.         return $this->viewEntries;
  1466.     }
  1467.     public function addViewEntry(JournalArticleViewEntry $viewEntry): self
  1468.     {
  1469.         if ($this->viewEntries->contains($viewEntry)) {
  1470.             return $this;
  1471.         }
  1472.         $this->viewEntries[] = $viewEntry;
  1473.         return $this;
  1474.     }
  1475.     public function removeViewEntry(JournalArticleViewEntry $viewEntry): self
  1476.     {
  1477.         $this->viewEntries->removeElement($viewEntry);
  1478.         return $this;
  1479.     }
  1480.     public function resetViewEntries(): self
  1481.     {
  1482.         $this->viewEntries = new ArrayCollection();
  1483.         return $this;
  1484.     }
  1485.     /**
  1486.      * @return Collection|JournalArticleDownloadEntry[]
  1487.      */
  1488.     public function getDownloadEntries(): Collection
  1489.     {
  1490.         return $this->downloadEntries;
  1491.     }
  1492.     public function addDownloadEntry(JournalArticleDownloadEntry $downloadEntry): self
  1493.     {
  1494.         if ($this->downloadEntries->contains($downloadEntry)) {
  1495.             return $this;
  1496.         }
  1497.         $this->downloadEntries[] = $downloadEntry;
  1498.         return $this;
  1499.     }
  1500.     public function removeDownloadEntry(JournalArticleDownloadEntry $downloadEntry): self
  1501.     {
  1502.         $this->downloadEntries->removeElement($downloadEntry);
  1503.         return $this;
  1504.     }
  1505.     public function resetDownloadEntries(): self
  1506.     {
  1507.         $this->downloadEntries = new ArrayCollection();
  1508.         return $this;
  1509.     }
  1510.     public function getSlugTitleLanguage(): ?Language
  1511.     {
  1512.         if (!$this->slugTitleLanguage) {
  1513.             if (isset($this->getPublicationLanguages()[0]) && in_array($this->getPublicationLanguages()[0], $this->getMetadataLanguages())) {
  1514.                 return $this->getPublicationLanguages()[0];
  1515.             }
  1516.             return $this->nativeMetadataLanguage;
  1517.         }
  1518.         return $this->slugTitleLanguage;
  1519.     }
  1520.     public function setSlugTitleLanguage(?Language $slugTitleLanguage): self
  1521.     {
  1522.         if ($this->slugTitleLanguage !== $slugTitleLanguage && $slugTitleLanguage !== null) {
  1523.             $this->slugTitleLanguage $slugTitleLanguage;
  1524.             foreach ($this->getMetadataTranslations() as $metadataTranslation) {
  1525.                 $metadataTranslation->setTitle($metadataTranslation->getTitle());
  1526.             }
  1527.         } else {
  1528.             $this->slugTitleLanguage $slugTitleLanguage;
  1529.         }
  1530.         return $this;
  1531.     }
  1532.     /**
  1533.      * @return Collection<int, JournalArticleSlugHistory>
  1534.      */
  1535.     public function getSlugHistory(): Collection
  1536.     {
  1537.         return $this->slugHistory;
  1538.     }
  1539.     public function addCurrentSlugToHistory(): void
  1540.     {
  1541.         // check if already in history
  1542.         foreach ($this->slugHistory as $entry) {
  1543.             if ($entry->getSlug() === $this->slug) {
  1544.                 return;
  1545.             }
  1546.         }
  1547.         $entry = new JournalArticleSlugHistory($this$this->slug);
  1548.         $this->addSlugHistory($entry);
  1549.     }
  1550.     public function addSlugHistory(JournalArticleSlugHistory $slugHistory): self
  1551.     {
  1552.         if (!$this->slugHistory->contains($slugHistory)) {
  1553.             $this->slugHistory->add($slugHistory);
  1554.         }
  1555.         return $this;
  1556.     }
  1557.     public function removeSlugHistory(JournalArticleSlugHistory $slugHistory): self
  1558.     {
  1559.         $this->slugHistory->removeElement($slugHistory);
  1560.         return $this;
  1561.     }
  1562.     public function getIsCrossmarkButtonVisible(): ?bool
  1563.     {
  1564.         if ($this->isCrossmarkButtonVisible === null) {
  1565.             return $this->getIssue()->getIsCrossmarkButtonVisible();
  1566.         }
  1567.         return $this->isCrossmarkButtonVisible;
  1568.     }
  1569.     public function setIsCrossmarkButtonVisible(?bool $isCrossmarkButtonVisible): self
  1570.     {
  1571.         $this->isCrossmarkButtonVisible $isCrossmarkButtonVisible;
  1572.         return $this;
  1573.     }
  1574.     #[Groups(['read:stats'])]
  1575.     public function getStatsAuthors(): ?string
  1576.     {
  1577.         $authors = [];
  1578.         /** @var JournalArticleAuthor */
  1579.         foreach ($this->authors as $entry) {
  1580.             if ($entry->getRole() !== JournalArticleAuthorRole::AUTHOR) {
  1581.                 continue;
  1582.             }
  1583.             $author $entry->getAuthor();
  1584.             $authors[] = $author->getFullName();
  1585.         }
  1586.         sort($authors);
  1587.         return implode(', '$authors);
  1588.     }
  1589.     #[Groups(['read:stats'])]
  1590.     public function getStatsAuthorsAffiliations(): ?string
  1591.     {
  1592.         $affiliations = [];
  1593.         /** @var JournalArticleAuthor */
  1594.         foreach ($this->authors as $entry) {
  1595.             if ($entry->getRole() !== JournalArticleAuthorRole::AUTHOR) {
  1596.                 continue;
  1597.             }
  1598.             $author $entry->getAuthor();
  1599.             /** @var Affiliation */
  1600.             foreach ($author->getAffiliations() as $affiliation) {
  1601.                 if (isset($affiliations[$affiliation->getUuid()->toString()])) {
  1602.                     continue;
  1603.                 }
  1604.                 $affiliations[$affiliation->getUuid()->toString()] = $affiliation->readNativeLanguageTranslation('title');
  1605.             }
  1606.         }
  1607.         sort($affiliations);
  1608.         return implode(', ',  $affiliations);
  1609.     }
  1610.     #[Groups(['read:stats'])]
  1611.     public function getStatsAuthorsCountries(): ?string
  1612.     {
  1613.         $countries = [];
  1614.         /** @var JournalArticleAuthor */
  1615.         foreach ($this->authors as $entry) {
  1616.             if ($entry->getRole() !== JournalArticleAuthorRole::AUTHOR) {
  1617.                 continue;
  1618.             }
  1619.             $author $entry->getAuthor();
  1620.             /** @var Affiliation */
  1621.             foreach ($author->getAffiliations() as $affiliation) {
  1622.                 if (!$affiliation->getCountry()) {
  1623.                     continue;
  1624.                 }
  1625.                 if (isset($countries[$affiliation->getCountry()->value])) {
  1626.                     continue;
  1627.                 }
  1628.                 $countries[$affiliation->getCountry()->value] = $affiliation->getCountry()->value;
  1629.             }
  1630.         }
  1631.         sort($countries);
  1632.         return implode(', ',  $countries);
  1633.     }
  1634.     #[Groups(['read:stats'])]
  1635.     public function getStatsTranslators(): ?string
  1636.     {
  1637.         $authors = [];
  1638.         /** @var JournalArticleAuthor */
  1639.         foreach ($this->authors as $entry) {
  1640.             if ($entry->getRole() !== JournalArticleAuthorRole::TRANSLATOR) {
  1641.                 continue;
  1642.             }
  1643.             $author $entry->getAuthor();
  1644.             $authors[] = $author->getFullName();
  1645.         }
  1646.         sort($authors);
  1647.         return implode(', '$authors);
  1648.     }
  1649.     #[Groups(['read:stats'])]
  1650.     public function getStatsJournal(): string
  1651.     {
  1652.         return $this->parent->readNativeLanguageTranslation('title');
  1653.     }
  1654.     #[Groups(['read:stats'])]
  1655.     public function getStatsJournalAffiliations(): string
  1656.     {
  1657.         $affiliations = [];
  1658.         /** @var JournalAffiliation */
  1659.         foreach ($this->parent->getAffiliations() as $entry) {
  1660.             if (isset($affiliations[$entry->getAffiliation()->getUuid()->toString()])) {
  1661.                 continue;
  1662.             }
  1663.             $affiliations[$entry->getAffiliation()->getUuid()->toString()] =
  1664.                 $entry->getAffiliation()->readNativeLanguageTranslation('title');
  1665.         }
  1666.         sort($affiliations);
  1667.         return implode(', ',  $affiliations);
  1668.     }
  1669.     #[Groups(['read:stats'])]
  1670.     public function getStatsIssue(): string
  1671.     {
  1672.         return $this->issue->readNativeLanguageTranslation('title');
  1673.     }
  1674.     #[Groups(['read:stats'])]
  1675.     public function getStatsVolume(): string
  1676.     {
  1677.         return $this->issue->getVolume()->readNativeLanguageTranslation('title');
  1678.     }
  1679.     #[Groups(['read:stats'])]
  1680.     public function getStatsIssueFundingDescription(): ?string
  1681.     {
  1682.         return $this->issue->readNativeLanguageTranslation('fundingDescription');
  1683.     }
  1684.     #[Groups(['read:stats'])]
  1685.     public function getStatsVolumeFundingDescription(): ?string
  1686.     {
  1687.         return $this->issue->getVolume()->readNativeLanguageTranslation('fundingDescription');
  1688.     }
  1689.     #[Groups(['read:stats'])]
  1690.     public function getStatsSection(): ?string
  1691.     {
  1692.         return $this->section?->readNativeLanguageTranslation('title');
  1693.     }
  1694.     #[Groups(['read:stats'])]
  1695.     public function getStatsKeywords(): ?string
  1696.     {
  1697.         $keywords = [];
  1698.         $entries $this->readMetadataTranslation($this->nativeMetadataLanguage'keywords');
  1699.         if ($entries) {
  1700.             /** @var JournalArticleMetadataTranslationKeyword */
  1701.             foreach ($entries as $entry) {
  1702.                 $keywords[] = $entry->getTitle();
  1703.             }
  1704.         }
  1705.         sort($keywords);
  1706.         return implode(', '$keywords);
  1707.     }
  1708.     public function getStatsFullTextUrl(): ?string
  1709.     {
  1710.         return $this->statsFullTextUrl;
  1711.     }
  1712.     public function setStatsFullTextUrl(?string $statsFullTextUrl): self
  1713.     {
  1714.         $this->statsFullTextUrl $statsFullTextUrl;
  1715.         return $this;
  1716.     }
  1717.     public function getStatsArticleUrl(): ?string
  1718.     {
  1719.         return $this->statsArticleUrl;
  1720.     }
  1721.     public function setStatsArticleUrl(?string $statsArticleUrl): self
  1722.     {
  1723.         $this->statsArticleUrl $statsArticleUrl;
  1724.         return $this;
  1725.     }
  1726.     public function isForSale(): bool
  1727.     {
  1728.         if ($this->getIsOpen()) {
  1729.             return false;
  1730.         }
  1731.         if (!$this->getPriceReal()) {
  1732.             return false;
  1733.         }
  1734.         return true;
  1735.     }
  1736.     #[Groups(['read:' self::class])]
  1737.     public function getJournalSlug(): ?string
  1738.     {
  1739.         return $this->parent->getSlug();
  1740.     }
  1741.     #[Groups(['read:' self::class])]
  1742.     public function getParentLanguages(): array
  1743.     {
  1744.         return $this->getLanguageableParent()->getLanguages();
  1745.     }
  1746.     public function clone(): self
  1747.     {
  1748.         $clone = clone $this;
  1749.         $clone->id null;
  1750.         $clone->setUuid();
  1751.         $clone->ord 0;
  1752.         $clone->stat false;
  1753.         $clone->importId null;
  1754.         ClassUtils::cloneCollection($this$clone'translations');
  1755.         ClassUtils::cloneCollection($this$clone'metadataTranslations');
  1756.         $clone->synchronizeTranslations();
  1757.         // title jest przeniesione do metadataTranslations, i wlasciwie to bezsensu jest teraz
  1758.         // $nativeLanguage = $clone->parent->getNativeLanguage();
  1759.         // $nativeTranslation = $clone->getMetadataTranslation($nativeLanguage);
  1760.         // $nativeTranslation->setTitle($nativeTranslation->getTitle() . ' [KOPIA]');
  1761.         // lepiej dopisac [KOPIA] do working title
  1762.         $clone->setWorkingTitle($clone->getWorkingTitle() . ' [KOPIA]');
  1763.         ClassUtils::cloneCollection($this$clone'classifications');
  1764.         ClassUtils::cloneCollection($this$clone'researchDatas');
  1765.         ClassUtils::cloneCollection($this$clone'contentBlocks');
  1766.         ClassUtils::cloneCollection($this$clone'files');
  1767.         $clone->statsAmountOfViews 0;
  1768.         $clone->statsAmountOfDownloads 0;
  1769.         $clone
  1770.             ->resetAdditionalBlocks()
  1771.             ->resetBibliographyEntries()
  1772.             ->resetAuthors()
  1773.             ->resetViewEntries()
  1774.             ->resetDownloadEntries();
  1775.         $clone->createdAt = new \DateTimeImmutable();
  1776.         $clone->updatedAt = new \DateTimeImmutable();
  1777.         return $clone;
  1778.     }
  1779.     /**
  1780.      * @return Collection<int, CartProduct>
  1781.      */
  1782.     public function getCartProducts(): Collection
  1783.     {
  1784.         return $this->cartProducts;
  1785.     }
  1786.     public function addCartProduct(CartProduct $cartProduct): self
  1787.     {
  1788.         if (!$this->cartProducts->contains($cartProduct)) {
  1789.             $this->cartProducts->add($cartProduct);
  1790.             $cartProduct->setArticle($this);
  1791.         }
  1792.         return $this;
  1793.     }
  1794.     public function removeCartProduct(CartProduct $cartProduct): self
  1795.     {
  1796.         if ($this->cartProducts->removeElement($cartProduct)) {
  1797.             // set the owning side to null (unless already changed)
  1798.             if ($cartProduct->getArticle() === $this) {
  1799.                 $cartProduct->setArticle(null);
  1800.             }
  1801.         }
  1802.         return $this;
  1803.     }
  1804. }