src/Entity/JournalIssue.php line 138

  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 Gedmo\Sluggable\Util\Urlizer;
  8. use Symfony\Component\Serializer\Annotation\Groups,
  9.     Symfony\Component\Validator\Constraints as Assert;
  10. use ApiPlatform\Metadata\ApiResource,
  11.     ApiPlatform\Metadata\ApiProperty,
  12.     ApiPlatform\Metadata\Get,
  13.     ApiPlatform\Metadata\GetCollection,
  14.     ApiPlatform\Metadata\Post,
  15.     ApiPlatform\Metadata\Put,
  16.     ApiPlatform\Metadata\Delete,
  17.     ApiPlatform\Metadata\ApiFilter,
  18.     ApiPlatform\Doctrine\Orm\Filter\SearchFilter,
  19.     ApiPlatform\Doctrine\Orm\Filter\OrderFilter,
  20.     ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
  21. use App\Entity\Trait\IdTrait,
  22.     App\Entity\Trait\UuidTrait,
  23.     App\Entity\Trait\OrdStatTrait,
  24.     App\Entity\Trait\TimestampableTrait,
  25.     App\Entity\Trait\TranslatableTrait,
  26.     App\Entity\Trait\Languages\LanguageableChildTrait,
  27.     App\Entity\Trait\ArchivedTrait,
  28.     App\Entity\Trait\EditorialGroupableTrait,
  29.     App\Entity\Trait\ImportIdTrait,
  30.     App\Entity\Interface\TranslatableInterface,
  31.     App\Entity\Interface\OrdStatableInterface,
  32.     App\Entity\Interface\LanguageableChildInterface,
  33.     App\Entity\Interface\ArchivableInterface,
  34.     App\Entity\Interface\EditorialGroupableInterface,
  35.     App\Entity\Interface\CloneableInterface;
  36. use App\Enum\JournalLicence,
  37.     App\Enum\JournalDateFormat,
  38.     App\Enum\AuthorProperty,
  39.     App\Enum\JournalIssueState,
  40.     App\Doctrine\DBAL\Types\AuthorPropertiesType,
  41.     App\Repository\JournalIssueRepository,
  42.     App\Lib\Actions,
  43.     App\DTO\CloneDTO,
  44.     App\Security\Voter\ArchivableVoter,
  45.     App\StateProcessor\CloneProcessor,
  46.     App\Security\Voter\CloneVoter;
  47. use App\Util\ClassUtils,
  48.     App\Filter\IriFilter;
  49. use App\Controller\Export\JournalIssue\JournalIssueCrossrefExporter,
  50.     App\Controller\Export\JournalIssue\JournalIssueArticlesCrossrefExporter,
  51.     App\Controller\Export\JournalIssue\JournalIssueSimcheckCrossrefExporter,
  52.     App\Controller\Export\JournalIssue\JournalIssueFundingCrossrefExporter,
  53.     App\Controller\Export\JournalIssue\JournalIssueLicenceFulltextCrossrefExporter,
  54.     App\Controller\Export\JournalIssue\JournalIssueArticlesCopernicusExporter,
  55.     App\Controller\Export\JournalIssue\JournalIssueArticlesPubMedExporter;
  56. use App\Enum\Language;
  57. use App\Util\RomanNumber;
  58. #[ApiResource(
  59.     description'Journal issues',
  60.     normalizationContext: ['groups' => [
  61.         'read',
  62.         'read:' self::class,
  63.         'read:' self::class . 'Translation'
  64.     ]],
  65.     denormalizationContext: ['groups' => ['write']],
  66.     security'is_granted("' Journal::class . '")',
  67.     order: ['ord' => 'desc'],
  68.     operations: [
  69.         new GetCollection(),
  70.         new Post(
  71.             uriTemplate'/journal_issues/clone',
  72.             inputCloneDTO::class,
  73.             processorCloneProcessor::class,
  74.             security'is_granted("' Actions::CLONE .'")',
  75.             securityMessageCloneVoter::MESSAGE
  76.         ),
  77.         new Post(denormalizationContext: ['groups' => ['write',  'post']]),
  78.         new Get(),
  79.         new Get(
  80.             name'get_journal_issue_crossref',
  81.             uriTemplate'/journal_issues/{uuid}/export/crossref',
  82.             controllerJournalIssueCrossrefExporter::class
  83.         ),
  84.         new Get(
  85.             name'get_journal_issue_articles_crossref',
  86.             uriTemplate'/journal_issues/{uuid}/articles/export/crossref',
  87.             controllerJournalIssueArticlesCrossrefExporter::class
  88.         ),
  89.         new Get(
  90.             name'get_journal_issue_articles_copernicus',
  91.             uriTemplate'/journal_issues/{uuid}/articles/export/copernicus',
  92.             controllerJournalIssueArticlesCopernicusExporter::class
  93.         ),
  94.         new Get(
  95.             name'get_journal_issue_articles_pubmed',
  96.             uriTemplate'/journal_issues/{uuid}/articles/export/pubmed',
  97.             controllerJournalIssueArticlesPubMedExporter::class
  98.         ),
  99.         new Get(
  100.             name'get_journal_issue_simcheck_crossref',
  101.             uriTemplate'/journal_issues/{uuid}/export/simcheck_crossref',
  102.             controllerJournalIssueSimcheckCrossrefExporter::class
  103.         ),
  104.         new Get(
  105.             name'get_journal_issue_funding_crossref',
  106.             uriTemplate'/journal_issues/{uuid}/export/funding_crossref',
  107.             controllerJournalIssueFundingCrossrefExporter::class
  108.         ),
  109.         new Get(
  110.             name'get_journal_issue_licence_fulltext_crossref',
  111.             uriTemplate'/journal_issues/{uuid}/export/licence_fulltext_crossref',
  112.             controllerJournalIssueLicenceFulltextCrossrefExporter::class
  113.         ),
  114.         new Put(),
  115.         new Delete(
  116.             securityPostDenormalize'is_granted("' Actions::DELETE '", object)',
  117.             securityPostDenormalizeMessageArchivableVoter::MESSAGE
  118.         ),
  119.     ],
  120.     extraProperties: ['standard_put' => false],
  121. )]
  122. #[ApiFilter(SearchFilter::class, properties: [
  123.     'translations.title' => 'partial',
  124. ])]
  125. #[ApiFilter(IriFilter::class, properties: ['parent''volume'])]
  126. #[ApiFilter(BooleanFilter::class, properties: ['isArchived''stat'])]
  127. #[ApiFilter(OrderFilter::class, properties: ['ord'])]
  128. #[ORM\Entity(repositoryClassJournalIssueRepository::class)]
  129. class JournalIssue implements
  130.     TranslatableInterface,
  131.     OrdStatableInterface,
  132.     LanguageableChildInterface,
  133.     EditorialGroupableInterface,
  134.     ArchivableInterface,
  135.     CloneableInterface
  136. {
  137.     use IdTrait,
  138.         UuidTrait,
  139.         OrdStatTrait,
  140.         TimestampableTrait,
  141.         TranslatableTrait,
  142.         LanguageableChildTrait,
  143.         ArchivedTrait,
  144.         EditorialGroupableTrait,
  145.         ImportIdTrait;
  146.     #[ApiProperty(description'Parent'writableLinkfalse)]
  147.     #[Groups(['read''post'])]
  148.     #[ORM\ManyToOne(targetEntityJournal::class, inversedBy'issues')]
  149.     #[ORM\JoinColumn(onDelete'cascade'nullablefalse)]
  150.     private Journal $parent;
  151.     #[ApiProperty(description'Volume'writableLinkfalse)]
  152.     #[Groups(['read''write'])]
  153.     #[Assert\Expression(
  154.         expression'!value || value.getParent() === this.getParent()',
  155.         message'Cannot assign volume from the outside of current journal.'
  156.     )]
  157.     #[ORM\ManyToOne(targetEntityJournalVolume::class, inversedBy'issues')]
  158.     #[ORM\JoinColumn(onDelete'cascade'nullablefalse)]
  159.     private JournalVolume $volume;
  160.     #[ApiProperty(description'Is archived')]
  161.     #[Groups(['read:' self::class, 'read:' JournalVolume::class, 'read:list''write'])]
  162.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  163.     protected bool $isArchived false;
  164.     #[ApiProperty(description'Planned articles amount')]
  165.     #[Groups(['read:' self::class, 'write'])]
  166.     #[Assert\Positive]
  167.     #[ORM\Column(typeTypes::INTEGERoptions: ['default' => 1])]
  168.     private int $plannedArticlesAmount 1;
  169.     #[ApiProperty(description'Is new')]
  170.     #[Groups(['read:' self::class, 'write'])]
  171.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  172.     private bool $isNew false;
  173.     #[ApiProperty(description'Is newest issue')]
  174.     #[Groups(['read:' self::class, 'write'])]
  175.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => true])]
  176.     private bool $isNewestIssue true;
  177.     #[ApiProperty(description'Is first view')]
  178.     #[Groups(['read:' self::class, 'write'])]
  179.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  180.     private bool $isFirstView false;
  181.     #[ApiProperty(description'State'writableLinkfalse)]
  182.     #[Groups(['read:' self::class, 'write'])]
  183.     #[ORM\Column(
  184.         typeTypes::STRING,
  185.         enumTypeJournalIssueState::class,
  186.         length255,
  187.         options: ['default' => JournalIssueState::LIST]
  188.     )]
  189.     private JournalIssueState $state JournalIssueState::LIST;
  190.     #[ApiProperty(description'Is continous accession')]
  191.     #[Groups(['read:' self::class, 'write'])]
  192.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  193.     private bool $isContinousAccession false;
  194.     #[ApiProperty(description'Number in year')]
  195.     #[Groups(['read:' self::class, 'write'])]
  196.     #[Assert\Positive]
  197.     #[ORM\Column(typeTypes::INTEGERoptions: ['default' => 1])]
  198.     private int $numberInYear 1;
  199.     #[ApiProperty(description'Is special issue')]
  200.     #[Groups(['read:' self::class, 'write'])]
  201.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  202.     private bool $isSpecialIssue false;
  203.     #[ApiProperty(description'Is crossmark button visible')]
  204.     #[Groups(['read:' self::class, 'write'])]
  205.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  206.     private bool $isCrossmarkButtonVisible false;
  207.     #[ApiProperty(description'Is open')]
  208.     #[Groups(['read:' self::class, 'write'])]
  209.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => true])]
  210.     private bool $isOpen true;
  211.     #[ApiProperty(description'Price')]
  212.     #[Groups(['read:' self::class])]
  213.     #[Assert\PositiveOrZero]
  214.     #[ORM\Column(typeTypes::INTEGERnullabletrue)]
  215.     private ?int $price null;
  216.     #[ApiProperty(description'Grace date')]
  217.     #[Groups(['read:' self::class, 'write'])]
  218.     #[ORM\Column(typeTypes::DATE_IMMUTABLEnullabletrue)]
  219.     private ?\DateTimeImmutable $graceDate null;
  220.     #[ApiProperty(description'Is open access granted after newest issue expired')]
  221.     #[Groups(['read:' self::class, 'write'])]
  222.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  223.     private bool $isOpenAccessGrantedAfterNewestIssueExpired false;
  224.     #[ApiProperty(description'Published at')]
  225.     #[Groups(['read:' self::class, 'write'])]
  226.     #[ORM\Column(typeTypes::DATE_IMMUTABLEnullabletrue)]
  227.     private ?\DateTimeImmutable $publishedAt null;
  228.     #[ApiProperty(description'Published at format'writableLinkfalse)]
  229.     #[Groups(['read:' self::class, 'write'])]
  230.     #[ORM\Column(
  231.         typeTypes::STRING,
  232.         enumTypeJournalDateFormat::class,
  233.         length255,
  234.         options: ['default' => JournalDateFormat::COMPLETE]
  235.     )]
  236.     private JournalDateFormat $publishedAtFormat JournalDateFormat::COMPLETE;
  237.     #[ApiProperty(description'DOI')]
  238.     #[Groups(['read:' self::class, 'write'])]
  239.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  240.     private ?string $doi null;
  241.     #[ApiProperty(description'Licence'writableLinkfalse)]
  242.     #[Groups(['read:' self::class, 'write'])]
  243.     #[ORM\Column(
  244.         typeTypes::STRING,
  245.         enumTypeJournalLicence::class,
  246.         length255,
  247.         options: ['default' => JournalLicence::CC_BY]
  248.     )]
  249.     private JournalLicence $licence JournalLicence::CC_BY;
  250.     #[ApiProperty(description'Suggested citation type'writableLinkfalse)]
  251.     #[Groups(['read:' Journal::class, 'write'])]
  252.     #[ORM\ManyToOne(targetEntityCitationType::class)]
  253.     #[ORM\JoinColumn(onDelete'set null')]
  254.     private ?CitationType $suggestedCitationType null;
  255.     #[ApiProperty(description'Funding number')]
  256.     #[Groups(['read:' self::class, 'write'])]
  257.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  258.     private ?string $fundingNumber null;
  259.     #[ApiProperty(description'Meta link')]
  260.     #[Groups(['read:' self::class, 'write'])]
  261.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  262.     private ?string $metaLink null;
  263.     #[ApiProperty(description'Visible editorial group member properties'writableLinkfalse)]
  264.     #[Groups(['read:' self::class, 'write'])]
  265.     #[Assert\Choice(callback: [AuthorProperty::class, 'cases'], multipletrue)]
  266.     #[Assert\Unique]
  267.     #[Assert\Count(min1)]
  268.     #[ORM\Column(typeAuthorPropertiesType::NAME)]
  269.     private array $visibleEditorialGroupMemberProperties = [
  270.         AuthorProperty::PREFIX,
  271.         AuthorProperty::NAME,
  272.         AuthorProperty::SURNAME,
  273.         AuthorProperty::AFFILIATION
  274.     ];
  275.     #[ApiProperty(description'Files')]
  276.     #[ORM\OneToMany(targetEntityJournalIssueFile::class, mappedBy'parent'cascade: ['persist'])]
  277.     #[ORM\OrderBy(['ord' => 'desc'])]
  278.     private Collection $files;
  279.     #[ApiProperty(description'Sponsors')]
  280.     #[ORM\OneToMany(targetEntityJournalIssueSponsor::class, mappedBy'parent'cascade: ['persist'])]
  281.     #[ORM\OrderBy(['ord' => 'desc'])]
  282.     private Collection $sponsors;
  283.     #[ApiProperty(description'Blocks')]
  284.     #[ORM\OneToMany(targetEntityJournalIssueBlock::class, mappedBy'parent'cascade: ['persist'])]
  285.     #[ORM\OrderBy(['ord' => 'desc'])]
  286.     private Collection $blocks;
  287.     #[ApiProperty(description'Sections')]
  288.     #[ORM\OneToMany(targetEntityJournalIssueSection::class, mappedBy'parent'cascade: ['persist'])]
  289.     #[ORM\OrderBy(['ord' => 'desc'])]
  290.     private Collection $sections;
  291.     #[ApiProperty(description'Articles'writableLinkfalse)]
  292.     #[Groups(['read:' self::class])]
  293.     #[ORM\OneToMany(targetEntityJournalArticle::class, mappedBy'issue'cascade: ['persist'])]
  294.     #[ORM\OrderBy(['pageFrom' => 'asc'])]
  295.     private Collection $articles;
  296.     private ?array $sortedArticles null;
  297.     public function __construct(Journal $parent)
  298.     {
  299.         $this->setUuid();
  300.         $this->parent $parent;
  301.         $this->translations = new ArrayCollection();
  302.         $this->files = new ArrayCollection();
  303.         $this->sponsors = new ArrayCollection();
  304.         $this->blocks = new ArrayCollection();
  305.         $this->sections = new ArrayCollection();
  306.         $this->articles = new ArrayCollection();
  307.         $this->editorialGroups = new ArrayCollection();
  308.         foreach ($this->parent->getLanguages() as $lang) {
  309.             new JournalIssueTranslation($this$lang);
  310.         }
  311.         foreach($this->parent->getEditorialGroups() as $editorialGroup) {
  312.             $copied JournalIssueEditorialGroup::constructFrom(parent$thistemplate$editorialGroup);
  313.             $this->addEditorialGroup($copied);
  314.         }
  315.         $this->isOpen $parent->getData()->getIsOpen();
  316.         $this->price $parent->getData()->getIssuePrice();
  317.         $this->isOpenAccessGrantedAfterNewestIssueExpired $parent->getData()->getIsOpenAccessGrantedAfterNewestIssueExpired();
  318.         $this->visibleEditorialGroupMemberProperties $parent->getView()->getVisibleEditorialGroupMemberProperties();
  319.         $this->suggestedCitationType $parent->getData()->getSuggestedCitationType();
  320.         $this->licence $parent->getData()->getLicence();
  321.         $this->createdAt = new \DateTimeImmutable();
  322.         $this->updatedAt = new \DateTimeImmutable();
  323.         $parent->addIssue($this);
  324.     }
  325.     public function getParent(): Journal
  326.     {
  327.         return $this->parent;
  328.     }
  329.     public function getVolume(): JournalVolume
  330.     {
  331.         return $this->volume;
  332.     }
  333.     public function setVolume(JournalVolume $volume): self
  334.     {
  335.         $this->volume $volume;
  336.         return $this;
  337.     }
  338.     public function getPlannedArticlesAmount(): int
  339.     {
  340.         return $this->plannedArticlesAmount;
  341.     }
  342.     public function setPlannedArticlesAmount(int $plannedArticlesAmount): self
  343.     {
  344.         $this->plannedArticlesAmount $plannedArticlesAmount;
  345.         return $this;
  346.     }
  347.     #[Groups(['read:' self::class])]
  348.     public function getArticlesRatio(): string
  349.     {
  350.         $articles count($this->articles);
  351.         return "{$articles}/{$this->plannedArticlesAmount}";
  352.     }
  353.     public function getIsNew(): bool
  354.     {
  355.         return $this->isNew;
  356.     }
  357.     public function setIsNew(bool $isNew): self
  358.     {
  359.         $this->isNew $isNew;
  360.         return $this;
  361.     }
  362.     public function getIsNewestIssue(): bool
  363.     {
  364.         return $this->isNewestIssue;
  365.     }
  366.     public function setIsNewestIssue(bool $isNewestIssue): self
  367.     {
  368.         $wasNewestIssue $this->isNewestIssue;
  369.         $this->isNewestIssue $isNewestIssue;
  370.         if (
  371.             $this->isOpenAccessGrantedAfterNewestIssueExpired
  372.             && $wasNewestIssue
  373.             && ! $isNewestIssue
  374.         ) {
  375.             $this->isOpen true;
  376.         }
  377.         return $this;
  378.     }
  379.     public function getIsFirstView(): bool
  380.     {
  381.         return $this->isFirstView;
  382.     }
  383.     public function setIsFirstView(bool $isFirstView): self
  384.     {
  385.         $this->isFirstView $isFirstView;
  386.         return $this;
  387.     }
  388.     public function getState(): JournalIssueState
  389.     {
  390.         return $this->state;
  391.     }
  392.     public function setState(JournalIssueState $state): self
  393.     {
  394.         $this->state $state;
  395.         return $this;
  396.     }
  397.     public function getIsContinousAccession(): bool
  398.     {
  399.         return $this->isContinousAccession;
  400.     }
  401.     public function setIsContinousAccession(bool $isContinousAccession): self
  402.     {
  403.         $this->isContinousAccession $isContinousAccession;
  404.         return $this;
  405.     }
  406.     public function getNumberInYear(): int
  407.     {
  408.         return $this->numberInYear;
  409.     }
  410.     public function setNumberInYear(int $numberInYear): self
  411.     {
  412.         $this->numberInYear $numberInYear;
  413.         return $this;
  414.     }
  415.     public function getIsSpecialIssue(): bool
  416.     {
  417.         return $this->isSpecialIssue;
  418.     }
  419.     public function setIsSpecialIssue(bool $isSpecialIssue): self
  420.     {
  421.         $this->isSpecialIssue $isSpecialIssue;
  422.         return $this;
  423.     }
  424.     public function getIsCrossmarkButtonVisible(): bool
  425.     {
  426.         return $this->isCrossmarkButtonVisible;
  427.     }
  428.     public function setIsCrossmarkButtonVisible(bool $isCrossmarkButtonVisible): self
  429.     {
  430.         $this->isCrossmarkButtonVisible $isCrossmarkButtonVisible;
  431.         return $this;
  432.     }
  433.     public function getIsOpen(): bool
  434.     {
  435.         return $this->isOpen;
  436.     }
  437.     public function setIsOpen(bool $isOpen): self
  438.     {
  439.         $this->isOpen $isOpen;
  440.         return $this;
  441.     }
  442.     public function getPrice(): ?int
  443.     {
  444.         return $this->price;
  445.     }
  446.     #[Groups(['read:' self::class, 'read:' Journal::class])]
  447.     public function getPriceReal(): ?float
  448.     {
  449.         return $this->price round($this->price 10002) : null;
  450.     }
  451.     public function setPrice(?int $price): self
  452.     {
  453.         $this->price $price;
  454.         return $this;
  455.     }
  456.     #[Groups(['write'])]
  457.     public function setPriceReal(?float $priceReal): self
  458.     {
  459.         $this->price = (int) ($priceReal 1000);
  460.         return $this;
  461.     }
  462.     public function getGraceDate(): ?\DateTimeImmutable
  463.     {
  464.         return $this->graceDate;
  465.     }
  466.     public function setGraceDate(?\DateTimeImmutable $graceDate): self
  467.     {
  468.         $this->graceDate $graceDate;
  469.         return $this;
  470.     }
  471.     public function getIsOpenAccessGrantedAfterNewestIssueExpired(): bool
  472.     {
  473.         return $this->isOpenAccessGrantedAfterNewestIssueExpired;
  474.     }
  475.     public function setIsOpenAccessGrantedAfterNewestIssueExpired(bool $isOpenAccessGrantedAfterNewestIssueExpired): self
  476.     {
  477.         $this->isOpenAccessGrantedAfterNewestIssueExpired $isOpenAccessGrantedAfterNewestIssueExpired;
  478.         return $this;
  479.     }
  480.     public function getPublishedAt(): ?\DateTimeImmutable
  481.     {
  482.         return $this->publishedAt;
  483.     }
  484.     public function setPublishedAt(?\DateTimeImmutable $publishedAt): self
  485.     {
  486.         if ($this->publishedAt === $publishedAt) {
  487.             return $this;
  488.         }
  489.         $this->publishedAt $publishedAt;
  490.         if (! $publishedAt) {
  491.             return $this;
  492.         }
  493.         $period $this->parent->getData()->getOpenAccessGrantedAfterPeriod();
  494.         if (! $period) {
  495.             return $this;
  496.         }
  497.         $graceDate = new \DateTime("@{$this->publishedAt->getTimestamp()}");
  498.         $graceDate->modify($period);
  499.         $this->graceDate = new \DateTimeImmutable("@{$graceDate->getTimestamp()}");
  500.         return $this;
  501.     }
  502.     public function getPublishedAtFormat(): JournalDateFormat
  503.     {
  504.         return $this->publishedAtFormat;
  505.     }
  506.     public function setPublishedAtFormat(JournalDateFormat $publishedAtFormat): self
  507.     {
  508.         $this->publishedAtFormat $publishedAtFormat;
  509.         return $this;
  510.     }
  511.     public function getDoi(): ?string
  512.     {
  513.         return $this->doi;
  514.     }
  515.     public function setDoi(?string $doi): self
  516.     {
  517.         $this->doi $doi;
  518.         return $this;
  519.     }
  520.     public function getLicence(): JournalLicence
  521.     {
  522.         return $this->licence;
  523.     }
  524.     public function setLicence(JournalLicence $licence): self
  525.     {
  526.         if ($this->licence !== $licence) {
  527.             foreach($this->articles as $article) {
  528.                 $article->setLicence($licence);
  529.             }
  530.         }
  531.         $this->licence $licence;
  532.         return $this;
  533.     }
  534.     public function getSuggestedCitationType(): ?CitationType
  535.     {
  536.         return $this->suggestedCitationType;
  537.     }
  538.     public function setSuggestedCitationType(?CitationType $suggestedCitationType): self
  539.     {
  540.         $this->suggestedCitationType $suggestedCitationType;
  541.         return $this;
  542.     }
  543.     public function getFundingNumber(): ?string
  544.     {
  545.         return $this->fundingNumber;
  546.     }
  547.     public function setFundingNumber(?string $fundingNumber): self
  548.     {
  549.         $this->fundingNumber $fundingNumber;
  550.         return $this;
  551.     }
  552.     public function getMetaLink(): ?string
  553.     {
  554.         return $this->metaLink;
  555.     }
  556.     public function setMetaLink(?string $metaLink): self
  557.     {
  558.         $this->metaLink $metaLink;
  559.         return $this;
  560.     }
  561.     public function getVisibleEditorialGroupMemberProperties(): array
  562.     {
  563.         return $this->visibleEditorialGroupMemberProperties;
  564.     }
  565.     public function setVisibleEditorialGroupMemberProperties(array $visibleEditorialGroupMemberProperties): self
  566.     {
  567.         $this->visibleEditorialGroupMemberProperties $visibleEditorialGroupMemberProperties;
  568.         return $this;
  569.     }
  570.     /**
  571.      * @return Collection|JournalIssueFile[]
  572.      */
  573.     public function getFiles(): Collection
  574.     {
  575.         return $this->files;
  576.     }
  577.     public function addFile(JournalIssueFile $file): self
  578.     {
  579.         if ($this->files->contains($file)) {
  580.             return $this;
  581.         }
  582.         $this->files[] = $file;
  583.         return $this;
  584.     }
  585.     public function removeFile(JournalIssueFile $file): self
  586.     {
  587.         $this->files->removeElement($file);
  588.         return $this;
  589.     }
  590.     public function resetFiles(): self
  591.     {
  592.         $this->files = new ArrayCollection();
  593.         return $this;
  594.     }
  595.     public function getVisibleFiles(string $lang): Collection
  596.     {
  597.         return $this->files->filter(
  598.             fn (JournalIssueFile $file) =>
  599.                 $file->getStat() === true &&
  600.                 $file->readAvailableTranslation($lang'media')
  601.         );
  602.     }
  603.     /**
  604.      * @return Collection|JournalIssueSponsor[]
  605.      */
  606.     public function getSponsors(): Collection
  607.     {
  608.         return $this->sponsors;
  609.     }
  610.     public function addSponsor(JournalIssueSponsor $sponsor): self
  611.     {
  612.         if ($this->sponsors->contains($sponsor)) {
  613.             return $this;
  614.         }
  615.         $this->sponsors[] = $sponsor;
  616.         return $this;
  617.     }
  618.     public function removeSponsor(JournalIssueSponsor $sponsor): self
  619.     {
  620.         $this->sponsors->removeElement($sponsor);
  621.         return $this;
  622.     }
  623.     public function resetSponsors(): self
  624.     {
  625.         $this->sponsors = new ArrayCollection();
  626.         return $this;
  627.     }
  628.     /**
  629.      * @return Collection|JournalIssueBlock[]
  630.      */
  631.     public function getBlocks(): Collection
  632.     {
  633.         return $this->blocks;
  634.     }
  635.     public function addBlock(JournalIssueBlock $block): self
  636.     {
  637.         if ($this->blocks->contains($block)) {
  638.             return $this;
  639.         }
  640.         $this->blocks[] = $block;
  641.         return $this;
  642.     }
  643.     public function removeBlock(JournalIssueBlock $block): self
  644.     {
  645.         $this->blocks->removeElement($block);
  646.         return $this;
  647.     }
  648.     public function resetBlocks(): self
  649.     {
  650.         $this->blocks = new ArrayCollection();
  651.         return $this;
  652.     }
  653.     /**
  654.      * @return Collection|JournalIssueSection[]
  655.      */
  656.     public function getSections(): Collection
  657.     {
  658.         return $this->sections;
  659.     }
  660.     public function addSection(JournalIssueSection $section): self
  661.     {
  662.         if ($this->sections->contains($section)) {
  663.             return $this;
  664.         }
  665.         $this->sections[] = $section;
  666.         return $this;
  667.     }
  668.     public function removeSection(JournalIssueSection $section): self
  669.     {
  670.         $this->sections->removeElement($section);
  671.         return $this;
  672.     }
  673.     public function resetSections(): self
  674.     {
  675.         $this->sections = new ArrayCollection();
  676.         return $this;
  677.     }
  678.     /**
  679.      * @return array|JournalArticle[]
  680.      */
  681.     public function getArticles(): array
  682.     {
  683.         if (null === $this->sortedArticles) {
  684.             $this->sortedArticles $this->articles->toArray();
  685.             if ($this->getParent()->isSortArticlesByOrd()) {
  686.                 usort(
  687.                     $this->sortedArticles,
  688.                     function(JournalArticle $aJournalArticle $b) {
  689.                         return $b->getOrd() > $a->getOrd();
  690.                     }
  691.                 );
  692.             } else {
  693.                 usort(
  694.                     $this->sortedArticles,
  695.                     function(JournalArticle $aJournalArticle $b) {
  696.                         $aPageFrom RomanNumber::isRoman($a->getPageFrom())
  697.                             ? RomanNumber::convertToInt($a->getPageFrom())
  698.                             : (int) $a->getPageFrom();
  699.                         $bPageFrom RomanNumber::isRoman($b->getPageFrom())
  700.                             ? RomanNumber::convertToInt($b->getPageFrom())
  701.                             : (int) $b->getPageFrom();
  702.                         return $aPageFrom $bPageFrom;
  703.                     }
  704.                 );
  705.             }
  706.         }
  707.         return $this->sortedArticles;
  708.     }
  709.     public function addArticle(JournalArticle $article): self
  710.     {
  711.         if ($this->articles->contains($article)) {
  712.             return $this;
  713.         }
  714.         $this->articles[] = $article;
  715.         return $this;
  716.     }
  717.     public function removeArticle(JournalArticle $article): self
  718.     {
  719.         $this->articles->removeElement($article);
  720.         return $this;
  721.     }
  722.     public function resetArticles(): self
  723.     {
  724.         $this->articles = new ArrayCollection();
  725.         return $this;
  726.     }
  727.     public function getArticlesVisible(bool $sectionable false): array
  728.     {
  729.         $articles = [];
  730.         /** @var JournalArticle $article */
  731.         foreach ($this->getArticles() as $article) {
  732.             if (! $article->getStat()) {
  733.                 continue;
  734.             }
  735.             if (
  736.                 ($sectionable && ! $article->getSection()) ||
  737.                 (! $sectionable && $article->getSection())
  738.             ) {
  739.                 continue;
  740.             }
  741.             $articles[] = $article;
  742.         }
  743.         return $articles;
  744.     }
  745.     public function getArticlesVisibleIgnoreSections(): array
  746.     {
  747.         $articles = [];
  748.         foreach ($this->getArticles() as $article) {
  749.             if (! $article->getStat()) {
  750.                 continue;
  751.             }
  752.             $articles[] = $article;
  753.         }
  754.         return $articles;
  755.     }
  756.     public function getSectionGroupedArticles(): array
  757.     {
  758.         $articles = [];
  759.         /** @var JournalArticle $article */
  760.         foreach ($this->getArticles() as $article) {
  761.             if (! $article->getStat() || ! $article->getSection()?->getStat()) {
  762.                 continue;
  763.             }
  764.             $sectionOrd $this->getSectionOrd($article->getSection());
  765.             if (! isset($result[$sectionOrd])) {
  766.                 $result[$sectionOrd] = [];
  767.             }
  768.             $articles[$sectionOrd][] = $article;
  769.         }
  770.         return $articles;
  771.     }
  772.     private function getSectionOrd(JournalSection $search): ?int
  773.     {
  774.         foreach($this->sections as $section) {
  775.             if ($search === $section->getSection()) {
  776.                 return $section->getOrd();
  777.             }
  778.         }
  779.         return null;
  780.     }
  781.     public function getArticlesKeywords(string|Language $lang): array
  782.     {
  783.         $results = [];
  784.         /** @var JournalArticle $article */
  785.         foreach ($this->getArticles() as $article) {
  786.             if (! $article->getStat()) {
  787.                 continue;
  788.             }
  789.             $keywords $article->readAvailableMetadataTranslation($lang'keywords');
  790.             if (! $keywords) {
  791.                 continue;
  792.             }
  793.             $results array_merge($results$keywords->toArray());
  794.         }
  795.         return $results;
  796.     }
  797.     public function getVisibleEditorialGroups(): Collection
  798.     {
  799.         return $this->editorialGroups;
  800.     }
  801.     #[Groups(['read:' self::class, 'read:' JournalVolume::class])]
  802.     public function getParentNativeLanguage(): Language
  803.     {
  804.         return $this->getLanguageableParent()->getNativeLanguage();
  805.     }
  806.     #[Groups(['read:' self::class, 'read:' JournalVolume::class])]
  807.     public function getParentLanguages(): array
  808.     {
  809.         return $this->getLanguageableParent()->getLanguages();
  810.     }
  811.     public function getNativeTitle(): string
  812.     {
  813.         return $this->getTranslation($this->parent->getNativeLanguage())?->getTitle();
  814.     }
  815.     public function getOaiId(): string
  816.     {
  817.         return Urlizer::urlize($this->getNativeTitle());
  818.     }
  819.     public function clone(Journal $parent null): self
  820.     {
  821.         $clone = clone $this;
  822.         $clone->id null;
  823.         $clone->setUuid();
  824.         $clone->importId null;
  825.         ClassUtils::cloneCollection($this$clone'translations');
  826.         $clone->synchronizeTranslations();
  827.         if (! $parent) {
  828.             $nativeLanguage $clone->parent->getNativeLanguage();
  829.             $nativeTranslation $clone->getTranslation($nativeLanguage);
  830.             $nativeTranslation->setTitle($nativeTranslation->getTitle() . ' [KOPIA]');
  831.             $clone->ord 0;
  832.             $clone->stat false;
  833.         } else {
  834.             $clone->parent $parent;
  835.             $clone->parent->addIssue($clone);
  836.         }
  837.         ClassUtils::cloneCollection($this$clone'sponsors');
  838.         ClassUtils::cloneCollection($this$clone'blocks');
  839.         ClassUtils::cloneCollection($this$clone'editorialGroups');
  840.         ClassUtils::cloneCollection($this$clone'sections');
  841.         $this
  842.             ->resetFiles()
  843.             ->resetArticles();
  844.         $clone->createdAt = new \DateTimeImmutable();
  845.         $clone->updatedAt = new \DateTimeImmutable();
  846.         return $clone;
  847.     }
  848. }