Skip to content

Commit

Permalink
Merge pull request #1174 from didoda/feat/clone-translations
Browse files Browse the repository at this point in the history
Clone object translations
  • Loading branch information
didoda authored Aug 14, 2024
2 parents 2e2e5a2 + 750c287 commit 971722c
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ bundle-report.*.html
/webroot/css/date-input*
/webroot/css/email-input*
/webroot/css/flash-message*
/webroot/css/folder-picker*
/webroot/css/import-result*
/webroot/css/index-cell*
/webroot/css/labels-form*
Expand All @@ -84,10 +85,12 @@ bundle-report.*.html
/webroot/css/object-property*
/webroot/css/objects-history*
/webroot/css/permission*
/webroot/css/placeholder-list*
/webroot/css/property-view*
/webroot/css/roles-list-view*
/webroot/css/secret*
/webroot/css/show-hide*
/webroot/css/system-info*
/webroot/css/tag-form*
/webroot/css/tag-picker*
/webroot/css/thumbnail*
Expand Down
5 changes: 4 additions & 1 deletion locales/default.pot
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: BEdita 4 \n"
"POT-Creation-Date: 2024-07-10 10:08:33 \n"
"POT-Creation-Date: 2024-08-13 09:24:59 \n"
"MIME-Version: 1.0 \n"
"Content-Transfer-Encoding: 8bit \n"
"Language-Team: BEdita I18N & I10N Team \n"
Expand Down Expand Up @@ -1136,6 +1136,9 @@ msgstr ""
msgid "Clone relations"
msgstr ""

msgid "Clone translations"
msgstr ""

msgid "If you confirm, this data will be gone forever. Are you sure?"
msgstr ""

Expand Down
5 changes: 4 additions & 1 deletion locales/en_US/default.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: BEdita Manager \n"
"POT-Creation-Date: 2024-07-10 10:08:33 \n"
"POT-Creation-Date: 2024-08-13 09:24:59 \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: BEdita I18N & I10N Team \n"
Expand Down Expand Up @@ -1139,6 +1139,9 @@ msgstr ""
msgid "Clone relations"
msgstr ""

msgid "Clone translations"
msgstr ""

msgid "If you confirm, this data will be gone forever. Are you sure?"
msgstr ""

Expand Down
5 changes: 4 additions & 1 deletion locales/it_IT/default.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: BEdita Manager \n"
"POT-Creation-Date: 2024-07-10 10:08:33 \n"
"POT-Creation-Date: 2024-08-13 09:24:59 \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: BEdita I18N & I10N Team \n"
Expand Down Expand Up @@ -1144,6 +1144,9 @@ msgstr "copia"
msgid "Clone relations"
msgstr "Clona le relazioni"

msgid "Clone translations"
msgstr "Clona le traduzioni"

msgid "If you confirm, this data will be gone forever. Are you sure?"
msgstr "Questo dato verrà eliminato definitivamente. Vuoi proseguire?"

