src/Entity/JournalArticle.php line 193

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