Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone object translations #1174

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading