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}