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": {