355 lines
14 KiB
PHP
355 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @defgroup submission_reviewRound Review Round
|
|
*/
|
|
/**
|
|
* @file classes/submission/reviewRound/ReviewRound.php
|
|
*
|
|
* Copyright (c) 2014-2021 Simon Fraser University
|
|
* Copyright (c) 2003-2021 John Willinsky
|
|
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
|
*
|
|
* @class ReviewRound
|
|
*
|
|
* @ingroup submission_reviewRound
|
|
*
|
|
* @see ReviewRoundDAO
|
|
*
|
|
* @brief Basic class describing a review round.
|
|
*/
|
|
|
|
namespace PKP\submission\reviewRound;
|
|
|
|
use APP\decision\Decision;
|
|
use APP\facades\Repo;
|
|
use PKP\db\DAORegistry;
|
|
use PKP\stageAssignment\StageAssignmentDAO;
|
|
use PKP\submission\reviewAssignment\ReviewAssignment;
|
|
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
|
|
|
class ReviewRound extends \PKP\core\DataObject
|
|
{
|
|
// The first four statuses are set explicitly by Decisions, which override
|
|
// the current status.
|
|
public const REVIEW_ROUND_STATUS_REVISIONS_REQUESTED = 1;
|
|
public const REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW = 2;
|
|
public const REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL = 3;
|
|
public const REVIEW_ROUND_STATUS_ACCEPTED = 4;
|
|
public const REVIEW_ROUND_STATUS_DECLINED = 5;
|
|
|
|
// The following statuses are calculated based on the statuses of ReviewAssignments
|
|
// in this round.
|
|
public const REVIEW_ROUND_STATUS_PENDING_REVIEWERS = 6; // No reviewers have been assigned
|
|
public const REVIEW_ROUND_STATUS_PENDING_REVIEWS = 7; // Waiting for reviews to be submitted by reviewers
|
|
public const REVIEW_ROUND_STATUS_REVIEWS_READY = 8; // One or more reviews is ready for an editor to view
|
|
public const REVIEW_ROUND_STATUS_REVIEWS_COMPLETED = 9; // All assigned reviews have been confirmed by an editor
|
|
public const REVIEW_ROUND_STATUS_REVIEWS_OVERDUE = 10; // One or more reviews is overdue
|
|
// The following status is calculated when the round is in ReviewRound::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED and
|
|
// at least one revision file has been uploaded.
|
|
public const REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED = 11;
|
|
|
|
// The following statuses are calculated based on the statuses of recommendOnly EditorAssignments
|
|
// and their decisions in this round.
|
|
public const REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS = 12; // Waiting for recommendations to be submitted by recommendOnly editors
|
|
public const REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY = 13; // One or more recommendations are ready for an editor to view
|
|
public const REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED = 14; // All assigned recommendOnly editors have made a recommendation
|
|
|
|
// The following status is calculated when the round is in ReviewRound::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW and
|
|
// at least one revision file has been uploaded.
|
|
public const REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED = 15;
|
|
|
|
// The following status is set when a submission return back from copyediting stage to last review round again
|
|
public const REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW = 16;
|
|
|
|
//
|
|
// Get/set methods
|
|
//
|
|
|
|
/**
|
|
* get submission id
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getSubmissionId()
|
|
{
|
|
return $this->getData('submissionId');
|
|
}
|
|
|
|
/**
|
|
* set submission id
|
|
*
|
|
* @param int $submissionId
|
|
*/
|
|
public function setSubmissionId($submissionId)
|
|
{
|
|
$this->setData('submissionId', $submissionId);
|
|
}
|
|
|
|
/**
|
|
* Get review stage id (internal or external review).
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getStageId()
|
|
{
|
|
return $this->getData('stageId');
|
|
}
|
|
|
|
/**
|
|
* Set review stage id
|
|
*
|
|
* @param int $stageId
|
|
*/
|
|
public function setStageId($stageId)
|
|
{
|
|
$this->setData('stageId', $stageId);
|
|
}
|
|
|
|
/**
|
|
* Get review round
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getRound()
|
|
{
|
|
return $this->getData('round');
|
|
}
|
|
|
|
/**
|
|
* Set review round
|
|
*/
|
|
public function setRound($round)
|
|
{
|
|
$this->setData('round', $round);
|
|
}
|
|
|
|
/**
|
|
* Get current round status
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getStatus()
|
|
{
|
|
return $this->getData('status');
|
|
}
|
|
|
|
/**
|
|
* Set current round status
|
|
*
|
|
* @param int $status
|
|
*/
|
|
public function setStatus($status)
|
|
{
|
|
$this->setData('status', $status);
|
|
}
|
|
|
|
/**
|
|
* Calculate the status of this review round.
|
|
*
|
|
* If the round is in revisions, it will search for revision files and set
|
|
* the status accordingly. If the round has not reached a revision status
|
|
* yet, it will determine the status based on the statuses of the round's
|
|
* ReviewAssignments.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function determineStatus()
|
|
{
|
|
// If revisions have been requested, check to see if any have been
|
|
// submitted
|
|
if ($this->getStatus() == self::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED || $this->getStatus() == self::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED) {
|
|
// get editor decisions
|
|
$pendingRevisionDecision = Repo::decision()->getActivePendingRevisionsDecision($this->getSubmissionId(), $this->getStageId(), Decision::PENDING_REVISIONS);
|
|
|
|
if ($pendingRevisionDecision) {
|
|
if (Repo::decision()->revisionsUploadedSinceDecision($pendingRevisionDecision, $this->getSubmissionId())) {
|
|
return self::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED;
|
|
}
|
|
}
|
|
return self::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED;
|
|
}
|
|
|
|
// If revisions have been requested for re-submission, check to see if any have been
|
|
// submitted
|
|
if ($this->getStatus() == self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW || $this->getStatus() == self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED) {
|
|
// get editor decisions
|
|
$pendingRevisionDecision = Repo::decision()->getActivePendingRevisionsDecision($this->getSubmissionId(), $this->getStageId(), Decision::RESUBMIT);
|
|
|
|
if ($pendingRevisionDecision) {
|
|
if (Repo::decision()->revisionsUploadedSinceDecision($pendingRevisionDecision, $this->getSubmissionId())) {
|
|
return self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED;
|
|
}
|
|
}
|
|
return self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW;
|
|
}
|
|
|
|
$statusFinished = in_array(
|
|
$this->getStatus(),
|
|
[
|
|
self::REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL,
|
|
self::REVIEW_ROUND_STATUS_ACCEPTED,
|
|
self::REVIEW_ROUND_STATUS_DECLINED
|
|
]
|
|
);
|
|
if ($statusFinished) {
|
|
return $this->getStatus();
|
|
}
|
|
|
|
// Determine the round status by looking at the recommendOnly editor assignment statuses
|
|
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
|
$pendingRecommendations = false;
|
|
$recommendationsFinished = true;
|
|
$recommendationsReady = false;
|
|
$editorsStageAssignments = $stageAssignmentDao->getEditorsAssignedToStage($this->getSubmissionId(), $this->getStageId());
|
|
foreach ($editorsStageAssignments as $editorsStageAssignment) {
|
|
if ($editorsStageAssignment->getRecommendOnly()) {
|
|
$pendingRecommendations = true;
|
|
// Get recommendation from the assigned recommendOnly editor
|
|
$decisions = Repo::decision()->getCollector()
|
|
->filterBySubmissionIds([$this->getSubmissionId()])
|
|
->filterByStageIds([$this->getStageId()])
|
|
->filterByReviewRoundIds([$this->getId()])
|
|
->filterByEditorIds([$editorsStageAssignment->getUserId()])
|
|
->getCount();
|
|
|
|
if (!$decisions) {
|
|
$recommendationsFinished = false;
|
|
} else {
|
|
$recommendationsReady = true;
|
|
}
|
|
}
|
|
}
|
|
if ($pendingRecommendations) {
|
|
if ($recommendationsFinished) {
|
|
return self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED;
|
|
} elseif ($recommendationsReady) {
|
|
return self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY;
|
|
}
|
|
}
|
|
|
|
// Determine the round status by looking at the assignment statuses
|
|
$anyOverdueReview = false;
|
|
$anyIncompletedReview = false;
|
|
$anyUnreadReview = false;
|
|
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
|
$reviewAssignments = $reviewAssignmentDao->getByReviewRoundId($this->getId());
|
|
foreach ($reviewAssignments as $reviewAssignment) {
|
|
assert($reviewAssignment instanceof ReviewAssignment);
|
|
|
|
$assignmentStatus = $reviewAssignment->getStatus();
|
|
|
|
switch ($assignmentStatus) {
|
|
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_DECLINED:
|
|
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_CANCELLED:
|
|
break;
|
|
|
|
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE:
|
|
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE:
|
|
$anyOverdueReview = true;
|
|
break;
|
|
|
|
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE:
|
|
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_ACCEPTED:
|
|
$anyIncompletedReview = true;
|
|
break;
|
|
|
|
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RECEIVED:
|
|
$anyUnreadReview = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find the correct review round status based on the state of
|
|
// the current review assignments. The check order matters: the
|
|
// first conditions override the others.
|
|
if (empty($reviewAssignments)) {
|
|
return self::REVIEW_ROUND_STATUS_PENDING_REVIEWERS;
|
|
} elseif ($anyOverdueReview) {
|
|
return self::REVIEW_ROUND_STATUS_REVIEWS_OVERDUE;
|
|
} elseif ($anyUnreadReview) {
|
|
return self::REVIEW_ROUND_STATUS_REVIEWS_READY;
|
|
} elseif ($anyIncompletedReview) {
|
|
return self::REVIEW_ROUND_STATUS_PENDING_REVIEWS;
|
|
} elseif ($pendingRecommendations) {
|
|
return self::REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS;
|
|
}
|
|
|
|
// The submission back form copy editing stage to last review round
|
|
if ($this->getStatus() == self::REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW) {
|
|
return self::REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW;
|
|
}
|
|
|
|
return self::REVIEW_ROUND_STATUS_REVIEWS_COMPLETED;
|
|
}
|
|
|
|
/**
|
|
* Get locale key associated with current status
|
|
*
|
|
* @param bool $isAuthor True iff the status is to be shown to the author (slightly tweaked phrasing)
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getStatusKey($isAuthor = false)
|
|
{
|
|
switch ($this->determineStatus()) {
|
|
case self::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED:
|
|
return 'editor.submission.roundStatus.revisionsRequested';
|
|
case self::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED:
|
|
return 'editor.submission.roundStatus.revisionsSubmitted';
|
|
case self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW:
|
|
return 'editor.submission.roundStatus.resubmitForReview';
|
|
case self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED:
|
|
return 'editor.submission.roundStatus.submissionResubmitted';
|
|
case self::REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL:
|
|
return 'editor.submission.roundStatus.sentToExternal';
|
|
case self::REVIEW_ROUND_STATUS_ACCEPTED:
|
|
return 'editor.submission.roundStatus.accepted';
|
|
case self::REVIEW_ROUND_STATUS_DECLINED:
|
|
return 'editor.submission.roundStatus.declined';
|
|
case self::REVIEW_ROUND_STATUS_PENDING_REVIEWERS:
|
|
return 'editor.submission.roundStatus.pendingReviewers';
|
|
case self::REVIEW_ROUND_STATUS_PENDING_REVIEWS:
|
|
return 'editor.submission.roundStatus.pendingReviews';
|
|
case self::REVIEW_ROUND_STATUS_REVIEWS_READY:
|
|
return $isAuthor ? 'author.submission.roundStatus.reviewsReady' : 'editor.submission.roundStatus.reviewsReady';
|
|
case self::REVIEW_ROUND_STATUS_REVIEWS_COMPLETED:
|
|
return 'editor.submission.roundStatus.reviewsCompleted';
|
|
case self::REVIEW_ROUND_STATUS_REVIEWS_OVERDUE:
|
|
return $isAuthor ? 'author.submission.roundStatus.reviewOverdue' : 'editor.submission.roundStatus.reviewOverdue';
|
|
case self::REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS:
|
|
return 'editor.submission.roundStatus.pendingRecommendations';
|
|
case self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY:
|
|
return 'editor.submission.roundStatus.recommendationsReady';
|
|
case self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED:
|
|
return 'editor.submission.roundStatus.recommendationsCompleted';
|
|
case self::REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW:
|
|
return 'editor.submission.roundStatus.returnedToReview';
|
|
default: return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!PKP_STRICT_MODE) {
|
|
class_alias('\PKP\submission\reviewRound\ReviewRound', '\ReviewRound');
|
|
foreach ([
|
|
'REVIEW_ROUND_STATUS_REVISIONS_REQUESTED',
|
|
'REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW',
|
|
'REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL',
|
|
'REVIEW_ROUND_STATUS_ACCEPTED',
|
|
'REVIEW_ROUND_STATUS_DECLINED',
|
|
'REVIEW_ROUND_STATUS_PENDING_REVIEWERS',
|
|
'REVIEW_ROUND_STATUS_PENDING_REVIEWS',
|
|
'REVIEW_ROUND_STATUS_REVIEWS_READY',
|
|
'REVIEW_ROUND_STATUS_REVIEWS_COMPLETED',
|
|
'REVIEW_ROUND_STATUS_REVIEWS_OVERDUE',
|
|
'REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED',
|
|
'REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS',
|
|
'REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY',
|
|
'REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED',
|
|
'REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED',
|
|
] as $constantName) {
|
|
define($constantName, constant('\ReviewRound::' . $constantName));
|
|
}
|
|
}
|