Skip to content

Commit 7035136

Browse files
committed
Add locale validation to translationCreator
Signed-off-by: iudi <iudi@lepidus.com.br>
1 parent 99bd33b commit 7035136

5 files changed

Lines changed: 201 additions & 23 deletions

File tree

api/v1/doiForTranslation/DoiForTranslationHandler.inc.php

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import('lib.pkp.classes.handler.APIHandler');
44
import('plugins.generic.doiForTranslation.classes.TranslationCreator');
5+
import('plugins.generic.doiForTranslation.classes.TranslationLocaleValidator');
6+
import('plugins.generic.doiForTranslation.classes.TranslationsService');
57

68
class DoiForTranslationHandler extends APIHandler
79
{
@@ -32,13 +34,16 @@ public function authorize($request, &$args, $roleAssignments)
3234
public function createTranslation($slimRequest, $response, $args)
3335
{
3436
$requestParams = $slimRequest->getParsedBody();
35-
$translationLocale = $requestParams['translationLocale'];
37+
$translationLocale = $requestParams['translationLocale'] ?? '';
3638
$submission = $this->getAuthorizedContextObject(ASSOC_TYPE_SUBMISSION);
3739

38-
if (is_null($translationLocale)
39-
|| $translationLocale == $submission->getData('locale')
40-
|| !is_null($submission->getData('isTranslationOf'))
41-
) {
40+
if (!$this->getValidator()->isAvailable(
41+
$translationLocale,
42+
$submission->getData('locale'),
43+
!is_null($submission->getData('isTranslationOf')),
44+
$this->getSupportedSubmissionLocales(),
45+
$this->getExistingTranslationLocales($submission->getId())
46+
)) {
4247
return $response->withStatus(400);
4348
}
4449

@@ -47,4 +52,24 @@ public function createTranslation($slimRequest, $response, $args)
4752

4853
return $response->withStatus(201);
4954
}
55+
56+
protected function getValidator(): TranslationLocaleValidator
57+
{
58+
return new TranslationLocaleValidator();
59+
}
60+
61+
protected function getSupportedSubmissionLocales(): array
62+
{
63+
$context = $this->getRequest()->getContext();
64+
return $context->getSupportedSubmissionLocales() ?: [];
65+
}
66+
67+
protected function getExistingTranslationLocales(int $submissionId): array
68+
{
69+
$translationsService = new TranslationsService();
70+
return array_column(
71+
$translationsService->getTranslations($submissionId, TranslationsService::PLACE_WORKFLOW),
72+
'locale'
73+
);
74+
}
5075
}

classes/TranslationCreator.inc.php

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,40 @@
11
<?php
22

3+
use Illuminate\Database\Capsule\Manager as Capsule;
4+
35
class TranslationCreator
46
{
57
public function createTranslation($submissionId, $translationLocale)
68
{
7-
$submissionDao = DAORegistry::getDAO('SubmissionDAO');
8-
$submission = $submissionDao->getById($submissionId);
9+
return Capsule::connection()->transaction(function () use ($submissionId, $translationLocale) {
10+
$submissionDao = DAORegistry::getDAO('SubmissionDAO');
11+
$submission = $submissionDao->getById($submissionId);
912

10-
$newSubmission = clone $submission;
11-
$newSubmission->setData('id', null);
12-
$newSubmission->setData('locale', $translationLocale);
13-
$newSubmission->setData('isTranslationOf', $submissionId);
14-
$newSubmission->setData('status', STATUS_QUEUED);
13+
$newSubmission = clone $submission;
14+
$newSubmission->setData('id', null);
15+
$newSubmission->setData('locale', $translationLocale);
16+
$newSubmission->setData('isTranslationOf', $submissionId);
17+
$newSubmission->setData('status', STATUS_QUEUED);
1518

16-
$newSubmissionId = $submissionDao->insertObject($newSubmission);
17-
$newSubmission->setData('id', $newSubmissionId);
19+
$newSubmissionId = $submissionDao->insertObject($newSubmission);
20+
$newSubmission->setData('id', $newSubmissionId);
1821

19-
$originalLocale = $submission->getData('locale');
22+
$originalLocale = $submission->getData('locale');
2023

21-
foreach ($submission->getData('publications') as $publication) {
22-
$newPublicationId = $this->createTranslationPublication($publication, $newSubmissionId, $translationLocale, $originalLocale);
24+
foreach ($submission->getData('publications') as $publication) {
25+
$newPublicationId = $this->createTranslationPublication($publication, $newSubmissionId, $translationLocale, $originalLocale);
2326

24-
if ($publication->getId() == $submission->getData('currentPublicationId')) {
25-
$newSubmission->setData('currentPublicationId', $newPublicationId);
26-
$submissionDao->updateObject($newSubmission);
27+
if ($publication->getId() == $submission->getData('currentPublicationId')) {
28+
$newSubmission->setData('currentPublicationId', $newPublicationId);
29+
$submissionDao->updateObject($newSubmission);
30+
}
2731
}
28-
}
2932

30-
return $newSubmissionId;
33+
return $newSubmissionId;
34+
});
3135
}
3236

33-
private function createTranslationPublication($publication, $newSubmissionId, $translationLocale, $originalLocale)
37+
protected function createTranslationPublication($publication, $newSubmissionId, $translationLocale, $originalLocale)
3438
{
3539
$newPublication = clone $publication;
3640
$newPublication->setData('id', null);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
class TranslationLocaleValidator
4+
{
5+
public function isAvailable(
6+
string $translationLocale,
7+
string $originalLocale,
8+
bool $isTranslation,
9+
array $supportedLocales,
10+
array $existingTranslationLocales
11+
): bool {
12+
if ($translationLocale === '') {
13+
return false;
14+
}
15+
16+
if ($isTranslation) {
17+
return false;
18+
}
19+
20+
if ($translationLocale === $originalLocale) {
21+
return false;
22+
}
23+
24+
if (!in_array($translationLocale, $supportedLocales, true)) {
25+
return false;
26+
}
27+
28+
if (in_array($translationLocale, $existingTranslationLocales, true)) {
29+
return false;
30+
}
31+
32+
return true;
33+
}
34+
}

tests/TranslationCreatorTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import('plugins.generic.doiForTranslation.classes.TranslationCreator');
88
import('plugins.generic.doiForTranslation.DoiForTranslationPlugin');
99

10+
use Illuminate\Database\Capsule\Manager as Capsule;
11+
1012
class TranslationCreatorTest extends DatabaseTestCase
1113
{
1214
private $translationCreator;
@@ -84,6 +86,38 @@ private function updateCurrentPublication()
8486
$submissionDao->updateObject($submission);
8587
}
8688

89+
public function testRollsBackWhenPublicationCreationFails(): void
90+
{
91+
$failingCreator = $this->getMockBuilder(TranslationCreator::class)
92+
->onlyMethods(['createTranslationPublication'])
93+
->getMock();
94+
$failingCreator->method('createTranslationPublication')
95+
->willThrowException(new Exception('publication insert failed'));
96+
97+
$translationsBefore = $this->countTranslationsOf($this->submissionId);
98+
99+
try {
100+
$failingCreator->createTranslation($this->submissionId, $this->translationLocale);
101+
$this->fail('Expected exception was not thrown');
102+
} catch (Exception $e) {
103+
$this->assertSame('publication insert failed', $e->getMessage());
104+
}
105+
106+
$this->assertSame(
107+
$translationsBefore,
108+
$this->countTranslationsOf($this->submissionId),
109+
'No translation submission should remain after failure'
110+
);
111+
}
112+
113+
private function countTranslationsOf(int $originalSubmissionId): int
114+
{
115+
return Capsule::table('submission_settings')
116+
->where('setting_name', 'isTranslationOf')
117+
->where('setting_value', (string) $originalSubmissionId)
118+
->count();
119+
}
120+
87121
public function testCreatesTranslationSubmission(): void
88122
{
89123
$translationSubmissionId = $this->translationCreator->createTranslation($this->submissionId, $this->translationLocale);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
import('plugins.generic.doiForTranslation.classes.TranslationLocaleValidator');
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
class TranslationLocaleValidatorTest extends TestCase
8+
{
9+
private $validator;
10+
11+
public function setUp(): void
12+
{
13+
$this->validator = new TranslationLocaleValidator();
14+
}
15+
16+
public function testRejectsEmptyLocale(): void
17+
{
18+
$this->assertFalse($this->validator->isAvailable(
19+
'',
20+
'en_US',
21+
false,
22+
['en_US', 'pt_BR'],
23+
[]
24+
));
25+
}
26+
27+
public function testRejectsLocaleEqualToOriginal(): void
28+
{
29+
$this->assertFalse($this->validator->isAvailable(
30+
'en_US',
31+
'en_US',
32+
false,
33+
['en_US', 'pt_BR'],
34+
[]
35+
));
36+
}
37+
38+
public function testRejectsWhenSubmissionIsItselfATranslation(): void
39+
{
40+
$this->assertFalse($this->validator->isAvailable(
41+
'pt_BR',
42+
'en_US',
43+
true,
44+
['en_US', 'pt_BR'],
45+
[]
46+
));
47+
}
48+
49+
public function testRejectsLocaleNotSupportedByContext(): void
50+
{
51+
$this->assertFalse($this->validator->isAvailable(
52+
'kl_GL',
53+
'en_US',
54+
false,
55+
['en_US', 'pt_BR', 'es_ES'],
56+
[]
57+
));
58+
}
59+
60+
public function testRejectsLocaleAlreadyUsedByAnotherTranslation(): void
61+
{
62+
$this->assertFalse($this->validator->isAvailable(
63+
'pt_BR',
64+
'en_US',
65+
false,
66+
['en_US', 'pt_BR', 'es_ES'],
67+
['pt_BR']
68+
));
69+
}
70+
71+
public function testAcceptsSupportedLocaleNotUsedYet(): void
72+
{
73+
$this->assertTrue($this->validator->isAvailable(
74+
'es_ES',
75+
'en_US',
76+
false,
77+
['en_US', 'pt_BR', 'es_ES'],
78+
['pt_BR']
79+
));
80+
}
81+
}

0 commit comments

Comments
 (0)