diff --git a/api/v1/submissions/PKPSubmissionController.php b/api/v1/submissions/PKPSubmissionController.php index 97f1ef1001f..25c11d64a33 100644 --- a/api/v1/submissions/PKPSubmissionController.php +++ b/api/v1/submissions/PKPSubmissionController.php @@ -17,6 +17,7 @@ namespace PKP\API\v1\submissions; +use APP\author\Author; use APP\core\Application; use APP\facades\Repo; use APP\mail\variables\ContextEmailVariable; @@ -65,6 +66,7 @@ use PKP\submission\GenreDAO; use PKP\submission\PKPSubmission; use PKP\submission\reviewAssignment\ReviewAssignment; +use PKP\submissionFile\SubmissionFile; use PKP\userGroup\UserGroup; class PKPSubmissionController extends PKPBaseController @@ -84,6 +86,7 @@ class PKPSubmissionController extends PKPBaseController 'saveForLater', 'submit', 'delete', + 'changeLocale', 'getGalleys', 'getDecisions', 'getParticipants', @@ -116,6 +119,7 @@ class PKPSubmissionController extends PKPBaseController 'deleteContributor', 'editContributor', 'saveContributorsOrder', + 'changeLocale', ]; /** @var array Handlers that must be authorized to access a submission's production stage */ @@ -218,6 +222,10 @@ public function getGroupRoutes(): void Route::delete('{submissionId}', $this->delete(...)) ->name('submission.delete') ->whereNumber('submissionId'); + + Route::put('{submissionId}/publications/{publicationId}/changeLocale', $this->changeLocale(...)) + ->name('submission.publication.changeLocale') + ->whereNumber(['submissionId', 'publicationId']); }); Route::middleware([ @@ -867,6 +875,59 @@ public function delete(Request $illuminateRequest): JsonResponse return response()->json($submissionProps, Response::HTTP_OK); } + /** + * Change submission language + */ + public function changeLocale(Request $illuminateRequest): JsonResponse + { + $publication = Repo::publication()->get((int) $illuminateRequest->route('publicationId')); + + if (!$publication) { + return response()->json([ + 'error' => __('api.404.resourceNotFound'), + ], Response::HTTP_NOT_FOUND); + } + + $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + + if ($submission->getId() !== $publication->getData('submissionId')) { + return response()->json([ + 'error' => __('api.publications.403.submissionsDidNotMatch'), + ], Response::HTTP_FORBIDDEN); + } + + $paramsSubmission = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_SUBMISSION, $illuminateRequest->input()); + $newLocale = $paramsSubmission['locale'] ?? null; + + // Submission language can not be changed when there are more than one publication or a publication's status is published + if (!$newLocale || count($submission->getData('publications')) > 1 || $publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) { + return response()->json(['error' => __('api.submission.403.cantChangeSubmissionLanguage')], Response::HTTP_FORBIDDEN); + } + + // Convert a form field value to multilingual (if it is not) and merge rest values + collect(app()->get('schema')->getMultilingualProps(PKPSchemaService::SCHEMA_PUBLICATION)) + ->each(fn (string $prop) => + $illuminateRequest->whenHas($prop, fn ($value) => + $illuminateRequest->merge([ + $prop => array_merge( + $publication->getData($prop) ?? [], + (is_array($value) && array_key_exists($newLocale, $value)) ? $value : [$newLocale => $value] + ) + ]) + ) + ); + + $responsePublication = $this->editPublication($illuminateRequest); + + if ($responsePublication->status() !== 200) { + return $responsePublication; + } + + $this->copyMultilingualData($submission, $newLocale); + + return $this->edit($illuminateRequest); + } + /** * Get the decisions recorded on a submission */ @@ -1986,4 +2047,40 @@ protected function getSubmissionAndPublicationData(Request $illuminateRequest): ]; } + /** + * Copy author, files, etc. multilingual fields from old to new changed language + */ + protected function copyMultilingualData(Submission $submission, string $newLocale): void { + $oldLocale = $submission->getData('locale'); + $editProps = fn (Author|SubmissionFile $item, array $props): array => collect($props) + ->mapWithKeys(fn (string $prop): array => [$prop => ($data = $item->getData($prop)[$oldLocale] ?? null) ? [$newLocale => $data] : null]) + ->filter() + ->toArray(); + + // Submission files + $fileProps = [ + 'name', + ]; + Repo::submissionFile() + ->getCollector() + ->filterBySubmissionIds([$submission->getId()]) + ->getMany() + ->each(fn (SubmissionFile $file) => Repo::submissionFile()->edit($file, $editProps($file, $fileProps))); + + // Contributor + $contributorProps = [ + 'givenName', + 'familyName', + 'preferredPublicName', + ]; + Repo::author() + ->getCollector() + ->filterByPublicationIds([$submission->getLatestPublication()->getId()]) + ->getMany() + ->each(function (Author $contributor) use ($contributorProps, $editProps, $newLocale) { + if (!($contributor->getData('givenName')[$newLocale] ?? null)) { + Repo::author()->edit($contributor, $editProps($contributor, $contributorProps)); + } + }); + } } diff --git a/classes/components/forms/submission/ChangeSubmissionLanguageMetadataForm.php b/classes/components/forms/submission/ChangeSubmissionLanguageMetadataForm.php new file mode 100644 index 00000000000..3085b753f12 --- /dev/null +++ b/classes/components/forms/submission/ChangeSubmissionLanguageMetadataForm.php @@ -0,0 +1,118 @@ +action = $action; + + $this->addGroup([ + 'id' => 'language', + ]); + + $submissionLocale = $submission->getData('locale'); + $supportedLocaleNames = collect($context->getSupportedSubmissionLocaleNames()); + $submissionLocaleNames = collect(Locale::getSubmissionLocaleDisplayNames([$submissionLocale])); + $showWhen = ['locale', $supportedLocaleNames->diffKeys($submissionLocaleNames)->keys()->toArray()]; + + $this->addGroup([ + 'id' => 'metadata', + 'showWhen' => $showWhen, + ]); + + // Language + $localeOptions = $supportedLocaleNames + ->union($submissionLocaleNames) + ->sortKeys() + ->map(fn ($name, $key) => ['value' => $key, 'label' => $name]) + ->values() + ->toArray(); + $this->addField(new FieldRadioInput('locale', [ + 'label' => __('submission.submit.submissionLocale'), + 'description' => __('submission.list.changeSubmissionLanguage.languageDescription'), + 'groupId' => 'language', + 'type' => 'radio', + 'options' => $localeOptions, + 'isRequired' => true, + 'value' => $submissionLocale, + ])); + + // Metadata description + $this->addField(new FieldHTML('metadataDescription', [ + 'description' => __('submission.list.changeSubmissionLanguage.metadataDescription'), + 'groupId' => 'metadata', + ])); + + $submissionLocaleName = $submissionLocaleNames->get($submissionLocale); + + // Title and abstract + $titleAbstractForm = $this->getTitleAbstractForm(FormComponent::ACTION_EMIT, [$submissionLocale], $publication, $context); + $this->setField($titleAbstractForm->getField('title'), $submissionLocaleName, $submissionLocale); + $this->setField($titleAbstractForm->getField('abstract'), $submissionLocaleName, $submissionLocale); + + // Add cancel button + $this->addCancel(); + } + + protected function addCancel() { + $this->addPage([ + 'id' => 'default', + 'submitButton' => ['label' => __('common.confirm')], + 'cancelButton' => ['label' => __('common.cancel')], + ]); + collect($this->groups)->each(fn ($_, $i) => ($this->groups[$i]['pageId'] = 'default')); + } + + protected function getTitleAbstractForm(string $publicationApiUrl, array $locales, Publication $publication, Context $context): TitleAbstractForm + { + $pubSecId = $publication->getData('sectionId'); + $section = $pubSecId ? Repo::section()->get($pubSecId, $context->getId()) : null; + return new TitleAbstractForm( + $publicationApiUrl, + $locales, + $publication, + $section ? (int) $section->getData('wordCount') : 0, + $section && !$section->getData('abstractsNotRequired') + ); + } + + protected function setField(Field $field, string $submissionLocaleName, string $submissionLocale): void { + if ($field->isRequired) { + $field->groupId = 'metadata'; + $field->description = __("submission.list.changeSubmissionLanguage.metadataDescription.{$field->name}", ['language' => $submissionLocaleName]); + if ($field->isMultilingual) { + $field->isMultilingual = false; + $field->value = $field->value[$submissionLocale]; + } + $this->addField($field); + } + } +} diff --git a/locale/en/api.po b/locale/en/api.po index bf05f2c3a4c..c070f61afaa 100644 --- a/locale/en/api.po +++ b/locale/en/api.po @@ -275,6 +275,9 @@ msgstr "The requested volume, number or year is not valid." msgid "api.submissions.400.invalidSubmitAs" msgstr "You are not allowed to submit in this user role." +msgid "api.submission.403.cantChangeSubmissionLanguage" +msgstr "You can not change language of this submission because it already has more than one publication version or a published publication." + msgid "api.submissions.403.csrfTokenFailure" msgstr "Your request was denied. This may be because your login has expired. Try reloading the page and trying again." diff --git a/locale/en/submission.po b/locale/en/submission.po index 6ba547a4170..b9c2c65e84b 100644 --- a/locale/en/submission.po +++ b/locale/en/submission.po @@ -2043,6 +2043,28 @@ msgstr "You must assign at least one participant to this submission before initi msgid "submission.query.allowedEditTime" msgstr "You can update this discussion for {$allowedEditTimeNoticeLimit} minutes." +msgid "submission.list.changeSubmissionLanguage.currentLanguage" +msgstr "Current Submission Language:" + +msgid "submission.list.changeSubmissionLanguage.buttonLabel" +msgstr "Change" + +msgid "submission.list.changeSubmissionLanguage.title" +msgstr "Change Submission Language For" + +msgid "submission.list.changeSubmissionLanguage.languageDescription" +msgstr "This is the primary submission language. Changing this will have effects on the submission and the metadata entered" + +msgid "submission.list.changeSubmissionLanguage.metadataDescription" +msgstr "Before changing the submission language, ensure you have filled out the following metadata fields to maintain system integrity. " +"Also, note that contributor information and file names will be copied from previously entered information." + +msgid "submission.list.changeSubmissionLanguage.metadataDescription.title" +msgstr "Enter submission title here in {$language}. You can format your title as needed" + +msgid "submission.list.changeSubmissionLanguage.metadataDescription.abstract" +msgstr "Including the abstract in {$language} is recommended. This helps ensure that the content is accessible" + msgid "submission.list.infoCenter" msgstr "Activity Log & Notes" @@ -2430,3 +2452,5 @@ msgstr "Competing Interests" msgid "author.competingInterests.description" msgstr "Please disclose any competing interests this author may have with the research subject." +msgid "submission.localeNotSupported" +msgstr "The language of the submission ({$language}) is not one of the supported submission languages. You can still edit the submission details but new submissions are not currently accepted with this language." diff --git a/pages/authorDashboard/PKPAuthorDashboardHandler.php b/pages/authorDashboard/PKPAuthorDashboardHandler.php index ab48e2fe483..c65036023d9 100644 --- a/pages/authorDashboard/PKPAuthorDashboardHandler.php +++ b/pages/authorDashboard/PKPAuthorDashboardHandler.php @@ -34,6 +34,7 @@ use PKP\core\PKPApplication; use PKP\core\PKPRequest; use PKP\db\DAORegistry; +use PKP\facades\Locale; use PKP\log\SubmissionEmailLogEventType; use PKP\security\authorization\AuthorDashboardAccessPolicy; use PKP\security\Role; @@ -305,6 +306,7 @@ public function setupTemplate($request) $state = [ 'canEditPublication' => $canEditPublication, + 'currentSubmissionLanguageLabel' => Locale::getSubmissionLocaleDisplayNames([$submissionLocale])[$submissionLocale], 'components' => [ $titleAbstractForm::FORM_TITLE_ABSTRACT => $this->getLocalizedForm($titleAbstractForm, $submissionLocale, $locales), $citationsForm::FORM_CITATIONS => $this->getLocalizedForm($citationsForm, $submissionLocale, $locales), diff --git a/pages/workflow/PKPWorkflowHandler.php b/pages/workflow/PKPWorkflowHandler.php index 6cbf4696986..1d35de0a7c1 100644 --- a/pages/workflow/PKPWorkflowHandler.php +++ b/pages/workflow/PKPWorkflowHandler.php @@ -32,6 +32,7 @@ use PKP\components\forms\publication\PKPMetadataForm; use PKP\components\forms\publication\PKPPublicationLicenseForm; use PKP\components\forms\publication\TitleAbstractForm; +use PKP\components\forms\submission\ChangeSubmissionLanguageMetadataForm; use PKP\components\listPanels\ContributorsListPanel; use PKP\components\PublicationSectionJats; use PKP\context\Context; @@ -40,6 +41,7 @@ use PKP\core\PKPRequest; use PKP\db\DAORegistry; use PKP\decision\Decision; +use PKP\facades\Locale; use PKP\notification\Notification; use PKP\plugins\PluginRegistry; use PKP\security\authorization\internal\SubmissionCompletePolicy; @@ -293,11 +295,15 @@ public function index($args, $request) $canEditPublication ); + $changeSubmissionLanguageApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getData('urlPath'), "submissions/{$submission->getId()}/publications/{$latestPublication->getId()}/changeLocale"); + $changeSubmissionLanguageMetadataForm = new ChangeSubmissionLanguageMetadataForm($changeSubmissionLanguageApiUrl, $submission, $latestPublication, $submissionContext); + $templateMgr->setConstants([ 'STATUS_QUEUED' => PKPSubmission::STATUS_QUEUED, 'STATUS_PUBLISHED' => PKPSubmission::STATUS_PUBLISHED, 'STATUS_DECLINED' => PKPSubmission::STATUS_DECLINED, 'STATUS_SCHEDULED' => PKPSubmission::STATUS_SCHEDULED, + 'FORM_CHANGE_SUBMISSION_LANGUAGE_METADATA' => $changeSubmissionLanguageMetadataForm::FORM_CHANGE_SUBMISSION_LANGUAGE_METADATA, 'FORM_CITATIONS' => $citationsForm::FORM_CITATIONS, 'FORM_PUBLICATION_LICENSE' => $publicationLicenseForm::FORM_PUBLICATION_LICENSE, 'FORM_PUBLISH' => PublishForm::FORM_PUBLISH, @@ -334,8 +340,11 @@ public function index($args, $request) $state = [ 'activityLogLabel' => __('submission.list.infoCenter'), 'canAccessPublication' => $canAccessPublication, + 'canChangeSubmissionLanguage' => $canPublish || $canEditPublication, 'canEditPublication' => $canEditPublication, + 'currentSubmissionLanguageLabel' => Locale::getSubmissionLocaleDisplayNames([$submissionLocale])[$submissionLocale], 'components' => [ + $changeSubmissionLanguageMetadataForm->id => $this->getLocalizedForm($changeSubmissionLanguageMetadataForm, $submissionLocale, $locales), $contributorsListPanel->id => $contributorsListPanel->getConfig(), $citationsForm->id => $citationsForm->getConfig(), $publicationLicenseForm->id => $this->getLocalizedForm($publicationLicenseForm, $submissionLocale, $locales), @@ -362,6 +371,7 @@ public function index($args, $request) 'submissionApiUrl' => $submissionApiUrl, 'submissionLibraryLabel' => __('grid.libraryFiles.submission.title'), 'submissionLibraryUrl' => $submissionLibraryUrl, + 'submissionSupportedLocales' => $submissionContext->getSupportedSubmissionLocales(), 'supportsReferences' => !!$submissionContext->getData('citations'), 'unpublishConfirmLabel' => __('publication.unpublish.confirm'), 'unpublishLabel' => __('publication.unpublish'),