diff --git a/bundle/Command/TagContentByTypesCommand.php b/bundle/Command/TagContentByTypesCommand.php new file mode 100644 index 00000000..669d2522 --- /dev/null +++ b/bundle/Command/TagContentByTypesCommand.php @@ -0,0 +1,181 @@ +setDescription('Add tags to content') + ->addOption('parent-location', null, InputOption::VALUE_REQUIRED) + ->addOption('content-types', null, InputOption::VALUE_REQUIRED) + ->addOption('tag-id', null, InputOption::VALUE_REQUIRED) + ->addOption('field-identifier', null, InputOption::VALUE_REQUIRED); + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->style = new SymfonyStyle($input, $output); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $query = new Query(); + $query->filter = new Criterion\LogicalAnd([ + new Criterion\ParentLocationId($this->getParentLocation((int) $input->getOption('parent-location'))), + new Criterion\ContentTypeIdentifier($this->getContentTypes($input->getOption('content-types'))), + ]); + + $batchSize = 50; + + $searchResults = $this->searchService->findContent($query); + $totalResults = $searchResults->totalCount ?? 0; + + $this->style->newLine(); + $this->style->progressStart($totalResults); + + $fieldIdentifier = $input->getOption('field-identifier'); + + for ($offset = 0; $offset < $totalResults; $offset += $batchSize) { + $query->offset = $offset; + $query->limit = $batchSize; + $searchResults = $this->searchService->findContent($query); + + foreach ($searchResults->searchHits as $searchHit) { + /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Content $content */ + $content = $searchHit->valueObject; + + if ($content->getField($fieldIdentifier) === null) { + continue; + } + + if (!$content->getField($fieldIdentifier)->value instanceof TagFieldValue) { + $this->style->warning(sprintf('Field with identifier %s must be a type of eztags', $fieldIdentifier)); + + continue; + } + + $tag = $this->tagsService->loadTag($this->getTagId((int) $input->getOption('tag-id'))); + $alreadyAssignedTags = $content->getFieldValue($fieldIdentifier)->tags; + $targetTagAlreadyAssigned = array_filter($alreadyAssignedTags, static fn ($alreadyAssignedTag) => $tag->id === $alreadyAssignedTag->id); + + if (count($targetTagAlreadyAssigned) > 0) { + continue; + } + + $tagsToAssign = $alreadyAssignedTags; + $tagsToAssign[] = $tag; + + $this->repository->sudo( + function () use ($content, $fieldIdentifier, $tagsToAssign): void { + $this->repository->beginTransaction(); + + try { + $contentDraft = $this->contentService->createContentDraft($content->contentInfo); + $contentUpdateStruct = $this->contentService->newContentUpdateStruct(); + + $contentUpdateStruct->setField($fieldIdentifier, new TagFieldValue($tagsToAssign)); + + $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct); + $this->contentService->publishVersion($contentDraft->versionInfo); + + $this->repository->commit(); + } catch (Throwable $t) { + $this->style->error($t->getMessage()); + + $this->repository->rollback(); + } + }, + ); + $this->style->progressAdvance(); + } + } + + $this->style->progressFinish(); + $this->style->success('Tags assigned successfully'); + + return Command::SUCCESS; + } + + private function getParentLocation(int $parentLocation): int + { + if ($parentLocation < 1) { + throw new InvalidOptionException( + sprintf("Argument --parent-location must be an integer > 0, you provided '%s'", $parentLocation), + ); + } + + return $parentLocation; + } + + /** + * @return array + */ + private function getContentTypes(string $contentTypes): array + { + return $this->parseCommaDelimited($contentTypes); + } + + private function getTagId(int $tagId): int + { + if ($tagId < 1) { + throw new InvalidOptionException( + sprintf("Argument --tag-id must be an integer > 0, you provided '%s'", $tagId), + ); + } + + return $tagId; + } + + /** + * @return array + */ + private function parseCommaDelimited(string $value): array + { + $value = trim($value); + + if ($value === '') { + return []; + } + + return array_values(array_unique(array_filter(array_map('trim', explode(',', $value))))); + } +} diff --git a/bundle/Resources/config/services/commands.yaml b/bundle/Resources/config/services/commands.yaml index 38ab46bd..ade17d24 100644 --- a/bundle/Resources/config/services/commands.yaml +++ b/bundle/Resources/config/services/commands.yaml @@ -53,3 +53,13 @@ services: - "@?profiler" tags: - { name: console.command, command: 'ngsite:profiler:clear-cache' } + + ngsite.command.tag_content: + class: Netgen\Bundle\SiteBundle\Command\TagContentByTypesCommand + arguments: + - "@ibexa.api.repository" + - "@ibexa.api.service.content" + - "@netgen.ibexa_site_api.repository.filtering_search_service" + - "@netgen_tags.api.service.tags" + tags: + - { name: console.command, command: 'ngsite:content:tag-content' }