diff --git a/api/v1/submissions/PKPSubmissionController.php b/api/v1/submissions/PKPSubmissionController.php index 0cf5b342128..aa72ce6a843 100644 --- a/api/v1/submissions/PKPSubmissionController.php +++ b/api/v1/submissions/PKPSubmissionController.php @@ -53,6 +53,7 @@ use PKP\notification\NotificationSubscriptionSettingsDAO; use PKP\plugins\Hook; use PKP\plugins\PluginRegistry; +use PKP\publication\enums\JavStage; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\authorization\DecisionWritePolicy; use PKP\security\authorization\internal\SubmissionCompletePolicy; @@ -111,7 +112,10 @@ class PKPSubmissionController extends PKPBaseController 'getPublicationIdentifierForm', 'getPublicationLicenseForm', 'getPublicationTitleAbstractForm', - 'getChangeLanguageMetadata' + 'getChangeLanguageMetadata', + 'getJAVStageMetadata', + 'changeJavStageAndNumbering', + 'getNextAvailableJavVersionNumberingForStage', ]; /** @var array Handlers that must be authorized to write to a publication */ @@ -122,6 +126,7 @@ class PKPSubmissionController extends PKPBaseController 'editContributor', 'saveContributorsOrder', 'changeLocale', + 'changeJavStageAndNumbering' ]; /** @var array Handlers that must be authorized to access a submission's production stage */ @@ -236,6 +241,14 @@ public function getGroupRoutes(): void Route::put('{submissionId}/publications/{publicationId}/changeLocale', $this->changeLocale(...)) ->name('submission.publication.changeLocale') ->whereNumber(['submissionId', 'publicationId']); + + Route::put('{submissionId}/publications/{publicationId}/changeJavStageAndNumbering', $this->changeJavStageAndNumbering(...)) + ->name('submission.publication.changeJavStageAndNumbering') + ->whereNumber(['submissionId', 'publicationId']); + + Route::get('{submissionId}/getNextAvailableJavVersionNumberingForStage', $this->getNextAvailableJavVersionNumberingForStage(...)) + ->name('submission.getNextAvailableJavVersionNumberingForStage') + ->whereNumber(['submissionId']); }); Route::middleware([ @@ -301,6 +314,7 @@ public function getGroupRoutes(): void Route::get('reference', $this->getPublicationReferenceForm(...))->name('submission.publication._components.reference'); Route::get('titleAbstract', $this->getPublicationTitleAbstractForm(...))->name('submission.publication._components.titleAbstract'); Route::get('changeLanguageMetadata', $this->getChangeLanguageMetadata(...))->name('submission.publication._components.changeLanguageMetadata'); + Route::get('getJAVStageMetadata', $this->getJAVStageMetadata(...))->name('submission.publication._components.getJAVStageMetadata'); })->whereNumber(['submissionId', 'publicationId']); }); @@ -943,6 +957,68 @@ public function changeLocale(Request $illuminateRequest): JsonResponse return $this->edit($illuminateRequest); } + /** + * Change version stage + */ + public function changeJavStageAndNumbering(Request $illuminateRequest): JsonResponse + { + $request = $this->getRequest(); + $publication = Repo::publication()->get((int) $illuminateRequest->route('publicationId')); + + if (!$publication) { + return response()->json([ + 'error' => __('api.404.resourceNotFound'), + ], Response::HTTP_NOT_FOUND); + } + + // $submission = Repo::submission()->get((int) $illuminateRequest->route('submissionId')); + $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + + if ($submission->getId() !== $publication->getData('submissionId')) { + return response()->json([ + 'error' => __('api.publications.403.submissionsDidNotMatch'), + ], Response::HTTP_FORBIDDEN); + } + + $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_PUBLICATION, $illuminateRequest->input()); + + $submissionContext = $request->getContext(); + + $errors = Repo::publication()->validate($publication, $params, $submission, $submissionContext); + + if (!empty($errors)) { + return response()->json($errors, Response::HTTP_BAD_REQUEST); + } + + $versionStage = $params['javVersionStage']; + $versionStageIsMinor = (bool) $params['javVersionIsMinor']; + + Repo::publication()->updateJavVersionStageAndNumbering($publication, JavStage::from($versionStage), $versionStageIsMinor); + + return $this->edit($illuminateRequest); + } + + /** + * Get next potential JAV version stage + */ + public function getNextAvailableJavVersionNumberingForStage(Request $illuminateRequest): JsonResponse + { + $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + + $params = $illuminateRequest->input(); + $potentialVersionStage = JavStage::from($params['javVersionStage']); + $potentialIsMinor = ($params['javVersionIsMinor'] === 'false') ? false : (bool) $params['javVersionIsMinor']; + + $potentialVersionStage = $submission->getNextAvailableJavVersionNumberingForStage($potentialVersionStage, $potentialIsMinor); + + $retValue = $potentialVersionStage->getVersionStageDisplay(); + + return response()->json( + $retValue, + Response::HTTP_OK + ); + } + /** * Get the decisions recorded on a submission */ diff --git a/classes/migration/upgrade/v3_5_0/I4860_AddJavStageDataToPublication.php b/classes/migration/upgrade/v3_5_0/I4860_AddJavStageDataToPublication.php new file mode 100644 index 00000000000..297fb719c43 --- /dev/null +++ b/classes/migration/upgrade/v3_5_0/I4860_AddJavStageDataToPublication.php @@ -0,0 +1,59 @@ +enum('jav_version_stage', array_column(JavStage::cases(), 'value')) + ->default(JavStage::VERSION_OF_RECORD); + + // Adding minorVersion and majorVersion as integers + $table->integer('jav_version_minor') + ->default(JavStageAndNumbering::JAV_DEFAULT_NUMBERING_MINOR); + + $table->integer('jav_version_major') + ->default(JavStageAndNumbering::JAV_DEFAULT_NUMBERING_MAJOR); + }); + + // Update the version_major column based on the version column + DB::table('publications')->update([ + 'jav_version_major' => DB::raw('version') + ]); + } + + /** + * Reverses the migration + */ + public function down(): void + { + Schema::table('publications', function (Blueprint $table) { + $table->dropColumn(['jav_version_stage', 'jav_version_minor', 'jav_version_major']); + }); + } +} diff --git a/classes/publication/PKPPublication.php b/classes/publication/PKPPublication.php index 849eb63b9fe..1f5d2faf9c5 100644 --- a/classes/publication/PKPPublication.php +++ b/classes/publication/PKPPublication.php @@ -23,7 +23,8 @@ use PKP\core\Core; use PKP\core\PKPString; use PKP\facades\Locale; -use PKP\i18n\LocaleMetadata; +use PKP\publication\enums\JavStage; +use PKP\publication\helpers\JavStageAndNumbering; use PKP\services\PKPSchemaService; use PKP\userGroup\UserGroup; @@ -470,6 +471,50 @@ public function getLanguages(?array ...$additionalLocales): array ->values() ->toArray(); } + + /** + * Get the JAV string that describes the + * given publication. + */ + public function getJavStageAndNumberingDisplay(): ?string + { + $currentVersionStage = $this->getCurrentVersionStage(); + if (!isset($currentVersionStage)) { + return null; + } + + return $currentVersionStage->getVersionStageDisplay(); + } + + /** + * Get the current JAV stage object that describes the + * given publication version. + */ + public function getCurrentVersionStage(): ?JavStageAndNumbering + { + $versionStageStr = $this->getData('javVersionStage'); + if (!isset($versionStageStr)) { + return null; + } + + $versionStage = new JavStageAndNumbering(); + $versionStage->javStage = JavStage::from($versionStageStr); + $versionStage->javVersionMajor = $this->getData('javVersionMajor'); + $versionStage->javVersionMinor = $this->getData('javVersionMinor'); + + return $versionStage; + } + + /** + * Set the current JAV stage of the publication + * given a JavStageAndNumbering object + */ + public function setVersionStage(JavStageAndNumbering $versionStage): void + { + $this->setData('javVersionStage', $versionStage->javStage->value); + $this->setData('javVersionMajor', $versionStage->javVersionMajor); + $this->setData('javVersionMinor', $versionStage->javVersionMinor); + } } if (!PKP_STRICT_MODE) { class_alias('\PKP\publication\PKPPublication', '\PKPPublication'); diff --git a/classes/publication/Repository.php b/classes/publication/Repository.php index 56dc336996d..14bb1dd24f6 100644 --- a/classes/publication/Repository.php +++ b/classes/publication/Repository.php @@ -32,6 +32,7 @@ use PKP\observers\events\PublicationUnpublished; use PKP\orcid\OrcidManager; use PKP\plugins\Hook; +use PKP\publication\enums\JavStage; use PKP\security\Validation; use PKP\services\PKPSchemaService; use PKP\submission\Genre; @@ -335,6 +336,19 @@ public function version(Publication $publication): int $newPublication->setData('datePublished', null); $newPublication->setData('status', Submission::STATUS_QUEUED); $newPublication->setData('version', $publication->getData('version') + 1); + + // VersionStage Update + $currentVersionStage = $newPublication->getCurrentVersionStage(); + + if (isset($currentVersionStage)) { + $submission = Repo::submission()->get($publication->getData('submissionId')); + + $newVersionStage = $submission->getNextAvailableJavVersionNumberingForStage($currentVersionStage->javStage); + $newPublication->setVersionStage($newVersionStage); + + $newPublication->setData('versionDescription', null); + } + $newPublication->stampModified(); $request = Application::get()->getRequest(); @@ -380,8 +394,6 @@ public function version(Publication $publication): int Hook::call('Publication::version', [&$newPublication, $publication]); - $submission = Repo::submission()->get($newPublication->getData('submissionId')); - $eventLog = Repo::eventLog()->newDataObject([ 'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION, 'assocId' => $submission->getId(), @@ -662,6 +674,37 @@ public function delete(Publication $publication) Hook::call('Publication::delete', [&$publication]); } + /** + * Given a JAV Stage and a flag of whether the Version isMinor, + * the publication's related data is being updated + * + * @hook 'Publication::updateJavVersionStageAndNumbering::before' [[ &$newPublication, $publication ]] + */ + public function updateJavVersionStageAndNumbering(Publication $publication, JavStage $versioningStage, bool $isMinor = true): Publication + { + $submission = Repo::submission()->get($publication->getData('submissionId')); + $nextAvailableVersionStage = $submission->getNextAvailableJavVersionNumberingForStage($versioningStage, $isMinor); + + $newPublication = clone $publication; + $newPublication->setData('javVersionIsMinor', $isMinor); + $newPublication->setVersionStage($nextAvailableVersionStage); + + $newPublication->stampModified(); + Hook::call( + 'Publication::updateJavVersionStageAndNumbering::before', + [ + &$newPublication, + $publication + ] + ); + + $this->dao->update($newPublication); + + $newPublication = Repo::publication()->get($newPublication->getId()); + + return $newPublication; + } + /** * Handle a publication setting for an uploaded file * diff --git a/classes/publication/enums/JavStage.php b/classes/publication/enums/JavStage.php new file mode 100644 index 00000000000..ac7ea7808bb --- /dev/null +++ b/classes/publication/enums/JavStage.php @@ -0,0 +1,24 @@ +javStage->value; + + return "$versionStageValue $this->javVersionMajor.$this->javVersionMinor"; + } +} + diff --git a/classes/publication/maps/Schema.php b/classes/publication/maps/Schema.php index 23782abe327..99aa6c3b1d3 100644 --- a/classes/publication/maps/Schema.php +++ b/classes/publication/maps/Schema.php @@ -156,6 +156,9 @@ function ($citation) { case 'fullTitle': $output[$prop] = $publication->getFullTitles('html'); break; + case 'javStageDisplay': + $output[$prop] = $publication->getJavStageAndNumberingDisplay(); + break; default: $output[$prop] = $publication->getData($prop); break; diff --git a/classes/submission/PKPSubmission.php b/classes/submission/PKPSubmission.php index 7c7985a38e3..7eaf3b77fe5 100644 --- a/classes/submission/PKPSubmission.php +++ b/classes/submission/PKPSubmission.php @@ -29,10 +29,12 @@ use APP\facades\Repo; use APP\publication\Publication; use APP\submission\DAO; +use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; use PKP\core\Core; use PKP\facades\Locale; -use PKP\i18n\LocaleMetadata; +use PKP\publication\enums\JavStage; +use PKP\publication\helpers\JavStageAndNumbering; /** * @extends \PKP\core\DataObject @@ -289,6 +291,59 @@ public function getUIDisplayString() { return __('plugins.importexport.submission.cli.display', ['submissionId' => $this->getId(), 'submissionTitle' => $this->getCurrentPublication()->getLocalizedTitle()]); } + + /** + * Return a collection of all existing JAV Stages + * for the submission's publications + * + * @return Collection + */ + public function getExistingJavStagesByPublications(): Collection + { + $publications = $this->getData('publications'); + if ($publications->isEmpty()) { + return collect(); // Return an empty collection + } + + return collect($publications->map(function ($publication) { + return $publication->getCurrentVersionStage(); + })->filter()); // Remove any null entries from the collection + } + + public function getNextAvailableJavVersionNumberingForStage(JavStage $versioningStage, bool $isMinorChange = true): JavStageAndNumbering + { + $nextVersionStage = new JavStageAndNumbering(); + $nextVersionStage->javStage = $versioningStage; + $nextVersionStage->javVersionMajor = JavStageAndNumbering::JAV_DEFAULT_NUMBERING_MAJOR; + $nextVersionStage->javVersionMinor = JavStageAndNumbering::JAV_DEFAULT_NUMBERING_MINOR; + + $submissionVersionStages = $this->getExistingJavStagesByPublications(); + + // Filter to find the version stages that match the current versioningStage but with potentially updated versionMajor or versionMinor + $matchingVersionStages = $submissionVersionStages->filter(function ($existingVersionStage) use ($versioningStage) { + return $existingVersionStage->javStage === $versioningStage; + }); + + if (!$matchingVersionStages->isEmpty()) { + // Sort the version stages by versionMajor and versionMinor (descending order) + $sortedVersionStages = $matchingVersionStages->sort(function ($a, $b) { + return $b->javVersionMajor <=> $a->javVersionMajor ?: $b->javVersionMinor <=> $a->javVersionMinor; + }); + + $nextVersionStage = $sortedVersionStages->first(); + + if ($isMinorChange) { + // Increment the minor version + $nextVersionStage->javVersionMinor += 1; + } else { + // Increment the major version and reset the minor version + $nextVersionStage->javVersionMajor += 1; + $nextVersionStage->javVersionMinor = JavStageAndNumbering::JAV_DEFAULT_NUMBERING_MINOR; + } + } + + return $nextVersionStage; + } } // Expose global constants unless operating in strict mode. diff --git a/locale/en/submission.po b/locale/en/submission.po index b9c2c65e84b..668a5d2edb1 100644 --- a/locale/en/submission.po +++ b/locale/en/submission.po @@ -2454,3 +2454,51 @@ msgstr "Please disclose any competing interests this author may have with the re 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." + +msgid "publication.changeVersionStage.title" +msgstr "Version Stage" + +msgid "publication.changeVersionStage.buttonLabel" +msgstr "Manage version stage" + +msgid "publication.versionStage.minorOrMajor" +msgstr "Version Impact" + +msgid "publication.versionStage.minorOrMajor.description" +msgstr "" + +msgid "publication.versionStage.minorOrMajor.minor" +msgstr "Minor Version" + +msgid "publication.versionStage.minorOrMajor.major" +msgstr "Major Version" + +msgid "publication.versionStage" +msgstr "Version Stage" + +msgid "publication.versionStage.description" +msgstr "Description of this version changes" + +msgid "versioning.authorOriginal" +msgstr "Author Original" + +msgid "versioning.acceptedManuscript" +msgstr "Accepted Manuscript" + +msgid "versioning.submittedManuscript" +msgstr "Submitted Manuscript" + +msgid "versioning.proof" +msgstr "Proof" + +msgid "versioning.versionOfRecord" +msgstr "Version of Record" + +msgid "publication.changeVersionStage.currentStage" +msgstr "Current Publication Version Stage" + +msgid "publication.changeVersionStage.currentStage.notAssigned" +msgstr "Version not assigned" + +msgid "publication.changeVersionStage.newStage" +msgstr "Publication Version Stage after submit" diff --git a/schemas/publication.json b/schemas/publication.json index 2e4864a16ca..609f362aad0 100644 --- a/schemas/publication.json +++ b/schemas/publication.json @@ -332,6 +332,44 @@ "validation": [ "min:1" ] + }, + "javVersionStage": { + "type": "string", + "description": "", + "apiSummary": true, + "validation": [ + "nullable" + ] + }, + "javVersionIsMinor": { + "type": "boolean", + "description": "", + "apiSummary": true, + "validation": [ + "nullable" + ] + }, + "javVersionMinor": { + "type": "integer", + "description": "", + "apiSummary": true, + "validation": [ + "min:0" + ] + }, + "javVersionMajor": { + "type": "integer", + "description": "", + "apiSummary": true, + "validation": [ + "min:1" + ] + }, + "javStageDisplay": { + "type": "string", + "description": "", + "apiSummary": true, + "readOnly": true } } } \ No newline at end of file