From 655365959d7a3afda80dd2573bb53e95ae7dfcf7 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 11 Jan 2024 19:51:23 +0600 Subject: [PATCH 01/23] pkp/pkp-lib#5885 Review remainder update --- .../forms/context/PKPReviewSetupForm.php | 127 ++++++++++--- .../validation/FormValidatorDateCompare.php | 46 +++++ .../UnableToCreateJATSContentException.php | 6 +- ...5885_RenameReviewRemainderSettingsName.php | 65 +++++++ classes/task/ReviewReminder.php | 175 +++++++++--------- .../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, 578 insertions(+), 122 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 673f89e2931..bb3f500ebb3 100644 --- a/classes/components/forms/context/PKPReviewSetupForm.php +++ b/classes/components/forms/context/PKPReviewSetupForm.php @@ -2,8 +2,8 @@ /** * @file classes/components/form/context/PKPReviewSetupForm.php * - * Copyright (c) 2014-2021 Simon Fraser University - * Copyright (c) 2000-2021 John Willinsky + * Copyright (c) 2014-2024 Simon Fraser University + * Copyright (c) 2000-2024 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class PKPReviewSetupForm @@ -17,10 +17,11 @@ namespace PKP\components\forms\context; use PKP\components\forms\FieldHTML; +use PKP\context\Context; use PKP\components\forms\FieldOptions; +use PKP\components\forms\FieldSlider; use PKP\components\forms\FieldText; use PKP\components\forms\FormComponent; -use PKP\config\Config; use PKP\submission\reviewAssignment\ReviewAssignment; class PKPReviewSetupForm extends FormComponent @@ -29,6 +30,12 @@ class PKPReviewSetupForm extends FormComponent public $id = self::FORM_REVIEW_SETUP; public $method = 'PUT'; + protected const REVIEW_SETTINGS_GROUP = 'reviewSettingsGroup'; + protected const REVIEW_REMINDER_GROUP = 'reviewReminderGroup'; + + public const MIN_REMINDER_NOTIFICATION_SEND_IN_DAYS = 0; + public const MAX_REMINDER_NOTIFICATION_SEND_IN_DAYS = 14; + /** * Constructor * @@ -41,23 +48,39 @@ public function __construct($action, $locales, $context) $this->action = $action; $this->locales = $locales; - $this->addField(new FieldOptions('defaultReviewMode', [ - 'label' => __('manager.setup.reviewOptions.reviewMode'), - 'type' => 'radio', - 'value' => $context->getData('defaultReviewMode'), - 'options' => [ - ['value' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS, 'label' => __('editor.submissionReview.doubleAnonymous')], - ['value' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS, 'label' => __('editor.submissionReview.anonymous')], - ['value' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN, 'label' => __('editor.submissionReview.open')], - ], - ])) + $this + ->addDefaultFields($context) + ->addReminderFields($context); + } + + /** + * Add the default review control fields + */ + protected function addDefaultFields(Context $context): static + { + $this + ->addGroup([ + 'id' => self::REVIEW_SETTINGS_GROUP + ]) + ->addField(new FieldOptions('defaultReviewMode', [ + 'label' => __('manager.setup.reviewOptions.reviewMode'), + 'type' => 'radio', + 'value' => $context->getData('defaultReviewMode'), + 'options' => [ + ['value' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS, 'label' => __('editor.submissionReview.doubleAnonymous')], + ['value' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS, 'label' => __('editor.submissionReview.anonymous')], + ['value' => ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN, 'label' => __('editor.submissionReview.open')], + ], + 'groupId' => self::REVIEW_SETTINGS_GROUP, + ])) ->addField(new FieldOptions('restrictReviewerFileAccess', [ 'label' => __('manager.setup.reviewOptions.restrictReviewerFileAccess'), 'type' => 'checkbox', 'value' => $context->getData('restrictReviewerFileAccess'), 'options' => [ ['value' => true, 'label' => __('manager.setup.reviewOptions.restrictReviewerFileAccess.description')], - ] + ], + 'groupId' => self::REVIEW_SETTINGS_GROUP, ])) ->addField(new FieldOptions('reviewerAccessKeysEnabled', [ 'label' => __('manager.setup.reviewOptions.reviewerAccessKeysEnabled'), @@ -66,37 +89,89 @@ public function __construct($action, $locales, $context) 'value' => $context->getData('reviewerAccessKeysEnabled'), 'options' => [ ['value' => true, 'label' => __('manager.setup.reviewOptions.reviewerAccessKeysEnabled.label')], - ] + ], + 'groupId' => self::REVIEW_SETTINGS_GROUP, ])) ->addField(new FieldText('numWeeksPerResponse', [ 'label' => __('manager.setup.reviewOptions.defaultReviewResponseTime'), 'description' => __('manager.setup.reviewOptions.numWeeksPerResponse'), 'value' => $context->getData('numWeeksPerResponse'), 'size' => 'small', + 'groupId' => self::REVIEW_SETTINGS_GROUP, ])) ->addField(new FieldText('numWeeksPerReview', [ 'label' => __('manager.setup.reviewOptions.defaultReviewCompletionTime'), 'description' => __('manager.setup.reviewOptions.numWeeksPerReview'), 'value' => $context->getData('numWeeksPerReview'), 'size' => 'small', + 'groupId' => self::REVIEW_SETTINGS_GROUP, ])) ->addField(new FieldText('numReviewersPerSubmission', [ 'label' => __('manager.setup.reviewOptions.numReviewersPerSubmission'), 'description' => __('manager.setup.reviewOptions.numReviewersPerSubmission.description'), 'value' => $context->getData('numReviewersPerSubmission'), 'size' => 'small', + 'groupId' => self::REVIEW_SETTINGS_GROUP, + ])); + + return $this; + } + + /** + * Add the review reminder control fields + */ + protected function addReminderFields(Context $context): static + { + $this + ->addGroup([ + 'id' => self::REVIEW_REMINDER_GROUP, + ]) + ->addField(new FieldHTML('reminderForReview', [ + 'label' => __('manager.setup.reviewOptions.reminders'), + 'description' => __('manager.setup.reviewOptions.reminders.description'), + 'groupId' => self::REVIEW_REMINDER_GROUP, ])) - ->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 FieldSlider('numDaysBeforeReviewResponseReminderDue', [ + 'label' => __('manager.setup.reviewOptions.reminders.response.before'), + 'value' => $context->getData('numDaysBeforeReviewResponseReminderDue'), + 'min' => static::MIN_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'max' => static::MAX_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'minLabel' => __('manager.setup.reviewOptions.reminders.min.label'), + 'valueLabel' => __('manager.setup.reviewOptions.reminders.label.before.days'), + 'valueLabelMin' => __('manager.setup.reviewOptions.reminders.disbale.label'), + 'groupId' => self::REVIEW_REMINDER_GROUP, ])) - ->addField(new FieldText('numDaysBeforeSubmitReminder', [ - 'label' => __('manager.setup.reviewOptions.reminders.submit'), - 'description' => __('manager.setup.reviewOptions.reminders.submit.description'), - 'value' => $context->getData('numDaysBeforeSubmitReminder'), - 'size' => 'small', + ->addField(new FieldSlider('numDaysAfterReviewResponseReminderDue', [ + 'label' => __('manager.setup.reviewOptions.reminders.response.after'), + 'value' => $context->getData('numDaysAfterReviewResponseReminderDue'), + 'min' => static::MIN_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'max' => static::MAX_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'minLabel' => __('manager.setup.reviewOptions.reminders.min.label'), + 'valueLabel' => __('manager.setup.reviewOptions.reminders.label.after.days'), + 'valueLabelMin' => __('manager.setup.reviewOptions.reminders.disbale.label'), + 'groupId' => self::REVIEW_REMINDER_GROUP, + ])) + ->addField(new FieldSlider('numDaysBeforeReviewSubmitReminderDue', [ + 'label' => __('manager.setup.reviewOptions.reminders.submit.before'), + 'value' => $context->getData('numDaysBeforeReviewSubmitReminderDue'), + 'min' => static::MIN_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'max' => static::MAX_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'minLabel' => __('manager.setup.reviewOptions.reminders.min.label'), + 'valueLabel' => __('manager.setup.reviewOptions.reminders.label.before.days'), + 'valueLabelMin' => __('manager.setup.reviewOptions.reminders.disbale.label'), + 'groupId' => self::REVIEW_REMINDER_GROUP, + ])) + ->addField(new FieldSlider('numDaysAfterReviewSubmitReminderDue', [ + 'label' => __('manager.setup.reviewOptions.reminders.submit.after'), + 'value' => $context->getData('numDaysAfterReviewSubmitReminderDue'), + 'min' => static::MIN_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'max' => static::MAX_REMINDER_NOTIFICATION_SEND_IN_DAYS, + 'minLabel' => __('manager.setup.reviewOptions.reminders.min.label'), + 'valueLabel' => __('manager.setup.reviewOptions.reminders.label.after.days'), + 'valueLabelMin' => __('manager.setup.reviewOptions.reminders.disbale.label'), + 'groupId' => self::REVIEW_REMINDER_GROUP, ])); + + return $this; } -} +} \ No newline at end of file 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 e6badd4cd36..eb3d0387f4c 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,69 +35,6 @@ public function getName(): string 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(); - $reviewInvitation->initialize($reviewAssignment->getReviewerId(), $context->getId(), null); - - $reviewInvitation->reviewAssignmentId = $reviewAssignment->getId(); - $reviewInvitation->updatePayload(); - - $reviewInvitation->invite(); - $reviewInvitation->updateMailableWithUrl($mailable); - } - - // 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); - } - /** * @copydoc ScheduledTask::executeActions() */ @@ -112,15 +44,13 @@ public function executeActions(): bool $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); @@ -140,26 +70,93 @@ public function executeActions(): bool unset($context); $context = $contextDao->getById($submission->getData('contextId')); - $inviteReminderDays = $context->getData('numDaysBeforeInviteReminder'); - $submitReminderDays = $context->getData('numDaysBeforeSubmitReminder'); + $numDaysBeforeReviewResponseReminderDue = $context->getData('numDaysBeforeReviewResponseReminderDue'); + $numDaysAfterReviewResponseReminderDue = $context->getData('numDaysAfterReviewResponseReminderDue'); + + $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 d66bb254ac2..d32c0f13f9a 100644 --- a/controllers/grid/users/reviewer/form/EditReviewForm.php +++ b/controllers/grid/users/reviewer/form/EditReviewForm.php @@ -61,6 +61,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 c96b937209c..83292349c27 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 3fada52787d..4e7e0240b33 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 2f3e8a5b3c3..21c07be9c11 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1244,7 +1244,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" @@ -1252,7 +1258,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 a2445718f66..b21cf817216 100644 --- a/schemas/context.json +++ b/schemas/context.json @@ -520,14 +520,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 c360cc68c3d..664ca0124cc 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 426380a8efa..9056033ee67 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} From 1c6b00788db14c099150187edb4a43afd8698b02 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Mon, 29 Jan 2024 16:04:48 +0600 Subject: [PATCH 02/23] pkp/pkp-lib#5885 Review remainder update issues fixed --- .../forms/context/PKPReviewSetupForm.php | 3 +- .../validation/FormValidatorDateCompare.php | 6 ++-- ...5885_RenameReviewReminderSettingsName.php} | 10 +++--- classes/task/ReviewReminder.php | 34 +++++++++++-------- ...arison.php => ValidatorDateComparison.php} | 8 ++--- .../users/reviewer/form/EditReviewForm.php | 2 +- .../grid/users/reviewer/form/ReviewerForm.php | 4 +-- ...ReviewRemainder.php => ReviewReminder.php} | 10 +++--- locale/en/common.po | 3 ++ 9 files changed, 44 insertions(+), 36 deletions(-) rename classes/migration/upgrade/v3_5_0/{I5885_RenameReviewRemainderSettingsName.php => I5885_RenameReviewReminderSettingsName.php} (83%) rename classes/validation/{ValidatorDateConparison.php => ValidatorDateComparison.php} (92%) rename jobs/email/{ReviewRemainder.php => ReviewReminder.php} (94%) diff --git a/classes/components/forms/context/PKPReviewSetupForm.php b/classes/components/forms/context/PKPReviewSetupForm.php index bb3f500ebb3..5660321adc5 100644 --- a/classes/components/forms/context/PKPReviewSetupForm.php +++ b/classes/components/forms/context/PKPReviewSetupForm.php @@ -22,6 +22,7 @@ use PKP\components\forms\FieldSlider; use PKP\components\forms\FieldText; use PKP\components\forms\FormComponent; +use PKP\config\Config; use PKP\submission\reviewAssignment\ReviewAssignment; class PKPReviewSetupForm extends FormComponent @@ -174,4 +175,4 @@ protected function addReminderFields(Context $context): static return $this; } -} \ No newline at end of file +} diff --git a/classes/form/validation/FormValidatorDateCompare.php b/classes/form/validation/FormValidatorDateCompare.php index 8e549776512..c9cbe9bab9f 100644 --- a/classes/form/validation/FormValidatorDateCompare.php +++ b/classes/form/validation/FormValidatorDateCompare.php @@ -18,7 +18,7 @@ namespace PKP\form\validation; -use PKP\validation\ValidatorDateConparison; +use PKP\validation\ValidatorDateComparison; use Carbon\Carbon; use DateTimeInterface; @@ -34,9 +34,9 @@ class FormValidatorDateCompare extends FormValidator * @param string $type the type of check, either "required" or "optional" * @param string $message the error message for validation failures (i18n key) */ - public function __construct(&$form, $field, $comparingDate, $comparingRule, $type = 'optional', $message = 'email.invalid') + public function __construct(&$form, $field, $comparingDate, $comparingRule, $type = 'optional', $message = 'validator.date.comparison') { - $validator = new ValidatorDateConparison($comparingDate, $comparingRule); + $validator = new ValidatorDateComparison($comparingDate, $comparingRule); parent::__construct($form, $field, $type, $message, $validator); } } diff --git a/classes/migration/upgrade/v3_5_0/I5885_RenameReviewRemainderSettingsName.php b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php similarity index 83% rename from classes/migration/upgrade/v3_5_0/I5885_RenameReviewRemainderSettingsName.php rename to classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php index f2702357612..79300b9a588 100644 --- a/classes/migration/upgrade/v3_5_0/I5885_RenameReviewRemainderSettingsName.php +++ b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php @@ -1,25 +1,23 @@ getDateDue())->startOfDay(); if ($reviewAssignment->getDateReminded() !== null) { - // we have a remainder sent previously + // we have a reminder 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 + // previous reminder was a BEFORE REVIEW REQUEST RESPONSE reminder - if ($dateReminded->lt($dateResponseDue) && + if ($numDaysAfterReviewResponseReminderDue && + $dateReminded->lt($dateResponseDue) && $currentDate->gte($dateResponseDue) && $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { - // ACTION:-> we need to sent a AFTER REVIEW REQUEST RESPONSE remainder + // ACTION:-> we need to sent a AFTER REVIEW REQUEST RESPONSE reminder $mailable = ReviewResponseRemindAuto::class; } } else { @@ -106,9 +107,9 @@ public function executeActions(): bool $currentDate->lt($dateDue) && $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { - // no review submit remainder has been sent + // no review submit reminder has been sent - // ACTION:-> we need to sent a BEFORE REVIEW SUBMIT remainder + // ACTION:-> we need to sent a BEFORE REVIEW SUBMIT reminder $mailable = ReviewRemindAuto::class; } else if ( $numDaysAfterReviewSubmitReminderDue && @@ -116,25 +117,25 @@ public function executeActions(): bool $currentDate->gt($dateDue) && $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { - // ACTION:-> we need to sent a AFTER REVIEW SUBMIT remainder + // ACTION:-> we need to sent a AFTER REVIEW SUBMIT reminder $mailable = ReviewRemindAuto::class; } } } 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 + // as long review request has respnded, only need to concern with BEFORE/AFTER REVIEW SUBMIT reminder if ($numDaysAfterReviewSubmitReminderDue && $currentDate->gt($dateDue) && $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { - // ACTION:-> we need to send AFTER REVIEW SUBMIT remainder + // ACTION:-> we need to send AFTER REVIEW SUBMIT reminder $mailable = ReviewRemindAuto::class; } else if ( $numDaysBeforeReviewSubmitReminderDue && $dateDue->gt($currentDate) && $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { - // ACTION:-> we need to send BEFORE REVIEW SUBMIT remainder + // ACTION:-> we need to send BEFORE REVIEW SUBMIT reminder $mailable = ReviewRemindAuto::class; } } else { @@ -143,20 +144,25 @@ public function executeActions(): bool $currentDate->gt($dateResponseDue) && $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { - // ACTION:-> we need to send AFTER REVIEW REQUEST RESPONSE remainder + // ACTION:-> we need to send AFTER REVIEW REQUEST RESPONSE reminder $mailable = ReviewResponseRemindAuto::class; } else if ( $numDaysBeforeReviewResponseReminderDue && $dateResponseDue->gt($currentDate) && $dateResponseDue->diffInDays($currentDate) <= $numDaysBeforeReviewResponseReminderDue) { - // ACTION:-> we need to send BEFORE REVIEW REQUEST RESPONSE remainder + // ACTION:-> we need to send BEFORE REVIEW REQUEST RESPONSE reminder $mailable = ReviewResponseRemindAuto::class; } } if ($mailable) { - ReviewRemainderJob::dispatch($reviewAssignment->getId(), $submission->getId(), $context->getId(), $mailable); + ReviewReminderJob::dispatch( + $reviewAssignment->getId(), + $submission->getId(), + $context->getId(), + $mailable + ); } } diff --git a/classes/validation/ValidatorDateConparison.php b/classes/validation/ValidatorDateComparison.php similarity index 92% rename from classes/validation/ValidatorDateConparison.php rename to classes/validation/ValidatorDateComparison.php index 846e311611d..ac63bf4e6de 100644 --- a/classes/validation/ValidatorDateConparison.php +++ b/classes/validation/ValidatorDateComparison.php @@ -1,13 +1,13 @@ getRequest()->getUserVar('responseDueDate')), - \PKP\validation\ValidatorDateConparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, + \PKP\validation\ValidatorDateComparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, 'optional', 'editor.review.errorAddingReviewer.dateValidationFailed' ) diff --git a/controllers/grid/users/reviewer/form/ReviewerForm.php b/controllers/grid/users/reviewer/form/ReviewerForm.php index 83292349c27..bdfef86771e 100644 --- a/controllers/grid/users/reviewer/form/ReviewerForm.php +++ b/controllers/grid/users/reviewer/form/ReviewerForm.php @@ -77,8 +77,8 @@ public function __construct($submission, $reviewRound) new \PKP\form\validation\FormValidatorDateCompare( $this, 'reviewDueDate', - \Carbon\Carbon::parse(Application::get()->getRequest()->getUserVar('responseDueDate')), - \PKP\validation\ValidatorDateConparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, + \Carbon\Carbon::parse($this->getData('responseDueDate')), + \PKP\validation\ValidatorDateComparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, 'optional', 'editor.review.errorAddingReviewer.dateValidationFailed' ) diff --git a/jobs/email/ReviewRemainder.php b/jobs/email/ReviewReminder.php similarity index 94% rename from jobs/email/ReviewRemainder.php rename to jobs/email/ReviewReminder.php index 0ce8bc970c1..40ca0522c00 100644 --- a/jobs/email/ReviewRemainder.php +++ b/jobs/email/ReviewReminder.php @@ -1,17 +1,17 @@ get($this->submissionId); - $contextService = Services::get("context"); + $contextService = Services::get('context'); $context = $contextService->get($this->contextId); /** @var ReviewRemindAuto|ReviewResponseRemindAuto $mailable */ diff --git a/locale/en/common.po b/locale/en/common.po index 3bd99b29238..63490c19b1c 100644 --- a/locale/en/common.po +++ b/locale/en/common.po @@ -2153,6 +2153,9 @@ msgstr "This is not a valid currency." msgid "validator.date" msgstr "This is not a valid date." +msgid "validator.date.comparison" +msgstr "Invalid dates provided for comparison." + msgid "validator.date_format" msgstr "This does not match the format {$format}." From 4634d8958f306be489c4f4ca2aa90ac1f3a8eb70 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Mon, 29 Jan 2024 16:10:07 +0600 Subject: [PATCH 03/23] pkp/pkp-lib#5885 typo fixed --- locale/en/manager.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/en/manager.po b/locale/en/manager.po index 21c07be9c11..0a3f8ed4926 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1244,7 +1244,7 @@ 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 " -"within this many days (left empty if no remainder):" +"within this many days (left empty if no reminder):" msgid "manager.setup.reviewOptions.reminders.response.description.before" msgstr "- before the response due date:" @@ -1258,7 +1258,7 @@ 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 (left empty if no remainder)" +"within this many days (left empty if no reminder)" msgid "manager.setup.reviewOptions.reminders.submit.description.before" msgstr "- before the review due date:" From 763efa1a96461c49fe769f8f0f68bb4cc856cb38 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Sat, 17 Feb 2024 00:21:28 +0600 Subject: [PATCH 04/23] pkp/pkp-lib#5885 update remainder calculation --- .../validation/FormValidatorDateCompare.php | 4 - ...I5885_RenameReviewReminderSettingsName.php | 2 - .../submission/reviewAssignment/Collector.php | 51 ++++++++- classes/task/ReviewReminder.php | 107 ++++++++---------- jobs/email/ReviewReminder.php | 12 +- 5 files changed, 104 insertions(+), 72 deletions(-) diff --git a/classes/form/validation/FormValidatorDateCompare.php b/classes/form/validation/FormValidatorDateCompare.php index c9cbe9bab9f..643ad87438c 100644 --- a/classes/form/validation/FormValidatorDateCompare.php +++ b/classes/form/validation/FormValidatorDateCompare.php @@ -40,7 +40,3 @@ public function __construct(&$form, $field, $comparingDate, $comparingRule, $ty parent::__construct($form, $field, $type, $message, $validator); } } - -if (!PKP_STRICT_MODE) { - class_alias('\PKP\form\validation\FormValidatorDateCompare', '\FormValidatorDateCompare'); -} diff --git a/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php index 79300b9a588..8cf288225f1 100644 --- a/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php +++ b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php @@ -27,14 +27,12 @@ abstract protected function getContextSettingsTable(): string; public function up(): void { DB::table($this->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' diff --git a/classes/submission/reviewAssignment/Collector.php b/classes/submission/reviewAssignment/Collector.php index efca0813fa0..7dc62b755e2 100644 --- a/classes/submission/reviewAssignment/Collector.php +++ b/classes/submission/reviewAssignment/Collector.php @@ -27,6 +27,9 @@ */ class Collector implements CollectorInterface, ViewsCount { + public const ORDER_DIR_ASC = 'ASC'; + public const ORDER_DIR_DESC = 'DESC'; + public DAO $dao; public ?array $contextIds = null; public ?array $submissionIds = null; @@ -41,6 +44,10 @@ class Collector implements CollectorInterface, ViewsCount public ?array $reviewMethods = null; public ?int $stageId = null; public ?array $reviewFormIds = null; + public bool $orderByContextId = false; + public ?string $orderByContextIdDirection = null; + public bool $orderBySubmissionId = false; + public ?string $orderBySubmissionIdDirection = null; public function __construct(DAO $dao) { @@ -184,6 +191,26 @@ public function offset(?int $offset): static return $this; } + /** + * Order/Sort the review assignments by associated context id + */ + public function orderByContextId(string $direction = self::ORDER_DIR_ASC): static + { + $this->orderByContextId = true; + $this->orderByContextIdDirection = $direction; + return $this; + } + + /** + * Order/Sort the review assignments by associated submission id + */ + public function orderBySubmissionId(string $direction = self::ORDER_DIR_ASC): static + { + $this->orderBySubmissionId = true; + $this->orderBySubmissionIdDirection = $direction; + return $this; + } + /** * @copydoc CollectorInterface::getQueryBuilder() */ @@ -307,16 +334,36 @@ public function getQueryBuilder(): Builder $q->when( $this->count !== null, - fn () => + fn (Builder $q) => $q->limit($this->count) ); $q->when( $this->offset !== null, - fn () => + fn (Builder $q) => $q->offset($this->offset) ); + $q->when( + $this->orderByContextId, + fn (Builder $q) => + $q->orderBy( + DB::table('submissions') + ->select('context_id') + ->whereColumn('submission_id', 'ra.submission_id'), + $this->orderByContextIdDirection + ) + ); + + $q->when( + $this->orderBySubmissionId, + fn (Builder $q) => + $q->orderBy( + 'ra.submission_id', + $this->orderBySubmissionIdDirection + ) + ); + return $q; } diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index 5e315e78289..4f147b1c62f 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -47,6 +47,7 @@ public function executeActions(): bool $incompleteAssignments = Repo::reviewAssignment() ->getCollector() ->filterByIsIncomplete(true) + ->orderByContextId() ->getMany(); foreach ($incompleteAssignments as $reviewAssignment) { @@ -80,79 +81,69 @@ public function executeActions(): bool $mailable = null; $currentDate = Carbon::today(); - $dateResponseDue = Carbon::parse($reviewAssignment->getDateResponseDue())->startOfDay(); - $dateDue = Carbon::parse($reviewAssignment->getDateDue())->startOfDay(); - - if ($reviewAssignment->getDateReminded() !== null) { - // we have a reminder sent previously - - $dateReminded = Carbon::parse($reviewAssignment->getDateReminded())->startOfDay(); + $dateResponseDue = Carbon::parse($reviewAssignment->getDateResponseDue()); + $dateDue = Carbon::parse($reviewAssignment->getDateDue()); + + // after a REVIEW REQUEST has been responded, the value of `dateReminded` and `reminderWasAutomatic` + // get reset, see \PKP\submission\reviewer\ReviewerAction::confirmReview. + // + if ($reviewAssignment->getDateConfirmed() === null) { + // REVIEW REQUEST has not been responded + // only need to concern with BEFORE/AFTER REVIEW REQUEST RESPONSE reminder + + if ($reviewAssignment->getDateReminded() === null) { + // There has not been any reminder sent yet + // need to check should we sent a BEFORE REVIEW REQUEST RESPONSE reminder + if ($numDaysBeforeReviewResponseReminderDue && + $dateResponseDue->gt($currentDate) && + $dateResponseDue->diffInDays($currentDate) <= $numDaysBeforeReviewResponseReminderDue) { + + // ACTION:-> we need to send BEFORE REVIEW REQUEST RESPONSE reminder + $mailable = ReviewResponseRemindAuto::class; + } + } else { + // There has been a reminder already sent + // need to check should we sent a AFTER REVIEW REQUEST RESPONSE reminder - if ($reviewAssignment->getDateConfirmed() === null) { - // review request has not been responded - // previous reminder was a BEFORE REVIEW REQUEST RESPONSE reminder + $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); if ($numDaysAfterReviewResponseReminderDue && + $currentDate->gt($dateResponseDue) && $dateReminded->lt($dateResponseDue) && - $currentDate->gte($dateResponseDue) && $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { - - // ACTION:-> we need to sent a AFTER REVIEW REQUEST RESPONSE reminder + + // ACTION:-> we need to send AFTER REVIEW REQUEST RESPONSE reminder $mailable = ReviewResponseRemindAuto::class; } - } else { - - if ($numDaysBeforeReviewSubmitReminderDue && - $dateReminded->lt($dateDue) && - $currentDate->lt($dateDue) && + } + } else { + // REVIEW REQUEST has been responded + // only need to concern with BEFORE/AFTER REVIEW SUBMIT reminder + + if ($reviewAssignment->getDateReminded() === null) { + // There has not been any reminder sent after responding to REVIEW REQUEST + // no REVIEW SUBMIT reminder has been sent + if ($numDaysBeforeReviewSubmitReminderDue && + $currentDate->lt($dateDue) && $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { - - // no review submit reminder has been sent // ACTION:-> we need to sent a BEFORE REVIEW SUBMIT reminder $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 reminder - $mailable = ReviewRemindAuto::class; } - } - } 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 reminder - if ($numDaysAfterReviewSubmitReminderDue && - $currentDate->gt($dateDue) && - $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { - - // ACTION:-> we need to send AFTER REVIEW SUBMIT reminder - $mailable = ReviewRemindAuto::class; + } else { + // There has been already sent a reminder after responding to REVIEW REQUEST + // need to check should we sent a AFTER REVIEW SUBMIT reminder - } else if ( $numDaysBeforeReviewSubmitReminderDue && - $dateDue->gt($currentDate) && - $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { - - // ACTION:-> we need to send BEFORE REVIEW SUBMIT reminder - $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 reminder - $mailable = ReviewResponseRemindAuto::class; + $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); - } else if ( $numDaysBeforeReviewResponseReminderDue && - $dateResponseDue->gt($currentDate) && - $dateResponseDue->diffInDays($currentDate) <= $numDaysBeforeReviewResponseReminderDue) { + if ($numDaysAfterReviewSubmitReminderDue && + $currentDate->gt($dateDue) && + $dateReminded->lt($dateDue) && + $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { - // ACTION:-> we need to send BEFORE REVIEW REQUEST RESPONSE reminder - $mailable = ReviewResponseRemindAuto::class; + // ACTION:-> we need to send AFTER REVIEW SUBMIT reminder + $mailable = ReviewRemindAuto::class; + } } } diff --git a/jobs/email/ReviewReminder.php b/jobs/email/ReviewReminder.php index 40ca0522c00..cc0c03d036c 100644 --- a/jobs/email/ReviewReminder.php +++ b/jobs/email/ReviewReminder.php @@ -30,12 +30,12 @@ class ReviewReminder extends BaseJob { - protected int $reviewAssignmentId; - protected int $submissionId; - protected int $contextId; - protected string $mailableClass; - - public function __construct(int $reviewAssignmentId, int $submissionId, int $contextId, string $mailableClass) + public function __construct( + protected int $reviewAssignmentId, + protected int $submissionId, + protected int $contextId, + protected string $mailableClass + ) { parent::__construct(); From b5f216f92c6738526a3ee263de23fe83883a69b9 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Sun, 18 Feb 2024 13:03:29 +0600 Subject: [PATCH 05/23] pkp/pkp-lib#5885 removed unnecessary select from migration --- .../upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php index 8cf288225f1..9819ec1f977 100644 --- a/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php +++ b/classes/migration/upgrade/v3_5_0/I5885_RenameReviewReminderSettingsName.php @@ -45,14 +45,12 @@ public function up(): void 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' From 9534d0b1d7908e784d165a77a4b31e5bae36bade Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Wed, 28 Feb 2024 20:44:52 +0600 Subject: [PATCH 06/23] pkp/pkp-lib#5885 updated and added new from component to handle reminder --- classes/components/forms/FieldCheckbox.php | 67 ++++++++++ classes/components/forms/FieldRangeSlider.php | 121 ++++++++++++++++++ .../forms/context/PKPReviewSetupForm.php | 1 - classes/task/ReviewReminder.php | 18 ++- js/load.js | 2 + locale/en/manager.po | 40 +++--- schemas/context.json | 24 ++++ 7 files changed, 249 insertions(+), 24 deletions(-) create mode 100644 classes/components/forms/FieldCheckbox.php create mode 100644 classes/components/forms/FieldRangeSlider.php diff --git a/classes/components/forms/FieldCheckbox.php b/classes/components/forms/FieldCheckbox.php new file mode 100644 index 00000000000..e6db5550789 --- /dev/null +++ b/classes/components/forms/FieldCheckbox.php @@ -0,0 +1,67 @@ + $this->viewAsButton, + 'disable' => $this->disable, + 'checkedLabel' => $this->checkedLabel, + 'uncheckedLabel' => $this->uncheckedLabel, + 'options' => $this->options, + ]); + return $config; + } +} diff --git a/classes/components/forms/FieldRangeSlider.php b/classes/components/forms/FieldRangeSlider.php new file mode 100644 index 00000000000..4322506017f --- /dev/null +++ b/classes/components/forms/FieldRangeSlider.php @@ -0,0 +1,121 @@ +size, static::RANGE_SLIDER_SIZES)) { + throw new Exception( + sprintf( + 'Invalid size %s give, must be among [%s]', + $this->size, + implode(static::RANGE_SLIDER_SIZES) + ) + ); + } + + if (!in_array($this->valuePositionInUpdateLabel, static::RANGE_SLIDER_VALUE_POSITION_IN_UPDATE_LABEL)) { + throw new Exception( + sprintf( + 'Invalid value position %s give, must be among [%s]', + $this->valuePositionInUpdateLabel, + implode(static::RANGE_SLIDER_VALUE_POSITION_IN_UPDATE_LABEL) + ) + ); + } + + $config = parent::getConfig(); + + return array_merge($config, [ + 'disable' => $this->disable, + 'options' => $this->options, + 'max' => $this->max, + 'min' => $this->min, + 'size' => $this->size, + 'step' => $this->step, + 'updateLabel' => $this->updateLabel, + ]); + } +} diff --git a/classes/components/forms/context/PKPReviewSetupForm.php b/classes/components/forms/context/PKPReviewSetupForm.php index 5660321adc5..ab3b70019f0 100644 --- a/classes/components/forms/context/PKPReviewSetupForm.php +++ b/classes/components/forms/context/PKPReviewSetupForm.php @@ -22,7 +22,6 @@ use PKP\components\forms\FieldSlider; use PKP\components\forms\FieldText; use PKP\components\forms\FormComponent; -use PKP\config\Config; use PKP\submission\reviewAssignment\ReviewAssignment; class PKPReviewSetupForm extends FormComponent diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index 4f147b1c62f..a9362cef973 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -71,9 +71,13 @@ public function executeActions(): bool unset($context); $context = $contextDao->getById($submission->getData('contextId')); + $enableBeforeReviewResponseReminder = $context->getData('enableBeforeReviewResponseReminder'); + $enableAfterReviewResponseReminder = $context->getData('enableAfterReviewResponseReminder'); + $enableBeforeReviewSubmitReminder = $context->getData('enableBeforeReviewSubmitReminder'); + $enableAfterReviewSubmitReminder = $context->getData('enableAfterReviewSubmitReminder'); + $numDaysBeforeReviewResponseReminderDue = $context->getData('numDaysBeforeReviewResponseReminderDue'); $numDaysAfterReviewResponseReminderDue = $context->getData('numDaysAfterReviewResponseReminderDue'); - $numDaysBeforeReviewSubmitReminderDue = $context->getData('numDaysBeforeReviewSubmitReminderDue'); $numDaysAfterReviewSubmitReminderDue = $context->getData('numDaysAfterReviewSubmitReminderDue'); } @@ -94,7 +98,8 @@ public function executeActions(): bool if ($reviewAssignment->getDateReminded() === null) { // There has not been any reminder sent yet // need to check should we sent a BEFORE REVIEW REQUEST RESPONSE reminder - if ($numDaysBeforeReviewResponseReminderDue && + if ($enableBeforeReviewResponseReminder && + $numDaysBeforeReviewResponseReminderDue && $dateResponseDue->gt($currentDate) && $dateResponseDue->diffInDays($currentDate) <= $numDaysBeforeReviewResponseReminderDue) { @@ -107,7 +112,8 @@ public function executeActions(): bool $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); - if ($numDaysAfterReviewResponseReminderDue && + if ($enableAfterReviewResponseReminder && + $numDaysAfterReviewResponseReminderDue && $currentDate->gt($dateResponseDue) && $dateReminded->lt($dateResponseDue) && $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { @@ -123,7 +129,8 @@ public function executeActions(): bool if ($reviewAssignment->getDateReminded() === null) { // There has not been any reminder sent after responding to REVIEW REQUEST // no REVIEW SUBMIT reminder has been sent - if ($numDaysBeforeReviewSubmitReminderDue && + if ($enableBeforeReviewSubmitReminder && + $numDaysBeforeReviewSubmitReminderDue && $currentDate->lt($dateDue) && $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { @@ -136,7 +143,8 @@ public function executeActions(): bool $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); - if ($numDaysAfterReviewSubmitReminderDue && + if ($enableAfterReviewSubmitReminder && + $numDaysAfterReviewSubmitReminderDue && $currentDate->gt($dateDue) && $dateReminded->lt($dateDue) && $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { diff --git a/js/load.js b/js/load.js index 4437b860198..3ffa2c37a1e 100644 --- a/js/load.js +++ b/js/load.js @@ -176,6 +176,8 @@ VueRegistry.registerComponent('PkpFieldHtml', FieldHtml); VueRegistry.registerComponent('PkpFieldOrcid', FieldOrcid); VueRegistry.registerComponent('PkpFieldMetadataSetting', FieldMetadataSetting); VueRegistry.registerComponent('PkpFieldOptions', FieldOptions); +VueRegistry.registerComponent('PkpFieldCheckbox', FieldCheckbox); +VueRegistry.registerComponent('PkpFieldRangeSlider', FieldRangeSlider); VueRegistry.registerComponent('PkpFieldPreparedContent', FieldPreparedContent); VueRegistry.registerComponent('PkpFieldPubId', FieldPubId); VueRegistry.registerComponent('PkpFieldRadioInput', FieldRadioInput); diff --git a/locale/en/manager.po b/locale/en/manager.po index 0a3f8ed4926..ba7cbad7d2e 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1238,33 +1238,37 @@ msgstr "Never Remind" msgid "manager.setup.reviewOptions.noteOnModification" msgstr "Defaults can be modified for each review during the editorial process." +msgid "manager.setup.reviewOptions.reminders" +msgstr "Set Reminders for Review" + +msgid "manager.setup.reviewOptions.reminders.description" +msgstr "" +"Send an email reminder before or after for review request response(if reviewer has not responded " +"to review request yet) or review submission (if reviewer has not submitted review yet)" + msgid "manager.setup.reviewOptions.reminders.response" 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 " -"within this many days (left empty if no reminder):" +msgid "manager.setup.reviewOptions.reminders.description.before" +msgstr "Before due date" -msgid "manager.setup.reviewOptions.reminders.response.description.before" -msgstr "- before the response due date:" +msgid "manager.setup.reviewOptions.reminders.description.before.days" +msgstr "days before" -msgid "manager.setup.reviewOptions.reminders.response.description.after" -msgstr "- after the response due date:" +msgid "manager.setup.reviewOptions.reminders.description.after" +msgstr "After due date" -msgid "manager.setup.reviewOptions.reminders.submit" -msgstr "Review Reminder" +msgid "manager.setup.reviewOptions.reminders.description.after.days" +msgstr "days after" -msgid "manager.setup.reviewOptions.reminders.submit.description" -msgstr "" -"Send an email reminder if a reviewer has not submitted a recommendation " -"within this many days (left empty if no reminder)" +msgid "manager.setup.reviewOptions.reminders.disable" +msgstr "Disbale Reminder" -msgid "manager.setup.reviewOptions.reminders.submit.description.before" -msgstr "- before the review due date:" +msgid "manager.setup.reviewOptions.reminders.enable" +msgstr "Enable Reminder" -msgid "manager.setup.reviewOptions.reminders.submit.description.after" -msgstr "- after the review due date:" +msgid "manager.setup.reviewOptions.reminders.submit" +msgstr "Review Reminder" msgid "manager.setup.reviewOptions.reviewMode" msgstr "Default Review Mode" diff --git a/schemas/context.json b/schemas/context.json index b21cf817216..3df89da2b69 100644 --- a/schemas/context.json +++ b/schemas/context.json @@ -548,6 +548,30 @@ "min:0" ] }, + "enableBeforeReviewResponseReminder": { + "type": "boolean", + "validation": [ + "nullable" + ] + }, + "enableAfterReviewResponseReminder": { + "type": "boolean", + "validation": [ + "nullable" + ] + }, + "enableBeforeReviewSubmitReminder": { + "type": "boolean", + "validation": [ + "nullable" + ] + }, + "enableAfterReviewSubmitReminder": { + "type": "boolean", + "validation": [ + "nullable" + ] + }, "numPageLinks": { "type": "integer", "default": 10, From 10c568441e9970356d9b6c9db5f8592c4f8d5ee3 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 14 Mar 2024 15:25:28 +0600 Subject: [PATCH 07/23] pkp/pkp-lib#5885 removed unnecessary fields and controls with field range component enhancement --- classes/components/forms/FieldCheckbox.php | 67 ------------------- classes/components/forms/FieldRangeSlider.php | 66 ++++++++++++++++-- classes/task/ReviewReminder.php | 17 ++--- js/load.js | 1 - schemas/context.json | 24 ------- 5 files changed, 63 insertions(+), 112 deletions(-) delete mode 100644 classes/components/forms/FieldCheckbox.php diff --git a/classes/components/forms/FieldCheckbox.php b/classes/components/forms/FieldCheckbox.php deleted file mode 100644 index e6db5550789..00000000000 --- a/classes/components/forms/FieldCheckbox.php +++ /dev/null @@ -1,67 +0,0 @@ - $this->viewAsButton, - 'disable' => $this->disable, - 'checkedLabel' => $this->checkedLabel, - 'uncheckedLabel' => $this->uncheckedLabel, - 'options' => $this->options, - ]); - return $config; - } -} diff --git a/classes/components/forms/FieldRangeSlider.php b/classes/components/forms/FieldRangeSlider.php index 4322506017f..b807b888bd9 100644 --- a/classes/components/forms/FieldRangeSlider.php +++ b/classes/components/forms/FieldRangeSlider.php @@ -32,6 +32,13 @@ class FieldRangeSlider extends Field */ public $disable = false; + /** + * Should the range slider be disable if input field value set to NULL on render + * + * @var bool + */ + public $disableOnNull = true; + /** * Range min value * @@ -53,6 +60,13 @@ class FieldRangeSlider extends Field */ public $step = 1; + /** + * Range value on reset + * + * @var int|float|null + */ + public $onResetValue; + /** * Range input size * @@ -81,6 +95,27 @@ class FieldRangeSlider extends Field */ public $valuePositionInUpdateLabel = 'before'; + /** + * Allow the slider range disbale/enable ability + * + * @var bool + */ + public $allowStateToggle = true; + + /** + * Show the label text when range slider is disable + * + * @var string + */ + public $controlLabelOnDisable; + + /** + * Show the label text when range slider is disable + * + * @var string + */ + public $controlLabelOnEnable; + /** * @copydoc Field::getConfig() */ @@ -108,14 +143,31 @@ public function getConfig() $config = parent::getConfig(); + if (!isset($this->controlLabelOnDisable)) { + $this->controlLabelOnDisable = __('common.enable'); + } + + if (!isset($this->controlLabelOnEnable)) { + $this->controlLabelOnEnable = __('common.disable'); + } + + if (!isset($this->onResetValue)) { + $this->onResetValue = $this->value ?? $this->default ?? null; + } + return array_merge($config, [ - 'disable' => $this->disable, - 'options' => $this->options, - 'max' => $this->max, - 'min' => $this->min, - 'size' => $this->size, - 'step' => $this->step, - 'updateLabel' => $this->updateLabel, + 'disable' => $this->disable, + 'disableOnNull' => $this->disableOnNull, + 'options' => $this->options, + 'max' => $this->max, + 'min' => $this->min, + 'size' => $this->size, + 'step' => $this->step, + 'onResetValue' => $this->onResetValue, + 'updateLabel' => $this->updateLabel, + 'allowStateToggle' => $this->allowStateToggle, + 'controlLabelOnDisable' => $this->controlLabelOnDisable, + 'controlLabelOnEnable' => $this->controlLabelOnEnable, ]); } } diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index a9362cef973..f1fa606d081 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -71,11 +71,6 @@ public function executeActions(): bool unset($context); $context = $contextDao->getById($submission->getData('contextId')); - $enableBeforeReviewResponseReminder = $context->getData('enableBeforeReviewResponseReminder'); - $enableAfterReviewResponseReminder = $context->getData('enableAfterReviewResponseReminder'); - $enableBeforeReviewSubmitReminder = $context->getData('enableBeforeReviewSubmitReminder'); - $enableAfterReviewSubmitReminder = $context->getData('enableAfterReviewSubmitReminder'); - $numDaysBeforeReviewResponseReminderDue = $context->getData('numDaysBeforeReviewResponseReminderDue'); $numDaysAfterReviewResponseReminderDue = $context->getData('numDaysAfterReviewResponseReminderDue'); $numDaysBeforeReviewSubmitReminderDue = $context->getData('numDaysBeforeReviewSubmitReminderDue'); @@ -98,8 +93,7 @@ public function executeActions(): bool if ($reviewAssignment->getDateReminded() === null) { // There has not been any reminder sent yet // need to check should we sent a BEFORE REVIEW REQUEST RESPONSE reminder - if ($enableBeforeReviewResponseReminder && - $numDaysBeforeReviewResponseReminderDue && + if ($numDaysBeforeReviewResponseReminderDue && $dateResponseDue->gt($currentDate) && $dateResponseDue->diffInDays($currentDate) <= $numDaysBeforeReviewResponseReminderDue) { @@ -112,8 +106,7 @@ public function executeActions(): bool $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); - if ($enableAfterReviewResponseReminder && - $numDaysAfterReviewResponseReminderDue && + if ($numDaysAfterReviewResponseReminderDue && $currentDate->gt($dateResponseDue) && $dateReminded->lt($dateResponseDue) && $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { @@ -129,8 +122,7 @@ public function executeActions(): bool if ($reviewAssignment->getDateReminded() === null) { // There has not been any reminder sent after responding to REVIEW REQUEST // no REVIEW SUBMIT reminder has been sent - if ($enableBeforeReviewSubmitReminder && - $numDaysBeforeReviewSubmitReminderDue && + if ($numDaysBeforeReviewSubmitReminderDue && $currentDate->lt($dateDue) && $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { @@ -143,8 +135,7 @@ public function executeActions(): bool $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); - if ($enableAfterReviewSubmitReminder && - $numDaysAfterReviewSubmitReminderDue && + if ($numDaysAfterReviewSubmitReminderDue && $currentDate->gt($dateDue) && $dateReminded->lt($dateDue) && $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { diff --git a/js/load.js b/js/load.js index 3ffa2c37a1e..5a91fbd3e86 100644 --- a/js/load.js +++ b/js/load.js @@ -176,7 +176,6 @@ VueRegistry.registerComponent('PkpFieldHtml', FieldHtml); VueRegistry.registerComponent('PkpFieldOrcid', FieldOrcid); VueRegistry.registerComponent('PkpFieldMetadataSetting', FieldMetadataSetting); VueRegistry.registerComponent('PkpFieldOptions', FieldOptions); -VueRegistry.registerComponent('PkpFieldCheckbox', FieldCheckbox); VueRegistry.registerComponent('PkpFieldRangeSlider', FieldRangeSlider); VueRegistry.registerComponent('PkpFieldPreparedContent', FieldPreparedContent); VueRegistry.registerComponent('PkpFieldPubId', FieldPubId); diff --git a/schemas/context.json b/schemas/context.json index 3df89da2b69..b21cf817216 100644 --- a/schemas/context.json +++ b/schemas/context.json @@ -548,30 +548,6 @@ "min:0" ] }, - "enableBeforeReviewResponseReminder": { - "type": "boolean", - "validation": [ - "nullable" - ] - }, - "enableAfterReviewResponseReminder": { - "type": "boolean", - "validation": [ - "nullable" - ] - }, - "enableBeforeReviewSubmitReminder": { - "type": "boolean", - "validation": [ - "nullable" - ] - }, - "enableAfterReviewSubmitReminder": { - "type": "boolean", - "validation": [ - "nullable" - ] - }, "numPageLinks": { "type": "integer", "default": 10, From 1c252e10c77b0049f43438a88b231a927e9a7041 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Mon, 15 Apr 2024 15:59:19 +0600 Subject: [PATCH 08/23] pkp/pkp-lib#5885 updated using new slider component --- classes/components/forms/FieldRangeSlider.php | 173 ------------------ classes/components/forms/FieldSlider.php | 15 +- classes/task/ReviewReminder.php | 17 +- js/load.js | 1 - locale/en/manager.po | 32 ++-- 5 files changed, 27 insertions(+), 211 deletions(-) delete mode 100644 classes/components/forms/FieldRangeSlider.php diff --git a/classes/components/forms/FieldRangeSlider.php b/classes/components/forms/FieldRangeSlider.php deleted file mode 100644 index b807b888bd9..00000000000 --- a/classes/components/forms/FieldRangeSlider.php +++ /dev/null @@ -1,173 +0,0 @@ -size, static::RANGE_SLIDER_SIZES)) { - throw new Exception( - sprintf( - 'Invalid size %s give, must be among [%s]', - $this->size, - implode(static::RANGE_SLIDER_SIZES) - ) - ); - } - - if (!in_array($this->valuePositionInUpdateLabel, static::RANGE_SLIDER_VALUE_POSITION_IN_UPDATE_LABEL)) { - throw new Exception( - sprintf( - 'Invalid value position %s give, must be among [%s]', - $this->valuePositionInUpdateLabel, - implode(static::RANGE_SLIDER_VALUE_POSITION_IN_UPDATE_LABEL) - ) - ); - } - - $config = parent::getConfig(); - - if (!isset($this->controlLabelOnDisable)) { - $this->controlLabelOnDisable = __('common.enable'); - } - - if (!isset($this->controlLabelOnEnable)) { - $this->controlLabelOnEnable = __('common.disable'); - } - - if (!isset($this->onResetValue)) { - $this->onResetValue = $this->value ?? $this->default ?? null; - } - - return array_merge($config, [ - 'disable' => $this->disable, - 'disableOnNull' => $this->disableOnNull, - 'options' => $this->options, - 'max' => $this->max, - 'min' => $this->min, - 'size' => $this->size, - 'step' => $this->step, - 'onResetValue' => $this->onResetValue, - 'updateLabel' => $this->updateLabel, - 'allowStateToggle' => $this->allowStateToggle, - 'controlLabelOnDisable' => $this->controlLabelOnDisable, - 'controlLabelOnEnable' => $this->controlLabelOnEnable, - ]); - } -} diff --git a/classes/components/forms/FieldSlider.php b/classes/components/forms/FieldSlider.php index c8299c5bb7e..a30489a757c 100644 --- a/classes/components/forms/FieldSlider.php +++ b/classes/components/forms/FieldSlider.php @@ -20,27 +20,20 @@ class FieldSlider extends Field /** @copydoc Field::$component */ public $component = 'field-slider'; - /** * Range min value - * - * @var int|float */ - public $min; + public int|float $min; /** * Range max value - * - * @var int|float */ - public $max; + public int|float $max; /** * Range step value - * - * @var int|float */ - public $step = 1; + public int|float $step = 1; /** * Label for min value, it displays actual value when not present @@ -67,8 +60,6 @@ class FieldSlider extends Field */ public ?string $valueLabelMax = null; - - /** * @copydoc Field::getConfig() */ diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index f1fa606d081..a18f68e7dbd 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -71,10 +71,10 @@ public function executeActions(): bool unset($context); $context = $contextDao->getById($submission->getData('contextId')); - $numDaysBeforeReviewResponseReminderDue = $context->getData('numDaysBeforeReviewResponseReminderDue'); - $numDaysAfterReviewResponseReminderDue = $context->getData('numDaysAfterReviewResponseReminderDue'); - $numDaysBeforeReviewSubmitReminderDue = $context->getData('numDaysBeforeReviewSubmitReminderDue'); - $numDaysAfterReviewSubmitReminderDue = $context->getData('numDaysAfterReviewSubmitReminderDue'); + $numDaysBeforeReviewResponseReminderDue = (int) $context->getData('numDaysBeforeReviewResponseReminderDue'); + $numDaysAfterReviewResponseReminderDue = (int) $context->getData('numDaysAfterReviewResponseReminderDue'); + $numDaysBeforeReviewSubmitReminderDue = (int) $context->getData('numDaysBeforeReviewSubmitReminderDue'); + $numDaysAfterReviewSubmitReminderDue = (int) $context->getData('numDaysAfterReviewSubmitReminderDue'); } $mailable = null; @@ -85,7 +85,6 @@ public function executeActions(): bool // after a REVIEW REQUEST has been responded, the value of `dateReminded` and `reminderWasAutomatic` // get reset, see \PKP\submission\reviewer\ReviewerAction::confirmReview. - // if ($reviewAssignment->getDateConfirmed() === null) { // REVIEW REQUEST has not been responded // only need to concern with BEFORE/AFTER REVIEW REQUEST RESPONSE reminder @@ -93,7 +92,7 @@ public function executeActions(): bool if ($reviewAssignment->getDateReminded() === null) { // There has not been any reminder sent yet // need to check should we sent a BEFORE REVIEW REQUEST RESPONSE reminder - if ($numDaysBeforeReviewResponseReminderDue && + if ($numDaysBeforeReviewResponseReminderDue > 0 && $dateResponseDue->gt($currentDate) && $dateResponseDue->diffInDays($currentDate) <= $numDaysBeforeReviewResponseReminderDue) { @@ -106,7 +105,7 @@ public function executeActions(): bool $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); - if ($numDaysAfterReviewResponseReminderDue && + if ($numDaysAfterReviewResponseReminderDue > 0 && $currentDate->gt($dateResponseDue) && $dateReminded->lt($dateResponseDue) && $currentDate->diffInDays($dateResponseDue) >= $numDaysAfterReviewResponseReminderDue) { @@ -122,7 +121,7 @@ public function executeActions(): bool if ($reviewAssignment->getDateReminded() === null) { // There has not been any reminder sent after responding to REVIEW REQUEST // no REVIEW SUBMIT reminder has been sent - if ($numDaysBeforeReviewSubmitReminderDue && + if ($numDaysBeforeReviewSubmitReminderDue > 0 && $currentDate->lt($dateDue) && $dateDue->diffInDays($currentDate) <= $numDaysBeforeReviewSubmitReminderDue) { @@ -135,7 +134,7 @@ public function executeActions(): bool $dateReminded = Carbon::parse($reviewAssignment->getDateReminded()); - if ($numDaysAfterReviewSubmitReminderDue && + if ($numDaysAfterReviewSubmitReminderDue > 0 && $currentDate->gt($dateDue) && $dateReminded->lt($dateDue) && $currentDate->diffInDays($dateDue) >= $numDaysAfterReviewSubmitReminderDue) { diff --git a/js/load.js b/js/load.js index 5a91fbd3e86..4437b860198 100644 --- a/js/load.js +++ b/js/load.js @@ -176,7 +176,6 @@ VueRegistry.registerComponent('PkpFieldHtml', FieldHtml); VueRegistry.registerComponent('PkpFieldOrcid', FieldOrcid); VueRegistry.registerComponent('PkpFieldMetadataSetting', FieldMetadataSetting); VueRegistry.registerComponent('PkpFieldOptions', FieldOptions); -VueRegistry.registerComponent('PkpFieldRangeSlider', FieldRangeSlider); VueRegistry.registerComponent('PkpFieldPreparedContent', FieldPreparedContent); VueRegistry.registerComponent('PkpFieldPubId', FieldPubId); VueRegistry.registerComponent('PkpFieldRadioInput', FieldRadioInput); diff --git a/locale/en/manager.po b/locale/en/manager.po index ba7cbad7d2e..01758b2fcec 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1246,29 +1246,29 @@ msgstr "" "Send an email reminder before or after for review request response(if reviewer has not responded " "to review request yet) or review submission (if reviewer has not submitted review yet)" -msgid "manager.setup.reviewOptions.reminders.response" -msgstr "Response Reminder" +msgid "manager.setup.reviewOptions.reminders.response.before" +msgstr "Response Request Response - Before Due Date" -msgid "manager.setup.reviewOptions.reminders.description.before" -msgstr "Before due date" +msgid "manager.setup.reviewOptions.reminders.response.after" +msgstr "Response Request Response - After Due Date" -msgid "manager.setup.reviewOptions.reminders.description.before.days" -msgstr "days before" +msgid "manager.setup.reviewOptions.reminders.submit.before" +msgstr "Review Submission - Before Due Date" -msgid "manager.setup.reviewOptions.reminders.description.after" -msgstr "After due date" +msgid "manager.setup.reviewOptions.reminders.submit.after" +msgstr "Review Submission - After Due Date" -msgid "manager.setup.reviewOptions.reminders.description.after.days" -msgstr "days after" +msgid "manager.setup.reviewOptions.reminders.label.before.days" +msgstr "{$value} days before due date" -msgid "manager.setup.reviewOptions.reminders.disable" -msgstr "Disbale Reminder" +msgid "manager.setup.reviewOptions.reminders.label.after.days" +msgstr "{$value} days after due date" -msgid "manager.setup.reviewOptions.reminders.enable" -msgstr "Enable Reminder" +msgid "manager.setup.reviewOptions.reminders.min.label" +msgstr "None" -msgid "manager.setup.reviewOptions.reminders.submit" -msgstr "Review Reminder" +msgid "manager.setup.reviewOptions.reminders.disbale.label" +msgstr "No reminder set" msgid "manager.setup.reviewOptions.reviewMode" msgstr "Default Review Mode" From 9eabe38367ceff7596fc144904b648aa65fa013a Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Fri, 19 Apr 2024 17:27:02 +0600 Subject: [PATCH 09/23] pkp/pkp-lib#5885 updated job class properties to public --- jobs/email/ReviewReminder.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jobs/email/ReviewReminder.php b/jobs/email/ReviewReminder.php index cc0c03d036c..76a0e057e30 100644 --- a/jobs/email/ReviewReminder.php +++ b/jobs/email/ReviewReminder.php @@ -31,10 +31,10 @@ class ReviewReminder extends BaseJob { public function __construct( - protected int $reviewAssignmentId, - protected int $submissionId, - protected int $contextId, - protected string $mailableClass + public int $reviewAssignmentId, + public int $submissionId, + public int $contextId, + public string $mailableClass ) { parent::__construct(); From 88250e1876f5a6204d7fd1f9bdf01ee69e9a402e Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Mon, 22 Apr 2024 17:28:42 +0600 Subject: [PATCH 10/23] pkp/pkp-lib#5885 updated date validation rules to enum --- .../validation/FormValidatorDateCompare.php | 10 +-- classes/task/ReviewReminder.php | 7 +- .../validation/ValidatorDateComparison.php | 72 +++++-------------- .../validation/enums/DateComparisonRule.php | 27 +++++++ .../users/reviewer/form/EditReviewForm.php | 2 +- .../grid/users/reviewer/form/ReviewerForm.php | 6 +- jobs/email/ReviewReminder.php | 17 +---- locale/en/editor.po | 2 +- 8 files changed, 59 insertions(+), 84 deletions(-) create mode 100644 classes/validation/enums/DateComparisonRule.php diff --git a/classes/form/validation/FormValidatorDateCompare.php b/classes/form/validation/FormValidatorDateCompare.php index 643ad87438c..186bbb06f3e 100644 --- a/classes/form/validation/FormValidatorDateCompare.php +++ b/classes/form/validation/FormValidatorDateCompare.php @@ -3,8 +3,8 @@ /** * @file classes/form/validation/FormValidatorDateCompare.php * - * Copyright (c) 2014-2021 Simon Fraser University - * Copyright (c) 2000-2021 John Willinsky + * Copyright (c) 2014-2024 Simon Fraser University + * Copyright (c) 2000-2024 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class FormValidatorDateCompare @@ -18,7 +18,9 @@ namespace PKP\form\validation; +use PKP\form\Form; use PKP\validation\ValidatorDateComparison; +use PKP\validation\enums\DateComparisonRule; use Carbon\Carbon; use DateTimeInterface; @@ -27,10 +29,10 @@ class FormValidatorDateCompare extends FormValidator /** * Constructor. * - * @param \PKP\form\Form $form the associated form + * @param Form $form the associated form * @param string $field the name of the associated field * @param DateTimeInterface|Carbon $comparingDate the comparing date - * @param string $comparingRule the comparing rule e.g. equal/greater/lesser/... + * @param DateComparisonRule $comparingRule the comparing rule * @param string $type the type of check, either "required" or "optional" * @param string $message the error message for validation failures (i18n key) */ diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index a18f68e7dbd..10c7e1e7546 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -146,12 +146,7 @@ public function executeActions(): bool } if ($mailable) { - ReviewReminderJob::dispatch( - $reviewAssignment->getId(), - $submission->getId(), - $context->getId(), - $mailable - ); + ReviewReminderJob::dispatch($reviewAssignment->getId(), $mailable); } } diff --git a/classes/validation/ValidatorDateComparison.php b/classes/validation/ValidatorDateComparison.php index ac63bf4e6de..c5e2c2f7bf0 100644 --- a/classes/validation/ValidatorDateComparison.php +++ b/classes/validation/ValidatorDateComparison.php @@ -3,8 +3,8 @@ /** * @file classes/validation/ValidatorDateComparison.php * - * Copyright (c) 2014-2021 Simon Fraser University - * Copyright (c) 2000-2021 John Willinsky + * Copyright (c) 2014-2024 Simon Fraser University + * Copyright (c) 2000-2024 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class ValidatorDateComparison @@ -20,54 +20,25 @@ use Carbon\Carbon; use DateTimeInterface; -use Exception; +use PKP\validation\enums\DateComparisonRule; use PKP\validation\ValidatorFactory; class ValidatorDateComparison extends Validator { - public const DATE_COMPARE_RULE_EQUAL = 'equal'; - public const DATE_COMPARE_RULE_GREATER = 'greater'; - public const DATE_COMPARE_RULE_LESSER = 'lesser'; - public const DATE_COMPARE_RULE_GREATER_OR_EQUAL = 'greaterOrEqual'; - public const DATE_COMPARE_RULE_LESSER_OR_EQUAL = 'lesserOrEqual'; - - protected DateTimeInterface|Carbon $comparingDate; - - protected string $comparingRule; - - protected array $validationRulesMapping = [ - self::DATE_COMPARE_RULE_EQUAL => '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 + /** + * Constructor. + * + * @param DateTimeInterface|Carbon $comparingDate the comparing date + * @param DateComparisonRule $rule the comparing rule + */ + public function __construct( + protected DateTimeInterface|Carbon $comparingDate, + protected DateComparisonRule $rule + ) { - 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 , - ]; + $this->comparingDate = $comparingDate instanceof Carbon + ? $comparingDate + : Carbon::parse($comparingDate); } /** @@ -79,19 +50,10 @@ public function isValid($value) ['value' => $value], ['value' => [ 'date', - $this->getValidationApplicableRule($this->comparingRule) . ':' . $this->comparingDate->toDateString() + $this->rule->value . ':' . $this->comparingDate->toDateString() ]] ); return $validator->passes(); } - - protected function getValidationApplicableRule(string $rule): mixed - { - return $this->validationRulesMapping[$rule]; - } -} - -if (!PKP_STRICT_MODE) { - class_alias('\PKP\validation\ValidatorDateComparison', '\ValidatorDateComparison'); } diff --git a/classes/validation/enums/DateComparisonRule.php b/classes/validation/enums/DateComparisonRule.php new file mode 100644 index 00000000000..79388ae6a9b --- /dev/null +++ b/classes/validation/enums/DateComparisonRule.php @@ -0,0 +1,27 @@ +getRequest()->getUserVar('responseDueDate')), - \PKP\validation\ValidatorDateComparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, + \PKP\validation\enums\DateComparisonRule::GREATER_OR_EQUAL, 'optional', 'editor.review.errorAddingReviewer.dateValidationFailed' ) diff --git a/controllers/grid/users/reviewer/form/ReviewerForm.php b/controllers/grid/users/reviewer/form/ReviewerForm.php index bdfef86771e..031b0ccc525 100644 --- a/controllers/grid/users/reviewer/form/ReviewerForm.php +++ b/controllers/grid/users/reviewer/form/ReviewerForm.php @@ -77,9 +77,9 @@ public function __construct($submission, $reviewRound) new \PKP\form\validation\FormValidatorDateCompare( $this, 'reviewDueDate', - \Carbon\Carbon::parse($this->getData('responseDueDate')), - \PKP\validation\ValidatorDateComparison::DATE_COMPARE_RULE_GREATER_OR_EQUAL, - 'optional', + \Carbon\Carbon::parse(Application::get()->getRequest()->getUserVar('responseDueDate')), + \PKP\validation\enums\DateComparisonRule::GREATER_OR_EQUAL, + 'required', 'editor.review.errorAddingReviewer.dateValidationFailed' ) ); diff --git a/jobs/email/ReviewReminder.php b/jobs/email/ReviewReminder.php index 76a0e057e30..07a5499255f 100644 --- a/jobs/email/ReviewReminder.php +++ b/jobs/email/ReviewReminder.php @@ -27,22 +27,11 @@ use PKP\mail\mailables\ReviewRemindAuto; use PKP\jobs\BaseJob; - class ReviewReminder extends BaseJob { - public function __construct( - public int $reviewAssignmentId, - public int $submissionId, - public int $contextId, - public string $mailableClass - ) + public function __construct(public int $reviewAssignmentId, public string $mailableClass) { parent::__construct(); - - $this->reviewAssignmentId = $reviewAssignmentId; - $this->submissionId = $submissionId; - $this->contextId = $contextId; - $this->mailableClass = $mailableClass; } /** @@ -57,10 +46,10 @@ public function handle(): void return; } - $submission = Repo::submission()->get($this->submissionId); + $submission = Repo::submission()->get($reviewAssignment->getData('submissionId')); $contextService = Services::get('context'); - $context = $contextService->get($this->contextId); + $context = $contextService->get($submission->getData('contextId')); /** @var ReviewRemindAuto|ReviewResponseRemindAuto $mailable */ $mailable = new $this->mailableClass($context, $submission, $reviewAssignment); diff --git a/locale/en/editor.po b/locale/en/editor.po index 4e7e0240b33..bb71149fe06 100644 --- a/locale/en/editor.po +++ b/locale/en/editor.po @@ -206,7 +206,7 @@ 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." +msgstr "Review due date must be greater or equal to response due date." msgid "editor.review.uploadRevision" msgstr "Upload Revision" From 7644c86578f3d50c7cc85190fc1af83ce6b3f4ea Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Tue, 23 Apr 2024 13:20:04 +0600 Subject: [PATCH 11/23] pkp/pkp-lib#5885 fixed displaying error on date comparison validation failure --- .../grid/users/reviewer/PKPReviewerGridHandler.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php b/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php index 4e4d2418b7f..5bffcaa653b 100644 --- a/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php +++ b/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php @@ -409,15 +409,16 @@ public function updateReviewer($args, $request) // Form handling $reviewerForm = new $formClassName($this->getSubmission(), $this->getReviewRound()); $reviewerForm->readInputData(); + if ($reviewerForm->validate()) { $reviewAssignment = $reviewerForm->execute(); $json = DAO::getDataChangedEvent($reviewAssignment->getId()); $json->setGlobalEvent('update:decisions'); return $json; - } else { - // There was an error, redisplay the form - return new JSONMessage(true, $reviewerForm->fetch($request)); } + + // There was an error, redisplay the form + return new JSONMessage(false); } /** From 3c6e1568eaa7724df67f3287f69fd200923e2b46 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Wed, 24 Apr 2024 17:47:48 +0600 Subject: [PATCH 12/23] pkp/pkp-lib#5885 test added for queue job --- classes/task/ReviewReminder.php | 2 +- jobs/email/ReviewReminder.php | 12 +- tests/PKPTestCase.php | 5 +- tests/jobs/email/ReviewReminderTest.php | 168 ++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 tests/jobs/email/ReviewReminderTest.php diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index 10c7e1e7546..ce5fb7a5622 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -146,7 +146,7 @@ public function executeActions(): bool } if ($mailable) { - ReviewReminderJob::dispatch($reviewAssignment->getId(), $mailable); + ReviewReminderJob::dispatch($context->getId(), $reviewAssignment->getId(), $mailable); } } diff --git a/jobs/email/ReviewReminder.php b/jobs/email/ReviewReminder.php index 07a5499255f..dd861b6aff1 100644 --- a/jobs/email/ReviewReminder.php +++ b/jobs/email/ReviewReminder.php @@ -29,7 +29,11 @@ class ReviewReminder extends BaseJob { - public function __construct(public int $reviewAssignmentId, public string $mailableClass) + public function __construct( + public int $contextId, + public int $reviewAssignmentId, + public string $mailableClass + ) { parent::__construct(); } @@ -41,7 +45,7 @@ public function handle(): void { $reviewAssignment = Repo::reviewAssignment()->get($this->reviewAssignmentId); $reviewer = Repo::user()->get($reviewAssignment->getReviewerId()); - + if (!isset($reviewer)) { return; } @@ -49,7 +53,7 @@ public function handle(): void $submission = Repo::submission()->get($reviewAssignment->getData('submissionId')); $contextService = Services::get('context'); - $context = $contextService->get($submission->getData('contextId')); + $context = $contextService->get($this->contextId); /** @var ReviewRemindAuto|ReviewResponseRemindAuto $mailable */ $mailable = new $this->mailableClass($context, $submission, $reviewAssignment); @@ -59,6 +63,7 @@ public function handle(): void $context->getId(), $mailable::getEmailTemplateKey() ); + $mailable->subject($emailTemplate->getLocalizedData('subject', $primaryLocale)) ->body($emailTemplate->getLocalizedData('body', $primaryLocale)) ->from($context->getData('contactEmail'), $context->getData('contactName')) @@ -67,6 +72,7 @@ public function handle(): void $mailable->setData($primaryLocale); $reviewerAccessKeysEnabled = $context->getData('reviewerAccessKeysEnabled'); + if ($reviewerAccessKeysEnabled) { // Give one-click access if enabled $reviewInvitation = new ReviewerAccessInvite( $reviewAssignment->getReviewerId(), diff --git a/tests/PKPTestCase.php b/tests/PKPTestCase.php index daa08d13cb1..f17b6adc4d3 100644 --- a/tests/PKPTestCase.php +++ b/tests/PKPTestCase.php @@ -181,7 +181,7 @@ protected function setTestConfiguration($config, $configPath = 'config') * * @return Request */ - protected function mockRequest($path = 'index/test-page/test-op', $userId = null) + protected function mockRequest(string $path = 'index/test-page/test-op', int $userId = 0) { // Back up the default request. if (!isset($this->registryBackup['request'])) { @@ -205,8 +205,7 @@ protected function mockRequest($path = 'index/test-page/test-op', $userId = null $request->setRouter($router); // Test user. - $session = $request->getSession(); - $session->setUserId($userId); + $request->getSessionGuard()->setUserId($userId); return $request; } diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php new file mode 100644 index 00000000000..8ec7f41e481 --- /dev/null +++ b/tests/jobs/email/ReviewReminderTest.php @@ -0,0 +1,168 @@ +assertInstanceOf(ReviewReminder::class, unserialize($this->serializedJobData)); + } + + /** + * Test job will not fail when no reviewer associated with review assignment + */ + public function testJobWillRunWithIfNoReviewerExists(): void + { + $reviewReminderJob = unserialize($this->serializedJobData); + + $reviewAssignmentMock = Mockery::mock(ReviewAssignment::class) + ->shouldReceive([ + 'getReviewerId' => 0, + 'getData' => 0, + 'getSubmissionId' => 0, + 'getRound' => 0, + 'getReviewMethod' => '', + 'getRecommendation' => '', + 'getReviewerFullName' => '', + 'getId' => 0, + 'getDateResponseDue' => \Carbon\Carbon::now()->format('Y-m-d H:i:s'), + 'getDateAssigned' => \Carbon\Carbon::now()->format('Y-m-d H:i:s'), + 'getDateDue' => \Carbon\Carbon::now()->format('Y-m-d H:i:s'), + ]) + ->withAnyArgs() + ->getMock(); + + app()->instance(ReviewAssignment::class, $reviewAssignmentMock); + + $reviewAssignmentRepoMock = Mockery::mock(app(ReviewAssignmentRepository::class)) + ->makePartial() + ->shouldReceive([ + 'get' => $reviewAssignmentMock, + 'edit' => null, + ]) + ->withAnyArgs() + ->getMock(); + + app()->instance(ReviewAssignmentRepository::class, $reviewAssignmentRepoMock); + + $this->assertNull($reviewReminderJob->handle()); + } + + /** + * Test job will not fail + */ + public function testRunSerializedJob(): void + { + Mail::fake(); + + $reviewReminderJob = unserialize($this->serializedJobData); + + // need to mock request so that a valid context information is set and can be retrived + $contextService = Services::get("context"); + $context = $contextService->get($reviewReminderJob->contextId); + $this->mockRequest($context->getPath() . '/test-page/test-op'); + + $publicationMock = Mockery::mock(\APP\publication\Publication::class) + ->makePartial() + ->shouldReceive('getData') + ->with('authors') + ->andReturn(\Illuminate\Support\LazyCollection::make([new \PKP\author\Author()])) + ->getMock(); + + $submissionMock = Mockery::mock(\APP\submission\Submission::class) + ->makePartial() + ->shouldReceive([ + 'getId' => 0, + 'getData' => 0, + 'getCurrentPublication' => $publicationMock + ]) + ->withAnyArgs() + ->getMock(); + + $submissionRepoMock = Mockery::mock(app(SubmissionRepository::class)) + ->makePartial() + ->shouldReceive('get') + ->withAnyArgs() + ->andReturn($submissionMock) + ->getMock(); + + app()->instance(SubmissionRepository::class, $submissionRepoMock); + + $emailTemplateMock = Mockery::mock(\PKP\emailTemplate\EmailTemplate::class) + ->makePartial() + ->shouldReceive([ + "getLocalizedData" => "" + ]) + ->withAnyArgs() + ->getMock(); + + $emailTemplateRepoMock = Mockery::mock(app(EmailTemplateRepository::class)) + ->makePartial() + ->shouldReceive([ + 'getByKey' => $emailTemplateMock, + ]) + ->withAnyArgs() + ->getMock(); + + app()->instance(EmailTemplateRepository::class, $emailTemplateRepoMock); + + $invitationRepoMock = Mockery::mock(app(InvitationRepository::class)) + ->makePartial() + ->shouldReceive([ + 'addInvitation' => 0, + 'getMailable' => null, + ]) + ->withAnyArgs() + ->getMock(); + + app()->instance(InvitationRepository::class, $invitationRepoMock); + + $eventRepoMock = Mockery::mock(app(EventRepository::class)) + ->makePartial() + ->shouldReceive([ + 'newDataObject' => new \PKP\log\event\EventLogEntry, + 'add' => 0, + ]) + ->withAnyArgs() + ->getMock(); + + app()->instance(EventRepository::class, $eventRepoMock); + + $this->assertNull($reviewReminderJob->handle()); + } +} From 25881f3307b3673c38d4abf9ffce6b7803bd4eef Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Sun, 28 Apr 2024 14:40:32 +0600 Subject: [PATCH 13/23] pkp/pkp-lib#5885 added mocked context service and context class for job testing --- tests/jobs/email/ReviewReminderTest.php | 38 ++++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php index 8ec7f41e481..f834685fd27 100644 --- a/tests/jobs/email/ReviewReminderTest.php +++ b/tests/jobs/email/ReviewReminderTest.php @@ -13,7 +13,6 @@ namespace PKP\tests\classes\core; use Mockery; -use APP\core\Services; use PKP\tests\PKPTestCase; use PKP\jobs\email\ReviewReminder; use Illuminate\Support\Facades\Mail; @@ -88,14 +87,39 @@ public function testJobWillRunWithIfNoReviewerExists(): void */ public function testRunSerializedJob(): void { - Mail::fake(); - - $reviewReminderJob = unserialize($this->serializedJobData); + // Fake the mail facade + Mail::fake(); // need to mock request so that a valid context information is set and can be retrived - $contextService = Services::get("context"); - $context = $contextService->get($reviewReminderJob->contextId); - $this->mockRequest($context->getPath() . '/test-page/test-op'); + $this->mockRequest(); + + // Need to replace the container binding of `context` with a mock object + \APP\core\Services::register( + new class extends \APP\services\OJSServiceProvider + { + public function register(\Pimple\Container $pimple) + { + $pimple['context'] = Mockery::mock(\APP\services\ContextService::class) + ->makePartial() + ->shouldReceive('get') + ->withAnyArgs() + ->andReturn( + // Mock the context(Journal/Press/Server) object + Mockery::mock(\APP\journal\Journal::class) + ->makePartial() + ->shouldReceive([ + 'getPath' => '', + 'getId' => 0, + ]) + ->withAnyArgs() + ->getMock() + ) + ->getMock(); + } + } + ); + + $reviewReminderJob = unserialize($this->serializedJobData); $publicationMock = Mockery::mock(\APP\publication\Publication::class) ->makePartial() From c87ff62841e77def0f19a60c0a49f59a3b66c66e Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 2 May 2024 19:46:03 +0600 Subject: [PATCH 14/23] pkp/pkp-lib#5885 added context checking first if available to resolve context path before resolving from request --- .../invitation/invitations/BaseInvitation.php | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 classes/invitation/invitations/BaseInvitation.php diff --git a/classes/invitation/invitations/BaseInvitation.php b/classes/invitation/invitations/BaseInvitation.php new file mode 100644 index 00000000000..cf2a8248d29 --- /dev/null +++ b/classes/invitation/invitations/BaseInvitation.php @@ -0,0 +1,255 @@ +expirationDate = Carbon::now()->addDays($expiryDays); + $this->className = $this::class; + } + + public function getPayload(): array + { + $values = []; + + $reflection = new ReflectionClass($this->className); + $constructor = $reflection->getConstructor(); + + if ($constructor) { + // If the constructor exists, get its parameters + $parameters = $constructor->getParameters(); + + // Loop through the parameters and get their values from the object + foreach ($parameters as $parameter) { + $propertyName = $parameter->getName(); + $propertyValue = $reflection->getProperty($propertyName)->getValue($this); + $values[$propertyName] = $propertyValue; + } + } + + return $values; + } + + public function markStatus(InvitationStatus $status): void + { + $invitation = Repo::invitation() + ->getByKeyHash($this->keyHash); + + if (is_null($invitation)) { + throw new Exception('This invitation was not found'); + } + + $invitation->markAs($status); + } + + public function acceptHandle(): void + { + $this->markStatus(InvitationStatus::ACCEPTED); + } + public function declineHandle(): void + { + $this->markStatus(InvitationStatus::DECLINED); + } + + abstract public function getMailable(): ?Mailable; + abstract public function preDispatchActions(): bool; + + public function getAcceptUrl(): string + { + $request = Application::get()->getRequest(); + return $request->getDispatcher() + ->url( + $request, + Application::ROUTE_PAGE, + $this->context?->getPath() ?? $request->getContext()->getPath(), + PKPInvitationHandler::REPLY_PAGE, + PKPInvitationHandler::REPLY_OP_ACCEPT, + null, + [ + 'id' => $this->getId(), + 'key' => $this->key, + ] + ); + } + public function getDeclineUrl(): string + { + $request = Application::get()->getRequest(); + return $request->getDispatcher() + ->url( + $request, + Application::ROUTE_PAGE, + $this->context?->getPath() ?? $request->getContext()->getPath(), + PKPInvitationHandler::REPLY_PAGE, + PKPInvitationHandler::REPLY_OP_DECLINE, + null, + [ + 'id' => $this->getId(), + 'key' => $this->key, + ] + ); + } + + public function dispatch(bool $sendEmail = false): bool + { + // Need to return error messages also? + if (!$this->preDispatchActions()) { + return false; + } + + if (!isset($this->keyHash)) { + if (!isset($this->key)) { + $this->key = Validation::generatePassword(); + } + + $this->keyHash = self::makeKeyHash($this->key); + } + + $invitationId = Repo::invitation()->addInvitation($this); + + $this->setId($invitationId); + + $mailable = $this->getMailable(); + + if ($sendEmail && isset($mailable)) { + try { + Mail::to($this->email) + ->send($mailable); + + } catch (TransportException $e) { + trigger_error('Failed to send email invitation: ' . $e->getMessage(), E_USER_ERROR); + } + } + + return true; + } + + public function isKeyValid(string $key): bool + { + $keyHash = self::makeKeyHash($key); + + return $keyHash == $this->keyHash; + } + + public function getExcludedPayloadVariables(): array + { + return [ + 'mailable', + 'context', + 'userId', + 'assocId', + 'key', + 'keyHash', + 'expirationDate', + 'className', + 'email', + 'contextId', + ]; + } + + public function setMailable(Mailable $mailable): void + { + $this->mailable = $mailable; + } + + public function setKeyHash(string $keyHash): void + { + $this->keyHash = $keyHash; + } + + public function getKeyHash(): string + { + return $this->keyHash; + } + + public function setExpirationDate(Carbon $expirationDate): void + { + $this->expirationDate = $expirationDate; + } + + public function setInvitationModel(Invitation $invitationModel) + { + $this->keyHash = $invitationModel->keyHash; + $this->expirationDate = $invitationModel->expiryDate; + $this->id = $invitationModel->id; + } + + /** + * Check if invitation is expired + */ + public function isExpired(): bool + { + $currentDateTime = Carbon::now(); + + if ($this->expirationDate > $currentDateTime) { + return false; + } + + return false; + } + + public static function makeKeyHash($key): string + { + return password_hash($key, PASSWORD_BCRYPT); + } + + public function setId($id): void + { + $this->id = $id; + } + + public function getId(): int + { + return $this->id; + } +} From 2f3f0238228e5292e070bd7737c53465801c33c2 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Fri, 24 May 2024 15:37:06 +0600 Subject: [PATCH 15/23] pkp/pkp-lib#5885 job test update --- tests/jobs/email/ReviewReminderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php index f834685fd27..8edeb521e31 100644 --- a/tests/jobs/email/ReviewReminderTest.php +++ b/tests/jobs/email/ReviewReminderTest.php @@ -105,7 +105,7 @@ public function register(\Pimple\Container $pimple) ->withAnyArgs() ->andReturn( // Mock the context(Journal/Press/Server) object - Mockery::mock(\APP\journal\Journal::class) + Mockery::mock(\PKP\context\Context::class) ->makePartial() ->shouldReceive([ 'getPath' => '', From a10b50a64308debb8db3ba89b20809243838de4a Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 13 Jun 2024 18:33:44 +0600 Subject: [PATCH 16/23] pkp/pkp-lib#5885 job test update --- tests/jobs/email/ReviewReminderTest.php | 52 ++++++++++++++----------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php index 8edeb521e31..4461f0b0f15 100644 --- a/tests/jobs/email/ReviewReminderTest.php +++ b/tests/jobs/email/ReviewReminderTest.php @@ -10,7 +10,7 @@ * @brief Tests for review response and submit due reminder job. */ -namespace PKP\tests\classes\core; +namespace PKP\tests\jobs\email; use Mockery; use PKP\tests\PKPTestCase; @@ -22,6 +22,7 @@ use PKP\invitation\repositories\Invitation as InvitationRepository; use PKP\submission\reviewAssignment\Repository as ReviewAssignmentRepository; use PKP\log\event\Repository as EventRepository; +use APP\user\Repository as UserRepository; /** * @runTestsInSeparateProcesses @@ -37,18 +38,25 @@ class ReviewReminderTest extends PKPTestCase /** * Test job is a proper instance */ - public function testUnserializationGetProperReviewReminderJobInstance(): void + public function testUnserializationGetProperJobInstance(): void { $this->assertInstanceOf(ReviewReminder::class, unserialize($this->serializedJobData)); } /** - * Test job will not fail when no reviewer associated with review assignment + * Ensure that a serialized job can be unserialized and executed */ - public function testJobWillRunWithIfNoReviewerExists(): void + public function testRunSerializedJob(): void { + /** @var ReviewReminder $reviewReminderJob */ $reviewReminderJob = unserialize($this->serializedJobData); + // Fake the mail facade + Mail::fake(); + + // need to mock request so that a valid context information is set and can be retrived + $this->mockRequest(); + $reviewAssignmentMock = Mockery::mock(ReviewAssignment::class) ->shouldReceive([ 'getReviewerId' => 0, @@ -66,8 +74,6 @@ public function testJobWillRunWithIfNoReviewerExists(): void ->withAnyArgs() ->getMock(); - app()->instance(ReviewAssignment::class, $reviewAssignmentMock); - $reviewAssignmentRepoMock = Mockery::mock(app(ReviewAssignmentRepository::class)) ->makePartial() ->shouldReceive([ @@ -78,24 +84,28 @@ public function testJobWillRunWithIfNoReviewerExists(): void ->getMock(); app()->instance(ReviewAssignmentRepository::class, $reviewAssignmentRepoMock); - - $this->assertNull($reviewReminderJob->handle()); - } - - /** - * Test job will not fail - */ - public function testRunSerializedJob(): void - { - // Fake the mail facade - Mail::fake(); - // need to mock request so that a valid context information is set and can be retrived - $this->mockRequest(); + $userMock = Mockery::mock(\PKP\user\User::class) + ->makePartial() + ->shouldReceive([ + 'getId' => 0, + 'getFullName' => 'Test User', + ]) + ->withAnyArgs() + ->getMock(); + $userRepoMock = Mockery::mock(app(UserRepository::class)) + ->makePartial() + ->shouldReceive('get') + ->withAnyArgs() + ->andReturn($userMock) + ->getMock(); + + app()->instance(UserRepository::class, $userRepoMock); + // Need to replace the container binding of `context` with a mock object \APP\core\Services::register( - new class extends \APP\services\OJSServiceProvider + new class implements \Pimple\ServiceProviderInterface { public function register(\Pimple\Container $pimple) { @@ -119,8 +129,6 @@ public function register(\Pimple\Container $pimple) } ); - $reviewReminderJob = unserialize($this->serializedJobData); - $publicationMock = Mockery::mock(\APP\publication\Publication::class) ->makePartial() ->shouldReceive('getData') From 8b0ff1248086c72c3156491372255f1dad841f5c Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 13 Jun 2024 18:47:16 +0600 Subject: [PATCH 17/23] pkp/pkp-lib#5885 removed mistakenly pulled BaseInvitation during rebase --- .../invitation/invitations/BaseInvitation.php | 255 ------------------ 1 file changed, 255 deletions(-) delete mode 100644 classes/invitation/invitations/BaseInvitation.php diff --git a/classes/invitation/invitations/BaseInvitation.php b/classes/invitation/invitations/BaseInvitation.php deleted file mode 100644 index cf2a8248d29..00000000000 --- a/classes/invitation/invitations/BaseInvitation.php +++ /dev/null @@ -1,255 +0,0 @@ -expirationDate = Carbon::now()->addDays($expiryDays); - $this->className = $this::class; - } - - public function getPayload(): array - { - $values = []; - - $reflection = new ReflectionClass($this->className); - $constructor = $reflection->getConstructor(); - - if ($constructor) { - // If the constructor exists, get its parameters - $parameters = $constructor->getParameters(); - - // Loop through the parameters and get their values from the object - foreach ($parameters as $parameter) { - $propertyName = $parameter->getName(); - $propertyValue = $reflection->getProperty($propertyName)->getValue($this); - $values[$propertyName] = $propertyValue; - } - } - - return $values; - } - - public function markStatus(InvitationStatus $status): void - { - $invitation = Repo::invitation() - ->getByKeyHash($this->keyHash); - - if (is_null($invitation)) { - throw new Exception('This invitation was not found'); - } - - $invitation->markAs($status); - } - - public function acceptHandle(): void - { - $this->markStatus(InvitationStatus::ACCEPTED); - } - public function declineHandle(): void - { - $this->markStatus(InvitationStatus::DECLINED); - } - - abstract public function getMailable(): ?Mailable; - abstract public function preDispatchActions(): bool; - - public function getAcceptUrl(): string - { - $request = Application::get()->getRequest(); - return $request->getDispatcher() - ->url( - $request, - Application::ROUTE_PAGE, - $this->context?->getPath() ?? $request->getContext()->getPath(), - PKPInvitationHandler::REPLY_PAGE, - PKPInvitationHandler::REPLY_OP_ACCEPT, - null, - [ - 'id' => $this->getId(), - 'key' => $this->key, - ] - ); - } - public function getDeclineUrl(): string - { - $request = Application::get()->getRequest(); - return $request->getDispatcher() - ->url( - $request, - Application::ROUTE_PAGE, - $this->context?->getPath() ?? $request->getContext()->getPath(), - PKPInvitationHandler::REPLY_PAGE, - PKPInvitationHandler::REPLY_OP_DECLINE, - null, - [ - 'id' => $this->getId(), - 'key' => $this->key, - ] - ); - } - - public function dispatch(bool $sendEmail = false): bool - { - // Need to return error messages also? - if (!$this->preDispatchActions()) { - return false; - } - - if (!isset($this->keyHash)) { - if (!isset($this->key)) { - $this->key = Validation::generatePassword(); - } - - $this->keyHash = self::makeKeyHash($this->key); - } - - $invitationId = Repo::invitation()->addInvitation($this); - - $this->setId($invitationId); - - $mailable = $this->getMailable(); - - if ($sendEmail && isset($mailable)) { - try { - Mail::to($this->email) - ->send($mailable); - - } catch (TransportException $e) { - trigger_error('Failed to send email invitation: ' . $e->getMessage(), E_USER_ERROR); - } - } - - return true; - } - - public function isKeyValid(string $key): bool - { - $keyHash = self::makeKeyHash($key); - - return $keyHash == $this->keyHash; - } - - public function getExcludedPayloadVariables(): array - { - return [ - 'mailable', - 'context', - 'userId', - 'assocId', - 'key', - 'keyHash', - 'expirationDate', - 'className', - 'email', - 'contextId', - ]; - } - - public function setMailable(Mailable $mailable): void - { - $this->mailable = $mailable; - } - - public function setKeyHash(string $keyHash): void - { - $this->keyHash = $keyHash; - } - - public function getKeyHash(): string - { - return $this->keyHash; - } - - public function setExpirationDate(Carbon $expirationDate): void - { - $this->expirationDate = $expirationDate; - } - - public function setInvitationModel(Invitation $invitationModel) - { - $this->keyHash = $invitationModel->keyHash; - $this->expirationDate = $invitationModel->expiryDate; - $this->id = $invitationModel->id; - } - - /** - * Check if invitation is expired - */ - public function isExpired(): bool - { - $currentDateTime = Carbon::now(); - - if ($this->expirationDate > $currentDateTime) { - return false; - } - - return false; - } - - public static function makeKeyHash($key): string - { - return password_hash($key, PASSWORD_BCRYPT); - } - - public function setId($id): void - { - $this->id = $id; - } - - public function getId(): int - { - return $this->id; - } -} From 182d711a82ff4fe8d4cbbbafc9253bfcedd9afe9 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 13 Jun 2024 23:44:19 +0600 Subject: [PATCH 18/23] pkp/pkp-lib#5885 updated job and test based on new inviation functionality implementation --- classes/task/ReviewReminder.php | 1 + jobs/email/ReviewReminder.php | 13 +++++++------ tests/jobs/email/ReviewReminderTest.php | 14 +------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/classes/task/ReviewReminder.php b/classes/task/ReviewReminder.php index ce5fb7a5622..dae480fbce4 100644 --- a/classes/task/ReviewReminder.php +++ b/classes/task/ReviewReminder.php @@ -48,6 +48,7 @@ public function executeActions(): bool ->getCollector() ->filterByIsIncomplete(true) ->orderByContextId() + ->orderBySubmissionId() ->getMany(); foreach ($incompleteAssignments as $reviewAssignment) { diff --git a/jobs/email/ReviewReminder.php b/jobs/email/ReviewReminder.php index dd861b6aff1..8d8b1a14445 100644 --- a/jobs/email/ReviewReminder.php +++ b/jobs/email/ReviewReminder.php @@ -74,13 +74,14 @@ public function handle(): void $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 = new ReviewerAccessInvite(); + $reviewInvitation->initialize($reviewAssignment->getReviewerId(), $context->getId(), null); + + $reviewInvitation->reviewAssignmentId = $reviewAssignment->getId(); + $reviewInvitation->updatePayload(); + $reviewInvitation->dispatch(); + $reviewInvitation->updateMailableWithUrl($mailable); } // deprecated template variables OJS 2.x diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php index 4461f0b0f15..ebda6e33409 100644 --- a/tests/jobs/email/ReviewReminderTest.php +++ b/tests/jobs/email/ReviewReminderTest.php @@ -19,7 +19,6 @@ use PKP\submission\reviewAssignment\ReviewAssignment; use APP\submission\Repository as SubmissionRepository; use PKP\emailTemplate\Repository as EmailTemplateRepository; -use PKP\invitation\repositories\Invitation as InvitationRepository; use PKP\submission\reviewAssignment\Repository as ReviewAssignmentRepository; use PKP\log\event\Repository as EventRepository; use APP\user\Repository as UserRepository; @@ -158,7 +157,7 @@ public function register(\Pimple\Container $pimple) $emailTemplateMock = Mockery::mock(\PKP\emailTemplate\EmailTemplate::class) ->makePartial() ->shouldReceive([ - "getLocalizedData" => "" + 'getLocalizedData' => '', ]) ->withAnyArgs() ->getMock(); @@ -173,17 +172,6 @@ public function register(\Pimple\Container $pimple) app()->instance(EmailTemplateRepository::class, $emailTemplateRepoMock); - $invitationRepoMock = Mockery::mock(app(InvitationRepository::class)) - ->makePartial() - ->shouldReceive([ - 'addInvitation' => 0, - 'getMailable' => null, - ]) - ->withAnyArgs() - ->getMock(); - - app()->instance(InvitationRepository::class, $invitationRepoMock); - $eventRepoMock = Mockery::mock(app(EventRepository::class)) ->makePartial() ->shouldReceive([ From 2391787513cf10142b56ece351fb3ce87a84d926 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Sun, 30 Jun 2024 23:44:32 +0600 Subject: [PATCH 19/23] pkp/pkp-lib#5885 updated task --- jobs/email/ReviewReminder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/email/ReviewReminder.php b/jobs/email/ReviewReminder.php index 8d8b1a14445..ab22a8c9639 100644 --- a/jobs/email/ReviewReminder.php +++ b/jobs/email/ReviewReminder.php @@ -80,7 +80,7 @@ public function handle(): void $reviewInvitation->reviewAssignmentId = $reviewAssignment->getId(); $reviewInvitation->updatePayload(); - $reviewInvitation->dispatch(); + $reviewInvitation->invite(); $reviewInvitation->updateMailableWithUrl($mailable); } From 0c1cc5d0528cce5cae2cd764f3a5512dae63b8db Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Thu, 25 Jul 2024 18:44:15 +0600 Subject: [PATCH 20/23] pkp/pkp-lib#5885 translation update --- locale/en/manager.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locale/en/manager.po b/locale/en/manager.po index 01758b2fcec..54e1e87cfb6 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1243,14 +1243,14 @@ msgstr "Set Reminders for Review" msgid "manager.setup.reviewOptions.reminders.description" msgstr "" -"Send an email reminder before or after for review request response(if reviewer has not responded " +"Send an email reminder before or after for review request response (if reviewer has not responded " "to review request yet) or review submission (if reviewer has not submitted review yet)" msgid "manager.setup.reviewOptions.reminders.response.before" -msgstr "Response Request Response - Before Due Date" +msgstr "Review Request Response - Before Due Date" msgid "manager.setup.reviewOptions.reminders.response.after" -msgstr "Response Request Response - After Due Date" +msgstr "Review Request Response - After Due Date" msgid "manager.setup.reviewOptions.reminders.submit.before" msgstr "Review Submission - Before Due Date" From f77d1e63200558d56343bf88e83c9c5ecbcf5052 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Wed, 14 Aug 2024 13:25:59 +0600 Subject: [PATCH 21/23] pkp/pkp-lib#5885 tests updated and removed reference of deprecated Services class --- jobs/email/ReviewReminder.php | 3 +- tests/jobs/email/ReviewReminderTest.php | 63 ++++++++++++------------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/jobs/email/ReviewReminder.php b/jobs/email/ReviewReminder.php index ab22a8c9639..b2c023a5eae 100644 --- a/jobs/email/ReviewReminder.php +++ b/jobs/email/ReviewReminder.php @@ -17,7 +17,6 @@ namespace PKP\jobs\email; use APP\facades\Repo; -use APP\core\Services; use Illuminate\Support\Facades\Mail; use PKP\log\event\PKPSubmissionEventLogEntry; use PKP\core\PKPApplication; @@ -52,7 +51,7 @@ public function handle(): void $submission = Repo::submission()->get($reviewAssignment->getData('submissionId')); - $contextService = Services::get('context'); + $contextService = app()->get('context'); $context = $contextService->get($this->contextId); /** @var ReviewRemindAuto|ReviewResponseRemindAuto $mailable */ diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php index ebda6e33409..8b000e88992 100644 --- a/tests/jobs/email/ReviewReminderTest.php +++ b/tests/jobs/email/ReviewReminderTest.php @@ -16,30 +16,35 @@ use PKP\tests\PKPTestCase; use PKP\jobs\email\ReviewReminder; use Illuminate\Support\Facades\Mail; +use APP\user\Repository as UserRepository; +use PKP\log\event\Repository as EventRepository; use PKP\submission\reviewAssignment\ReviewAssignment; use APP\submission\Repository as SubmissionRepository; use PKP\emailTemplate\Repository as EmailTemplateRepository; use PKP\submission\reviewAssignment\Repository as ReviewAssignmentRepository; -use PKP\log\event\Repository as EventRepository; -use APP\user\Repository as UserRepository; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\Attributes\CoversClass; -/** - * @runTestsInSeparateProcesses - * @see https://docs.phpunit.de/en/9.6/annotations.html#runtestsinseparateprocesses - */ +#[RunTestsInSeparateProcesses] +#[CoversClass(ReviewReminder::class)] class ReviewReminderTest extends PKPTestCase { /** * Serializion from OJS 3.5.0 */ - protected string $serializedJobData = 'O:29:"PKP\jobs\email\ReviewReminder":5:{s:9:"contextId";i:1;s:18:"reviewAssignmentId";i:57;s:13:"mailableClass";s:43:"PKP\mail\mailables\ReviewResponseRemindAuto";s:10:"connection";s:8:"database";s:5:"queue";s:5:"queue";}'; + protected string $serializedJobData = <<assertInstanceOf(ReviewReminder::class, unserialize($this->serializedJobData)); + $this->assertInstanceOf( + ReviewReminder::class, + unserialize($this->serializedJobData) + ); } /** @@ -101,32 +106,24 @@ public function testRunSerializedJob(): void ->getMock(); app()->instance(UserRepository::class, $userRepoMock); + + $contextMock = Mockery::mock(\PKP\context\Context::class) + ->makePartial() + ->shouldReceive([ + 'getPath' => '', + 'getId' => 0, + ]) + ->withAnyArgs() + ->getMock(); - // Need to replace the container binding of `context` with a mock object - \APP\core\Services::register( - new class implements \Pimple\ServiceProviderInterface - { - public function register(\Pimple\Container $pimple) - { - $pimple['context'] = Mockery::mock(\APP\services\ContextService::class) - ->makePartial() - ->shouldReceive('get') - ->withAnyArgs() - ->andReturn( - // Mock the context(Journal/Press/Server) object - Mockery::mock(\PKP\context\Context::class) - ->makePartial() - ->shouldReceive([ - 'getPath' => '', - 'getId' => 0, - ]) - ->withAnyArgs() - ->getMock() - ) - ->getMock(); - } - } - ); + $contextServiceMock = Mockery::mock(\APP\services\ContextService::class) + ->makePartial() + ->shouldReceive('get') + ->withAnyArgs() + ->andReturn($contextMock) + ->getMock(); + + app()->instance('context', $contextServiceMock); $publicationMock = Mockery::mock(\APP\publication\Publication::class) ->makePartial() From 98a5db2759736aff3509c5f70dc07d7138cdb23c Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Wed, 14 Aug 2024 15:09:25 +0600 Subject: [PATCH 22/23] pkp/pkp-lib#5885 test update --- tests/jobs/email/ReviewReminderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php index 8b000e88992..0d1f9c7d70f 100644 --- a/tests/jobs/email/ReviewReminderTest.php +++ b/tests/jobs/email/ReviewReminderTest.php @@ -16,7 +16,7 @@ use PKP\tests\PKPTestCase; use PKP\jobs\email\ReviewReminder; use Illuminate\Support\Facades\Mail; -use APP\user\Repository as UserRepository; +use PKP\user\Repository as UserRepository; use PKP\log\event\Repository as EventRepository; use PKP\submission\reviewAssignment\ReviewAssignment; use APP\submission\Repository as SubmissionRepository; From e96748444a0d4f115ffb3f21d797f29b3720fa65 Mon Sep 17 00:00:00 2001 From: Touhidur Rahman Date: Fri, 30 Aug 2024 14:43:11 +0600 Subject: [PATCH 23/23] pkp/pkp-lib#5885 test update --- classes/validation/enums/DateComparisonRule.php | 1 - tests/jobs/email/ReviewReminderTest.php | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/classes/validation/enums/DateComparisonRule.php b/classes/validation/enums/DateComparisonRule.php index 79388ae6a9b..63bfd75684b 100644 --- a/classes/validation/enums/DateComparisonRule.php +++ b/classes/validation/enums/DateComparisonRule.php @@ -24,4 +24,3 @@ enum DateComparisonRule: string case GREATER_OR_EQUAL = 'after_or_equal'; case LESSER_OR_EQUAL = 'before_or_equal'; } - \ No newline at end of file diff --git a/tests/jobs/email/ReviewReminderTest.php b/tests/jobs/email/ReviewReminderTest.php index 0d1f9c7d70f..564a4e116dd 100644 --- a/tests/jobs/email/ReviewReminderTest.php +++ b/tests/jobs/email/ReviewReminderTest.php @@ -180,6 +180,8 @@ public function testRunSerializedJob(): void app()->instance(EventRepository::class, $eventRepoMock); - $this->assertNull($reviewReminderJob->handle()); + $reviewReminderJob->handle(); + + $this->expectNotToPerformAssertions(); } }