From 6d005d71410635b93dbb60ad9340086134f6493b Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 11 Jan 2024 19:51:23 +0600 Subject: [PATCH] pkp/pkp-lib#5885 Review remainder update --- .../forms/context/PKPReviewSetupForm.php | 46 ++++- .../validation/FormValidatorDateCompare.php | 46 +++++ .../UnableToCreateJATSContentException.php | 9 +- ...5885_RenameReviewRemainderSettingsName.php | 65 +++++++ classes/task/ReviewReminder.php | 180 +++++++++--------- .../validation/ValidatorDateConparison.php | 97 ++++++++++ .../users/reviewer/form/EditReviewForm.php | 12 ++ .../grid/users/reviewer/form/ReviewerForm.php | 11 ++ jobs/email/ReviewRemainder.php | 117 ++++++++++++ locale/en/editor.po | 6 + locale/en/manager.po | 16 +- schemas/context.json | 18 +- .../users/reviewer/form/editReviewForm.tpl | 2 +- .../reviewer/form/reviewerFormFooter.tpl | 2 +- 14 files changed, 520 insertions(+), 107 deletions(-) create mode 100644 classes/form/validation/FormValidatorDateCompare.php create mode 100644 classes/migration/upgrade/v3_5_0/I5885_RenameReviewRemainderSettingsName.php create mode 100644 classes/validation/ValidatorDateConparison.php create mode 100644 jobs/email/ReviewRemainder.php diff --git a/classes/components/forms/context/PKPReviewSetupForm.php b/classes/components/forms/context/PKPReviewSetupForm.php index c98ba682a3d..0029999fb78 100644 --- a/classes/components/forms/context/PKPReviewSetupForm.php +++ b/classes/components/forms/context/PKPReviewSetupForm.php @@ -92,16 +92,46 @@ public function __construct($action, $locales, $context) ])); if (Config::getVar('general', 'scheduled_tasks')) { - $this->addField(new FieldText('numDaysBeforeInviteReminder', [ - 'label' => __('manager.setup.reviewOptions.reminders.response'), - 'description' => __('manager.setup.reviewOptions.reminders.response.description'), - 'value' => $context->getData('numDaysBeforeInviteReminder'), - 'size' => 'small', - ])) - ->addField(new FieldText('numDaysBeforeSubmitReminder', [ + // $this->addField(new FieldText('numDaysBeforeInviteReminder', [ + // 'label' => __('manager.setup.reviewOptions.reminders.response'), + // 'description' => __('manager.setup.reviewOptions.reminders.response.description'), + // 'value' => $context->getData('numDaysBeforeInviteReminder'), + // 'size' => 'small', + // ])) + // ->addField(new FieldText('numDaysBeforeSubmitReminder', [ + // 'label' => __('manager.setup.reviewOptions.reminders.submit'), + // 'description' => __('manager.setup.reviewOptions.reminders.submit.description'), + // 'value' => $context->getData('numDaysBeforeSubmitReminder'), + // 'size' => 'small', + // ])); + + $this + ->addField(new FieldHTML('reviewRequestResponseRemainder', [ + 'label' => __('manager.setup.reviewOptions.reminders.response'), + 'description' => __('manager.setup.reviewOptions.reminders.response.description'), + ])) + ->addField(new FieldText('numDaysBeforeReviewResponseReminderDue', [ + 'description' => __('manager.setup.reviewOptions.reminders.response.description.before'), + 'value' => $context->getData('numDaysBeforeReviewResponseReminderDue'), + 'size' => 'small', + ])) + ->addField(new FieldText('numDaysAfterReviewResponseReminderDue', [ + 'description' => __('manager.setup.reviewOptions.reminders.response.description.after'), + 'value' => $context->getData('numDaysAfterReviewResponseReminderDue'), + 'size' => 'small', + ])) + ->addField(new FieldHTML('submissionReviewResponseRemainder', [ 'label' => __('manager.setup.reviewOptions.reminders.submit'), 'description' => __('manager.setup.reviewOptions.reminders.submit.description'), - 'value' => $context->getData('numDaysBeforeSubmitReminder'), + ])) + ->addField(new FieldText('numDaysBeforeReviewSubmitReminderDue', [ + 'description' => __('manager.setup.reviewOptions.reminders.submit.description.before'), + 'value' => $context->getData('numDaysBeforeReviewSubmitReminderDue'), + 'size' => 'small', + ])) + ->addField(new FieldText('numDaysAfterReviewSubmitReminderDue', [ + 'description' => __('manager.setup.reviewOptions.reminders.submit.description.after'), + 'value' => $context->getData('numDaysAfterReviewSubmitReminderDue'), 'size' => 'small', ])); } else { diff --git a/classes/form/validation/FormValidatorDateCompare.php b/classes/form/validation/FormValidatorDateCompare.php new file mode 100644 index 00000000000..8e549776512 --- /dev/null +++ b/classes/form/validation/FormValidatorDateCompare.php @@ -0,0 +1,46 @@ +getCode() ?? 0, + $innerException + ); } } diff --git a/classes/migration/upgrade/v3_5_0/I5885_RenameReviewRemainderSettingsName.php b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewRemainderSettingsName.php new file mode 100644 index 00000000000..f2702357612 --- /dev/null +++ b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewRemainderSettingsName.php @@ -0,0 +1,65 @@ +getContextSettingsTable()) + ->select(['setting_name']) + ->where('setting_name', 'numDaysBeforeInviteReminder') + ->update([ + 'setting_name' => 'numDaysAfterReviewResponseReminderDue' + ]); + + DB::table($this->getContextSettingsTable()) + ->select(['setting_name']) + ->where('setting_name', 'numDaysBeforeSubmitReminder') + ->update([ + 'setting_name' => 'numDaysAfterReviewSubmitReminderDue' + ]); + } + + /** + * Reverse the migration + */ + public function down(): void + { + DB::table($this->getContextSettingsTable()) + ->select(['setting_name']) + ->where('setting_name', 'numDaysAfterReviewResponseReminderDue') + ->update([ + 'setting_name' => 'numDaysBeforeInviteReminder' + ]); + + DB::table($this->getContextSettingsTable()) + ->select(['setting_name']) + ->where('setting_name', 'numDaysAfterReviewSubmitReminderDue') + ->update([ + 'setting_name' => 'numDaysBeforeSubmitReminder' + ]); + } +} diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index e5523526b25..8b452a73376 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -18,17 +18,12 @@ use APP\core\Application; use APP\facades\Repo; -use Illuminate\Support\Facades\Mail; -use PKP\context\Context; -use PKP\core\Core; -use PKP\core\PKPApplication; -use PKP\invitation\invitations\ReviewerAccessInvite; -use PKP\log\event\PKPSubmissionEventLogEntry; +use Carbon\Carbon; use PKP\mail\mailables\ReviewRemindAuto; use PKP\mail\mailables\ReviewResponseRemindAuto; use PKP\scheduledTask\ScheduledTask; use PKP\submission\PKPSubmission; -use PKP\submission\reviewAssignment\ReviewAssignment; +use PKP\jobs\email\ReviewRemainder as ReviewRemainderJob; class ReviewReminder extends ScheduledTask { @@ -40,68 +35,6 @@ public function getName() return __('admin.scheduledTask.reviewReminder'); } - /** - * Send the automatic review reminder to the reviewer. - */ - public function sendReminder( - ReviewAssignment $reviewAssignment, - PKPSubmission $submission, - Context $context, - ReviewRemindAuto|ReviewResponseRemindAuto $mailable - ): void { - - $reviewer = Repo::user()->get($reviewAssignment->getReviewerId()); - if (!isset($reviewer)) { - return; - } - - $primaryLocale = $context->getPrimaryLocale(); - $emailTemplate = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey()); - $mailable->subject($emailTemplate->getLocalizedData('subject', $primaryLocale)) - ->body($emailTemplate->getLocalizedData('body', $primaryLocale)) - ->from($context->getData('contactEmail'), $context->getData('contactName')) - ->recipients([$reviewer]); - - $mailable->setData($primaryLocale); - - $reviewerAccessKeysEnabled = $context->getData('reviewerAccessKeysEnabled'); - if ($reviewerAccessKeysEnabled) { // Give one-click access if enabled - $reviewInvitation = new ReviewerAccessInvite( - $reviewAssignment->getReviewerId(), - $context->getId(), - $reviewAssignment->getId() - ); - $reviewInvitation->setMailable($mailable); - $reviewInvitation->dispatch(); - } - - // deprecated template variables OJS 2.x - $mailable->addData([ - 'messageToReviewer' => __('reviewer.step1.requestBoilerplate'), - 'abstractTermIfEnabled' => ($submission->getLocalizedAbstract() == '' ? '' : __('common.abstract')), - ]); - - Mail::send($mailable); - - Repo::reviewAssignment()->edit($reviewAssignment, [ - 'dateReminded' => Core::getCurrentDate(), - 'reminderWasAutomatic' => 1 - ]); - - $eventLog = Repo::eventLog()->newDataObject([ - 'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION, - 'assocId' => $submission->getId(), - 'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_REMIND_AUTO, - 'userId' => null, - 'message' => 'submission.event.reviewer.reviewerRemindedAuto', - 'isTranslated' => false, - 'dateLogged' => Core::getCurrentDate(), - 'recipientId' => $reviewer->getId(), - 'recipientName' => $reviewer->getFullName(), - ]); - Repo::eventLog()->add($eventLog); - } - /** * @copydoc ScheduledTask::executeActions() */ @@ -111,15 +44,13 @@ public function executeActions() $context = null; $contextDao = Application::getContextDAO(); + $incompleteAssignments = Repo::reviewAssignment() + ->getCollector() + ->filterByIsIncomplete(true) + ->getMany(); - $incompleteAssignments = Repo::reviewAssignment()->getCollector()->filterByIsIncomplete(true)->getMany(); - $inviteReminderDays = $submitReminderDays = null; foreach ($incompleteAssignments as $reviewAssignment) { - // Avoid review assignments that a reminder exists for. - if ($reviewAssignment->getDateReminded() !== null) { - continue; - } - + // Fetch the submission if ($submission == null || $submission->getId() != $reviewAssignment->getSubmissionId()) { unset($submission); @@ -130,35 +61,102 @@ public function executeActions() } } - if ($submission->getStatus() != PKPSubmission::STATUS_QUEUED) { + if ($submission->getData('status') != PKPSubmission::STATUS_QUEUED) { continue; } // Fetch the context - if ($context == null || $context->getId() != $submission->getContextId()) { + if ($context == null || $context->getId() != $submission->getData('contextId')) { unset($context); - $context = $contextDao->getById($submission->getContextId()); + $context = $contextDao->getById($submission->getData('contextId')); + + $numDaysBeforeReviewResponseReminderDue = $context->getData('numDaysBeforeReviewResponseReminderDue'); + $numDaysAfterReviewResponseReminderDue = $context->getData('numDaysAfterReviewResponseReminderDue'); - $inviteReminderDays = $context->getData('numDaysBeforeInviteReminder'); - $submitReminderDays = $context->getData('numDaysBeforeSubmitReminder'); + $numDaysBeforeReviewSubmitReminderDue = $context->getData('numDaysBeforeReviewSubmitReminderDue'); + $numDaysAfterReviewSubmitReminderDue = $context->getData('numDaysAfterReviewSubmitReminderDue'); } $mailable = null; - if ($submitReminderDays >= 1 && $reviewAssignment->getDateDue() != null) { - $checkDate = strtotime($reviewAssignment->getDateDue()); - if (time() - $checkDate > 60 * 60 * 24 * $submitReminderDays) { - $mailable = new ReviewRemindAuto($context, $submission, $reviewAssignment); + $currentDate = Carbon::today(); + + $dateResponseDue = Carbon::parse($reviewAssignment->getDateResponseDue())->startOfDay(); + $dateDue = Carbon::parse($reviewAssignment->getDateDue())->startOfDay(); + + if ($reviewAssignment->getDateReminded() !== null) { + // we have a remainder sent previously + + $dateReminded = Carbon::parse($reviewAssignment->getDateReminded())->startOfDay(); + + if ($reviewAssignment->getDateConfirmed() === null) { + // review request has not been responded + // previous remainder was a BEFORE REVIEW REQUEST RESPONSE remainder + + if ($dateReminded->lt($dateResponseDue) && + $currentDate->gte($dateResponseDue) && + $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { + + // ACTION:-> we need to sent a AFTER REVIEW REQUEST RESPONSE remainder + $mailable = ReviewResponseRemindAuto::class; + } + } else { + + if ($numDaysBeforeReviewSubmitReminderDue && + $dateReminded->lt($dateDue) && + $currentDate->lt($dateDue) && + $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { + + // no review submit remainder has been sent + + // ACTION:-> we need to sent a BEFORE REVIEW SUBMIT remainder + $mailable = ReviewRemindAuto::class; + + } else if ( $numDaysAfterReviewSubmitReminderDue && + $dateReminded->lt($dateDue) && + $currentDate->gt($dateDue) && + $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { + + // ACTION:-> we need to sent a AFTER REVIEW SUBMIT remainder + $mailable = ReviewRemindAuto::class; + } } - } - if ($inviteReminderDays >= 1 && $reviewAssignment->getDateConfirmed() == null) { - $checkDate = strtotime($reviewAssignment->getDateResponseDue()); - if (time() - $checkDate > 60 * 60 * 24 * $inviteReminderDays) { - $mailable = new ReviewResponseRemindAuto($context, $submission, $reviewAssignment); + } else if ($reviewAssignment->getDateConfirmed() != null) { + // the review request has been responded + // as long review request has respnded, only need to concern with BEFORE/AFTER REVIEW SUBMIT remainder + if ($numDaysAfterReviewSubmitReminderDue && + $currentDate->gt($dateDue) && + $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { + + // ACTION:-> we need to send AFTER REVIEW SUBMIT remainder + $mailable = ReviewRemindAuto::class; + + } else if ( $numDaysBeforeReviewSubmitReminderDue && + $dateDue->gt($currentDate) && + $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { + + // ACTION:-> we need to send BEFORE REVIEW SUBMIT remainder + $mailable = ReviewRemindAuto::class; + } + } else { + // check for review response due + if ($numDaysAfterReviewResponseReminderDue && + $currentDate->gt($dateResponseDue) && + $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { + + // ACTION:-> we need to send AFTER REVIEW REQUEST RESPONSE remainder + $mailable = ReviewResponseRemindAuto::class; + + } else if ( $numDaysBeforeReviewResponseReminderDue && + $dateResponseDue->gt($currentDate) && + $dateResponseDue->diffInDays($currentDate) <= $numDaysBeforeReviewResponseReminderDue) { + + // ACTION:-> we need to send BEFORE REVIEW REQUEST RESPONSE remainder + $mailable = ReviewResponseRemindAuto::class; } } if ($mailable) { - $this->sendReminder($reviewAssignment, $submission, $context, $mailable); + ReviewRemainderJob::dispatch($reviewAssignment->getId(), $submission->getId(), $context->getId(), $mailable); } } diff --git a/classes/validation/ValidatorDateConparison.php b/classes/validation/ValidatorDateConparison.php new file mode 100644 index 00000000000..846e311611d --- /dev/null +++ b/classes/validation/ValidatorDateConparison.php @@ -0,0 +1,97 @@ + 'date_equals', + self::DATE_COMPARE_RULE_GREATER => 'after', + self::DATE_COMPARE_RULE_LESSER => 'before', + self::DATE_COMPARE_RULE_GREATER_OR_EQUAL => 'after_or_equal', + self::DATE_COMPARE_RULE_LESSER_OR_EQUAL => 'before_or_equal', + ]; + + public function __construct(DateTimeInterface|Carbon $comparingDate, string $comparingRule) + { + if (!in_array($comparingRule, static::getComparingRules())) { + throw new Exception( + sprintf( + 'Invalid comparison rule %s given, must be among [%s]', + $comparingRule, + implode(',', static::getComparingRules()) + ) + ); + } + + $this->comparingDate = $comparingDate instanceof Carbon ? $comparingDate : Carbon::parse($comparingDate); + $this->comparingRule = $comparingRule; + } + + public static function getComparingRules(): array + { + return [ + static::DATE_COMPARE_RULE_EQUAL, + static::DATE_COMPARE_RULE_GREATER, + static::DATE_COMPARE_RULE_LESSER, + static::DATE_COMPARE_RULE_GREATER_OR_EQUAL, + static::DATE_COMPARE_RULE_LESSER_OR_EQUAL , + ]; + } + + /** + * @copydoc Validator::isValid() + */ + public function isValid($value) + { + $validator = ValidatorFactory::make( + ['value' => $value], + ['value' => [ + 'date', + $this->getValidationApplicableRule($this->comparingRule) . ':' . $this->comparingDate->toDateString() + ]] + ); + + return $validator->passes(); + } + + protected function getValidationApplicableRule(string $rule): mixed + { + return $this->validationRulesMapping[$rule]; + } +} + +if (!PKP_STRICT_MODE) { + class_alias('\PKP\validation\ValidatorDateConparison', '\ValidatorDateConparison'); +} diff --git a/controllers/grid/users/reviewer/form/EditReviewForm.php b/controllers/grid/users/reviewer/form/EditReviewForm.php index fccf65df483..4a8bb347a51 100644 --- a/controllers/grid/users/reviewer/form/EditReviewForm.php +++ b/controllers/grid/users/reviewer/form/EditReviewForm.php @@ -62,6 +62,18 @@ public function __construct(ReviewAssignment $reviewAssignment, Submission $subm // Validation checks for this form $this->addCheck(new \PKP\form\validation\FormValidator($this, 'responseDueDate', 'required', 'editor.review.errorAddingReviewer')); $this->addCheck(new \PKP\form\validation\FormValidator($this, 'reviewDueDate', 'required', 'editor.review.errorAddingReviewer')); + + $this->addCheck( + new \PKP\form\validation\FormValidatorDateCompare( + $this, + 'reviewDueDate', + \Carbon\Carbon::parse(Application::get()->getRequest()->getUserVar('responseDueDate')), + \PKP\validation\ValidatorDateConparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, + 'optional', + 'editor.review.errorAddingReviewer.dateValidationFailed' + ) + ); + $this->addCheck(new \PKP\form\validation\FormValidatorPost($this)); $this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this)); } diff --git a/controllers/grid/users/reviewer/form/ReviewerForm.php b/controllers/grid/users/reviewer/form/ReviewerForm.php index 28179853488..72ce95b4cf8 100644 --- a/controllers/grid/users/reviewer/form/ReviewerForm.php +++ b/controllers/grid/users/reviewer/form/ReviewerForm.php @@ -73,6 +73,17 @@ public function __construct($submission, $reviewRound) $this->addCheck(new \PKP\form\validation\FormValidator($this, 'responseDueDate', 'required', 'editor.review.errorAddingReviewer')); $this->addCheck(new \PKP\form\validation\FormValidator($this, 'reviewDueDate', 'required', 'editor.review.errorAddingReviewer')); + $this->addCheck( + new \PKP\form\validation\FormValidatorDateCompare( + $this, + 'reviewDueDate', + \Carbon\Carbon::parse(Application::get()->getRequest()->getUserVar('responseDueDate')), + \PKP\validation\ValidatorDateConparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, + 'optional', + 'editor.review.errorAddingReviewer.dateValidationFailed' + ) + ); + $this->addCheck(new \PKP\form\validation\FormValidatorPost($this)); $this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this)); } diff --git a/jobs/email/ReviewRemainder.php b/jobs/email/ReviewRemainder.php new file mode 100644 index 00000000000..0ce8bc970c1 --- /dev/null +++ b/jobs/email/ReviewRemainder.php @@ -0,0 +1,117 @@ +reviewAssignmentId = $reviewAssignmentId; + $this->submissionId = $submissionId; + $this->contextId = $contextId; + $this->mailableClass = $mailableClass; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $reviewAssignment = Repo::reviewAssignment()->get($this->reviewAssignmentId); + $reviewer = Repo::user()->get($reviewAssignment->getReviewerId()); + + if (!isset($reviewer)) { + return; + } + + $submission = Repo::submission()->get($this->submissionId); + + $contextService = Services::get("context"); + $context = $contextService->get($this->contextId); + + /** @var ReviewRemindAuto|ReviewResponseRemindAuto $mailable */ + $mailable = new $this->mailableClass($context, $submission, $reviewAssignment); + + $primaryLocale = $context->getPrimaryLocale(); + $emailTemplate = Repo::emailTemplate()->getByKey( + $context->getId(), + $mailable::getEmailTemplateKey() + ); + $mailable->subject($emailTemplate->getLocalizedData('subject', $primaryLocale)) + ->body($emailTemplate->getLocalizedData('body', $primaryLocale)) + ->from($context->getData('contactEmail'), $context->getData('contactName')) + ->recipients([$reviewer]); + + $mailable->setData($primaryLocale); + + $reviewerAccessKeysEnabled = $context->getData('reviewerAccessKeysEnabled'); + if ($reviewerAccessKeysEnabled) { // Give one-click access if enabled + $reviewInvitation = new ReviewerAccessInvite( + $reviewAssignment->getReviewerId(), + $context->getId(), + $reviewAssignment->getId() + ); + $reviewInvitation->setMailable($mailable); + $reviewInvitation->dispatch(); + } + + // deprecated template variables OJS 2.x + $mailable->addData([ + 'messageToReviewer' => __('reviewer.step1.requestBoilerplate'), + 'abstractTermIfEnabled' => ($submission->getCurrentPublication()->getLocalizedData('abstract') == '' ? '' : __('common.abstract')), + ]); + + Mail::send($mailable); + + Repo::reviewAssignment()->edit($reviewAssignment, [ + 'dateReminded' => Core::getCurrentDate(), + 'reminderWasAutomatic' => 1 + ]); + + $eventLog = Repo::eventLog()->newDataObject([ + 'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION, + 'assocId' => $submission->getId(), + 'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_REMIND_AUTO, + 'userId' => null, + 'message' => 'submission.event.reviewer.reviewerRemindedAuto', + 'isTranslated' => false, + 'dateLogged' => Core::getCurrentDate(), + 'recipientId' => $reviewer->getId(), + 'recipientName' => $reviewer->getFullName(), + ]); + Repo::eventLog()->add($eventLog); + } +} diff --git a/locale/en/editor.po b/locale/en/editor.po index 892d98fea9f..e885b3b650d 100644 --- a/locale/en/editor.po +++ b/locale/en/editor.po @@ -205,6 +205,9 @@ msgstr "Email to be sent to reviewer" msgid "editor.review.importantDates" msgstr "Important Dates" +msgid "editor.review.importantDates.notice" +msgstr "Review due date must be greater or euqal to response due date." + msgid "editor.review.uploadRevision" msgstr "Upload Revision" @@ -321,6 +324,9 @@ msgstr "You must select a reviewer" msgid "editor.review.errorAddingReviewer" msgstr "There was an error adding the reviewer. Please try again." +msgid "editor.review.errorAddingReviewer.dateValidationFailed" +msgstr "There was an error adding the reviewer as review due date must be equal or greater than responde due date." + msgid "editor.review.errorDeletingReviewer" msgstr "There was an error deleting the reviewer. Please try again." diff --git a/locale/en/manager.po b/locale/en/manager.po index 3db82af2ee0..2b6b3dc94eb 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1226,7 +1226,13 @@ msgstr "Response Reminder" msgid "manager.setup.reviewOptions.reminders.response.description" msgstr "" "Send an email reminder if a reviewer has not responded to a review request " -"this many days after the response due date." +"within this many days (left empty if no remainder):" + +msgid "manager.setup.reviewOptions.reminders.response.description.before" +msgstr "- before the response due date:" + +msgid "manager.setup.reviewOptions.reminders.response.description.after" +msgstr "- after the response due date:" msgid "manager.setup.reviewOptions.reminders.submit" msgstr "Review Reminder" @@ -1234,7 +1240,13 @@ msgstr "Review Reminder" msgid "manager.setup.reviewOptions.reminders.submit.description" msgstr "" "Send an email reminder if a reviewer has not submitted a recommendation " -"within this many days after the review's due date." +"within this many days (left empty if no remainder)" + +msgid "manager.setup.reviewOptions.reminders.submit.description.before" +msgstr "- before the review due date:" + +msgid "manager.setup.reviewOptions.reminders.submit.description.after" +msgstr "- after the review due date:" msgid "manager.setup.reviewOptions.reviewMode" msgstr "Default Review Mode" diff --git a/schemas/context.json b/schemas/context.json index f56fbbb62a0..d89b236af44 100644 --- a/schemas/context.json +++ b/schemas/context.json @@ -511,14 +511,28 @@ "min:0" ] }, - "numDaysBeforeInviteReminder": { + "numDaysAfterReviewResponseReminderDue": { "type": "integer", "validation": [ "nullable", "min:0" ] }, - "numDaysBeforeSubmitReminder": { + "numDaysBeforeReviewResponseReminderDue": { + "type": "integer", + "validation": [ + "nullable", + "min:0" + ] + }, + "numDaysAfterReviewSubmitReminderDue": { + "type": "integer", + "validation": [ + "nullable", + "min:0" + ] + }, + "numDaysBeforeReviewSubmitReminderDue": { "type": "integer", "validation": [ "nullable", diff --git a/templates/controllers/grid/users/reviewer/form/editReviewForm.tpl b/templates/controllers/grid/users/reviewer/form/editReviewForm.tpl index c564733c668..0a90e152b0e 100644 --- a/templates/controllers/grid/users/reviewer/form/editReviewForm.tpl +++ b/templates/controllers/grid/users/reviewer/form/editReviewForm.tpl @@ -23,7 +23,7 @@ - {fbvFormSection title="editor.review.importantDates"} + {fbvFormSection title="editor.review.importantDates" description="editor.review.importantDates.notice"} {fbvElement type="text" id="responseDueDate" name="responseDueDate" label="submission.task.responseDueDate" value=$responseDueDate inline=true size=$fbvStyles.size.MEDIUM class="datepicker"} {fbvElement type="text" id="reviewDueDate" name="reviewDueDate" label="editor.review.reviewDueDate" value=$reviewDueDate inline=true size=$fbvStyles.size.MEDIUM class="datepicker"} {/fbvFormSection} diff --git a/templates/controllers/grid/users/reviewer/form/reviewerFormFooter.tpl b/templates/controllers/grid/users/reviewer/form/reviewerFormFooter.tpl index 6d4196d76c5..f6ad22e9cba 100644 --- a/templates/controllers/grid/users/reviewer/form/reviewerFormFooter.tpl +++ b/templates/controllers/grid/users/reviewer/form/reviewerFormFooter.tpl @@ -29,7 +29,7 @@ {fbvElement type="checkbox" id="skipEmail" name="skipEmail" label="editor.review.skipEmail"} {/fbvFormSection} - {fbvFormSection title="editor.review.importantDates"} + {fbvFormSection title="editor.review.importantDates" description="editor.review.importantDates.notice"} {fbvElement type="text" id="responseDueDate" name="responseDueDate" label="submission.task.responseDueDate" value=$responseDueDate inline=true size=$fbvStyles.size.MEDIUM class="datepicker"} {fbvElement type="text" id="reviewDueDate" name="reviewDueDate" label="editor.review.reviewDueDate" value=$reviewDueDate inline=true size=$fbvStyles.size.MEDIUM class="datepicker"} {/fbvFormSection}