diff --git a/api/v1/submissions/PKPSubmissionController.php b/api/v1/submissions/PKPSubmissionController.php index 09ef8c0b9de..b6d1b7d416e 100644 --- a/api/v1/submissions/PKPSubmissionController.php +++ b/api/v1/submissions/PKPSubmissionController.php @@ -26,6 +26,7 @@ use APP\section\Section; use APP\submission\Collector; use APP\submission\Submission; +use DOMDocument; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -38,6 +39,8 @@ use PKP\core\PKPRequest; use PKP\db\DAORegistry; use PKP\decision\DecisionType; +use PKP\file\FileManager; +use PKP\file\TemporaryFileManager; use PKP\log\event\PKPSubmissionEventLogEntry; use PKP\mail\mailables\PublicationVersionNotify; use PKP\mail\mailables\SubmissionSavedForLater; @@ -57,6 +60,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 @@ -101,6 +105,9 @@ class PKPSubmissionController extends PKPBaseController 'deleteContributor', 'editContributor', 'saveContributorsOrder', + 'getJats', + 'addJats', + 'deleteJats', ]; /** @var array Handlers that must be authorized to access a submission's production stage */ @@ -262,6 +269,18 @@ public function getGroupRoutes(): void Route::delete('{submissionId}/publications/{publicationId}/contributors/{contributorId}', $this->deleteContributor(...)) ->name('submission.publication.contributor.delete') ->whereNumber(['submissionId', 'publicationId', 'contributorId']); + + Route::get('{submissionId}/publications/{publicationId}/jats', $this->getJats(...)) + ->name('submission.jats.add') + ->whereNumber(['submissionId', 'publicationId']); + + Route::post('{submissionId}/publications/{publicationId}/jats', $this->addJats(...)) + ->name('submission.jats.add') + ->whereNumber(['submissionId', 'publicationId']); + + Route::delete('{submissionId}/publications/{publicationId}/jats', $this->deleteJats(...)) + ->name('submission.jats.add') + ->whereNumber(['submissionId', 'publicationId']); }); Route::middleware([ @@ -292,9 +311,6 @@ public function getGroupRoutes(): void ]); } - - - /** * @copydoc \PKP\core\PKPBaseController::authorize() */ @@ -361,9 +377,9 @@ public function getMany(Request $illuminateRequest): JsonResponse ->filterByContextIds([$context->getId()]) ->getMany(); - /** @var \PKP\submission\GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($context->getId())->toArray(); + $genres = Repo::submissionFile() + ->getGenres($context->getId()) + ->toArray(); return response()->json([ 'itemsMax' => $collector->limit(null)->offset(null)->getCount(), @@ -469,9 +485,9 @@ public function get(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), Response::HTTP_OK); } @@ -594,9 +610,9 @@ public function add(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), Response::HTTP_OK); } @@ -633,9 +649,9 @@ public function edit(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), Response::HTTP_OK); } @@ -689,9 +705,9 @@ public function saveForLater(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), Response::HTTP_OK); } @@ -769,9 +785,9 @@ public function submit(Request $illuminateRequest): JsonResponse ->filterByContextIds([$context->getId()]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), Response::HTTP_OK); } @@ -793,9 +809,9 @@ public function delete(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); $submissionProps = Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres); @@ -838,6 +854,7 @@ public function getParticipants(Request $illuminateRequest): JsonResponse $request = Application::get()->getRequest(); $context = $request->getContext(); $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + $args = $illuminateRequest->input(); $stageId = $args['stageId'] ?? null; if (!$submission || $submission->getData('contextId') !== $context->getId()) { @@ -895,9 +912,9 @@ public function getPublications(Request $illuminateRequest): JsonResponse $anonymize = $currentUserReviewAssignment && $currentUserReviewAssignment->getReviewMethod() === ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS; - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json([ 'itemsMax' => $collector->limit(null)->offset(null)->getCount(), @@ -930,9 +947,9 @@ public function getPublication(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json( Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication), @@ -971,9 +988,9 @@ public function addPublication(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json( Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication), @@ -1054,9 +1071,9 @@ public function versionPublication(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json( Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication), @@ -1132,9 +1149,9 @@ public function editPublication(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json( Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication), @@ -1194,9 +1211,9 @@ public function publishPublication(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json( Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication), @@ -1238,9 +1255,9 @@ public function unpublishPublication(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); return response()->json( Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication), @@ -1282,9 +1299,9 @@ public function deletePublication(Request $illuminateRequest): JsonResponse ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); - /** @var GenreDAO $genreDao */ - $genreDao = DAORegistry::getDAO('GenreDAO'); - $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + $genres = Repo::submissionFile() + ->getGenres($submission->getData('contextId')) + ->toArray(); $output = Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication); @@ -1691,4 +1708,151 @@ protected function getWriteDisabledErrors(string $schemaName, array $params): ar return $errors; } + + /** + * Get JATS XML Files + */ + public function getJats(Request $illuminateRequest): JsonResponse + { + $request = $this->getRequest(); + $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + + $publication = Repo::publication()->get((int) $illuminateRequest->route('publicationId')); + + if (!$publication) { + return response()->json([ + 'error' => __('api.404.resourceNotFound'), + ], Response::HTTP_NOT_FOUND); + } + + if ($submission->getId() !== $publication->getData('submissionId')) { + return response()->json([ + 'error' => __('api.publications.403.submissionsDidNotMatch'), + ], Response::HTTP_FORBIDDEN); + } + + $jatsFilesProp = $this->getJatsUIProps($request, $submission->getId(), $publication->getId()); + + return response()->json($jatsFilesProp, Response::HTTP_OK); + } + + /** + * Add a JATS XML Submission File to a publication + */ + public function addJats(Request $illuminateRequest): JsonResponse + { + $request = $this->getRequest(); + $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + + $publication = Repo::publication()->get((int) $illuminateRequest->route('publicationId')); + + if (!$publication) { + return response()->json([ + 'error' => __('api.404.resourceNotFound'), + ], Response::HTTP_NOT_FOUND); + } + + if ($submission->getId() !== $publication->getData('submissionId')) { + return response()->json([ + 'error' => __('api.publications.403.submissionsDidNotMatch'), + ], Response::HTTP_FORBIDDEN); + } + + if (empty($_FILES)) { + return response()->json([ + 'error' => __('api.files.400.noUpload'), + ], Response::HTTP_BAD_REQUEST); + } + + if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) { + return $this->getUploadErrorResponse($_FILES['file']['error']); + } + + $params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_SUBMISSION_FILE, $illuminateRequest->input()); + + $submissionFile = Repo::submissionFile() + ->addJatsFile( + $_FILES['file']['tmp_name'], + $_FILES['file']['name'], + $submission->getId(), + $publication->getId(), + SubmissionFile::SUBMISSION_FILE_JATS, + $params); + + $jatsFilesProp = $this->getJatsUIProps($request, $submission->getId(), $publication->getId()); + + return response()->json($jatsFilesProp, Response::HTTP_OK); + } + + /** + * Delete the publication's JATS Submission file + */ + public function deleteJats(Request $illuminateRequest): JsonResponse + { + $request = $this->getRequest(); + $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); + + $publication = Repo::publication()->get((int) $illuminateRequest->route('publicationId')); + + if (!$publication) { + return response()->json([ + 'error' => __('api.404.resourceNotFound'), + ], Response::HTTP_NOT_FOUND); + } + + if ($submission->getId() !== $publication->getData('submissionId')) { + return response()->json([ + 'error' => __('api.publications.403.submissionsDidNotMatch'), + ], Response::HTTP_FORBIDDEN); + } + + $submissionFiles = Repo::submissionFile() + ->getCollector() + ->filterBySubmissionIds([$publication->getData('submissionId')]) + ->filterByFileStages([SubmissionFile::SUBMISSION_FILE_JATS]) + ->filterByAssoc(Application::ASSOC_TYPE_PUBLICATION, [$publication->getId()]) + ->getMany(); + + if ($submissionFiles->isEmpty()) { + return response()->json([ + 'error' => __('api.404.resourceNotFound'), + ], Response::HTTP_NOT_FOUND); + } + + $file = $submissionFiles->first(); + + Repo::submissionFile()->delete($file); + + $jatsFilesProp = $this->getJatsUIProps($request, $submission->getId(), $publication->getId()); + + return response()->json($jatsFilesProp, Response::HTTP_OK); + } + + /** + * Helper function to create the props array that returns the appropriate JATS data + * either the SubmissionFile Prop or the default JATS content created by the given + * submission/publication metadata + */ + protected function getJatsUIProps($request, $submissionId, $publicationId): array + { + $jatsFile = Repo::submissionFile() + ->getJatsFiles($submissionId, $publicationId); + + $jatsFilesProp = []; + if (!isset($jatsFile)) { + $jatsFilesProp['defaultJatsContent'] = Repo::submissionFile() + ->createDefaultContentJatsFile($submissionId, $publicationId); + } else { + /** @var GenreDAO */ + $genreDao = DAORegistry::getDAO('GenreDAO'); + $genres = $genreDao->getEnabledByContextId($request->getContext()->getId()); + + $existingJatsFileProp = Repo::submissionFile() + ->getJatsFileProps($jatsFile, $genres->toArray()); + + $jatsFilesProp['existingJatsFile'] = $existingJatsFileProp; + } + + return $jatsFilesProp; + } } diff --git a/classes/components/listPanels/JatsListPanel.php b/classes/components/listPanels/JatsListPanel.php new file mode 100644 index 00000000000..848cb625cdc --- /dev/null +++ b/classes/components/listPanels/JatsListPanel.php @@ -0,0 +1,97 @@ +submission = $submission; + $this->context = $context; + $this->locales = $locales; + $this->canEditPublication = $canEditPublication; + $this->publication = $publication; + } + + /** + * @copydoc ListPanel::getConfig() + */ + public function getConfig() + { + $config = parent::getConfig(); + + // Remove some props not used in this list panel + unset($config['description']); + unset($config['expanded']); + unset($config['headingLevel']); + + $config = array_merge( + $config, + [ + 'canEditPublication' => $this->canEditPublication, + 'publicationApiUrlFormat' => $this->getPublicationUrlFormat(), + 'uploadProgressLabel' => __('submission.upload.percentComplete'), + 'fileStage' => SubmissionFile::SUBMISSION_FILE_JATS, + 'i18nConfirmDeleteFileTitle' => __('publication.jats.confirmDeleteFileTitle'), + 'i18nDeleteFileMessage' => __('publication.jats.confirmDeleteFileMessage'), + 'i18nConfirmDeleteFileButton' => __('publication.jats.confirmDeleteFileButton'), + 'downloadDefaultJatsFileName' => Repo::submissionFile()->getDefaultJatsFileName($this->submission->getId(), $this->publication->getId()), + ] + ); + + return $config; + } + + /** + * Get an example of the url to a publication's API endpoint, + * with a placeholder instead of the publication id, eg: + * + * http://example.org/api/v1/submissions/1/publications/__publicationId__ + */ + protected function getPublicationUrlFormat(): string + { + return Application::get()->getRequest()->getDispatcher()->url( + Application::get()->getRequest(), + Application::ROUTE_API, + $this->context->getPath(), + 'submissions/' . $this->submission->getId() . '/publications/__publicationId__/jats' + ); + } +} diff --git a/classes/db/DAOResultFactory.php b/classes/db/DAOResultFactory.php index 66cf4163cca..a65d60bbe09 100644 --- a/classes/db/DAOResultFactory.php +++ b/classes/db/DAOResultFactory.php @@ -259,6 +259,22 @@ public function toAssociativeArray($idField = 'id') } return $returner; } + + /** + * Convert this iterator to a collection. + * + * @return Collection + */ + public function toCollection(): Collection + { + $retCollection = collect(); + + while ($object = $this->next()) { + $retCollection->push($object); + } + + return $retCollection; + } } if (!PKP_STRICT_MODE) { diff --git a/classes/submissionFile/Repository.php b/classes/submissionFile/Repository.php index 640a72fc938..3a2ca1845b0 100644 --- a/classes/submissionFile/Repository.php +++ b/classes/submissionFile/Repository.php @@ -19,19 +19,31 @@ use APP\facades\Repo; use APP\notification\Notification; use APP\notification\NotificationManager; +use APP\plugins\generic\jatsTemplate\classes\Article; +use APP\plugins\generic\jatsTemplate\classes\ArticleBack; +use APP\plugins\generic\jatsTemplate\classes\ArticleBody; +use APP\plugins\generic\jatsTemplate\classes\ArticleFront; +use APP\publication\Publication; +use APP\submission\Submission; +use DOMDocument; use Exception; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\LazyCollection; +use PKP\config\Config; use PKP\core\Core; use PKP\core\PKPApplication; use PKP\db\DAORegistry; +use PKP\file\FileManager; +use PKP\file\TemporaryFileManager; use PKP\log\event\SubmissionFileEventLogEntry; use PKP\log\SubmissionEmailLogDAO; use PKP\log\SubmissionEmailLogEntry; use PKP\mail\mailables\RevisedVersionNotify; use PKP\note\NoteDAO; use PKP\notification\PKPNotification; +use PKP\oai\OAIRecord; use PKP\plugins\Hook; use PKP\query\QueryDAO; use PKP\security\authorization\SubmissionFileAccessPolicy; @@ -54,6 +66,9 @@ abstract class Repository /** @var array $reviewFileStages The file stages that are part of a review workflow stage */ public array $reviewFileStages = []; + public ?Submission $submission = null; + public ?Publication $publication = null; + public function __construct(DAO $dao, Request $request, PKPSchemaService $schemaService) { $this->schemaService = $schemaService; @@ -662,7 +677,8 @@ public function getWorkflowStageId(SubmissionFile $submissionFile): ?int if ( $fileStage === SubmissionFile::SUBMISSION_FILE_PROOF || - $fileStage === SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY + $fileStage === SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY || + $fileStage === SubmissionFile::SUBMISSION_FILE_JATS ) { return WORKFLOW_STAGE_ID_PRODUCTION; } @@ -843,4 +859,220 @@ protected function getSubmissionFileLogData(SubmissionFile $submissionFile): arr 'username' => $user?->getUsername(), ]; } + + /** + * Add jatsContent for Submission files that correspont to + */ + public function getJatsFileProps(SubmissionFile $submissionFile, array $genres): array + { + $fileProps = $this->getSchemaMap()->summarize($submissionFile, $genres); + $jatsFileName = Config::getVar('files', 'files_dir') . '/' . $fileProps['path'] .''; + $xmlString = file_get_contents($jatsFileName); + + if ($xmlString) { + $fileProps['jatsContent'] = $xmlString; + } + + return $fileProps; + } + + /** + * Returns the SubmissionFile, if any, that corresponds to the JATS contents of the given submission/publication + */ + public function getJatsFiles(int $submissionId, int $publicationId): ?SubmissionFile + { + $this->validateSubmissionAndPublication($submissionId, $publicationId); + + $submissionFile = Repo::submissionFile() + ->getCollector() + ->filterBySubmissionIds([$this->submission->getId()]) + ->filterByFileStages([SubmissionFile::SUBMISSION_FILE_JATS]) + ->filterByAssoc(Application::ASSOC_TYPE_PUBLICATION, [$this->publication->getId()]) + ->getMany() + ->first(); + + return $submissionFile; + } + + /** + * Returns the name of the file that will contain the default JATS content + */ + public function getDefaultJatsFileName(int $submissionId, int $publicationId): string + { + return 'jats-' . $submissionId . '-' . $publicationId . '-' . (string) mt_rand(100000, 999999) . '.xml'; + } + + /** + * Creates the default JATS XML Content from the given submission/publication metadata + */ + public function createDefaultContentJatsFile(int $submissionId, int $publicationId): string + { + $submission = Repo::submission()->get($submissionId); + $context = Services::get('context')->get($submission->getData('contextId')); + $publication = Repo::publication()->get($publicationId); + $section = Repo::section()->get($submission->getSectionId()); + + $issue = null; + if ($publication->getData('issueId')) { + $issue = Repo::issue()->get($publication->getData('issueId')); + } + + $exportXml = $this->convertSubmissionToJatsXml($submission, $context, $section, $issue, $publication, Application::get()->getRequest()); + + return $exportXml; + } + + /** + * Base function that will add a new JATS file + */ + public function addJatsFile( + string $fileTmpName, + string $fileName, + int $submissionId, + ?int $publicationId = null, + int $type = SubmissionFile::SUBMISSION_FILE_JATS, + array $params = [] + ): SubmissionFile + { + $this->validateSubmissionAndPublication($submissionId, $publicationId); + + $context = $this->request->getContext(); + $user = $this->request->getUser(); + + $existingJatsFile = $this->getJatsFiles($this->submission->getId(), $this->publication->getId()); + if ($existingJatsFile) { + throw new Exception('A JATS file already exists'); + } + + $fileManager = new FileManager(); + $extension = $fileManager->parseFileExtension($fileName); + + $submissionDir = Repo::submissionFile() + ->getSubmissionDir( + $this->submission->getData('contextId'), + $this->submission->getId() + ); + + $fileId = Services::get('file')->add( + $fileTmpName, + $submissionDir . '/' . uniqid() . '.' . $extension + ); + + $params['fileId'] = $fileId; + $params['submissionId'] = $this->submission->getId(); + $params['uploaderUserId'] = $user->getId(); + $params['fileStage'] = $type; + + $primaryLocale = $context->getPrimaryLocale(); + $allowedLocales = $context->getData('supportedSubmissionLocales'); + + $params['name'] = null; + $params['name'][$primaryLocale] = $fileName; + + // If no genre has been set and there is only one genre possible, set it automatically + /** @var GenreDAO */ + $genreDao = DAORegistry::getDAO('GenreDAO'); + $genres = $genreDao->getEnabledByContextId($context->getId()); + + if (empty($params['genreId'])) { + + [$firstGenre, $secondGenre] = [$genres->next(), $genres->next()]; + if ($firstGenre && !$secondGenre) { + $params['genreId'] = $firstGenre->getId(); + } + } + + $params['assocType'] = Application::ASSOC_TYPE_PUBLICATION; + $params['assocId'] = $this->publication->getId(); + + $errors = Repo::submissionFile() + ->validate( + null, + $params, + $allowedLocales, + $primaryLocale + ); + + if (!empty($errors)) { + Services::get('file')->delete($fileId); + throw new Exception(''. implode(', ', $errors)); + } + + $submissionFile = Repo::submissionFile() + ->newDataObject($params); + + $submissionFileId = Repo::submissionFile() + ->add($submissionFile); + + $submissionFile = Repo::submissionFile() + ->get($submissionFileId); + + return $submissionFile; + } + + /** + * Function to get the Context's genres + */ + public function getGenres(int $contextId) : Collection + { + /** @var \PKP\submission\GenreDAO $genreDao */ + $genreDao = DAORegistry::getDAO('GenreDAO'); + return $genreDao->getByContextId($contextId) + ->toCollection(); + } + + /** + * Given a submissionId and a publicationId this function returns true if the given publicationId + * corresponds to the given submission. If no publicationId given, the current publication will be + * used + */ + protected function validateSubmissionAndPublication(int $submissionId, ?int $publicationId) : bool + { + if (isset($this->submission) && isset($this->publication)) { + return true; + } + + $this->submission = Repo::submission() + ->get($submissionId); + + if (!$this->submission) { + throw new Exception('The given submission can not be found'); + } + + $this->publication = $this->submission->getCurrentPublication(); + + if ($publicationId) { + $this->publication = Repo::publication() + ->get($publicationId); + + if (!$this->publication) { + throw new Exception('The given publication can not be found'); + } + + if ($this->publication->getData('submissionId') != $submissionId) { + throw new Exception('The given publication is not part of the given submission'); + } + } + + return true; + } + + /** + * Given a submission and a publication this function returns the JATS XML contents provided by the + * submission/publication metadata + */ + protected function convertSubmissionToJatsXml($submission, $journal, $section, $issue, $publication, $request): string + { + $articleJats = new Article(); + + $articleJats->preserveWhiteSpace = false; + $articleJats->formatOutput = true; + + $jatsXML = $articleJats->convertSubmission($submission, $journal, $section, $issue, $publication, $request); + + $articleJats->loadXML($jatsXML); + $formattedXml = $articleJats->saveXML(); + + return $formattedXml; + } } diff --git a/classes/submissionFile/SubmissionFile.php b/classes/submissionFile/SubmissionFile.php index bdd41e612d9..5dc94715124 100644 --- a/classes/submissionFile/SubmissionFile.php +++ b/classes/submissionFile/SubmissionFile.php @@ -38,6 +38,7 @@ class SubmissionFile extends \PKP\core\DataObject public const SUBMISSION_FILE_QUERY = 18; public const SUBMISSION_FILE_INTERNAL_REVIEW_FILE = 19; public const SUBMISSION_FILE_INTERNAL_REVIEW_REVISION = 20; + public const SUBMISSION_FILE_JATS = 21; public const INTERNAL_REVIEW_STAGES = [ SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE, diff --git a/locale/en/submission.po b/locale/en/submission.po index 7b3fca6a6ef..665000a1a53 100644 --- a/locale/en/submission.po +++ b/locale/en/submission.po @@ -2395,3 +2395,18 @@ msgstr "Pending" msgid "submission.dashboard.view.reviewAssignments.archived" msgstr "Completed / Declined" + +msgid "publication.jats" +msgstr "JATS XML" + +msgid "publication.jats.confirmDeleteFileTitle" +msgstr "Confirm deleting JATS XML" + +msgid "publication.jats.confirmDeleteFileMessage" +msgstr "You are about to remove the existing JATS XML File from this publication. Are you sure?" + +msgid "publication.jats.confirmDeleteFileButton" +msgstr "Delete JATS File" + +msgid "publication.jats.autoCreatedMessage" +msgstr "This JATS file is generated automatically by the submission metadata" \ No newline at end of file diff --git a/pages/workflow/PKPWorkflowHandler.php b/pages/workflow/PKPWorkflowHandler.php index c5a291340ab..6f2921a9b65 100644 --- a/pages/workflow/PKPWorkflowHandler.php +++ b/pages/workflow/PKPWorkflowHandler.php @@ -32,6 +32,7 @@ use PKP\components\forms\publication\PKPPublicationLicenseForm; use PKP\components\forms\publication\TitleAbstractForm; use PKP\components\listPanels\ContributorsListPanel; +use PKP\components\listPanels\JatsListPanel; use PKP\context\Context; use PKP\core\JSONMessage; use PKP\core\PKPApplication; @@ -48,6 +49,7 @@ use PKP\submission\GenreDAO; use PKP\submission\PKPSubmission; use PKP\submission\reviewRound\ReviewRoundDAO; +use PKP\submissionFile\SubmissionFile; use PKP\user\User; use PKP\workflow\WorkflowStageDAO; @@ -323,12 +325,21 @@ public function index($args, $request) ? $workingPublicationProps : $mapper->map($submission->getCurrentPublication()); + $jatsPanel = $this->getJatsPanel( + $submission, + $submissionContext, + $locales, + $canEditPublication, + $latestPublication + ); + $state = [ 'activityLogLabel' => __('submission.list.infoCenter'), 'canAccessPublication' => $canAccessPublication, 'canEditPublication' => $canEditPublication, 'components' => [ $contributorsListPanel->id => $contributorsListPanel->getConfig(), + $jatsPanel->id => $jatsPanel->getConfig(), $citationsForm->id => $citationsForm->getConfig(), $publicationLicenseForm->id => $publicationLicenseForm->getConfig(), $titleAbstractForm->id => $titleAbstractForm->getConfig(), @@ -850,6 +861,21 @@ protected function getContributorsListPanel(Submission $submission, Context $con ); } + /** + * Get the contributor list panel + */ + protected function getJatsPanel(Submission $submission, Context $context, array $locales, bool $canEditPublication, Publication $publication): JatsListPanel + { + return new JatsListPanel( + 'jats', + __('publication.jats'), + $submission, + $context, + $locales, + $canEditPublication, + $publication + ); + } // // Abstract protected methods. diff --git a/schemas/submissionFile.json b/schemas/submissionFile.json index 12c1fe4ce2b..9ace44f835b 100644 --- a/schemas/submissionFile.json +++ b/schemas/submissionFile.json @@ -27,7 +27,7 @@ "apiSummary": true, "description": "Used with `assocId` to associate this file with an object such as a galley. One of the following constants: `ASSOC_TYPE_SUBMISSION_FILE` (dependent files), `ASSOC_TYPE_REVIEW_ASSIGNMENT` (files uploaded by a reviewer), `ASSOC_TYPE_NOTE` (files uploaded with a discussion), `ASSOC_TYPE_REPRESENTATION` (files uploaded to a galley or publication format), `ASSOC_TYPE_REVIEW_ROUND` (review files and revisions for a particular review round).", "validation": [ - "in:515,517,520,521,523" + "in:515,517,520,521,523,1048588" ] }, "caption": { @@ -118,7 +118,7 @@ "type": "integer", "apiSummary": true, "validation": [ - "in:2,3,4,5,6,7,8,9,10,11,13,15,17,18" + "in:2,3,4,5,6,7,8,9,10,11,13,15,17,18,19,20,21" ] }, "genreId": {