Expand Down
19 changes: 12 additions & 7 deletions resources/js/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,13 @@ const _vueInstance = new Vue({
const title = document.getElementById('title').value || t('Untitled');
const msg = t`Please insert a new title on "${title}" clone`;
const defaultTitle = title + '-' + t`copy`;
const confirmCallback = (cloneTitle, cloneRelations, dialog, unique) => {
let query = `?title=${cloneTitle || defaultTitle}`;
for (const uitem of unique) {
const confirmCallback = (dialog, options) => {
let query = `?title=${options?.title || defaultTitle}`;
for (const uitem of options?.unique || []) {
query += `&${uitem.field}=${uitem.value}`;
}
query += `&cloneRelations=${cloneRelations || false}`;
query += `&cloneRelations=${options?.relations || false}`;
query += `&cloneTranslations=${options?.translations || false}`;
const origin = window.location.origin;
const path = window.location.pathname.replace('/view/', '/clone/');
const url = `${origin}${path}${query}`;
Expand All @@ -239,9 +240,13 @@ const _vueInstance = new Vue({
value: document.getElementById(field).value + '-' + t`copy`,
});
}
const options = { checkLabel: t`Clone relations`, checkValue: false, unique: uniqueOptions };

prompt(msg, defaultTitle, confirmCallback, document.body, options);
prompt(msg, defaultTitle, confirmCallback, document.body, {
checks: [
{ name: 'relations', label: t`Clone relations`, value: false },
{ name: 'translations', label: t`Clone translations`, value: false }
],
unique: uniqueOptions
});
},

/**
Expand Down
27 changes: 18 additions & 9 deletions resources/js/app/components/dialog/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,18 @@ export const Dialog = Vue.extend({
<label><: uitem.label :></label>
<input type="text" :id="uitem.field" v-model.lazy="uitem.value" />
</div>
<div class="mt-1" v-if="dialogType === 'prompt'" v-show="checkLabel">
<input type="checkbox" id="_check" v-model.lazy="checkValue" />
<label for="_check"><: checkLabel :></label>
</div>
<template v-if="dialogType === 'prompt'" v-show="checks?.length > 0">
<div class="mt-05" v-for="(citem, index) in checks">
<input type="checkbox" :id="checkname(index)" v-model.lazy="citem.value" />
<label :for="checkname(index)"><: citem.label :></label>
</div>
</template>
<div class="actions mt-2">
<button
class="button-outlined-white confirm mr-1"
:class="{'is-loading-spinner': loading }"
:disabled="loading === true"
@click="prepareCallback() && confirmCallback(inputValue, checkValue, $root, unique)"
@click="prepareCallback() && confirmCallback($root, callbackOptions(inputValue, unique))"
v-if="confirmMessage">
<: confirmMessage :>
</button>
Expand Down Expand Up @@ -70,13 +72,21 @@ export const Dialog = Vue.extend({
confirmCallback: this.hide,
cancelMessage: false,
inputValue: '',
checkValue: '',
checkLabel: '',
checks: [],
loading: false,
unique: [],
};
},
methods: {
callbackOptions(title, unique) {
const relations = this.checks?.find(c => c?.name === 'relations')?.value || false;
const translations = this.checks?.find(c => c?.name === 'translations')?.value || false;

return {title, relations, translations, unique};
},
checkname(index) {
return `check_${index}`;
},
show(message, headerText = this.dialogType, root = document.body) {
this.headerText = headerText;
this.message = message;
Expand Down Expand Up @@ -126,8 +136,7 @@ export const Dialog = Vue.extend({
prompt(message, defaultValue, confirmCallback, root = document.body, options = {}) {
this.dialogType = 'prompt';
this.inputValue = defaultValue || '';
this.checkValue = options?.checkValue || '';
this.checkLabel = options?.checkLabel || '';
this.checks = options.checks || [];
this.unique = options?.unique || [];
this.confirmCallback = confirmCallback;
this.cancelMessage = t`cancel`;
Expand Down
43 changes: 41 additions & 2 deletions src/Controller/Component/CloneComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,21 @@ function ($key) use ($reset) {
*/
public function queryCloneRelations(): bool
{
$cloneRelations = $this->getController()->getRequest()->getQuery('cloneRelations');
return $this->queryClone('cloneRelations');
}

/**
* Get the value of query $param.
* Return true when $param is not false.
*
* @param string $param The query parameter
* @return bool
*/
public function queryClone(string $param): bool
{
$clone = $this->getController()->getRequest()->getQuery($param);

return filter_var($cloneRelations, FILTER_VALIDATE_BOOLEAN) !== false;
return filter_var($clone, FILTER_VALIDATE_BOOLEAN) !== false;
}

/**
Expand All @@ -143,6 +155,33 @@ public function relations(array $source, string $destinationId): bool
return true;
}

/**
* Clone translations from source object $source to destination object ID $destinationId.
*
* @param array $source The source object
* @param string $destinationId The destination ID
* @return bool
*/
public function translations(array $source, string $destinationId): bool
{
if (!$this->queryClone('cloneTranslations')) {
return false;
}
$sourceId = (string)Hash::get($source, 'data.id');
$response = $this->apiClient->get(sprintf('/translations?filter[object_id]=%s', $sourceId));
$responseData = (array)Hash::get($response, 'data');
foreach ($responseData as $translation) {
$this->apiClient->save('translations', [
'lang' => (string)Hash::get($translation, 'attributes.lang'),
'object_id' => $destinationId,
'status' => (string)Hash::get($translation, 'attributes.status'),
'translated_fields' => (array)Hash::get($translation, 'attributes.translated_fields'),
]);
}

return true;
}

/**
* Filter relationships, remove not allowed 'children', 'parents', 'translations'
*
Expand Down
1 change: 1 addition & 0 deletions src/Controller/ModulesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ public function clone($id): ?Response
$save = $this->apiClient->save($this->objectType, $attributes);
$destination = (string)Hash::get($save, 'data.id');
$this->Clone->relations($source, $destination);
$this->Clone->translations($source, $destination);
$id = $destination;
} catch (BEditaClientException $e) {
$this->log($e->getMessage(), LogLevel::ERROR);
Expand Down
13 changes: 13 additions & 0 deletions tests/TestCase/Controller/BaseControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ protected function createTestObject(): array
return $o;
}

/**
* Create a object for test purposes (if not available already)
*
* @return array
*/
public function createTestObjectWithTranslation(): array
{
$response = $this->client->save('documents', ['title' => 'test document with translation']);
$this->client->save('translations', ['object_id' => $response['data']['id'], 'status' => 'draft', 'lang' => 'it', 'translated_fields' => ['title' => 'Titolo di test']]);

return $response['data'];
}

/**
* Create a folder for test purposes (if not available already)
*
Expand Down
71 changes: 67 additions & 4 deletions tests/TestCase/Controller/Component/CloneComponentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,14 @@ public function relationsProvider(): array
* @return void
* @dataProvider relationsProvider()
* @covers ::queryCloneRelations()
* @covers ::queryClone()
*/
public function testQueryCloneRelations(bool $expected): void
{
$this->prepareClone($expected);
$this->setupApi();
$actual = $this->Clone->queryCloneRelations();
static::assertSame($expected, $actual);
static::assertSame($expected, $this->Clone->queryCloneRelations());
static::assertSame($expected, $this->Clone->queryClone('cloneRelations'));
}

/**
Expand Down Expand Up @@ -374,13 +375,75 @@ public function testStream(): void
static::assertArrayNotHasKey('id', $attributes);
}

private function prepareClone(bool $cloneRelations): void
/**
* Data provider for `testTranslations`
*
* @return array
*/
public function translationsProvider(): array
{
return [
'do not clone translations' => [false, false],
'clone translations' => [true, true],
];
}

/**
* Test `translations` method
*
* @param bool $clone Clone translations
* @param bool $expected Expected result
* @return void
* @dataProvider translationsProvider()
*/
public function testTranslations(bool $clone, bool $expected): void
{
$this->prepareClone($clone);
$destinationId = '';
$source = [];
$expectedTranslation = [];
if ($clone) {
$this->setupApi();
$this->Clone->startup();
$response = $this->client->save('documents', ['title' => 'translations clone test document']);
$destinationId = (string)Hash::get($response, 'data.id');
$source['data'] = (array)$this->createTestObjectWithTranslation();
$sourceId = (string)Hash::get($source, 'data.id');
$response = $this->client->get(sprintf('/translations?filter[object_id]=%s', $sourceId));
$data = (array)Hash::get($response, 'data.0.attributes');
$expectedTranslation = [
'lang' => (string)Hash::get($data, 'lang'),
'object_id' => $destinationId,
'status' => (string)Hash::get($data, 'status'),
'translated_fields' => (array)Hash::get($data, 'translated_fields'),
];
}
$actual = $this->Clone->translations($source, $destinationId);
static::assertSame($expected, $actual);
if (!empty($expectedTranslation)) {
$response = $this->client->get(sprintf('/translations?filter[object_id]=%s', $destinationId));
$data = (array)Hash::get($response, 'data.0.attributes');
static::assertSame($expectedTranslation, [
'lang' => (string)Hash::get($data, 'lang'),
'object_id' => $destinationId,
'status' => (string)Hash::get($data, 'status'),
'translated_fields' => (array)Hash::get($data, 'translated_fields'),
]);
}
}

/**
* Prepare clone component for test.
*
* @return void
*/
private function prepareClone(bool $clone): void
{
$config = [
'environment' => [
'REQUEST_METHOD' => 'POST',
],
'query' => compact('cloneRelations'),
'query' => ['cloneRelations' => $clone, 'cloneTranslations' => $clone],
];
$request = new ServerRequest($config);
$this->controller = new Controller($request);
Expand Down

0 comments on commit 971722c

Please sign in to comment.