src/Entity/JournalIssue.php line 146

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