diff --git a/README.md b/README.md index 4424ca77eb..d7dd1ee4a4 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ Here you can find the environment variables including feature flags | DATA_STORE_STATISTIC_PUB_SUB_TOPIC | Topic name for statistic metadata Pub/Sub | - | | REDIRECT_AFTER_LOGOUT_URL | Allows to configure the redirect after logout via environment variable. The fallback is the configured redirect on urlroute.conf.php | - | | PORTAL_URL | The Portal url used on the back button of Portal theme | - | +| FEATURE_TRANSLATION_ENABLED | Enable access to items/tests translations feature | - | # Routing diff --git a/actions/class.Main.php b/actions/class.Main.php index f17b3a72fb..f2d693b0a3 100644 --- a/actions/class.Main.php +++ b/actions/class.Main.php @@ -521,11 +521,13 @@ private function getSections($shownExtension, $shownStructure) $sections = []; $user = $this->getSession()->getUser(); $structure = MenuService::getPerspective($shownExtension, $shownStructure); + $sectionVisibilityFilter = $this->getSectionVisibilityFilter(); + if (!is_null($structure)) { foreach ($structure->getChildren() as $section) { $resolver = new ActionResolver($section->getUrl()); - if (!$this->getSectionVisibilityFilter()->isVisible($section->getId())) { + if (!$sectionVisibilityFilter->isVisible($section->getId())) { continue; } @@ -540,6 +542,19 @@ private function getSections($shownExtension, $shownStructure) if (FuncProxy::accessPossible($user, $resolver->getController(), $resolver->getAction())) { foreach ($section->getActions() as $action) { + $sectionPath = $sectionVisibilityFilter->createSectionPath( + [ + $section->getId(), + $action->getId() + ] + ); + + if (!$sectionVisibilityFilter->isVisible($sectionPath)) { + $section->removeAction($action); + + continue; + } + $this->propagate($action); $resolver = new ActionResolver($action->getUrl()); if ( @@ -580,7 +595,7 @@ protected function getUserService() return $this->getServiceLocator()->get(tao_models_classes_UserService::SERVICE_ID); } - private function getSectionVisibilityFilter(): SectionVisibilityFilterInterface + private function getSectionVisibilityFilter(): SectionVisibilityFilter { if (empty($this->sectionVisibilityFilter)) { $this->sectionVisibilityFilter = $this->getServiceLocator()->get(SectionVisibilityFilter::SERVICE_ID); diff --git a/actions/class.Translation.php b/actions/class.Translation.php new file mode 100644 index 0000000000..b7840c6ded --- /dev/null +++ b/actions/class.Translation.php @@ -0,0 +1,122 @@ +getTranslationUpdateService()->update( + new UpdateTranslationCommand( + $this->getRequestParameter('id'), + $this->getRequestParameter('progress'), + ) + ); + + $this->setSuccessJsonResponse( + [ + 'resourceUri' => $resource->getUri() + ] + ); + } catch (Throwable $exception) { + $this->setErrorJsonResponse($exception->getMessage()); + } + } + + /** + * @requiresRight id WRITE + */ + public function translate(): void + { + try { + $newResource = $this->getTranslationCreationService()->createByRequest($this->getPsrRequest()); + + $this->setSuccessJsonResponse( + [ + 'resourceUri' => $newResource->getUri() + ] + ); + } catch (Throwable $exception) { + $this->setErrorJsonResponse($exception->getMessage()); + } + } + + /** + * @requiresRight id READ + */ + public function translations(): void + { + try { + $this->setSuccessJsonResponse( + $this->getResourceTranslationRetriever()->getByRequest($this->getPsrRequest()) + ); + } catch (Throwable $exception) { + $this->setErrorJsonResponse($exception->getMessage()); + } + } + + /** + * @requiresRight id READ + */ + public function translatable(): void + { + try { + $this->setSuccessJsonResponse( + $this->getResourceTranslatableRetriever()->getByRequest($this->getPsrRequest()) + ); + } catch (Throwable $exception) { + $this->setErrorJsonResponse($exception->getMessage()); + } + } + + private function getResourceTranslationRetriever(): ResourceTranslationRetriever + { + return $this->getServiceManager()->getContainer()->get(ResourceTranslationRetriever::class); + } + + private function getResourceTranslatableRetriever(): ResourceTranslatableRetriever + { + return $this->getServiceManager()->getContainer()->get(ResourceTranslatableRetriever::class); + } + + private function getTranslationCreationService(): TranslationCreationService + { + return $this->getServiceManager()->getContainer()->get(TranslationCreationService::class); + } + + private function getTranslationUpdateService(): TranslationUpdateService + { + return $this->getServiceManager()->getContainer()->get(TranslationUpdateService::class); + } +} diff --git a/doc/taoApi.yml b/doc/taoApi.yml index d02290b0c6..c317cb667d 100644 --- a/doc/taoApi.yml +++ b/doc/taoApi.yml @@ -5,6 +5,89 @@ info: version: v1 paths: + /tao/Translation/update: + post: + summary: Return a list of translations for a given translatable resource + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + id: + type: string + description: The resource URI + progress: + type: string + description: The progress URI + responses: + 200: + $ref: '#/components/responses/TranslateResponse' + 400: + $ref: '#/components/responses/BadRequestResponse' + 500: + $ref: '#/components/responses/InternalServerErrorResponse' + /tao/Translation/translations: + get: + summary: Return a list of translations for a given translatable resource + parameters: + - in: query + name: id + required: true + schema: + type: string + description: The RDF resource id + - in: query + name: languageUri + required: false + schema: + type: string + description: The RDF language URI + responses: + 200: + $ref: '#/components/responses/TranslationsResponse' + 400: + $ref: '#/components/responses/BadRequestResponse' + 500: + $ref: '#/components/responses/InternalServerErrorResponse' + /tao/Translation/translatable: + get: + summary: Return translatable resources + parameters: + - in: query + name: id + required: true + schema: + type: string + description: The RDF resource id + responses: + 200: + $ref: '#/components/responses/TranslatableResponse' + 400: + $ref: '#/components/responses/BadRequestResponse' + 500: + $ref: '#/components/responses/InternalServerErrorResponse' + /tao/Translation/translate: + post: + summary: translate a resources + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + id: + type: string + description: The resource URI + responses: + 200: + $ref: '#/components/responses/TranslateResponse' + 400: + $ref: '#/components/responses/BadRequestResponse' + 500: + $ref: '#/components/responses/InternalServerErrorResponse' /tao/Languages/index: get: summary: Get a list of available languages in the TAO platform @@ -46,6 +129,70 @@ paths: $ref: '#/components/responses/InternalServerErrorResponse' components: schemas: + Translations: + properties: + success: + type: boolean + example: true + data: + type: object + properties: + resources: + type: array + items: + properties: + originResourceUri: + type: string + resourceUri: + type: string + resourceLabel: + type: string + metadata: + type: object + properties: + key: + type: object + properties: + value: + type: string + literal: + type: string + Translatable: + properties: + success: + type: boolean + example: true + data: + type: object + properties: + resources: + type: array + items: + properties: + resourceUri: + type: string + resourceLabel: + type: string + metadata: + type: object + properties: + key: + type: object + properties: + value: + type: string + literal: + type: string + Translated: + properties: + success: + type: boolean + example: true + data: + type: object + properties: + resourceUri: + type: string ResourceRelationResource: description: 'A resource related to another resources' type: object @@ -104,6 +251,24 @@ components: message: type: string responses: + TranslationsResponse: + description: The list of translations + content: + application/json: + schema: + $ref: '#/components/schemas/Translations' + TranslatableResponse: + description: The list of translatable + content: + application/json: + schema: + $ref: '#/components/schemas/Translatable' + TranslateResponse: + description: The resource is translated + content: + application/json: + schema: + $ref: '#/components/schemas/Translated' ResourceRelationsResponse: description: Bad request content: diff --git a/helpers/form/class.Form.php b/helpers/form/class.Form.php index 8caac01ad8..cd3724a0e3 100644 --- a/helpers/form/class.Form.php +++ b/helpers/form/class.Form.php @@ -666,20 +666,37 @@ public function setValues($values) foreach ($values as $key => $value) { foreach ($this->elements as $element) { if ($element->getName() === $key) { - if ( - $element instanceof tao_helpers_form_elements_Checkbox || - (method_exists($element, 'setValues') && is_array($value)) - ) { - $element->setValues($value); - } else { - $element->setValue($value); - } + $this->setElementValue($element, $value); + break; } } } } + public function setElementValue(tao_helpers_form_FormElement $element, $value): void + { + if ( + $element instanceof tao_helpers_form_elements_Checkbox + || (method_exists($element, 'setValues') && is_array($value)) + ) { + $element->setValues($value); + + return; + } + + $element->setValue($value); + } + + public function setValue(string $name, $value): void + { + $element = $this->getElement($name); + + if ($element) { + $this->setElementValue($element, $value); + } + } + /** * Disables the whole form */ diff --git a/helpers/form/class.FormContainer.php b/helpers/form/class.FormContainer.php index 7fe405faa3..bd5437a8ad 100644 --- a/helpers/form/class.FormContainer.php +++ b/helpers/form/class.FormContainer.php @@ -26,7 +26,9 @@ use oat\oatbox\service\ServiceManager; use oat\oatbox\validator\ValidatorInterface; +use oat\tao\model\form\Modifier\AbstractFormModifier; use oat\tao\model\security\xsrf\TokenService; +use Psr\Container\ContainerInterface; use tao_helpers_form_FormFactory as FormFactory; use oat\tao\helpers\form\elements\xhtml\CsrfToken; use oat\tao\helpers\form\elements\FormElementAware; @@ -46,6 +48,7 @@ abstract class tao_helpers_form_FormContainer public const IS_DISABLED = 'is_disabled'; public const ADDITIONAL_VALIDATORS = 'extraValidators'; public const ATTRIBUTE_VALIDATORS = 'attributeValidators'; + public const FORM_MODIFIERS = 'formModifiers'; public const WITH_SERVICE_MANAGER = 'withServiceManager'; @@ -118,6 +121,14 @@ public function __construct(array $data = [], array $options = []) // initialize the elements of the form $this->initElements(); + foreach ($options[self::FORM_MODIFIERS] ?? [] as $modifierClass) { + $modifier = $this->getContainer()->get($modifierClass); + + if ($modifier instanceof AbstractFormModifier) { + $modifier->modify($this->form, $options); + } + } + if ($this->form !== null) { $this->form->evaluateInputValues(); @@ -317,8 +328,7 @@ private function configureFormValidators(iterable $validators, tao_helpers_form_ private function getSanitizerValidationFeeder(): SanitizerValidationFeederInterface { if (!isset($this->sanitizerValidationFeeder)) { - $serviceManager = $this->serviceManager ?? ServiceManager::getServiceManager(); - $this->sanitizerValidationFeeder = $serviceManager->getContainer()->get(SanitizerValidationFeeder::class); + $this->sanitizerValidationFeeder = $this->getContainer()->get(SanitizerValidationFeeder::class); } return $this->sanitizerValidationFeeder; @@ -335,4 +345,11 @@ private function withServiceManager(array &$options): void unset($options[self::WITH_SERVICE_MANAGER]); } + + private function getContainer(): ContainerInterface + { + $serviceManager = $this->serviceManager ?? ServiceManager::getServiceManager(); + + return $serviceManager->getContainer(); + } } diff --git a/manifest.php b/manifest.php index a94598883d..00d6625fe1 100755 --- a/manifest.php +++ b/manifest.php @@ -57,6 +57,7 @@ use oat\tao\model\routing\ServiceProvider\RoutingServiceProvider; use oat\tao\model\search\ServiceProvider\SearchServiceProvider; use oat\tao\model\StatisticalMetadata\StatisticalMetadataServiceProvider; +use oat\tao\model\Translation\ServiceProvider\TranslationServiceProvider; use oat\tao\model\user\TaoRoles; use oat\tao\model\user\UserSettingsServiceProvider; use oat\tao\model\webhooks\WebhookServiceProvider; @@ -418,7 +419,8 @@ MenuServiceProvider::class, FormDataProviderServiceProvider::class, PropertyServiceProvider::class, - DynamicConfigServiceProvider::class + DynamicConfigServiceProvider::class, + TranslationServiceProvider::class ], 'middlewares' => [ MiddlewareConfig::class, diff --git a/migrations/Version202409040743452141_tao.php b/migrations/Version202409040743452141_tao.php new file mode 100644 index 0000000000..73c630b358 --- /dev/null +++ b/migrations/Version202409040743452141_tao.php @@ -0,0 +1,29 @@ +addReport(Report::createSuccess('Ontology models successfully synchronized')); + } + + public function down(Schema $schema): void + { + } +} diff --git a/models/classes/TaoOntology.php b/models/classes/TaoOntology.php index a25702de43..73a2ae426f 100644 --- a/models/classes/TaoOntology.php +++ b/models/classes/TaoOntology.php @@ -117,4 +117,28 @@ interface TaoOntology public const PROPERTY_STANCE_LANGUAGE_USAGE_DATA = 'http://www.tao.lu/Ontologies/TAO.rdf#LanguageUsageData'; /** @deprecated use tao_models_classes_LanguageService::CLASS_URI_LANGUAGES */ public const LANGUAGES_CLASS_URI = 'http://www.tao.lu/Ontologies/TAO.rdf#Languages'; + + public const PROPERTY_TRANSLATION_TYPE = 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationType'; + public const PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL = + 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationTypeOriginal'; + public const PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION = + 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationTypeTranslation'; + + public const PROPERTY_TRANSLATION_PROGRESS = 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationProgress'; + public const PROPERTY_VALUE_TRANSLATION_PROGRESS_PENDING = + 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationProgressStatusPending'; + public const PROPERTY_VALUE_TRANSLATION_PROGRESS_TRANSLATING = + 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationProgressStatusTranslating'; + public const PROPERTY_VALUE_TRANSLATION_PROGRESS_TRANSLATED = + 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationProgressStatusTranslated'; + + public const PROPERTY_TRANSLATION_STATUS = 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationStatus'; + public const PROPERTY_VALUE_TRANSLATION_STATUS_READY = + 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationStatusReadyForTranslation'; + public const PROPERTY_VALUE_TRANSLATION_STATUS_NOT_READY = + 'http://www.tao.lu/Ontologies/TAO.rdf#TranslationStatusNotReadyForTranslation'; + + public const PROPERTY_UNIQUE_IDENTIFIER = 'http://www.tao.lu/Ontologies/TAO.rdf#UniqueIdentifier'; + public const PROPERTY_LANGUAGE = 'http://www.tao.lu/Ontologies/TAO.rdf#Language'; + public const LANGUAGE_PREFIX = 'http://www.tao.lu/Ontologies/TAO.rdf#Lang'; } diff --git a/models/classes/Translation/Command/CreateTranslationCommand.php b/models/classes/Translation/Command/CreateTranslationCommand.php new file mode 100644 index 0000000000..01bb70a7e8 --- /dev/null +++ b/models/classes/Translation/Command/CreateTranslationCommand.php @@ -0,0 +1,52 @@ +resourceType = $resourceType; + $this->uniqueId = $uniqueId; + $this->languageUri = $languageUri; + } + + public function getResourceType(): string + { + return $this->resourceType; + } + + public function getUniqueId(): string + { + return $this->uniqueId; + } + + public function getLanguageUri(): string + { + return $this->languageUri; + } +} diff --git a/models/classes/Translation/Command/UpdateTranslationCommand.php b/models/classes/Translation/Command/UpdateTranslationCommand.php new file mode 100644 index 0000000000..48a4fd0cd6 --- /dev/null +++ b/models/classes/Translation/Command/UpdateTranslationCommand.php @@ -0,0 +1,62 @@ +resourceUri = $resourceUri; + $this->progressUri = $progressUri; + } + + public function getResourceUri(): string + { + return $this->resourceUri; + } + + public function getProgressUri(): string + { + return $this->progressUri; + } +} diff --git a/models/classes/Translation/Entity/AbstractResource.php b/models/classes/Translation/Entity/AbstractResource.php new file mode 100644 index 0000000000..ee0d83bfe5 --- /dev/null +++ b/models/classes/Translation/Entity/AbstractResource.php @@ -0,0 +1,122 @@ +resourceUri = $resourceUri; + $this->resourceLabel = $resourceLabel; + } + + public function getResourceUri(): string + { + return $this->resourceUri; + } + + public function getResourceLabel(): string + { + return $this->resourceLabel; + } + + public function getUniqueId(): ?string + { + return $this->getMetadataValue(TaoOntology::PROPERTY_UNIQUE_IDENTIFIER); + } + + public function getLanguageCode(): ?string + { + return $this->getMetadataLiteralValue(TaoOntology::PROPERTY_LANGUAGE); + } + + public function getLanguageUri(): ?string + { + return $this->getMetadataValue(TaoOntology::PROPERTY_LANGUAGE); + } + + /** + * @param string|array|null $value + * @param string|null $literal + */ + public function addMetadata(string $uri, $value, $literal): void + { + $this->metadata[$uri] = [ + 'value' => $value, + 'literal' => $literal + ]; + } + + public function getMetadataValue(string $metadata): ?string + { + if (!empty($this->metadata[$metadata])) { + return $this->metadata[$metadata]['value']; + } + + return null; + } + + public function getMetadataLiteralValue(string $metadata): ?string + { + if (!empty($this->metadata[$metadata])) { + return $this->metadata[$metadata]['literal']; + } + + return null; + } + + public function getMetadata(): array + { + return $this->metadata; + } + + public function addMetadataUri(string $uri): void + { + $this->metadataUris = array_unique(array_merge($this->metadataUris, [$uri])); + } + + public function getMetadataUris(): array + { + return $this->metadataUris; + } + + public function jsonSerialize(): array + { + return [ + 'resourceUri' => $this->getResourceUri(), + 'resourceLabel' => $this->getResourceLabel(), + 'metadata' => $this->metadata, + ]; + } +} diff --git a/models/classes/Translation/Entity/ResourceCollection.php b/models/classes/Translation/Entity/ResourceCollection.php new file mode 100644 index 0000000000..a9b368a0bd --- /dev/null +++ b/models/classes/Translation/Entity/ResourceCollection.php @@ -0,0 +1,41 @@ + $this->getArrayCopy() + ]; + } +} diff --git a/models/classes/Translation/Entity/ResourceTranslatable.php b/models/classes/Translation/Entity/ResourceTranslatable.php new file mode 100644 index 0000000000..c66619aaca --- /dev/null +++ b/models/classes/Translation/Entity/ResourceTranslatable.php @@ -0,0 +1,38 @@ +getMetadataValue(TaoOntology::PROPERTY_TRANSLATION_STATUS); + } + + public function isReadyForTranslation(): bool + { + return $this->getStatusUri() === TaoOntology::PROPERTY_VALUE_TRANSLATION_STATUS_READY; + } +} diff --git a/models/classes/Translation/Entity/ResourceTranslation.php b/models/classes/Translation/Entity/ResourceTranslation.php new file mode 100644 index 0000000000..0b9d0c0b11 --- /dev/null +++ b/models/classes/Translation/Entity/ResourceTranslation.php @@ -0,0 +1,55 @@ +originResourceUri = $originResourceUri; + } + + public function getOriginResourceUri(): string + { + return $this->originResourceUri; + } + + public function getProgressUri(): string + { + return $this->getMetadataValue(TaoOntology::PROPERTY_TRANSLATION_PROGRESS); + } + + public function jsonSerialize(): array + { + return array_merge( + [ + 'originResourceUri' => $this->getOriginResourceUri() + ], + parent::jsonSerialize(), + ); + } +} diff --git a/models/classes/Translation/Exception/ResourceTranslationException.php b/models/classes/Translation/Exception/ResourceTranslationException.php new file mode 100644 index 0000000000..c341028182 --- /dev/null +++ b/models/classes/Translation/Exception/ResourceTranslationException.php @@ -0,0 +1,29 @@ +metadataPopulateService = $metadataPopulateService; + } + + public function create(core_kernel_classes_Resource $originResource): ResourceTranslatable + { + $resource = new ResourceTranslatable($originResource->getUri(), $originResource->getLabel()); + $resource->addMetadataUri(TaoOntology::PROPERTY_TRANSLATION_STATUS); + + $this->metadataPopulateService->populate($resource, $originResource); + + return $resource; + } +} diff --git a/models/classes/Translation/Factory/ResourceTranslationFactory.php b/models/classes/Translation/Factory/ResourceTranslationFactory.php new file mode 100644 index 0000000000..70f3ee2045 --- /dev/null +++ b/models/classes/Translation/Factory/ResourceTranslationFactory.php @@ -0,0 +1,52 @@ +metadataPopulateService = $metadataPopulateService; + } + + public function create( + ResourceTranslatable $originResource, + core_kernel_classes_Resource $translationResource + ): ResourceTranslation { + $resource = new ResourceTranslation($translationResource->getUri(), $translationResource->getLabel()); + $resource->setOriginResourceUri($originResource->getResourceUri()); + $resource->addMetadataUri(TaoOntology::PROPERTY_TRANSLATION_PROGRESS); + + $this->metadataPopulateService->populate($resource, $translationResource); + + return $resource; + } +} diff --git a/models/classes/Translation/Form/Modifier/TranslationFormModifier.php b/models/classes/Translation/Form/Modifier/TranslationFormModifier.php new file mode 100644 index 0000000000..b9af7af51d --- /dev/null +++ b/models/classes/Translation/Form/Modifier/TranslationFormModifier.php @@ -0,0 +1,84 @@ +featureFlagChecker = $featureFlagChecker; + } + + public function modify(tao_helpers_form_Form $form, array $options = []): void + { + foreach ($this->getTranslationElementsToRemove($form) as $elementUri) { + $form->removeElement(tao_helpers_Uri::encode($elementUri)); + } + } + + private function getTranslationElementsToRemove(tao_helpers_form_Form $form): array + { + if (!$this->featureFlagChecker->isEnabled('FEATURE_TRANSLATION_ENABLED')) { + return [ + TaoOntology::PROPERTY_UNIQUE_IDENTIFIER, + TaoOntology::PROPERTY_LANGUAGE, + TaoOntology::PROPERTY_TRANSLATION_TYPE, + TaoOntology::PROPERTY_TRANSLATION_STATUS, + TaoOntology::PROPERTY_TRANSLATION_PROGRESS, + ]; + } + + $elementsToRemove = []; + + if (!$this->featureFlagChecker->isEnabled('FEATURE_TRANSLATION_DEVELOPER_MODE')) { + $elementsToRemove[] = TaoOntology::PROPERTY_TRANSLATION_TYPE; + } + + $translationTypeValue = $form->getValue(tao_helpers_Uri::encode(TaoOntology::PROPERTY_TRANSLATION_TYPE)); + $isTranslationTypeValueEmpty = empty($translationTypeValue); + + if ( + $isTranslationTypeValueEmpty + || $translationTypeValue === TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL + ) { + $elementsToRemove[] = TaoOntology::PROPERTY_TRANSLATION_PROGRESS; + } + + if ( + $isTranslationTypeValueEmpty + || $translationTypeValue === TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION + ) { + $elementsToRemove[] = TaoOntology::PROPERTY_TRANSLATION_STATUS; + } + + return $elementsToRemove; + } +} diff --git a/models/classes/Translation/Query/ResourceTranslatableQuery.php b/models/classes/Translation/Query/ResourceTranslatableQuery.php new file mode 100644 index 0000000000..6af00a9d1e --- /dev/null +++ b/models/classes/Translation/Query/ResourceTranslatableQuery.php @@ -0,0 +1,45 @@ +resourceType = $resourceType; + $this->uniqueIds = $uniqueIds; + } + + public function getResourceType(): string + { + return $this->resourceType; + } + + public function getUniqueIds(): array + { + return $this->uniqueIds; + } +} diff --git a/models/classes/Translation/Query/ResourceTranslationQuery.php b/models/classes/Translation/Query/ResourceTranslationQuery.php new file mode 100644 index 0000000000..a10c516876 --- /dev/null +++ b/models/classes/Translation/Query/ResourceTranslationQuery.php @@ -0,0 +1,52 @@ +resourceType = $resourceType; + $this->uniqueIds = $uniqueIds; + $this->languageUri = $languageUri; + } + + public function getResourceType(): string + { + return $this->resourceType; + } + + public function getUniqueIds(): array + { + return $this->uniqueIds; + } + + public function getLanguageUri(): ?string + { + return $this->languageUri; + } +} diff --git a/models/classes/Translation/Repository/ResourceTranslatableRepository.php b/models/classes/Translation/Repository/ResourceTranslatableRepository.php new file mode 100644 index 0000000000..3e12ae523d --- /dev/null +++ b/models/classes/Translation/Repository/ResourceTranslatableRepository.php @@ -0,0 +1,88 @@ +complexSearch = $complexSearch; + $this->factory = $factory; + $this->ontology = $ontology; + } + + public function find(ResourceTranslatableQuery $query): ResourceCollection + { + $queryBuilder = $this->complexSearch->query(); + $searchQuery = $this->complexSearch->searchType( + $queryBuilder, + 'http://www.tao.lu/Ontologies/TAO.rdf#AssessmentContentObject', + true + ); + $searchQuery->addCriterion( + TaoOntology::PROPERTY_TRANSLATION_TYPE, + SupportedOperatorHelper::EQUAL, + TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL + ); + + if (!empty($query->getUniqueIds())) { + $searchQuery->addCriterion( + TaoOntology::PROPERTY_UNIQUE_IDENTIFIER, + SupportedOperatorHelper::IN, + $query->getUniqueIds() + ); + } + + $queryBuilder->setCriteria($searchQuery); + + $result = $this->complexSearch->getGateway()->search($queryBuilder); + $output = []; + + $resourceTypeClass = $this->ontology->getClass($query->getResourceType()); + + /** @var core_kernel_classes_Resource $resource */ + foreach ($result as $resource) { + if ($resource->isInstanceOf($resourceTypeClass)) { + $output[] = $this->factory->create($resource); + } + } + + return new ResourceCollection(...$output); + } +} diff --git a/models/classes/Translation/Repository/ResourceTranslationRepository.php b/models/classes/Translation/Repository/ResourceTranslationRepository.php new file mode 100644 index 0000000000..f5c48a32a8 --- /dev/null +++ b/models/classes/Translation/Repository/ResourceTranslationRepository.php @@ -0,0 +1,137 @@ +ontology = $ontology; + $this->complexSearch = $complexSearch; + $this->resourceTranslatableRepository = $resourceTranslatableRepository; + $this->factory = $resourceTranslationFactory; + $this->logger = $logger; + } + + public function find(ResourceTranslationQuery $query): ResourceCollection + { + $uniqueIds = $query->getUniqueIds(); + $resources = $this->resourceTranslatableRepository->find( + new ResourceTranslatableQuery( + $query->getResourceType(), + $uniqueIds + ) + ); + + if ($resources->count() === 0) { + throw new Exception( + sprintf( + 'Translation Origin Resource [%s] does not exist', + implode(',', $uniqueIds) + ) + ); + } + + /** @var ResourceTranslatable $originResource */ + $originResource = $resources->current(); + $output = []; + + $queryBuilder = $this->complexSearch->query(); + $searchQuery = $this->complexSearch->searchType( + $queryBuilder, + 'http://www.tao.lu/Ontologies/TAO.rdf#AssessmentContentObject', + true + ); + $searchQuery->addCriterion( + TaoOntology::PROPERTY_TRANSLATION_TYPE, + SupportedOperatorHelper::EQUAL, + TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION + ); + $searchQuery->addCriterion( + TaoOntology::PROPERTY_UNIQUE_IDENTIFIER, + SupportedOperatorHelper::IN, + $uniqueIds + ); + + if ($query->getLanguageUri()) { + $searchQuery->addCriterion( + TaoOntology::PROPERTY_LANGUAGE, + SupportedOperatorHelper::EQUAL, + $query->getLanguageUri() + ); + } + + $queryBuilder->setCriteria($searchQuery); + + $result = $this->complexSearch->getGateway()->search($queryBuilder); + $resourceTypeClass = $this->ontology->getClass($query->getResourceType()); + + /** @var core_kernel_classes_Resource $translationResource */ + foreach ($result as $translationResource) { + try { + if (!$translationResource->isInstanceOf($resourceTypeClass)) { + continue; + } + + $output[] = $this->factory->create($originResource, $translationResource); + } catch (Throwable $exception) { + $this->logger->warning( + sprintf( + 'Cannot read translation status for [uniqueIds=%s, translationResourceId=%s]: %s - %s', + $uniqueIds, + $translationResource->getUri(), + $exception->getMessage(), + $exception->getTraceAsString() + ) + ); + } + } + + return new ResourceCollection(...$output); + } +} diff --git a/models/classes/Translation/Service/ResourceMetadataPopulateService.php b/models/classes/Translation/Service/ResourceMetadataPopulateService.php new file mode 100644 index 0000000000..2165126f35 --- /dev/null +++ b/models/classes/Translation/Service/ResourceMetadataPopulateService.php @@ -0,0 +1,80 @@ +ontology = $ontology; + } + + public function addMetadata(string $resourceType, string $metadataUri): void + { + $this->metadata[$resourceType] ??= []; + $this->metadata[$resourceType] = array_unique(array_merge($this->metadata[$resourceType], [$metadataUri])); + } + + public function populate(AbstractResource $resource, core_kernel_classes_Resource $originResource): void + { + $valueProperty = $this->ontology->getProperty(OntologyRdf::RDF_VALUE); + + $parentClasses = $originResource->getParentClassesIds(); + $resourceType = array_pop($parentClasses); + + foreach ($this->metadata[$resourceType] ?? [] as $metadataUri) { + $resource->addMetadataUri($metadataUri); + } + + foreach ($resource->getMetadataUris() as $metadataUri) { + $values = $originResource->getPropertyValues($this->ontology->getProperty($metadataUri)); + + if (empty($values)) { + continue; + } + + $literal = null; + $value = empty($values) ? null : current($values); + + if ($value) { + $literalProperty = $this->ontology->getProperty($value); + $oneValue = $literalProperty->getOnePropertyValue($valueProperty); + + if ($oneValue instanceof core_kernel_classes_Literal) { + $literal = $oneValue->literal; + } + } + + $resource->addMetadata($metadataUri, $value, $literal); + } + } +} diff --git a/models/classes/Translation/Service/ResourceTranslatableRetriever.php b/models/classes/Translation/Service/ResourceTranslatableRetriever.php new file mode 100644 index 0000000000..db39b69a1d --- /dev/null +++ b/models/classes/Translation/Service/ResourceTranslatableRetriever.php @@ -0,0 +1,81 @@ +resourceTranslatableRepository = $resourceTranslationRepository; + $this->ontology = $ontology; + } + + public function getByRequest(ServerRequestInterface $request): ResourceCollection + { + $queryParams = $request->getQueryParams(); + $id = $queryParams['id'] ?? null; + + if (empty($id)) { + throw new ResourceTranslationException('Resource id is required'); + } + + $resource = $this->ontology->getResource($id); + + $parentClassIds = $resource->getParentClassesIds(); + $resourceType = array_pop($parentClassIds); + + if (empty($resourceType)) { + throw new ResourceTranslationException(sprintf('Resource %s must have a resource type', $id)); + } + + $uniqueId = $resource->getUniquePropertyValue( + $this->ontology->getProperty(TaoOntology::PROPERTY_UNIQUE_IDENTIFIER) + ); + + if (empty($uniqueId)) { + throw new ResourceTranslationException(sprintf('Resource %s must have a unique identifier', $id)); + } + + return $this->resourceTranslatableRepository->find( + new ResourceTranslatableQuery( + $resourceType, + [ + (string)$uniqueId + ] + ) + ); + } +} diff --git a/models/classes/Translation/Service/ResourceTranslationRetriever.php b/models/classes/Translation/Service/ResourceTranslationRetriever.php new file mode 100644 index 0000000000..c64d951e22 --- /dev/null +++ b/models/classes/Translation/Service/ResourceTranslationRetriever.php @@ -0,0 +1,81 @@ +ontology = $ontology; + $this->resourceTranslationRepository = $resourceTranslationRepository; + } + + public function getByRequest(ServerRequestInterface $request): ResourceCollection + { + $queryParams = $request->getQueryParams(); + $languageUri = $queryParams['languageUri'] ?? null; + $id = $queryParams['id'] ?? null; + + if (empty($id)) { + throw new ResourceTranslationException('Resource id is required'); + } + + $resource = $this->ontology->getResource($id); + + $parentClassIds = $resource->getParentClassesIds(); + $resourceType = array_pop($parentClassIds); + + if (empty($resourceType)) { + throw new ResourceTranslationException(sprintf('Resource %s must have a resource type', $id)); + } + + $uniqueId = $resource->getUniquePropertyValue( + $this->ontology->getProperty(TaoOntology::PROPERTY_UNIQUE_IDENTIFIER) + ); + + if (empty($uniqueId)) { + throw new ResourceTranslationException(sprintf('Resource %s must have a unique identifier', $id)); + } + + return $this->resourceTranslationRepository->find( + new ResourceTranslationQuery( + $resourceType, + [(string)$uniqueId], + $languageUri + ) + ); + } +} diff --git a/models/classes/Translation/Service/TranslationCreationService.php b/models/classes/Translation/Service/TranslationCreationService.php new file mode 100644 index 0000000000..add93e8f83 --- /dev/null +++ b/models/classes/Translation/Service/TranslationCreationService.php @@ -0,0 +1,280 @@ +ontology = $ontology; + $this->resourceTranslatableRepository = $resourceTranslatableRepository; + $this->resourceTranslationRepository = $resourceTranslationRepository; + $this->languageRepository = $languageRepository; + $this->logger = $logger; + } + + public function setOntologyClassService(string $resourceType, OntologyClassService $ontologyClassService): void + { + $this->ontologyClassServices[$resourceType] = $ontologyClassService; + } + + public function addPostCreation(string $resourceType, callable $callable): void + { + $this->callables[$resourceType] = $this->callables[$resourceType] ?? []; + $this->callables[$resourceType][] = $callable; + } + + public function createByRequest(ServerRequestInterface $request): core_kernel_classes_Resource + { + $requestParams = $request->getParsedBody(); + $id = $requestParams['id'] ?? null; + + if (empty($id)) { + throw new ResourceTranslationException('Resource id is required'); + } + + $resource = $this->ontology->getResource($id); + + if (!$resource->exists()) { + throw new ResourceTranslationException( + sprintf( + 'Resource %s does not exist', + $id + ) + ); + } + + /** @var core_kernel_classes_Resource $translationType */ + $translationType = $resource->getUniquePropertyValue( + $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_TYPE) + ); + + if ($translationType->getUri() !== TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL) { + throw new ResourceTranslationException( + sprintf( + 'Resource %s is not the original', + $id + ) + ); + } + + $parentClassIds = $resource->getParentClassesIds(); + $resourceType = array_pop($parentClassIds); + + if (empty($resourceType)) { + throw new ResourceTranslationException(sprintf('Resource %s must have a resource type', $id)); + } + + $uniqueId = $resource->getUniquePropertyValue( + $this->ontology->getProperty(TaoOntology::PROPERTY_UNIQUE_IDENTIFIER) + ); + + if (empty($uniqueId)) { + throw new ResourceTranslationException(sprintf('Resource %s must have a unique identifier', $id)); + } + + if (empty($requestParams['languageUri'])) { + throw new ResourceTranslationException('Parameter languageUri is mandatory'); + } + + return $this->create( + new CreateTranslationCommand( + $resourceType, + (string)$uniqueId, + $requestParams['languageUri'] + ) + ); + } + + public function create(CreateTranslationCommand $command): core_kernel_classes_Resource + { + try { + return $this->doCreate($command); + } catch (Throwable $exception) { + $this->logger->error( + sprintf( + 'Could not translate [uniqueId=%s, resourceType=%s, language=%s] (%s): %s', + $command->getUniqueId(), + $command->getResourceType(), + $command->getLanguageUri(), + get_class($exception), + $exception->getMessage() + ) + ); + + throw $exception; + } + } + + private function doCreate(CreateTranslationCommand $command): core_kernel_classes_Resource + { + $translations = $this->resourceTranslationRepository->find( + new ResourceTranslationQuery( + $command->getResourceType(), + [$command->getUniqueId()], + $command->getLanguageUri() + ) + ); + + if ($translations->count() > 0) { + throw new ResourceTranslationException( + sprintf( + 'Translation already exists for [uniqueId=%s, locale=%s]', + $command->getUniqueId(), + $command->getLanguageUri() + ) + ); + } + + $resources = $this->resourceTranslatableRepository->find( + new ResourceTranslatableQuery( + $command->getResourceType(), + [$command->getUniqueId()] + ) + ); + + if ($resources->count() === 0) { + throw new ResourceTranslationException( + sprintf( + 'There is not translatable resource for [uniqueId=%s]', + $command->getUniqueId() + ) + ); + } + + /** @var ResourceTranslatable $resource */ + $resource = $resources->current(); + + if (!$resource->isReadyForTranslation()) { + throw new ResourceTranslationException( + sprintf( + 'Resource [uniqueId=%s] is not ready for translation', + $command->getUniqueId() + ) + ); + } + + $existingLanguages = $this->languageRepository->findAvailableLanguagesByUsage(); + $language = null; + + /** @var Language $language */ + foreach ($existingLanguages as $existingLanguage) { + if ($existingLanguage->getUri() === $command->getLanguageUri()) { + $language = $existingLanguage; + } + } + + if (!$language) { + throw new ResourceTranslationException( + sprintf( + 'Language %s does not exist', + $command->getLanguageUri() + ) + ); + } + + if ($resource->getLanguageUri() === $language->getUri()) { + throw new ResourceTranslationException( + sprintf( + 'Cannot translate to original language %s', + $command->getLanguageUri() + ) + ); + } + + $instance = $this->ontology->getResource($resource->getResourceUri()); + $types = $instance->getTypes(); + $type = array_pop($types); + + $clonedInstance = $this->getOntologyService($command->getResourceType())->cloneInstance($instance, $type); + + $clonedInstance->setLabel(sprintf('%s (%s)', $instance->getLabel(), $language->getCode())); + + $clonedInstance->editPropertyValues( + $this->ontology->getProperty(TaoOntology::PROPERTY_LANGUAGE), + $language->getUri() + ); + + $clonedInstance->editPropertyValues( + $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_TYPE), + TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION + ); + + $clonedInstance->editPropertyValues( + $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_PROGRESS), + TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_PENDING + ); + + foreach ($this->callables[$command->getResourceType()] ?? [] as $callable) { + $clonedInstance = $callable($clonedInstance); + } + + return $clonedInstance; + } + + private function getOntologyService(string $resourceType): OntologyClassService + { + $service = $this->ontologyClassServices[$resourceType] ?? null; + + if ($service) { + return $service; + } + + throw new ResourceTranslationException( + sprintf( + 'There is no OntologyClassService for resource type %s', + $resourceType + ) + ); + } +} diff --git a/models/classes/Translation/Service/TranslationUpdateService.php b/models/classes/Translation/Service/TranslationUpdateService.php new file mode 100644 index 0000000000..7508fabdad --- /dev/null +++ b/models/classes/Translation/Service/TranslationUpdateService.php @@ -0,0 +1,90 @@ +ontology = $ontology; + $this->logger = $logger; + } + + public function update(UpdateTranslationCommand $command): core_kernel_classes_Resource + { + try { + $instance = $this->ontology->getResource($command->getResourceUri()); + + if (!$instance->exists()) { + throw new ResourceTranslationException( + sprintf( + 'Resource %s does not exist', + $command->getResourceUri() + ) + ); + } + + $translationType = $instance->getOnePropertyValue( + $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_TYPE) + ); + + if ($translationType->getUri() !== TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION) { + throw new ResourceTranslationException( + sprintf( + 'Resource %s is not a translation', + $command->getResourceUri() + ) + ); + } + + $instance->editPropertyValues( + $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_PROGRESS), + $command->getProgressUri() + ); + + return $instance; + } catch (Throwable $exception) { + $this->logger->error( + sprintf( + 'Could not update translation status of [resourceUri=%s] (%s): %s', + $command->getResourceUri(), + get_class($exception), + $exception->getMessage() + ) + ); + + throw $exception; + } + } +} diff --git a/models/classes/Translation/ServiceProvider/TranslationServiceProvider.php b/models/classes/Translation/ServiceProvider/TranslationServiceProvider.php new file mode 100644 index 0000000000..dcafea5a52 --- /dev/null +++ b/models/classes/Translation/ServiceProvider/TranslationServiceProvider.php @@ -0,0 +1,141 @@ +services(); + $services->set(ResourceMetadataPopulateService::class, ResourceMetadataPopulateService::class) + ->args( + [ + service(Ontology::SERVICE_ID), + ] + ); + + $services->set(ResourceTranslationRepository::class, ResourceTranslationRepository::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(ComplexSearchService::SERVICE_ID), + service(ResourceTranslatableRepository::class), + service(ResourceTranslationFactory::class), + service(LoggerService::SERVICE_ID), + ] + ); + + $services->set(ResourceTranslatableRepository::class, ResourceTranslatableRepository::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(ComplexSearchService::SERVICE_ID), + service(ResourceTranslatableFactory::class) + ] + ); + + $services->set(ResourceTranslationFactory::class, ResourceTranslationFactory::class) + ->args( + [ + service(ResourceMetadataPopulateService::class) + ] + ); + + $services->set(ResourceTranslatableFactory::class, ResourceTranslatableFactory::class) + ->args( + [ + service(ResourceMetadataPopulateService::class) + ] + ); + + $services->set(ResourceTranslationRetriever::class, ResourceTranslationRetriever::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(ResourceTranslationRepository::class) + ] + ) + ->public(); + + $services->set(ResourceTranslatableRetriever::class, ResourceTranslatableRetriever::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(ResourceTranslatableRepository::class) + ] + ) + ->public(); + + $services + ->set(TranslationFormModifier::class, TranslationFormModifier::class) + ->args([ + service(FeatureFlagChecker::class), + ]); + + $services + ->set(TranslationCreationService::class, TranslationCreationService::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(ResourceTranslatableRepository::class), + service(ResourceTranslationRepository::class), + service(LanguageRepositoryInterface::class), + service(LoggerService::SERVICE_ID), + ] + ) + ->public(); + + $services + ->set(TranslationUpdateService::class, TranslationUpdateService::class) + ->args( + [ + service(Ontology::SERVICE_ID), + service(LoggerService::SERVICE_ID), + ] + ) + ->public(); + } +} diff --git a/models/classes/featureFlag/README.md b/models/classes/featureFlag/README.md index 1f0877c874..3936e2ddf6 100644 --- a/models/classes/featureFlag/README.md +++ b/models/classes/featureFlag/README.md @@ -71,20 +71,28 @@ sections that have to be disabled/enabled based on feature flag. return new oat\tao\model\menu\SectionVisibilityFilter(array( 'featureFlagSections' => [ - 'sectionName' => [ + 'sectionName1' => [ + 'FEATURE_FLAG_01' + ], + 'sectionName2/actionName' => [ 'FEATURE_FLAG_01' ] ], 'featureFlagSectionsToHide' => [ - 'sectionNameToHide' => [ + 'sectionNameToHide1' => [ + 'FEATURE_FLAG_02' + ], + 'sectionNameToHide2/actionName' => [ 'FEATURE_FLAG_02' ] ] )); ``` -This configuration will display `sectionName` when `FEATURE_FLAG_01` is enabled. +This configuration will display `sectionName1` when `FEATURE_FLAG_01` is enabled. +This configuration will display `actionName` from `sectionName2` when `FEATURE_FLAG_01` is enabled. This configuration will hide `sectionNameToHide` when `FEATURE_FLAG_02` is enabled. +This configuration will display `actionName` from `sectionNameToHide` when `FEATURE_FLAG_02` is enabled. ### Override configs on execution time diff --git a/models/classes/form/Modifier/AbstractFormModifier.php b/models/classes/form/Modifier/AbstractFormModifier.php new file mode 100644 index 0000000000..752c39bf5e --- /dev/null +++ b/models/classes/form/Modifier/AbstractFormModifier.php @@ -0,0 +1,45 @@ +modifiers, true)) { + $this->modifiers[] = $modifier; + } + } + + public function modify(tao_helpers_form_Form $form, array $options = []): void + { + foreach ($this->modifiers as $modifier) { + $modifier->modify($form, $options); + } + } +} diff --git a/models/classes/menu/SectionVisibilityFilter.php b/models/classes/menu/SectionVisibilityFilter.php index e236b6435e..4e19bc0cdd 100644 --- a/models/classes/menu/SectionVisibilityFilter.php +++ b/models/classes/menu/SectionVisibilityFilter.php @@ -22,7 +22,6 @@ namespace oat\tao\model\menu; -use LogicException; use oat\generis\model\GenerisRdf; use oat\oatbox\service\ConfigurableService; use oat\tao\model\featureFlag\FeatureFlagChecker; @@ -33,6 +32,7 @@ class SectionVisibilityFilter extends ConfigurableService implements SectionVisibilityFilterInterface { + private const SECTION_PATH_SEPARATOR = '/'; public const SERVICE_ID = 'tao/SectionVisibilityFilter'; public const OPTION_FEATURE_FLAG_SECTIONS = 'featureFlagSections'; public const OPTION_FEATURE_FLAG_SECTIONS_TO_HIDE = 'featureFlagSectionsToHide'; @@ -44,10 +44,7 @@ class SectionVisibilityFilter extends ConfigurableService implements SectionVisi public const SIMPLE_INTERFACE_MODE_HIDDEN_SECTIONS = []; - /** - * @throws LogicException - */ - public function isVisible(string $section): bool + public function isVisible(string $sectionPath): bool { $sections = $this->getOption(self::OPTION_FEATURE_FLAG_SECTIONS, []); $sectionToHide = array_merge_recursive( @@ -55,13 +52,13 @@ public function isVisible(string $section): bool self::DEFAULT_FEATURE_FLAG_SECTIONS_TO_HIDE ); - foreach ($sectionToHide[$section] ?? [] as $featureFlag) { + foreach ($sectionToHide[$sectionPath] ?? [] as $featureFlag) { if ($this->getFeatureFlagChecker()->isEnabled($featureFlag)) { return false; } } - foreach ($sections[$section] ?? [] as $featureFlag) { + foreach ($sections[$sectionPath] ?? [] as $featureFlag) { if (!$this->getFeatureFlagChecker()->isEnabled($featureFlag)) { return false; } @@ -73,7 +70,7 @@ public function isVisible(string $section): bool $userSettings->getSetting( UserSettingsInterface::INTERFACE_MODE ) === GenerisRdf::PROPERTY_USER_INTERFACE_MODE_SIMPLE - && in_array($section, self::SIMPLE_INTERFACE_MODE_HIDDEN_SECTIONS) + && in_array($sectionPath, self::SIMPLE_INTERFACE_MODE_HIDDEN_SECTIONS, true) ) { return false; } @@ -81,11 +78,16 @@ public function isVisible(string $section): bool return true; } - public function hideSectionByFeatureFlag(string $section, string $featureFlag): void + public function createSectionPath(array $segments): string + { + return implode(self::SECTION_PATH_SEPARATOR, $segments); + } + + public function hideSectionByFeatureFlag(string $sectionPath, string $featureFlag): void { $options = $this->getOption(SectionVisibilityFilter::OPTION_FEATURE_FLAG_SECTIONS_TO_HIDE, []); - $options[$section] = array_merge( - $options[$section] ?? [], + $options[$sectionPath] = array_merge( + $options[$sectionPath] ?? [], [ $featureFlag ] @@ -93,6 +95,18 @@ public function hideSectionByFeatureFlag(string $section, string $featureFlag): $this->setOption(SectionVisibilityFilter::OPTION_FEATURE_FLAG_SECTIONS_TO_HIDE, $options); } + public function showSectionByFeatureFlag(string $sectionPath, string $featureFlag): void + { + $options = $this->getOption(SectionVisibilityFilter::OPTION_FEATURE_FLAG_SECTIONS, []); + $options[$sectionPath] = array_merge( + $options[$sectionPath] ?? [], + [ + $featureFlag + ] + ); + $this->setOption(SectionVisibilityFilter::OPTION_FEATURE_FLAG_SECTIONS, $options); + } + private function getFeatureFlagChecker(): FeatureFlagCheckerInterface { return $this->getServiceManager()->getContainer()->get(FeatureFlagChecker::class); diff --git a/models/classes/menu/SectionVisibilityFilterInterface.php b/models/classes/menu/SectionVisibilityFilterInterface.php index bf7c86b035..77e0f3357f 100644 --- a/models/classes/menu/SectionVisibilityFilterInterface.php +++ b/models/classes/menu/SectionVisibilityFilterInterface.php @@ -24,5 +24,5 @@ interface SectionVisibilityFilterInterface { - public function isVisible(string $section): bool; + public function isVisible(string $sectionPath): bool; } diff --git a/models/ontology/tao.rdf b/models/ontology/tao.rdf index 912b1cba41..9b933bd752 100644 --- a/models/ontology/tao.rdf +++ b/models/ontology/tao.rdf @@ -17,6 +17,14 @@ + + + + + + + + @@ -340,4 +348,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/unit/models/classes/Translation/Command/CreateTranslationCommandTest.php b/test/unit/models/classes/Translation/Command/CreateTranslationCommandTest.php new file mode 100644 index 0000000000..fcd44e4335 --- /dev/null +++ b/test/unit/models/classes/Translation/Command/CreateTranslationCommandTest.php @@ -0,0 +1,44 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Command; + +use oat\tao\model\Translation\Command\CreateTranslationCommand; +use PHPUnit\Framework\TestCase; + +class CreateTranslationCommandTest extends TestCase +{ + public function testConstructorAndGetters(): void + { + $resourceType = 'http://www.tao.lu/Ontologies/TAO.rdf#SomeType'; + $uniqueId = 'id1'; + $languageUri = 'http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR'; + + $command = new CreateTranslationCommand($resourceType, $uniqueId, $languageUri); + + $this->assertSame($resourceType, $command->getResourceType()); + $this->assertSame($uniqueId, $command->getUniqueId()); + $this->assertSame($languageUri, $command->getLanguageUri()); + } +} diff --git a/test/unit/models/classes/Translation/Command/UpdateTranslationCommandTest.php b/test/unit/models/classes/Translation/Command/UpdateTranslationCommandTest.php new file mode 100644 index 0000000000..42deec4a4f --- /dev/null +++ b/test/unit/models/classes/Translation/Command/UpdateTranslationCommandTest.php @@ -0,0 +1,45 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Command; + +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Command\UpdateTranslationCommand; +use PHPUnit\Framework\TestCase; + +class UpdateTranslationCommandTest extends TestCase +{ + public function testConstructorAndGetters(): void + { + $resourceUri = 'http://example.com/resource/1'; + + $command = new UpdateTranslationCommand( + $resourceUri, + TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_TRANSLATED + ); + + $this->assertSame($resourceUri, $command->getResourceUri()); + $this->assertSame(TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_TRANSLATED, $command->getProgressUri()); + } +} diff --git a/test/unit/models/classes/Translation/Entity/ResourceCollectionTest.php b/test/unit/models/classes/Translation/Entity/ResourceCollectionTest.php new file mode 100644 index 0000000000..27fe187f78 --- /dev/null +++ b/test/unit/models/classes/Translation/Entity/ResourceCollectionTest.php @@ -0,0 +1,48 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Entity; + +use oat\tao\model\Translation\Entity\ResourceCollection; +use PHPUnit\Framework\TestCase; + +class ResourceCollectionTest extends TestCase +{ + private ResourceCollection $sut; + + public function setUp(): void + { + $this->sut = new ResourceCollection(...[]); + } + + public function testGetters(): void + { + $this->assertSame( + [ + 'resources' => [] + ], + $this->sut->jsonSerialize() + ); + } +} diff --git a/test/unit/models/classes/Translation/Entity/ResourceTranslatableTest.php b/test/unit/models/classes/Translation/Entity/ResourceTranslatableTest.php new file mode 100644 index 0000000000..505559a6e6 --- /dev/null +++ b/test/unit/models/classes/Translation/Entity/ResourceTranslatableTest.php @@ -0,0 +1,70 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Entity; + +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Entity\ResourceTranslatable; +use PHPUnit\Framework\TestCase; + +class ResourceTranslatableTest extends TestCase +{ + private ResourceTranslatable $sut; + + public function setUp(): void + { + $this->sut = new ResourceTranslatable('resourceUri', 'resourceLabel'); + $this->sut->addMetadata(TaoOntology::PROPERTY_LANGUAGE, 'languageUri', 'en-US'); + $this->sut->addMetadata(TaoOntology::PROPERTY_UNIQUE_IDENTIFIER, 'abc123', null); + $this->sut->addMetadata(TaoOntology::PROPERTY_TRANSLATION_STATUS, 'statusUri', null); + } + + public function testGetters(): void + { + $this->assertSame('en-US', $this->sut->getLanguageCode()); + $this->assertSame('languageUri', $this->sut->getLanguageUri()); + $this->assertSame('statusUri', $this->sut->getStatusUri()); + $this->assertSame( + [ + 'resourceUri' => 'resourceUri', + 'resourceLabel' => 'resourceLabel', + 'metadata' => [ + TaoOntology::PROPERTY_LANGUAGE => [ + 'value' => 'languageUri', + 'literal' => 'en-US', + ], + TaoOntology::PROPERTY_UNIQUE_IDENTIFIER => [ + 'value' => 'abc123', + 'literal' => null, + ], + TaoOntology::PROPERTY_TRANSLATION_STATUS => [ + 'value' => 'statusUri', + 'literal' => null, + ] + ], + ], + $this->sut->jsonSerialize() + ); + } +} diff --git a/test/unit/models/classes/Translation/Entity/ResourceTranslationTest.php b/test/unit/models/classes/Translation/Entity/ResourceTranslationTest.php new file mode 100644 index 0000000000..51cb86dec4 --- /dev/null +++ b/test/unit/models/classes/Translation/Entity/ResourceTranslationTest.php @@ -0,0 +1,72 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Entity; + +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Entity\ResourceTranslation; +use PHPUnit\Framework\TestCase; + +class ResourceTranslationTest extends TestCase +{ + private ResourceTranslation $sut; + + public function setUp(): void + { + $this->sut = new ResourceTranslation('resourceUri', 'resourceLabel'); + $this->sut->setOriginResourceUri('originResourceUri'); + $this->sut->addMetadata(TaoOntology::PROPERTY_LANGUAGE, 'languageUri', 'en-US'); + $this->sut->addMetadata(TaoOntology::PROPERTY_UNIQUE_IDENTIFIER, 'abc123', null); + $this->sut->addMetadata(TaoOntology::PROPERTY_TRANSLATION_PROGRESS, 'progressUri', null); + } + + public function testGetters(): void + { + $this->assertSame('en-US', $this->sut->getLanguageCode()); + $this->assertSame('languageUri', $this->sut->getLanguageUri()); + $this->assertSame('progressUri', $this->sut->getProgressUri()); + $this->assertSame( + [ + 'originResourceUri' => 'originResourceUri', + 'resourceUri' => 'resourceUri', + 'resourceLabel' => 'resourceLabel', + 'metadata' => [ + TaoOntology::PROPERTY_LANGUAGE => [ + 'value' => 'languageUri', + 'literal' => 'en-US', + ], + TaoOntology::PROPERTY_UNIQUE_IDENTIFIER => [ + 'value' => 'abc123', + 'literal' => null, + ], + TaoOntology::PROPERTY_TRANSLATION_PROGRESS => [ + 'value' => 'progressUri', + 'literal' => null, + ] + ], + ], + $this->sut->jsonSerialize() + ); + } +} diff --git a/test/unit/models/classes/Translation/Factory/ResourceTranslatableFactoryTest.php b/test/unit/models/classes/Translation/Factory/ResourceTranslatableFactoryTest.php new file mode 100644 index 0000000000..81e507fd2d --- /dev/null +++ b/test/unit/models/classes/Translation/Factory/ResourceTranslatableFactoryTest.php @@ -0,0 +1,71 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Factory; + +use core_kernel_classes_Resource; +use oat\tao\model\Translation\Entity\ResourceTranslatable; +use oat\tao\model\Translation\Factory\ResourceTranslatableFactory; +use oat\tao\model\Translation\Service\ResourceMetadataPopulateService; +use oat\tao\model\TaoOntology; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ResourceTranslatableFactoryTest extends TestCase +{ + private ResourceTranslatableFactory $sut; + + /** @var ResourceMetadataPopulateService|MockObject */ + private $metadataPopulateService; + + /** @var core_kernel_classes_Resource|MockObject */ + private $originResource; + + protected function setUp(): void + { + $this->metadataPopulateService = $this->createMock(ResourceMetadataPopulateService::class); + $this->originResource = $this->createMock(core_kernel_classes_Resource::class); + $this->sut = new ResourceTranslatableFactory($this->metadataPopulateService); + } + + public function testCreateReturnsResourceTranslatable(): void + { + $uri = 'http://example.com/resource'; + $label = 'Test Resource'; + + $this->originResource->method('getUri')->willReturn($uri); + $this->originResource->method('getLabel')->willReturn($label); + + $this->metadataPopulateService + ->expects($this->once()) + ->method('populate'); + + $resource = $this->sut->create($this->originResource); + + $this->assertInstanceOf(ResourceTranslatable::class, $resource); + $this->assertEquals($uri, $resource->getResourceUri()); + $this->assertEquals($label, $resource->getResourceLabel()); + $this->assertContains(TaoOntology::PROPERTY_TRANSLATION_STATUS, $resource->getMetadataUris()); + } +} diff --git a/test/unit/models/classes/Translation/Factory/ResourceTranslationFactoryTest.php b/test/unit/models/classes/Translation/Factory/ResourceTranslationFactoryTest.php new file mode 100644 index 0000000000..dfbc4123ad --- /dev/null +++ b/test/unit/models/classes/Translation/Factory/ResourceTranslationFactoryTest.php @@ -0,0 +1,79 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Factory; + +use core_kernel_classes_Resource; +use oat\tao\model\Translation\Entity\ResourceTranslation; +use oat\tao\model\Translation\Entity\ResourceTranslatable; +use oat\tao\model\Translation\Factory\ResourceTranslationFactory; +use oat\tao\model\Translation\Service\ResourceMetadataPopulateService; +use oat\tao\model\TaoOntology; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ResourceTranslationFactoryTest extends TestCase +{ + private ResourceTranslationFactory $sut; + + /** @var ResourceMetadataPopulateService|MockObject */ + private $metadataPopulateService; + + /** @var ResourceTranslatable|MockObject */ + private $resourceTranslatable; + + /** @var core_kernel_classes_Resource|MockObject */ + private $translationResource; + + protected function setUp(): void + { + $this->metadataPopulateService = $this->createMock(ResourceMetadataPopulateService::class); + $this->resourceTranslatable = $this->createMock(ResourceTranslatable::class); + $this->translationResource = $this->createMock(core_kernel_classes_Resource::class); + $this->sut = new ResourceTranslationFactory($this->metadataPopulateService); + } + + public function testCreateReturnsResourceTranslation(): void + { + $originUri = 'http://example.com/origin'; + $translationUri = 'http://example.com/translation'; + $label = 'Test Resource'; + + $this->resourceTranslatable->method('getResourceUri')->willReturn($originUri); + $this->translationResource->method('getUri')->willReturn($translationUri); + $this->translationResource->method('getLabel')->willReturn($label); + + $this->metadataPopulateService + ->expects($this->once()) + ->method('populate'); + + $resource = $this->sut->create($this->resourceTranslatable, $this->translationResource); + + $this->assertInstanceOf(ResourceTranslation::class, $resource); + $this->assertEquals($translationUri, $resource->getResourceUri()); + $this->assertEquals($label, $resource->getResourceLabel()); + $this->assertEquals($originUri, $resource->getOriginResourceUri()); + $this->assertContains(TaoOntology::PROPERTY_TRANSLATION_PROGRESS, $resource->getMetadataUris()); + } +} diff --git a/test/unit/models/classes/Translation/Form/Modifier/TranslationFormModifierTest.php b/test/unit/models/classes/Translation/Form/Modifier/TranslationFormModifierTest.php new file mode 100644 index 0000000000..4ec2adbf80 --- /dev/null +++ b/test/unit/models/classes/Translation/Form/Modifier/TranslationFormModifierTest.php @@ -0,0 +1,157 @@ +form = $this->createMock(tao_helpers_form_Form::class); + $this->featureFlagChecker = $this->createMock(FeatureFlagCheckerInterface::class); + $this->sut = new TranslationFormModifier($this->featureFlagChecker); + } + + public function testModifyTranslationDisabled(): void + { + $this->featureFlagChecker + ->expects($this->once()) + ->method('isEnabled') + ->with('FEATURE_TRANSLATION_ENABLED') + ->willReturn(false); + + $this->form + ->expects($this->exactly(5)) + ->method('removeElement') + ->withConsecutive( + [tao_helpers_Uri::encode(TaoOntology::PROPERTY_UNIQUE_IDENTIFIER)], + [tao_helpers_Uri::encode(TaoOntology::PROPERTY_LANGUAGE)], + [tao_helpers_Uri::encode(TaoOntology::PROPERTY_TRANSLATION_TYPE)], + [tao_helpers_Uri::encode(TaoOntology::PROPERTY_TRANSLATION_STATUS)], + [tao_helpers_Uri::encode(TaoOntology::PROPERTY_TRANSLATION_PROGRESS)], + ); + + $this->sut->modify($this->form); + } + + /** + * @dataProvider modifyTranslationEnabledDataProvider + */ + public function testModifyTranslationEnabled(bool $developerMode, ?string $type, array $removeElements): void + { + $this->featureFlagChecker + ->expects($this->exactly(2)) + ->method('isEnabled') + ->withConsecutive( + ['FEATURE_TRANSLATION_ENABLED'], + ['FEATURE_TRANSLATION_DEVELOPER_MODE'], + ) + ->willReturnOnConsecutiveCalls(true, $developerMode); + + $this->form + ->expects($this->once()) + ->method('getValue') + ->with(tao_helpers_Uri::encode(TaoOntology::PROPERTY_TRANSLATION_TYPE)) + ->willReturn($type); + + $this->form + ->expects($this->exactly(count($removeElements))) + ->method('removeElement') + ->withConsecutive( + ...array_map( + static fn ($element) => [tao_helpers_Uri::encode($element)], + $removeElements + ) + ); + + $this->sut->modify($this->form); + } + + private function modifyTranslationEnabledDataProvider(): array + { + return [ + 'Developer Mode enabled and no type provided' => [ + 'developerMode' => true, + 'type' => null, + 'removeElements' => [ + TaoOntology::PROPERTY_TRANSLATION_PROGRESS, + TaoOntology::PROPERTY_TRANSLATION_STATUS, + ], + ], + 'Developer Mode enabled and type original' => [ + 'developerMode' => true, + 'type' => TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL, + 'removeElements' => [ + TaoOntology::PROPERTY_TRANSLATION_PROGRESS, + ], + ], + 'Developer Mode enabled and type translation' => [ + 'developerMode' => true, + 'type' => TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION, + 'removeElements' => [ + TaoOntology::PROPERTY_TRANSLATION_STATUS, + ], + ], + 'Developer Mode disabled and no type provided' => [ + 'developerMode' => false, + 'type' => null, + 'removeElements' => [ + TaoOntology::PROPERTY_TRANSLATION_TYPE, + TaoOntology::PROPERTY_TRANSLATION_PROGRESS, + TaoOntology::PROPERTY_TRANSLATION_STATUS, + ], + ], + 'Developer Mode disabled and type original' => [ + 'developerMode' => false, + 'type' => TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL, + 'removeElements' => [ + TaoOntology::PROPERTY_TRANSLATION_TYPE, + TaoOntology::PROPERTY_TRANSLATION_PROGRESS, + ], + ], + 'Developer Mode disabled and type translation' => [ + 'developerMode' => false, + 'type' => TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION, + 'removeElements' => [ + TaoOntology::PROPERTY_TRANSLATION_TYPE, + TaoOntology::PROPERTY_TRANSLATION_STATUS, + ], + ], + ]; + } +} diff --git a/test/unit/models/classes/Translation/Query/ResourceTranslatableQueryTest.php b/test/unit/models/classes/Translation/Query/ResourceTranslatableQueryTest.php new file mode 100644 index 0000000000..c719536262 --- /dev/null +++ b/test/unit/models/classes/Translation/Query/ResourceTranslatableQueryTest.php @@ -0,0 +1,42 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Query; + +use oat\tao\model\Translation\Query\ResourceTranslatableQuery; +use PHPUnit\Framework\TestCase; + +class ResourceTranslatableQueryTest extends TestCase +{ + public function testConstructorAndGetters(): void + { + $resourceType = 'http://www.tao.lu/Ontologies/TAO.rdf#AssessmentContentObject'; + $uniqueIds = ['id1', 'id2']; + + $query = new ResourceTranslatableQuery($resourceType, $uniqueIds); + + $this->assertSame($resourceType, $query->getResourceType()); + $this->assertSame($uniqueIds, $query->getUniqueIds()); + } +} diff --git a/test/unit/models/classes/Translation/Query/ResourceTranslationQueryTest.php b/test/unit/models/classes/Translation/Query/ResourceTranslationQueryTest.php new file mode 100644 index 0000000000..9df704613e --- /dev/null +++ b/test/unit/models/classes/Translation/Query/ResourceTranslationQueryTest.php @@ -0,0 +1,54 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Query; + +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Query\ResourceTranslationQuery; +use PHPUnit\Framework\TestCase; + +class ResourceTranslationQueryTest extends TestCase +{ + public function testConstructorAndGetters(): void + { + $resourceType = 'http://www.tao.lu/Ontologies/TAO.rdf#AssessmentContentObject'; + $uniqueId = 'id1'; + $languageUri = TaoOntology::PROPERTY_LANGUAGE; + + $query = new ResourceTranslationQuery($resourceType, [$uniqueId], $languageUri); + + $this->assertSame($resourceType, $query->getResourceType()); + $this->assertSame([$uniqueId], $query->getUniqueIds()); + $this->assertSame($languageUri, $query->getLanguageUri()); + } + + public function testConstructorWithoutLanguageUri(): void + { + $query = new ResourceTranslationQuery( + 'http://www.tao.lu/Ontologies/TAO.rdf#AssessmentContentObject', + ['id1'] + ); + $this->assertNull($query->getLanguageUri()); + } +} diff --git a/test/unit/models/classes/Translation/Repository/ResourceTranslatableRepositoryTest.php b/test/unit/models/classes/Translation/Repository/ResourceTranslatableRepositoryTest.php new file mode 100644 index 0000000000..a6ce9ba37e --- /dev/null +++ b/test/unit/models/classes/Translation/Repository/ResourceTranslatableRepositoryTest.php @@ -0,0 +1,158 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Repository; + +use core_kernel_classes_Resource; +use core_kernel_classes_Class; +use oat\generis\model\data\Ontology; +use oat\generis\model\kernel\persistence\smoothsql\search\ComplexSearchService; +use oat\search\QueryBuilder; +use oat\search\base\QueryInterface; +use oat\search\base\SearchGateWayInterface; +use oat\tao\model\Translation\Entity\ResourceCollection; +use oat\tao\model\Translation\Entity\ResourceTranslatable; +use oat\tao\model\Translation\Factory\ResourceTranslatableFactory; +use oat\tao\model\Translation\Query\ResourceTranslatableQuery; +use oat\tao\model\Translation\Repository\ResourceTranslatableRepository; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ResourceTranslatableRepositoryTest extends TestCase +{ + private ResourceTranslatableRepository $sut; + + /** @var Ontology|MockObject */ + private $ontology; + + /** @var ComplexSearchService|MockObject */ + private $complexSearch; + + /** @var ResourceTranslatableFactory|MockObject */ + private $factory; + + /** @var ResourceTranslatableQuery|MockObject */ + private $query; + + /** @var QueryBuilder|MockObject */ + private $queryBuilder; + + /** @var QueryInterface|MockObject */ + private $searchQuery; + + /** @var SearchGateWayInterface|MockObject */ + private $gateway; + + /** @var core_kernel_classes_Class|MockObject */ + private $resourceTypeClass; + + protected function setUp(): void + { + $this->ontology = $this->createMock(Ontology::class); + $this->complexSearch = $this->createMock(ComplexSearchService::class); + $this->factory = $this->createMock(ResourceTranslatableFactory::class); + $this->query = $this->createMock(ResourceTranslatableQuery::class); + $this->queryBuilder = $this->createMock(QueryBuilder::class); + $this->searchQuery = $this->createMock(QueryInterface::class); + $this->gateway = $this->createMock(SearchGateWayInterface::class); + $this->resourceTypeClass = $this->createMock(core_kernel_classes_Class::class); + + $this->sut = new ResourceTranslatableRepository( + $this->ontology, + $this->complexSearch, + $this->factory + ); + } + + public function testFindReturnsResourceCollection(): void + { + $resourceType = 'http://www.tao.lu/Ontologies/TAO.rdf#AssessmentContentObject'; + $uniqueIds = [ + 'id1', + 'id2' + ]; + $resource1 = $this->createMock(core_kernel_classes_Resource::class); + $resource2 = $this->createMock(core_kernel_classes_Resource::class); + $translatable1 = $this->createMock(ResourceTranslatable::class); + $translatable2 = $this->createMock(ResourceTranslatable::class); + + $this->query + ->method('getResourceType') + ->willReturn($resourceType); + $this->query + ->method('getUniqueIds') + ->willReturn($uniqueIds); + + $this->complexSearch + ->method('query') + ->willReturn($this->queryBuilder); + $this->complexSearch + ->method('searchType') + ->willReturn($this->searchQuery); + $this->complexSearch + ->method('getGateway') + ->willReturn($this->gateway); + + $this->gateway + ->expects($this->once()) + ->method('search') + ->with($this->queryBuilder) + ->willReturn( + [ + $resource1, + $resource2 + ] + ); + + $this->ontology + ->method('getClass') + ->with($resourceType) + ->willReturn($this->resourceTypeClass); + + $resource1 + ->method('isInstanceOf') + ->with($this->resourceTypeClass) + ->willReturn(true); + $resource2 + ->method('isInstanceOf') + ->with($this->resourceTypeClass) + ->willReturn(true); + + $this->factory + ->method('create') + ->willReturnMap( + [ + [$resource1, $translatable1], + [$resource2, $translatable2] + ] + ); + + $result = $this->sut->find($this->query); + + $this->assertInstanceOf(ResourceCollection::class, $result); + $this->assertCount(2, $result); + $this->assertContains($translatable1, $result); + $this->assertContains($translatable2, $result); + } +} diff --git a/test/unit/models/classes/Translation/Repository/ResourceTranslationRepositoryTest.php b/test/unit/models/classes/Translation/Repository/ResourceTranslationRepositoryTest.php new file mode 100644 index 0000000000..37041b261c --- /dev/null +++ b/test/unit/models/classes/Translation/Repository/ResourceTranslationRepositoryTest.php @@ -0,0 +1,163 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Repository; + +use core_kernel_classes_Class; +use core_kernel_classes_Resource; +use Exception; +use oat\generis\model\data\Ontology; +use oat\generis\model\kernel\persistence\smoothsql\search\ComplexSearchService; +use oat\search\QueryBuilder; +use oat\search\base\QueryInterface; +use oat\search\base\SearchGateWayInterface; +use oat\tao\model\Translation\Entity\ResourceCollection; +use oat\tao\model\Translation\Entity\ResourceTranslatable; +use oat\tao\model\Translation\Entity\ResourceTranslation; +use oat\tao\model\Translation\Factory\ResourceTranslationFactory; +use oat\tao\model\Translation\Query\ResourceTranslatableQuery; +use oat\tao\model\Translation\Query\ResourceTranslationQuery; +use oat\tao\model\Translation\Repository\ResourceTranslatableRepository; +use oat\tao\model\Translation\Repository\ResourceTranslationRepository; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +class ResourceTranslationRepositoryTest extends TestCase +{ + private ResourceTranslationRepository $sut; + + /** @var Ontology|MockObject */ + private $ontology; + + /** @var ComplexSearchService|MockObject */ + private $complexSearch; + + /** @var ResourceTranslatableRepository|MockObject */ + private $resourceTranslatableRepository; + + /** @var ResourceTranslationFactory|MockObject */ + private $factory; + + /** @var LoggerInterface|MockObject */ + private $logger; + + /** @var QueryBuilder|MockObject */ + private $queryBuilder; + + /** @var QueryInterface|MockObject */ + private $searchQuery; + + /** @var SearchGateWayInterface|MockObject */ + private $gateway; + + /** @var core_kernel_classes_Class|MockObject */ + private $resourceTypeClass; + + protected function setUp(): void + { + $this->ontology = $this->createMock(Ontology::class); + $this->complexSearch = $this->createMock(ComplexSearchService::class); + $this->resourceTranslatableRepository = $this->createMock(ResourceTranslatableRepository::class); + $this->factory = $this->createMock(ResourceTranslationFactory::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->queryBuilder = $this->createMock(QueryBuilder::class); + $this->searchQuery = $this->createMock(QueryInterface::class); + $this->gateway = $this->createMock(SearchGateWayInterface::class); + $this->resourceTypeClass = $this->createMock(core_kernel_classes_Class::class); + + $this->sut = new ResourceTranslationRepository( + $this->ontology, + $this->complexSearch, + $this->resourceTranslatableRepository, + $this->factory, + $this->logger + ); + } + + public function testFindReturnsResourceCollection(): void + { + $resourceType = 'http://www.tao.lu/Ontologies/TAO.rdf#AssessmentContentObject'; + $uniqueIds = ['id1']; + $translatable1 = $this->createMock(ResourceTranslatable::class); + $translationResource1 = $this->createMock(core_kernel_classes_Resource::class); + $translation1 = $this->createMock(ResourceTranslation::class); + + $query = $this->createMock(ResourceTranslationQuery::class); + $query + ->method('getResourceType') + ->willReturn($resourceType); + $query + ->method('getUniqueIds') + ->willReturn($uniqueIds); + + $this->resourceTranslatableRepository + ->method('find') + ->willReturn(new ResourceCollection($translatable1)); + + $this->complexSearch + ->method('query') + ->willReturn($this->queryBuilder); + $this->complexSearch + ->method('searchType') + ->willReturn($this->searchQuery); + $this->complexSearch + ->method('getGateway') + ->willReturn($this->gateway); + + $this->gateway + ->expects($this->once()) + ->method('search') + ->with($this->queryBuilder) + ->willReturn( + [ + $translationResource1 + ] + ); + + $this->ontology + ->method('getClass') + ->with($resourceType) + ->willReturn($this->resourceTypeClass); + + $translationResource1 + ->method('isInstanceOf') + ->with($this->resourceTypeClass) + ->willReturn(true); + + $this->factory + ->method('create') + ->willReturnMap( + [ + [$translatable1, $translationResource1, $translation1] + ] + ); + + $result = $this->sut->find($query); + + $this->assertInstanceOf(ResourceCollection::class, $result); + $this->assertCount(1, $result); + $this->assertContains($translation1, $result); + } +} diff --git a/test/unit/models/classes/Translation/Service/ResourceMetadataPopulateServiceTest.php b/test/unit/models/classes/Translation/Service/ResourceMetadataPopulateServiceTest.php new file mode 100644 index 0000000000..d90db1b6e8 --- /dev/null +++ b/test/unit/models/classes/Translation/Service/ResourceMetadataPopulateServiceTest.php @@ -0,0 +1,117 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Service; + +use core_kernel_classes_Literal; +use core_kernel_classes_Property; +use core_kernel_classes_Resource; +use oat\generis\model\data\Ontology; +use oat\generis\model\OntologyRdf; +use oat\tao\model\Translation\Entity\AbstractResource; +use oat\tao\model\Translation\Service\ResourceMetadataPopulateService; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ResourceMetadataPopulateServiceTest extends TestCase +{ + private ResourceMetadataPopulateService $sut; + + /** @var Ontology|MockObject */ + private $ontology; + + /** @var AbstractResource|MockObject */ + private $resource; + + /** @var core_kernel_classes_Resource|MockObject */ + private $originResource; + + public function setUp(): void + { + $this->ontology = $this->createMock(Ontology::class); + $this->resource = $this->createMock(AbstractResource::class); + $this->originResource = $this->createMock(core_kernel_classes_Resource::class); + $this->sut = new ResourceMetadataPopulateService($this->ontology); + } + + public function testPopulateAddsMetadataToResource(): void + { + $resourceType = 'testResourceType'; + $metadataUri = 'http://example.com/metadata'; + $valuePropertyUri = OntologyRdf::RDF_VALUE; + $value = 'testValue'; + $literalValue = 'testLiteral'; + $valueProperty = $this->createMock(core_kernel_classes_Property::class); + $literal = $this->createMock(core_kernel_classes_Literal::class); + $literal->literal = $literalValue; + + $this->sut->addMetadata($resourceType, $metadataUri); + + $this->originResource + ->method('getParentClassesIds') + ->willReturn([$resourceType]); + + $this->originResource + ->method('getPropertyValues') + ->willReturn([$value]); + + $this->resource + ->expects($this->once()) + ->method('addMetadataUri') + ->with($metadataUri); + + $this->ontology->method('getProperty')->willReturnMap( + [ + [ + $metadataUri, + $valueProperty + ], + [ + $value, + $valueProperty + ], + [ + $valuePropertyUri, + $valueProperty + ], + ] + ); + + $valueProperty + ->method('getOnePropertyValue') + ->willReturn($literal); + + $this->resource + ->expects($this->once()) + ->method('addMetadata') + ->with($metadataUri, $value, $literalValue); + + $this->resource + ->expects($this->once()) + ->method('getMetadataUris') + ->willReturn([$metadataUri]); + + $this->sut->populate($this->resource, $this->originResource); + } +} diff --git a/test/unit/models/classes/Translation/Service/ResourceTranslatableRetrieverTest.php b/test/unit/models/classes/Translation/Service/ResourceTranslatableRetrieverTest.php new file mode 100644 index 0000000000..cbf322b201 --- /dev/null +++ b/test/unit/models/classes/Translation/Service/ResourceTranslatableRetrieverTest.php @@ -0,0 +1,156 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Service; + +use core_kernel_classes_Property; +use core_kernel_classes_Resource; +use oat\generis\model\data\Ontology; +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Entity\ResourceCollection; +use oat\tao\model\Translation\Exception\ResourceTranslationException; +use oat\tao\model\Translation\Query\ResourceTranslatableQuery; +use oat\tao\model\Translation\Repository\ResourceTranslatableRepository; +use oat\tao\model\Translation\Service\ResourceTranslatableRetriever; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; + +class ResourceTranslatableRetrieverTest extends TestCase +{ + private ResourceTranslatableRetriever $sut; + + /** @var ResourceTranslatableRepository|MockObject */ + private $resourceTranslatableRepository; + + /** @var Ontology|MockObject */ + private $ontology; + + /** @var MockObject|ServerRequestInterface */ + private $request; + + public function setUp(): void + { + $this->ontology = $this->createMock(Ontology::class); + $this->resourceTranslatableRepository = $this->createMock(ResourceTranslatableRepository::class); + $this->request = $this->createMock(ServerRequestInterface::class); + $this->sut = new ResourceTranslatableRetriever($this->ontology, $this->resourceTranslatableRepository); + } + + public function testGetByRequest(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn( + [ + 'id' => ['id'], + ] + ); + + $this->mockResource([TaoOntology::CLASS_URI_ITEM], 'uniqueId'); + + $result = new ResourceCollection(); + + $this->resourceTranslatableRepository + ->expects($this->once()) + ->method('find') + ->with( + new ResourceTranslatableQuery( + TaoOntology::CLASS_URI_ITEM, + ['uniqueId'] + ) + ) + ->willReturn($result); + + $this->assertSame($result, $this->sut->getByRequest($this->request)); + } + + public function testGetByRequestRequiresResourceId(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn([]); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource id is required'); + + $this->sut->getByRequest($this->request); + } + + public function testGetByRequestRequiresResourceType(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn(['id' => 'id']); + + $this->mockResource([], 'uniqueId'); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource id must have a resource type'); + + $this->sut->getByRequest($this->request); + } + + public function testGetByRequestRequiresUniqueId(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn( + [ + 'id' => 'id', + ] + ); + + $this->mockResource([TaoOntology::CLASS_URI_ITEM], ''); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource id must have a unique identifier'); + + $this->sut->getByRequest($this->request); + } + + private function mockResource(array $classIds, string $uniqueId): core_kernel_classes_Resource + { + $resource = $this->createMock(core_kernel_classes_Resource::class); + + $resource + ->method('getParentClassesIds') + ->willReturn($classIds); + + $resource + ->method('getUniquePropertyValue') + ->willReturn($uniqueId); + + $this->ontology + ->expects($this->once()) + ->method('getResource') + ->willReturn($resource); + + $this->ontology + ->method('getProperty') + ->willReturn($this->createMock(core_kernel_classes_Property::class)); + + return $resource; + } +} diff --git a/test/unit/models/classes/Translation/Service/ResourceTranslationRetrieverTest.php b/test/unit/models/classes/Translation/Service/ResourceTranslationRetrieverTest.php new file mode 100644 index 0000000000..a86b716bd0 --- /dev/null +++ b/test/unit/models/classes/Translation/Service/ResourceTranslationRetrieverTest.php @@ -0,0 +1,158 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\model\Translation\Service; + +use core_kernel_classes_Property; +use core_kernel_classes_Resource; +use InvalidArgumentException; +use oat\generis\model\data\Ontology; +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Entity\ResourceCollection; +use oat\tao\model\Translation\Exception\ResourceTranslationException; +use oat\tao\model\Translation\Query\ResourceTranslationQuery; +use oat\tao\model\Translation\Repository\ResourceTranslationRepository; +use oat\tao\model\Translation\Service\ResourceTranslationRetriever; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; + +class ResourceTranslationStatusServiceTest extends TestCase +{ + private ResourceTranslationRetriever $sut; + + /** @var Ontology|MockObject */ + private $ontology; + + /** @var ResourceTranslationRepository|MockObject */ + private $resourceTranslationRepository; + + /** @var MockObject|ServerRequestInterface */ + private $request; + + public function setUp(): void + { + $this->ontology = $this->createMock(Ontology::class); + $this->resourceTranslationRepository = $this->createMock(ResourceTranslationRepository::class); + $this->request = $this->createMock(ServerRequestInterface::class); + $this->sut = new ResourceTranslationRetriever($this->ontology, $this->resourceTranslationRepository); + } + + public function testGetByRequest(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn( + [ + 'id' => 'id', + ] + ); + + $this->mockResource([TaoOntology::CLASS_URI_ITEM], 'uniqueId'); + + $result = new ResourceCollection(); + + $this->resourceTranslationRepository + ->expects($this->once()) + ->method('find') + ->with( + new ResourceTranslationQuery( + TaoOntology::CLASS_URI_ITEM, + ['uniqueId'], + null + ) + ) + ->willReturn($result); + + $this->assertSame($result, $this->sut->getByRequest($this->request)); + } + + public function testGetByRequestRequiresResourceId(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn([]); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource id is required'); + + $this->sut->getByRequest($this->request); + } + + public function testGetByRequestRequiresResourceType(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn(['id' => 'id']); + + $this->mockResource([], 'uniqueId'); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource id must have a resource type'); + + $this->sut->getByRequest($this->request); + } + + public function testGetByRequestRequiresUniqueId(): void + { + $this->request->expects($this->once()) + ->method('getQueryParams') + ->willReturn( + [ + 'id' => 'id', + ] + ); + + $this->mockResource([TaoOntology::CLASS_URI_ITEM], ''); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource id must have a unique identifier'); + + $this->sut->getByRequest($this->request); + } + + private function mockResource(array $classIds, string $uniqueId): core_kernel_classes_Resource + { + $resource = $this->createMock(core_kernel_classes_Resource::class); + + $resource + ->method('getParentClassesIds') + ->willReturn($classIds); + + $resource + ->method('getUniquePropertyValue') + ->willReturn($uniqueId); + + $this->ontology + ->expects($this->once()) + ->method('getResource') + ->willReturn($resource); + + $this->ontology + ->method('getProperty') + ->willReturn($this->createMock(core_kernel_classes_Property::class)); + + return $resource; + } +} diff --git a/test/unit/models/classes/Translation/Service/TranslationCreationServiceTest.php b/test/unit/models/classes/Translation/Service/TranslationCreationServiceTest.php new file mode 100644 index 0000000000..a759f9facc --- /dev/null +++ b/test/unit/models/classes/Translation/Service/TranslationCreationServiceTest.php @@ -0,0 +1,320 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\models\classes\Translation\Service; + +use core_kernel_classes_Property; +use core_kernel_classes_Resource; +use oat\tao\model\Language\Business\Contract\LanguageRepositoryInterface; +use oat\tao\model\Language\Language; +use oat\tao\model\Language\LanguageCollection; +use oat\tao\model\OntologyClassService; +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Entity\ResourceTranslation; +use oat\tao\model\Translation\Exception\ResourceTranslationException; +use oat\tao\model\Translation\Service\TranslationCreationService; +use oat\tao\model\Translation\Command\CreateTranslationCommand; +use oat\tao\model\Translation\Repository\ResourceTranslatableRepository; +use oat\tao\model\Translation\Repository\ResourceTranslationRepository; +use oat\tao\model\Translation\Entity\ResourceCollection; +use oat\tao\model\Translation\Entity\ResourceTranslatable; +use oat\generis\model\data\Ontology; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +class TranslationCreationServiceTest extends TestCase +{ + private TranslationCreationService $service; + + /** @var Ontology|MockObject */ + private $ontology; + + /** @var ResourceTranslatableRepository|MockObject */ + private $resourceTranslatableRepository; + + /** @var ResourceTranslationRepository|MockObject */ + private $resourceTranslationRepository; + + /** @var LanguageRepositoryInterface|MockObject */ + private $languageRepository; + + /** @var OntologyClassService|MockObject */ + private $ontologyClassService; + + /** @var MockObject|LoggerInterface */ + private $logger; + + protected function setUp(): void + { + $this->ontology = $this->createMock(Ontology::class); + $this->resourceTranslatableRepository = $this->createMock(ResourceTranslatableRepository::class); + $this->resourceTranslationRepository = $this->createMock(ResourceTranslationRepository::class); + $this->languageRepository = $this->createMock(LanguageRepositoryInterface::class); + $this->ontologyClassService = $this->createMock(OntologyClassService::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->service = new TranslationCreationService( + $this->ontology, + $this->resourceTranslatableRepository, + $this->resourceTranslationRepository, + $this->languageRepository, + $this->logger, + ); + + $this->service->setOntologyClassService( + TaoOntology::CLASS_URI_ITEM, + $this->ontologyClassService + ); + } + + public function testCreate(): void + { + $resourceType = TaoOntology::CLASS_URI_ITEM; + $uniqueId = 'id1'; + $languageUri = 'http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR'; + + $translatable = $this->createMock(ResourceTranslatable::class); + + $languageProperty = $this->createMock(core_kernel_classes_Property::class); + $translationTypeProperty = $this->createMock(core_kernel_classes_Property::class); + $translationProgressProperty = $this->createMock(core_kernel_classes_Property::class); + + $resourceCollection = new ResourceCollection(); + $translatableCollection = new ResourceCollection($translatable); + + $translatable + ->method('isReadyForTranslation') + ->willReturn(true); + + $language = $this->createMock(Language::class); + $language + ->expects($this->exactly(3)) + ->method('getUri') + ->willReturn($languageUri); + $language + ->expects($this->once()) + ->method('getCode') + ->willReturn('pt-BR'); + + $instance = $this->createMock(core_kernel_classes_Resource::class); + $instance + ->expects($this->once()) + ->method('getTypes') + ->willReturn([]); + $instance + ->expects($this->once()) + ->method('getLabel') + ->willReturn('MyInstance'); + + $clonedInstance = $this->createMock(core_kernel_classes_Resource::class); + $clonedInstance + ->expects($this->once()) + ->method('setLabel') + ->with('MyInstance (pt-BR)'); + $clonedInstance + ->expects($this->exactly(3)) + ->method('editPropertyValues') + ->withConsecutive( + [ + $languageProperty, + $languageUri + ], + [ + $translationTypeProperty, + TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION + ], + [ + $translationProgressProperty, + TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_PENDING + ] + ); + + $this->resourceTranslationRepository + ->expects($this->once()) + ->method('find') + ->willReturn($resourceCollection); + + $this->resourceTranslatableRepository + ->expects($this->once()) + ->method('find') + ->willReturn($translatableCollection); + + $this->languageRepository + ->expects($this->once()) + ->method('findAvailableLanguagesByUsage') + ->willReturn(new LanguageCollection($language)); + + $this->ontology + ->expects($this->once()) + ->method('getResource') + ->willReturn($instance); + + $this->ontology + ->expects($this->exactly(3)) + ->method('getProperty') + ->willReturnMap( + [ + [ + TaoOntology::PROPERTY_LANGUAGE, + $languageProperty + ], + [ + TaoOntology::PROPERTY_TRANSLATION_TYPE, + $translationTypeProperty + ], + [ + TaoOntology::PROPERTY_TRANSLATION_PROGRESS, + $translationProgressProperty + ] + ] + ); + + $this->ontologyClassService + ->expects($this->once()) + ->method('cloneInstance') + ->willReturn($clonedInstance); + + $this->assertInstanceOf( + core_kernel_classes_Resource::class, + $this->service->create(new CreateTranslationCommand($resourceType, $uniqueId, $languageUri)) + ); + } + + public function testCreateWillFailIfTranslationAlreadyExists(): void + { + $translatable = $this->createMock(ResourceTranslatable::class); + + $translatable + ->method('isReadyForTranslation') + ->willReturn(true); + + $this->resourceTranslationRepository + ->expects($this->once()) + ->method('find') + ->willReturn(new ResourceCollection($translatable)); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage( + 'Translation already exists for [uniqueId=id1, locale=http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR]' + ); + + $this->service->create( + new CreateTranslationCommand( + TaoOntology::CLASS_URI_ITEM, + 'id1', + 'http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR' + ) + ); + } + + public function testCreateWillFailIfResourceDoesNotExist(): void + { + $this->resourceTranslationRepository + ->expects($this->once()) + ->method('find') + ->willReturn(new ResourceCollection()); + + $this->resourceTranslatableRepository + ->expects($this->once()) + ->method('find') + ->willReturn(new ResourceCollection()); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('There is not translatable resource for [uniqueId=id1]'); + + $this->service->create( + new CreateTranslationCommand( + TaoOntology::CLASS_URI_ITEM, + 'id1', + 'http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR' + ) + ); + } + + public function testCreateWillFailIfLanguageDoesNotExist(): void + { + $translatable = $this->createMock(ResourceTranslatable::class); + + $translatable + ->method('isReadyForTranslation') + ->willReturn(true); + + $this->resourceTranslationRepository + ->expects($this->once()) + ->method('find') + ->willReturn(new ResourceCollection()); + + $this->resourceTranslatableRepository + ->expects($this->once()) + ->method('find') + ->willReturn(new ResourceCollection($translatable)); + + $this->languageRepository + ->expects($this->once()) + ->method('findAvailableLanguagesByUsage') + ->willReturn(new LanguageCollection()); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Language http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR does not exist'); + + $this->service->create( + new CreateTranslationCommand( + TaoOntology::CLASS_URI_ITEM, + 'id1', + 'http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR' + ) + ); + } + + public function testCreateWillFailIfResourceIsNotReadyForTranslation(): void + { + $translatable = $this->createMock(ResourceTranslatable::class); + + $translatable + ->method('isReadyForTranslation') + ->willReturn(false); + + $this->resourceTranslationRepository + ->expects($this->once()) + ->method('find') + ->willReturn(new ResourceCollection()); + + $this->resourceTranslatableRepository + ->expects($this->once()) + ->method('find') + ->willReturn(new ResourceCollection($translatable)); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource [uniqueId=id1] is not ready for translation'); + + $this->service->create( + new CreateTranslationCommand( + TaoOntology::CLASS_URI_ITEM, + 'id1', + 'http://www.tao.lu/Ontologies/TAO.rdf#Langpt-BR' + ) + ); + } +} diff --git a/test/unit/models/classes/Translation/Service/TranslationUpdateServiceTest.php b/test/unit/models/classes/Translation/Service/TranslationUpdateServiceTest.php new file mode 100644 index 0000000000..fecc72c129 --- /dev/null +++ b/test/unit/models/classes/Translation/Service/TranslationUpdateServiceTest.php @@ -0,0 +1,155 @@ + + */ + +declare(strict_types=1); + +namespace oat\tao\test\unit\models\classes\Translation\Service; + +use core_kernel_classes_Property; +use core_kernel_classes_Resource; +use oat\generis\model\data\Ontology; +use oat\tao\model\TaoOntology; +use oat\tao\model\Translation\Command\UpdateTranslationCommand; +use oat\tao\model\Translation\Exception\ResourceTranslationException; +use oat\tao\model\Translation\Service\TranslationUpdateService; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +class TranslationUpdateServiceTest extends TestCase +{ + private TranslationUpdateService $service; + + /** @var Ontology|MockObject */ + private $ontology; + + /** @var LoggerInterface|MockObject */ + private $logger; + + protected function setUp(): void + { + $this->ontology = $this->createMock(Ontology::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->service = new TranslationUpdateService($this->ontology, $this->logger); + } + + public function testUpdateSuccess(): void + { + $typeProperty = $this->createMock(core_kernel_classes_Property::class); + $progressProperty = $this->createMock(core_kernel_classes_Property::class); + + $translationType = $this->createMock(core_kernel_classes_Resource::class); + $translationType + ->method('getUri') + ->willReturn(TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION); + + $resource = $this->createMock(core_kernel_classes_Resource::class); + $resource + ->method('getOnePropertyValue') + ->willReturn($translationType); + + $resource + ->method('exists') + ->willReturn(true); + + $resource + ->expects($this->once()) + ->method('editPropertyValues') + ->with( + $progressProperty, + TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_TRANSLATED + ); + + $this->ontology + ->method('getResource') + ->willReturn($resource); + + $this->ontology + ->method('getProperty') + ->willReturnMap( + [ + [ + TaoOntology::PROPERTY_TRANSLATION_TYPE, + $typeProperty + ], + [ + TaoOntology::PROPERTY_TRANSLATION_PROGRESS, + $progressProperty + ], + ] + ); + + $this->assertSame( + $resource, + $this->service->update( + new UpdateTranslationCommand( + 'http://example.com/resource/1', + TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_TRANSLATED + ) + ) + ); + } + + public function testOriginalCannotBeUpdated(): void + { + $typeProperty = $this->createMock(core_kernel_classes_Property::class); + + $translationType = $this->createMock(core_kernel_classes_Resource::class); + $translationType + ->method('getUri') + ->willReturn(TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_ORIGINAL); + + $resource = $this->createMock(core_kernel_classes_Resource::class); + $resource + ->method('getOnePropertyValue') + ->willReturn($translationType); + + $resource + ->method('exists') + ->willReturn(true); + + $this->ontology + ->method('getResource') + ->willReturn($resource); + + $this->ontology + ->method('getProperty') + ->willReturnMap( + [ + [ + TaoOntology::PROPERTY_TRANSLATION_TYPE, + $typeProperty + ] + ] + ); + + $this->expectException(ResourceTranslationException::class); + $this->expectExceptionMessage('Resource http://example.com/resource/1 is not a translation'); + + $this->service->update( + new UpdateTranslationCommand( + 'http://example.com/resource/1', + TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_TRANSLATED + ) + ); + } +}