diff --git a/src/lib/Repository/ContentService.php b/src/lib/Repository/ContentService.php index 0cdf1ea47c..5f426bab3f 100644 --- a/src/lib/Repository/ContentService.php +++ b/src/lib/Repository/ContentService.php @@ -1495,6 +1495,7 @@ public function publishVersion(APIVersionInfo $versionInfo, array $translations $this->repository->beginTransaction(); try { $this->copyTranslationsFromPublishedVersion($content->versionInfo, $translations); + $this->copyNonTranslatableFieldsFromPublishedVersion($content); $content = $this->internalPublishVersion($content->getVersionInfo(), null, $translations); $this->repository->commit(); } catch (Exception $e) { @@ -1505,6 +1506,87 @@ public function publishVersion(APIVersionInfo $versionInfo, array $translations return $content; } + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + */ + protected function copyNonTranslatableFieldsFromPublishedVersion(APIContent $currentVersionContent): void + { + $versionInfo = $currentVersionContent->getVersionInfo(); + $contentType = $currentVersionContent->getContentType(); + + $publishedContent = $this->internalLoadContentById($versionInfo->getContentInfo()->getId()); + $publishedVersionInfo = $publishedContent->getVersionInfo(); + + if ( + !$publishedVersionInfo->isPublished() + || ($versionInfo->versionNo >= $publishedVersionInfo->versionNo) + ) { + return; + } + + $publishedContentFieldsInMainLanguage = $publishedContent->getFieldsByLanguage( + $publishedContent->getVersionInfo()->getContentInfo()->getMainLanguageCode() + ); + + $fieldValues = []; + $persistenceFields = []; + foreach ($currentVersionContent->getFields() as $field) { + $fieldDefinition = $contentType->getFieldDefinition($field->fieldDefIdentifier); + $fieldValues[$fieldDefinition->identifier][$field->languageCode] = $field->getValue(); + + if ( + $fieldDefinition->isTranslatable + || $field->languageCode === $versionInfo->initialLanguageCode + ) { + continue; + } + + $fieldType = $this->fieldTypeRegistry->getFieldType( + $fieldDefinition->fieldTypeIdentifier + ); + + $newValue = $publishedContentFieldsInMainLanguage[$field->fieldDefIdentifier]->getValue(); + $fieldValues[$fieldDefinition->identifier][$field->languageCode] = $newValue; + + $persistenceFields[] = new SPIField( + [ + 'id' => $field->id, + 'fieldDefinitionId' => $fieldDefinition->id, + 'type' => $fieldDefinition->fieldTypeIdentifier, + 'value' => $fieldType->toPersistenceValue($newValue), + 'languageCode' => $field->languageCode, + 'versionNo' => $versionInfo->versionNo, + ] + ); + } + + if (count($persistenceFields) === 0) { + return; + } + + $updateStruct = new SPIContentUpdateStruct(); + $updateStruct->name = $this->nameSchemaService->resolveNameSchema( + $currentVersionContent, + $fieldValues, + $versionInfo->languageCodes, + $contentType + ); + $updateStruct->initialLanguageId = $this->persistenceHandler + ->contentLanguageHandler() + ->loadByLanguageCode( + $versionInfo->initialLanguageCode + )->id; + $updateStruct->creatorId = $versionInfo->creatorId; + $updateStruct->modificationDate = time(); + $updateStruct->fields = $persistenceFields; + + $this->persistenceHandler->contentHandler()->updateContent( + $versionInfo->getContentInfo()->getId(), + $versionInfo->versionNo, + $updateStruct + ); + } + /** * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo * @param array $translations diff --git a/tests/integration/Core/Repository/ContentService/CopyNonTranslatableFieldsFromPublishedVersionTest.php b/tests/integration/Core/Repository/ContentService/CopyNonTranslatableFieldsFromPublishedVersionTest.php new file mode 100644 index 0000000000..7fa008c875 --- /dev/null +++ b/tests/integration/Core/Repository/ContentService/CopyNonTranslatableFieldsFromPublishedVersionTest.php @@ -0,0 +1,173 @@ +createNonTranslatableContentType(); + + $contentService = self::getContentService(); + $contentTypeService = self::getContentTypeService(); + $locationService = self::getLocationService(); + + // Creating start content in eng-US language + $contentType = $contentTypeService->loadContentTypeByIdentifier(self::CONTENT_TYPE_IDENTIFIER); + $mainLanguageCode = self::ENG_US; + $contentCreateStruct = $contentService->newContentCreateStruct($contentType, $mainLanguageCode); + $contentCreateStruct->setField('title', 'Test title'); + + $contentDraft = $contentService->createContent( + $contentCreateStruct, + [ + $locationService->newLocationCreateStruct(2), + ] + ); + $publishedContent = $contentService->publishVersion($contentDraft->getVersionInfo()); + + // Creating a draft in ger-DE language with the only field updated being 'title' + $gerDraft = $contentService->createContentDraft($publishedContent->contentInfo); + + $contentUpdateStruct = new ContentUpdateStruct([ + 'initialLanguageCode' => self::GER_DE, + 'fields' => $contentDraft->getFields(), + ]); + + $contentUpdateStruct->setField('title', 'Folder GER', self::GER_DE); + $gerContent = $contentService->updateContent($gerDraft->getVersionInfo(), $contentUpdateStruct); + + // Updating non-translatable field in eng-US language (allowed) and publishing it + $engContent = $contentService->createContentDraft($publishedContent->contentInfo); + + $contentUpdateStruct = new ContentUpdateStruct([ + 'initialLanguageCode' => self::ENG_US, + 'fields' => $contentDraft->getFields(), + ]); + + $expectedBodyValue = 'Non-translatable value'; + $contentUpdateStruct->setField('title', 'Title v2', self::ENG_US); + $contentUpdateStruct->setField('body', $expectedBodyValue, self::ENG_US); + + $engContent = $contentService->updateContent($engContent->getVersionInfo(), $contentUpdateStruct); + $contentService->publishVersion($engContent->getVersionInfo()); + + // Publishing ger-DE draft with the empty non-translatable field + $contentService->publishVersion($gerContent->getVersionInfo()); + + // Loading main content + $mainPublishedContent = $contentService->loadContent($engContent->id); + $bodyFieldValue = $mainPublishedContent->getField('body')->getValue(); + + self::assertSame($expectedBodyValue, $bodyFieldValue->text); + } + + private function createNonTranslatableContentType(): void + { + $permissionResolver = self::getPermissionResolver(); + $contentTypeService = self::getContentTypeService(); + + $typeCreate = $contentTypeService->newContentTypeCreateStruct(self::CONTENT_TYPE_IDENTIFIER); + + $typeCreate->mainLanguageCode = 'eng-GB'; + $typeCreate->remoteId = '1234567890abcdef'; + $typeCreate->urlAliasSchema = ''; + $typeCreate->nameSchema = '<title>'; + $typeCreate->names = [ + 'eng-GB' => 'Non-translatable content type', + ]; + $typeCreate->descriptions = [ + 'eng-GB' => '', + ]; + $typeCreate->creatorId = $permissionResolver->getCurrentUserReference()->getUserId(); + $typeCreate->creationDate = new DateTime(); + + $fieldDefinitionPosition = 1; + $typeCreate->addFieldDefinition( + $this->buildFieldDefinitionCreateStructForNonTranslatableContentType( + $fieldDefinitionPosition, + 'title', + ['eng-GB' => 'Title'], + true, + true, + 'default title' + ) + ); + + $typeCreate->addFieldDefinition( + $this->buildFieldDefinitionCreateStructForNonTranslatableContentType( + ++$fieldDefinitionPosition, + 'body', + ['eng-GB' => 'Body'], + false, + false + ) + ); + + $contentTypeDraft = $contentTypeService->createContentType( + $typeCreate, + [$contentTypeService->loadContentTypeGroupByIdentifier('Media')], + ); + $contentTypeService->publishContentTypeDraft($contentTypeDraft); + } + + /** + * @param array<string, string> $names + */ + private function buildFieldDefinitionCreateStructForNonTranslatableContentType( + int $position, + string $fieldIdentifier, + array $names, + bool $isTranslatable, + bool $isRequired, + ?string $defaultValue = null + ): FieldDefinitionCreateStruct { + $contentTypeService = self::getContentTypeService(); + + $fieldDefinitionCreateStruct = $contentTypeService->newFieldDefinitionCreateStruct( + $fieldIdentifier, + self::TEXT_LINE_FIELD_TYPE_IDENTIFIER + ); + + $fieldDefinitionCreateStruct->names = $names; + $fieldDefinitionCreateStruct->descriptions = $names; + $fieldDefinitionCreateStruct->fieldGroup = 'content'; + $fieldDefinitionCreateStruct->position = $position; + $fieldDefinitionCreateStruct->isTranslatable = $isTranslatable; + $fieldDefinitionCreateStruct->isRequired = $isRequired; + $fieldDefinitionCreateStruct->isInfoCollector = false; + $fieldDefinitionCreateStruct->validatorConfiguration = [ + 'StringLengthValidator' => [ + 'minStringLength' => 0, + 'maxStringLength' => 0, + ], + ]; + $fieldDefinitionCreateStruct->fieldSettings = []; + $fieldDefinitionCreateStruct->isSearchable = true; + $fieldDefinitionCreateStruct->defaultValue = $defaultValue; + + return $fieldDefinitionCreateStruct; + } +}