Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.40% covered (warning)
84.40%
92 / 109
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
TranslationCreationService
84.40% covered (warning)
84.40%
92 / 109
33.33% covered (danger)
33.33%
2 / 6
19.23
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 setResourceTransfer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addPostCreation
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 createByRequest
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 create
95.29% covered (success)
95.29%
81 / 85
0.00% covered (danger)
0.00%
0 / 1
10
 getResourceTransfer
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2024 (original work) Open Assessment Technologies SA;
19 */
20
21declare(strict_types=1);
22
23namespace oat\tao\model\Translation\Service;
24
25use core_kernel_classes_Resource;
26use oat\generis\model\data\Ontology;
27use oat\oatbox\event\EventManager;
28use oat\tao\model\Language\Business\Contract\LanguageRepositoryInterface;
29use oat\tao\model\Language\Language;
30use oat\tao\model\resources\Command\ResourceTransferCommand;
31use oat\tao\model\resources\Contract\ResourceTransferInterface;
32use oat\tao\model\TaoOntology;
33use oat\tao\model\Translation\Command\CreateTranslationCommand;
34use oat\tao\model\Translation\Entity\ResourceTranslatable;
35use oat\tao\model\Translation\Event\TranslationActionEvent;
36use oat\tao\model\Translation\Exception\ResourceTranslationException;
37use oat\tao\model\Translation\Query\ResourceTranslatableQuery;
38use oat\tao\model\Translation\Query\ResourceTranslationQuery;
39use oat\tao\model\Translation\Repository\ResourceTranslatableRepository;
40use oat\tao\model\Translation\Repository\ResourceTranslationRepository;
41use Psr\Http\Message\ServerRequestInterface;
42use Psr\Log\LoggerInterface;
43use Throwable;
44
45class TranslationCreationService
46{
47    private Ontology $ontology;
48    private ResourceTranslatableRepository $resourceTranslatableRepository;
49    private ResourceTranslationRepository $resourceTranslationRepository;
50    private LanguageRepositoryInterface $languageRepository;
51    private LoggerInterface $logger;
52    private TranslatedIntoLanguagesSynchronizer $translatedIntoLanguagesSynchronizer;
53    private EventManager $eventManager;
54
55    private array $resourceTransferServices;
56    private array $callables;
57
58    public function __construct(
59        Ontology $ontology,
60        ResourceTranslatableRepository $resourceTranslatableRepository,
61        ResourceTranslationRepository $resourceTranslationRepository,
62        LanguageRepositoryInterface $languageRepository,
63        LoggerInterface $logger,
64        TranslatedIntoLanguagesSynchronizer $translatedIntoLanguagesSynchronizer,
65        EventManager $eventManager
66    ) {
67        $this->ontology = $ontology;
68        $this->resourceTranslatableRepository = $resourceTranslatableRepository;
69        $this->resourceTranslationRepository = $resourceTranslationRepository;
70        $this->languageRepository = $languageRepository;
71        $this->logger = $logger;
72        $this->translatedIntoLanguagesSynchronizer = $translatedIntoLanguagesSynchronizer;
73        $this->eventManager = $eventManager;
74    }
75
76    public function setResourceTransfer(string $resourceType, ResourceTransferInterface $resourceTransfer): void
77    {
78        $this->resourceTransferServices[$resourceType] = $resourceTransfer;
79    }
80
81    public function addPostCreation(string $resourceType, callable $callable): void
82    {
83        $this->callables[$resourceType] ??= [];
84        $this->callables[$resourceType][] = $callable;
85    }
86
87    public function createByRequest(ServerRequestInterface $request): core_kernel_classes_Resource
88    {
89        $requestParams = $request->getParsedBody();
90        $id = $requestParams['id'] ?? null;
91        $languageUri = $requestParams['languageUri'] ?? null;
92
93        if (empty($id)) {
94            throw new ResourceTranslationException('Resource id is required');
95        }
96
97        if (empty($languageUri)) {
98            throw new ResourceTranslationException('Parameter languageUri is mandatory');
99        }
100
101        return $this->create(new CreateTranslationCommand($id, $languageUri));
102    }
103
104    public function create(CreateTranslationCommand $command): core_kernel_classes_Resource
105    {
106        try {
107            $resourceUri = $command->getResourceUri();
108            $languageUri = $command->getLanguageUri();
109
110            $translations = $this->resourceTranslationRepository->find(
111                new ResourceTranslationQuery([$resourceUri], $languageUri)
112            );
113
114            if ($translations->count() > 0) {
115                throw new ResourceTranslationException(
116                    sprintf(
117                        'Translation already exists for [id=%s, locale=%s]',
118                        $resourceUri,
119                        $languageUri
120                    )
121                );
122            }
123
124            $resources = $this->resourceTranslatableRepository->find(new ResourceTranslatableQuery($resourceUri));
125
126            if ($resources->count() === 0) {
127                throw new ResourceTranslationException(sprintf('Resource [id=%s] is not translatable', $resourceUri));
128            }
129
130            /** @var ResourceTranslatable $resource */
131            $resource = $resources->current();
132
133            if (!$resource->isReadyForTranslation()) {
134                throw new ResourceTranslationException(
135                    sprintf(
136                        'Resource [id=%s] is not ready for translation',
137                        $resourceUri
138                    )
139                );
140            }
141
142            $existingLanguages = $this->languageRepository->findAvailableLanguagesByUsage();
143            $language = null;
144
145            /** @var Language $language */
146            foreach ($existingLanguages as $existingLanguage) {
147                if ($existingLanguage->getUri() === $languageUri) {
148                    $language = $existingLanguage;
149                }
150            }
151
152            if (!$language) {
153                throw new ResourceTranslationException(sprintf('Language %s does not exist', $languageUri));
154            }
155
156            if ($resource->getLanguageUri() === $language->getUri()) {
157                throw new ResourceTranslationException(
158                    sprintf('Cannot translate to original language %s', $languageUri)
159                );
160            }
161
162            $instance = $this->ontology->getResource($resourceUri);
163            $rootId = $instance->getRootId();
164
165            $clonedInstanceUri = $this->getResourceTransfer($rootId)->transfer(
166                new ResourceTransferCommand(
167                    $resourceUri,
168                    $instance->getParentClassId(),
169                    null,
170                    null
171                )
172            )->getDestination();
173            $clonedInstance = $this->ontology->getResource($clonedInstanceUri);
174            $clonedInstance->setLabel(sprintf('%s (%s)', $instance->getLabel(), $language->getCode()));
175
176            $clonedInstance->editPropertyValues(
177                $this->ontology->getProperty(TaoOntology::PROPERTY_LANGUAGE),
178                $language->getUri()
179            );
180
181            $clonedInstance->editPropertyValues(
182                $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_TYPE),
183                TaoOntology::PROPERTY_VALUE_TRANSLATION_TYPE_TRANSLATION
184            );
185
186            $clonedInstance->editPropertyValues(
187                $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_PROGRESS),
188                TaoOntology::PROPERTY_VALUE_TRANSLATION_PROGRESS_PENDING
189            );
190
191            $clonedInstance->editPropertyValues(
192                $this->ontology->getProperty(TaoOntology::PROPERTY_TRANSLATION_ORIGINAL_RESOURCE_URI),
193                $resourceUri
194            );
195
196            foreach ($this->callables[$rootId] ?? [] as $callable) {
197                $callable($clonedInstance);
198            }
199
200            $this->translatedIntoLanguagesSynchronizer->sync($instance);
201
202            $this->eventManager->trigger(new TranslationActionEvent(
203                TranslationActionEvent::ACTION_CREATED,
204                $rootId,
205                $resourceUri,
206                $clonedInstanceUri,
207                $language->getCode()
208            ));
209
210            return $clonedInstance;
211        } catch (Throwable $exception) {
212            $this->logger->error(
213                sprintf(
214                    'Could not translate [id=%s, language=%s] (%s): %s',
215                    $resourceUri,
216                    $languageUri,
217                    get_class($exception),
218                    $exception->getMessage()
219                )
220            );
221
222            throw $exception;
223        }
224    }
225
226    private function getResourceTransfer(string $resourceType): ResourceTransferInterface
227    {
228        $service = $this->resourceTransferServices[$resourceType] ?? null;
229
230        if ($service) {
231            return $service;
232        }
233
234        throw new ResourceTranslationException(
235            sprintf('Missing ResourceTransfer for resource type %s', $resourceType)
236        );
237    }
238}