From 04b0410f53ff0237076369b8803dd40f47b984c3 Mon Sep 17 00:00:00 2001 From: Taslan Graham Date: Fri, 26 Jul 2024 17:08:34 -0500 Subject: [PATCH] pkp#6528 implement endpoint to perform bulk delete of incomplete submissions --- .../PKPBackendSubmissionsController.php | 73 +++++++++++++++++++ classes/submission/Collector.php | 16 ++++ classes/submission/DAO.php | 1 + locale/en/admin.po | 5 +- locale/en/api.po | 6 ++ locale/en/common.po | 2 +- 6 files changed, 101 insertions(+), 2 deletions(-) diff --git a/api/v1/_submissions/PKPBackendSubmissionsController.php b/api/v1/_submissions/PKPBackendSubmissionsController.php index 56e2d4c70c4..8012343ec75 100644 --- a/api/v1/_submissions/PKPBackendSubmissionsController.php +++ b/api/v1/_submissions/PKPBackendSubmissionsController.php @@ -20,6 +20,7 @@ use APP\core\Application; use APP\facades\Repo; use APP\submission\Collector; +use APP\submission\Submission; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -87,6 +88,14 @@ public function getGroupRoutes(): void ), ]); + Route::delete('', $this->bulkDeleteIncompleteSubmissions(...)) + ->name('_submission.incomplete.delete') + ->middleware([ + self::roleAuthorizer([ + Role::ROLE_ID_SITE_ADMIN, + ]), + ]); + Route::delete('{submissionId}', $this->delete(...)) ->name('_submission.delete') ->middleware([ @@ -430,6 +439,70 @@ public function delete(Request $illuminateRequest): JsonResponse return response()->json([], Response::HTTP_OK); } + /** + * Delete a list of incomplete submissions + */ + public function bulkDeleteIncompleteSubmissions(Request $illuminateRequest): JsonResponse + { + $submissionIdsRaw = paramToArray($illuminateRequest->query('ids') ?? []); + + if (empty($submissionIdsRaw)) { + return response()->json([ + 'error' => __('api.submission.400.missingQueryParam'), + ], Response::HTTP_BAD_REQUEST); + } + + $submissionIds = []; + + foreach ($submissionIdsRaw as $id) { + $integerId = intval($id); + + if (!$integerId) { + return response()->json([ + 'error' => __('api.submission.400.invalidId', ['id' => $id]) + ], Response::HTTP_BAD_REQUEST); + } + + $submissionIds[] = $id; + } + + $submissions = $this->getSubmissionCollector($illuminateRequest->query()) + ->filterBySubmissionIds($submissionIds) + ->filterByIncomplete(true) + ->getMany() + ->all(); + + $submissionIdsFound = array_map(fn (Submission $submission) => $submission->getData('id'), $submissions); + + if (array_diff($submissionIds, $submissionIdsFound)) { + return response()->json([ + 'error' => __('api.404.resourceNotFound') + ], Response::HTTP_NOT_FOUND); + } + + $context = $this->getRequest()->getContext(); + + foreach ($submissions as $submission) { + if ($context->getId() != $submission->getData('contextId')) { + return response()->json([ + 'error' => __('api.submissions.403.deleteSubmissionOutOfContext'), + ], Response::HTTP_FORBIDDEN); + } + + if (!Repo::submission()->canCurrentUserDelete($submission)) { + return response()->json([ + 'error' => __('api.submissions.403.unauthorizedDeleteSubmission'), + ], Response::HTTP_FORBIDDEN); + } + } + + foreach ($submissions as $submission) { + Repo::submission()->delete($submission); + } + + return response()->json([], Response::HTTP_OK); + } + /** * Configure a submission Collector based on the query params */ diff --git a/classes/submission/Collector.php b/classes/submission/Collector.php index b2c73684adb..0264c11f8ec 100644 --- a/classes/submission/Collector.php +++ b/classes/submission/Collector.php @@ -53,6 +53,7 @@ abstract class Collector implements CollectorInterface, ViewsCount public DAO $dao; public ?array $categoryIds = null; public ?array $contextIds = null; + public ?array $submissionIds = null; public ?int $count = null; public ?int $daysInactive = null; public bool $isIncomplete = false; @@ -250,6 +251,17 @@ public function filterByRevisionsSubmitted(?bool $revisionsSubmitted): AppCollec return $this; } + /** + * Limit results to only submissions with the specified IDs + * + * @param ?int[] $submissionIds Submission IDs + */ + public function filterBySubmissionIds(?array $submissionIds): static + { + $this->submissionIds = $submissionIds; + return $this; + } + /** * Limit results to submissions assigned to these users * @@ -370,6 +382,10 @@ public function getQueryBuilder(): Builder $q->whereIn('s.context_id', $this->contextIds); } + if (isset($this->submissionIds)) { + $q->whereIn('s.submission_id', array_map(intval(...), $this->submissionIds)); + } + // Prepare keywords (allows short and numeric words) $keywords = collect(Application::getSubmissionSearchIndex()->filterKeywords($this->searchPhrase, false, true, true)) ->unique() diff --git a/classes/submission/DAO.php b/classes/submission/DAO.php index 7f46d566b21..532248d4426 100644 --- a/classes/submission/DAO.php +++ b/classes/submission/DAO.php @@ -26,6 +26,7 @@ use PKP\db\DAORegistry; use PKP\log\event\EventLogEntry; use PKP\note\NoteDAO; +use PKP\notification\Notification; use PKP\query\QueryDAO; use PKP\services\PKPSchemaService; use PKP\stageAssignment\StageAssignment; diff --git a/locale/en/admin.po b/locale/en/admin.po index 51f9d0fac1a..56c22843b8a 100644 --- a/locale/en/admin.po +++ b/locale/en/admin.po @@ -1022,4 +1022,7 @@ msgid "admin.submissions.incomplete.bulkDelete.body" msgstr "Are you sure you want to delete the selected items? This action cannot be undone. Please confirm to proceed." msgid "admin.submissions.incomplete.bulkDelete.selectionStatus" -msgstr "{$selected} of {$total} Incomplete Submissions Selected" \ No newline at end of file +msgstr "Incomplete Submissions Selected." + +msgid "admin.submissions.incomplete.bulkDelete.success" +msgstr "Submissions deleted successfully!" diff --git a/locale/en/api.po b/locale/en/api.po index 6ca82ab4935..2c67dcabcd6 100644 --- a/locale/en/api.po +++ b/locale/en/api.po @@ -331,3 +331,9 @@ msgstr "The reviewer for the assignment could not be found" msgid "api.submission.400.sectionDoesNotExist" msgstr "The provided section does not exist." + +msgid "api.submission.400.missingQueryParam" +msgstr "The request is missing the required query parameter `ids`. Please provide the `ids` of the submissions you wish to delete." + +msgid "api.submission.400.invalidId" +msgstr "Invalid ID: \"{$id}\" provided." diff --git a/locale/en/common.po b/locale/en/common.po index 56211e5ae0e..03a5d967a86 100644 --- a/locale/en/common.po +++ b/locale/en/common.po @@ -803,7 +803,7 @@ msgid "common.replaceFile" msgstr "Replace file" msgid "common.requiredField" -msgstr 'Required fields are marked with an asterisk: *' +msgstr "Required fields are marked with an asterisk: *" msgid "common.required" msgstr "Required"