src/Entity/JournalArticle.php line 185

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