From 931938ccdd55e38c3880b0797ed4a091216747ef Mon Sep 17 00:00:00 2001
From: jyhein <124268211+jyhein@users.noreply.github.com>
Date: Mon, 11 Mar 2024 15:18:42 +0200
Subject: [PATCH] Make submission language selection and metadata forms
independent from website language settings
---
classes/issue/IssueGalley.php | 4 +--
.../migration/install/JournalsMigration.php | 4 +--
classes/migration/install/OJSMigration.php | 14 ++++-----
.../I9425_SeparateUIAndSubmissionLocales.php | 31 +++++++++++++++++++
classes/publication/Repository.php | 11 +++----
classes/publication/maps/Schema.php | 4 ++-
classes/submission/Repository.php | 3 +-
classes/submission/maps/Schema.php | 8 ++++-
.../articleGalleys/form/ArticleGalleyForm.php | 19 ++++++------
.../20-CreateContext.cy.js | 9 ++++--
.../60-content/AmwandengaSubmission.cy.js | 4 +--
.../tests/integration/SubmissionWizard.cy.js | 22 ++++++-------
dbscripts/xml/upgrade.xml | 1 +
pages/submission/SubmissionHandler.php | 4 +--
pages/workflow/WorkflowHandler.php | 12 ++++---
schemas/galley.json | 2 +-
16 files changed, 101 insertions(+), 51 deletions(-)
create mode 100644 classes/migration/upgrade/v3_5_0/I9425_SeparateUIAndSubmissionLocales.php
diff --git a/classes/issue/IssueGalley.php b/classes/issue/IssueGalley.php
index 5cbeed9badd..43eaa8d1826 100644
--- a/classes/issue/IssueGalley.php
+++ b/classes/issue/IssueGalley.php
@@ -60,8 +60,8 @@ public function isPdfGalley()
public function getGalleyLabel()
{
$label = $this->getLabel();
- if ($this->getLocale() != Locale::getLocale()) {
- $label .= ' (' . Locale::getMetadata($this->getLocale())->getDisplayName() . ')';
+ if (($locale = $this->getLocale()) && $locale !== Locale::getLocale()) {
+ $label .= ' (' . Locale::getSubmissionLocaleDisplayNames([$locale])[$locale] . ')';
}
return $label;
}
diff --git a/classes/migration/install/JournalsMigration.php b/classes/migration/install/JournalsMigration.php
index e9fe25d36b0..04e2a229b4a 100644
--- a/classes/migration/install/JournalsMigration.php
+++ b/classes/migration/install/JournalsMigration.php
@@ -30,7 +30,7 @@ public function up(): void
$table->bigInteger('journal_id')->autoIncrement();
$table->string('path', 32);
$table->float('seq', 8, 2)->default(0)->comment('Used to order lists of journals');
- $table->string('primary_locale', 14);
+ $table->string('primary_locale', 28);
$table->smallInteger('enabled')->default(1)->comment('Controls whether or not the journal is considered "live" and will appear on the website. (Note that disabled journals may still be accessible, but only if the user knows the URL.)');
$table->unique(['path'], 'journals_path');
$table->bigInteger('current_issue_id')->nullable()->default(null);
@@ -45,7 +45,7 @@ public function up(): void
$table->foreign('journal_id', 'journal_settings_journal_id')->references('journal_id')->on('journals')->onDelete('cascade');
$table->index(['journal_id'], 'journal_settings_journal_id');
- $table->string('locale', 14)->default('');
+ $table->string('locale', 28)->default('');
$table->string('setting_name', 255);
$table->mediumText('setting_value')->nullable();
diff --git a/classes/migration/install/OJSMigration.php b/classes/migration/install/OJSMigration.php
index 3d2d6db0e69..d5535310845 100644
--- a/classes/migration/install/OJSMigration.php
+++ b/classes/migration/install/OJSMigration.php
@@ -58,7 +58,7 @@ public function up(): void
$table->foreign('section_id', 'section_settings_section_id')->references('section_id')->on('sections')->onDelete('cascade');
$table->index(['section_id'], 'section_settings_section_id');
- $table->string('locale', 14)->default('');
+ $table->string('locale', 28)->default('');
$table->string('setting_name', 255);
$table->mediumText('setting_value')->nullable();
@@ -107,7 +107,7 @@ public function up(): void
$table->foreign('issue_id', 'issue_settings_issue_id')->references('issue_id')->on('issues')->onDelete('cascade');
$table->index(['issue_id'], 'issue_settings_issue_id');
- $table->string('locale', 14)->default('');
+ $table->string('locale', 28)->default('');
$table->string('setting_name', 255);
$table->mediumText('setting_value')->nullable();
@@ -143,7 +143,7 @@ public function up(): void
$table->comment('Issue galleys are representations of the entire issue in a single file, such as a complete issue PDF.');
$table->bigInteger('galley_id')->autoIncrement();
- $table->string('locale', 14)->nullable();
+ $table->string('locale', 28)->nullable();
$table->bigInteger('issue_id');
$table->foreign('issue_id', 'issue_galleys_issue_id')->references('issue_id')->on('issues')->onDelete('cascade');
@@ -169,7 +169,7 @@ public function up(): void
$table->foreign('galley_id', 'issue_galleys_settings_galley_id')->references('galley_id')->on('issue_galleys')->onDelete('cascade');
$table->index(['galley_id'], 'issue_galley_settings_galley_id');
- $table->string('locale', 14)->default('');
+ $table->string('locale', 28)->default('');
$table->string('setting_name', 255);
$table->mediumText('setting_value')->nullable();
$table->string('setting_type', 6)->comment('(bool|int|float|string|object)');
@@ -262,7 +262,7 @@ public function up(): void
Schema::create('publication_galleys', function (Blueprint $table) {
$table->comment('Publication galleys are representations of a publication in a specific format, e.g. a PDF.');
$table->bigInteger('galley_id')->autoIncrement();
- $table->string('locale', 14)->nullable();
+ $table->string('locale', 28)->nullable();
$table->bigInteger('publication_id');
$table->foreign('publication_id', 'publication_galleys_publication_id')->references('publication_id')->on('publications')->onDelete('cascade');
@@ -295,7 +295,7 @@ public function up(): void
$table->foreign('galley_id', 'publication_galley_settings_galley_id')->references('galley_id')->on('publication_galleys')->onDelete('cascade');
$table->index(['galley_id'], 'publication_galley_settings_galley_id');
- $table->string('locale', 14)->default('');
+ $table->string('locale', 28)->default('');
$table->string('setting_name', 255);
$table->mediumText('setting_value')->nullable();
@@ -337,7 +337,7 @@ public function up(): void
$table->foreign('type_id', 'subscription_type_settings_type_id')->references('type_id')->on('subscription_types')->onDelete('cascade');
$table->index(['type_id'], 'subscription_type_settings_type_id');
- $table->string('locale', 14)->default('');
+ $table->string('locale', 28)->default('');
$table->string('setting_name', 255);
$table->mediumText('setting_value')->nullable();
$table->string('setting_type', 6);
diff --git a/classes/migration/upgrade/v3_5_0/I9425_SeparateUIAndSubmissionLocales.php b/classes/migration/upgrade/v3_5_0/I9425_SeparateUIAndSubmissionLocales.php
new file mode 100644
index 00000000000..d7ab131ba51
--- /dev/null
+++ b/classes/migration/upgrade/v3_5_0/I9425_SeparateUIAndSubmissionLocales.php
@@ -0,0 +1,31 @@
+getSupportedSubmissionLocales();
- $primaryLocale = $submission->getData('locale');
+ $submissionLocale = $submission->getData('locale');
// Ensure that the specified section exists
$section = null;
@@ -62,21 +60,22 @@ public function validate($publication, array $props, Submission $submission, Con
if ($section && !$submission->getData('submissionProgress')) {
// Require abstracts for new publications if the section requires them
if (is_null($publication) && !$section->getData('abstractsNotRequired') && empty($props['abstract'])) {
- $errors['abstract'][$primaryLocale] = [__('author.submit.form.abstractRequired')];
+ $errors['abstract'][$submissionLocale] = [__('author.submit.form.abstractRequired')];
}
if (isset($props['abstract']) && empty($errors['abstract'])) {
// Require abstracts in the primary language if the section requires them
if (!$section->getData('abstractsNotRequired')) {
- if (empty($props['abstract'][$primaryLocale])) {
+ if (empty($props['abstract'][$submissionLocale])) {
if (!isset($errors['abstract'])) {
$errors['abstract'] = [];
};
- $errors['abstract'][$primaryLocale] = [__('author.submit.form.abstractRequired')];
+ $errors['abstract'][$submissionLocale] = [__('author.submit.form.abstractRequired')];
}
}
// Check the word count on abstracts
+ $allowedLocales = $submission->getPublicationLanguages($context->getSupportedSubmissionMetadataLocales());
foreach ($allowedLocales as $localeKey) {
if (empty($props['abstract'][$localeKey])) {
continue;
diff --git a/classes/publication/maps/Schema.php b/classes/publication/maps/Schema.php
index e01c6337286..d8d813f48ce 100644
--- a/classes/publication/maps/Schema.php
+++ b/classes/publication/maps/Schema.php
@@ -49,7 +49,9 @@ protected function mapByProperties(array $props, Publication $publication, bool
);
}
- $output = $this->schemaService->addMissingMultilingualValues(PKPSchemaService::SCHEMA_PUBLICATION, $output, $this->context->getSupportedSubmissionLocales());
+ $locales = $this->submission->getPublicationLanguages($this->context->getSupportedSubmissionMetadataLocales());
+
+ $output = $this->schemaService->addMissingMultilingualValues(PKPSchemaService::SCHEMA_PUBLICATION, $output, $locales);
ksort($output);
diff --git a/classes/submission/Repository.php b/classes/submission/Repository.php
index 579c0c9a536..10d6ee5ac46 100644
--- a/classes/submission/Repository.php
+++ b/classes/submission/Repository.php
@@ -80,7 +80,8 @@ public function validateSubmit(Submission $submission, Context $context): array
$abstracts = $publication->getData('abstract');
if ($abstracts) {
$abstractErrors = [];
- foreach ($context->getSupportedSubmissionLocales() as $localeKey) {
+ $allowedLocales = $submission->getPublicationLanguages($context->getSupportedSubmissionMetadataLocales());
+ foreach ($allowedLocales as $localeKey) {
$abstract = $publication->getData('abstract', $localeKey);
$wordCount = $abstract ? PKPString::getWordCount($abstract) : 0;
if ($wordCount > $section->getAbstractWordCount()) {
diff --git a/classes/submission/maps/Schema.php b/classes/submission/maps/Schema.php
index 272fda89697..12f2097c384 100644
--- a/classes/submission/maps/Schema.php
+++ b/classes/submission/maps/Schema.php
@@ -36,7 +36,13 @@ protected function mapByProperties(array $props, Submission $submission): array
);
}
- $output = $this->schemaService->addMissingMultilingualValues($this->schemaService::SCHEMA_SUBMISSION, $output, $this->context->getSupportedSubmissionLocales());
+ $locales = $this->context->getSupportedSubmissionMetadataLocales();
+
+ if (!in_array($primaryLocale = $submission->getData('locale'), $locales)) {
+ $locales[] = $primaryLocale;
+ }
+
+ $output = $this->schemaService->addMissingMultilingualValues($this->schemaService::SCHEMA_SUBMISSION, $output, $locales);
ksort($output);
diff --git a/controllers/grid/articleGalleys/form/ArticleGalleyForm.php b/controllers/grid/articleGalleys/form/ArticleGalleyForm.php
index 0ad5450ca89..a66ef7b4eba 100644
--- a/controllers/grid/articleGalleys/form/ArticleGalleyForm.php
+++ b/controllers/grid/articleGalleys/form/ArticleGalleyForm.php
@@ -62,17 +62,15 @@ public function __construct($request, $submission, $publication, $articleGalley
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
// Ensure a locale is provided and valid
- $journal = $request->getJournal();
+ $locales = $submission->getPublicationLanguages($request->getJournal()->getSupportedSubmissionMetadataLocales(), $articleGalley?->getLanguages());
$this->addCheck(
- new \PKP\form\validation\FormValidator(
+ new \PKP\form\validation\FormValidatorCustom(
$this,
'locale',
'required',
- 'editor.issues.galleyLocaleRequired'
- ),
- function ($locale) use ($journal) {
- return in_array($locale, $journal->getSupportedSubmissionLocaleNames());
- }
+ 'editor.issues.galleyLocaleRequired',
+ fn ($locale) => in_array($locale, $locales)
+ )
);
}
@@ -93,9 +91,12 @@ public function fetch($request, $template = null, $display = false)
'supportsDependentFiles' => $articleGalleyFile ? Repo::submissionFile()->supportsDependentFiles($articleGalleyFile) : null,
]);
}
- $context = $request->getContext();
+
+ $supportedLocales = $request->getContext()->getSupportedSubmissionMetadataLocaleNames() + $this->_submission->getPublicationLanguageNames() + ($this->_articleGalley?->getLanguageNames() ?? []);
+ ksort($supportedLocales);
+
$templateMgr->assign([
- 'supportedLocales' => $context->getSupportedSubmissionLocaleNames(),
+ 'supportedLocales' => $supportedLocales,
'submissionId' => $this->_submission->getId(),
'publicationId' => $this->_publication->getId(),
'formDisabled' => !$this->_isEditable
diff --git a/cypress/tests/data/10-ApplicationSetup/20-CreateContext.cy.js b/cypress/tests/data/10-ApplicationSetup/20-CreateContext.cy.js
index feac3e8ca6d..4d45190b0fb 100644
--- a/cypress/tests/data/10-ApplicationSetup/20-CreateContext.cy.js
+++ b/cypress/tests/data/10-ApplicationSetup/20-CreateContext.cy.js
@@ -70,8 +70,13 @@ describe('Data suite tests', function() {
cy.get('#appearance [role="status"]').contains('Saved');
cy.get('button[id="languages-button"]').click();
- cy.get('input[id^=select-cell-fr_CA-submissionLocale]').click();
- cy.contains('Locale settings saved.');
+ cy.get('input[id^="select-cell-fr_CA-formLocale"]').click();
+ cy.get('a[id^=component-grid-settings-languages-submissionlanguagegrid-addLanguageModal-button]').click();
+ cy.get('#locale-fr_CA').should('exist').click();
+ cy.get('#addLanguageForm button[name="submitFormButton"]').click();
+ cy.contains('Submission locales updated.').should('exist');
+ cy.get('input[id^="select-cell-fr_CA-submissionLocale"]').click();
+ cy.get('input[id^="select-cell-fr_CA-submissionMetadataLocale"]').should('be.checked');
cy.get('button[id="indexing-button"]').click();
cy.get('input[name="searchDescription-en"]').type(Cypress.env('contextDescriptions')['en'], {delay: 0});
diff --git a/cypress/tests/data/60-content/AmwandengaSubmission.cy.js b/cypress/tests/data/60-content/AmwandengaSubmission.cy.js
index a9c5fc14a45..a6de75ba6ba 100644
--- a/cypress/tests/data/60-content/AmwandengaSubmission.cy.js
+++ b/cypress/tests/data/60-content/AmwandengaSubmission.cy.js
@@ -224,7 +224,7 @@ describe('Data suite: Amwandenga', function() {
.find('h4').contains('Keywords').siblings('.submissionWizard__reviewPanel__item__value').contains('None provided')
.parents('.submissionWizard__reviewPanel')
.find('h4').contains('Abstract').siblings('.submissionWizard__reviewPanel__item__value').contains(submission.abstract);
- cy.get('h3').contains('Details (French)')
+ cy.get('h3').contains('Details (French (Canada))')
.parents('.submissionWizard__reviewPanel')
.find('h4').contains('Title').siblings('.submissionWizard__reviewPanel__item__value').contains('None provided')
.parents('.submissionWizard__reviewPanel')
@@ -234,7 +234,7 @@ describe('Data suite: Amwandenga', function() {
cy.get('h3').contains('For the Editors (English)')
.parents('.submissionWizard__reviewPanel')
.find('h4').contains('Comments for the Editor').siblings('.submissionWizard__reviewPanel__item__value').contains('None');
- cy.get('h3').contains('For the Editors (French)') // FIXME: Should be French
+ cy.get('h3').contains('For the Editors (French (Canada))')
// Save for later
cy.get('button').contains('Save for Later').click();
diff --git a/cypress/tests/integration/SubmissionWizard.cy.js b/cypress/tests/integration/SubmissionWizard.cy.js
index 1011d67375e..6b9e000ebd0 100644
--- a/cypress/tests/integration/SubmissionWizard.cy.js
+++ b/cypress/tests/integration/SubmissionWizard.cy.js
@@ -479,20 +479,20 @@ describe('Submission Wizard', function() {
cy.get('label:contains("Abstract *")');
cy.contains('Word Count: 0/500');
- // Change submission language to French and section to Reviews
+ // Change submission language to French (Canada) and section to Reviews
cy.contains('Submitting to the Articles section in English');
cy.get('button:contains("Change")').click();
cy.get('h2:contains("Change Submission Settings")')
.parents('.modal')
.within(() => {
- cy.get('label:contains("French")').click();
+ cy.get('label:contains("French (Canada)")').click();
cy.get('label:contains("Reviews")').click();
cy.get('button:contains("Save")').click();
});
- // Forms load with French fields displayed instead of English
- cy.contains('Submitting to the Reviews section in French');
- cy.get('span.pkpFormLocales__locale:contains("French")');
+ // Forms load with French (Canada) fields displayed instead of English
+ cy.contains('Submitting to the Reviews section in French (Canada)');
+ cy.get('span.pkpFormLocales__locale:contains("French (Canada)")');
cy.get('#titleAbstract-keywords-control-fr_CA').type('Transformation Sociale', {delay: 0});
cy.get('li:contains("Transformation Sociale")');
cy.get('#titleAbstract-keywords-control-fr_CA').type('{downarrow}{enter}', {delay: 0});
@@ -502,7 +502,7 @@ describe('Submission Wizard', function() {
cy.get('label:contains("Abstract *")').should('not.exist');
cy.get('*:contains("Word Count: 0/500")').should('not.exist');
- // Show English fields alongside French fields
+ // Show English fields alongside French (Canada) fields
cy.get('.pkpStep:contains("Submission Details") button.pkpFormLocales__locale:contains("English")').click();
cy.get('label:contains("Title in English")');
cy.get('label:contains("Keywords in English")');
@@ -523,7 +523,7 @@ describe('Submission Wizard', function() {
cy.get('button:contains("Continue")').click();
cy.get('button:contains("Continue")').click();
- // Check metadata form shows in French only at first
+ // Check metadata form shows in French (Canada) only at first
const metadata = {
subjects: "Subjects",
disciplines: "Disciplines",
@@ -539,7 +539,7 @@ describe('Submission Wizard', function() {
cy.get('label:contains("' + metadata[prop] + ' in English")').should('not.be.visible');
});
- // Show English fields alongside French fields
+ // Show English fields alongside French (Canada) fields
cy.get('.pkpStep:contains("For the Editors") button.pkpFormLocales__locale:contains("English")').click();
Object.keys(metadata).forEach((prop) => {
cy.get('label:contains("' + metadata[prop] + ' in English")');
@@ -551,13 +551,13 @@ describe('Submission Wizard', function() {
// Errors in review
cy.get('button:contains("Continue")').click();
cy.contains('There are one or more problems');
- cy.get('h3:contains("Details (French)")')
+ cy.get('h3:contains("Details (French (Canada))")')
.parents('.submissionWizard__reviewPanel')
.find('h4:contains("Title")')
.parent()
.contains('This field is required.');
- cy.contains('The given name is missing in French for one or more of the contributors.');
- cy.get('h3:contains("For the Editors (French)")')
+ cy.contains('The given name is missing in French (Canada) for one or more of the contributors.');
+ cy.get('h3:contains("For the Editors (French (Canada))")')
.parents('.submissionWizard__reviewPanel')
.find('h4:contains("Subjects")')
.parent()
diff --git a/dbscripts/xml/upgrade.xml b/dbscripts/xml/upgrade.xml
index 9be2c8cc698..997aaea9b90 100644
--- a/dbscripts/xml/upgrade.xml
+++ b/dbscripts/xml/upgrade.xml
@@ -117,6 +117,7 @@
+
diff --git a/pages/submission/SubmissionHandler.php b/pages/submission/SubmissionHandler.php
index 6485286889c..81f81479355 100644
--- a/pages/submission/SubmissionHandler.php
+++ b/pages/submission/SubmissionHandler.php
@@ -94,7 +94,7 @@ protected function getSubmittingTo(Context $context, Submission $submission, arr
'submission.wizard.submittingToSectionInLanguage',
[
'section' => $section->getLocalizedTitle(),
- 'language' => Locale::getMetadata($submission->getData('locale'))->getDisplayName(),
+ 'language' => Locale::getSubmissionLocaleDisplayNames([$submission->getData('locale')])[$submission->getData('locale')],
]
);
} elseif ($sectionCount) {
@@ -108,7 +108,7 @@ protected function getSubmittingTo(Context $context, Submission $submission, arr
return __(
'submission.wizard.submittingInLanguage',
[
- 'language' => Locale::getMetadata($submission->getData('locale'))->getDisplayName(),
+ 'language' => Locale::getSubmissionLocaleDisplayNames([$submission->getData('locale')])[$submission->getData('locale')],
]
);
}
diff --git a/pages/workflow/WorkflowHandler.php b/pages/workflow/WorkflowHandler.php
index 24fe738c319..e1a47b01bb9 100644
--- a/pages/workflow/WorkflowHandler.php
+++ b/pages/workflow/WorkflowHandler.php
@@ -85,11 +85,15 @@ public function setupIndex($request)
$submissionContext = Services::get('context')->get($submission->getData('contextId'));
}
- $locales = $submissionContext->getSupportedSubmissionLocaleNames();
- $locales = array_map(fn (string $locale, string $name) => ['key' => $locale, 'label' => $name], array_keys($locales), $locales);
-
$latestPublication = $submission->getLatestPublication();
+ $submissionLocale = $submission->getData('locale');
+ $locales = collect($submissionContext->getSupportedSubmissionMetadataLocaleNames() + $submission->getPublicationLanguageNames())
+ ->map(fn (string $name, string $locale) => ['key' => $locale, 'label' => $name])
+ ->sortBy('key')
+ ->values()
+ ->toArray();
+
$latestPublicationApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getPath(), 'submissions/' . $submission->getId() . '/publications/' . $latestPublication->getId());
$temporaryFileApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getPath(), 'temporaryFiles');
$issueApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getData('urlPath'), 'issues/__issueId__');
@@ -113,7 +117,7 @@ class_exists(\APP\components\forms\publication\AssignToIssueForm::class); // For
]);
$components = $templateMgr->getState('components');
- $components[FORM_ISSUE_ENTRY] = $issueEntryForm->getConfig();
+ $components[FORM_ISSUE_ENTRY] = $this->getLocalizedForm($issueEntryForm, $submissionLocale, $locales);
$canEditPublication = Repo::submission()->canEditPublication($submission->getId(), $request->getUser()->getId());
diff --git a/schemas/galley.json b/schemas/galley.json
index b0aac2e72c5..211ae8c04cf 100644
--- a/schemas/galley.json
+++ b/schemas/galley.json
@@ -48,7 +48,7 @@
"description": "The primary locale of this galley.",
"apiSummary": true,
"validation": [
- "regex:/^([a-z]{2})((_[A-Z]{2})?)(@[a-z]{0,})?$/"
+ "regex:/^([A-Za-z]{2,4})(?[_-]([A-Za-z]{4,5}|[0-9]{4}))?([_-]([A-Za-z]{2}|[0-9]{3}))?(@[a-z]{2,30}(?&sc)?)?$/"
]
},
"label": {