first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,181 @@
<?php
/**
* @file controllers/grid/users/reviewer/AuthorReviewerGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class AuthorReviewerGridCellProvider
*
* @ingroup controllers_grid_users_reviewer
*
* @brief Base class for a cell provider that can retrieve labels for reviewer grid rows in author workflow
*/
namespace PKP\controllers\grid\users\reviewer;
use APP\facades\Repo;
use PKP\controllers\grid\DataObjectGridCellProvider;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\GridHandler;
use PKP\controllers\review\linkAction\ReviewNotesLinkAction;
use PKP\submission\reviewAssignment\ReviewAssignment;
class AuthorReviewerGridCellProvider extends DataObjectGridCellProvider
{
//
// Template methods from GridCellProvider
//
/**
* Gathers the state of a given cell given a $row/$column combination
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return string
*/
public function getCellState($row, $column)
{
$reviewAssignment = $row->getData();
$columnId = $column->getId();
assert($reviewAssignment instanceof \PKP\core\DataObject && !empty($columnId));
/** @var ReviewAssignment $reviewAssignment */
switch ($columnId) {
case 'name':
case 'method':
return '';
case 'considered':
case 'actions':
return $reviewAssignment->getStatus();
}
}
/**
* Extracts variables for a given column from a data element
* so that they may be assigned to template before rendering.
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
$element = $row->getData();
$columnId = $column->getId();
assert($element instanceof \PKP\core\DataObject && !empty($columnId));
/** @var ReviewAssignment $element */
switch ($columnId) {
case 'name':
return ['label' => $element->getReviewerFullName()];
case 'method':
return ['label' => __($element->getReviewMethodKey())];
case 'considered':
$statusText = $this->_getStatusText($this->getCellState($row, $column), $row);
$reviewAssignment = $row->getData();
$competingInterests = $reviewAssignment->getCompetingInterests();
if ($competingInterests) {
$statusText .= '<span class="details">' . __('reviewer.competingInterests') . '</span>';
}
return ['label' => $statusText];
case 'actions':
// Only attach actions to this column. See self::getCellActions()
return ['label' => ''];
}
return parent::getTemplateVarsFromRowColumn($row, $column);
}
/**
* Get cell actions associated with this row/column combination
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return ?array an array of LinkAction instances
*/
public function getCellActions($request, $row, $column, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
{
$reviewAssignment = $row->getData();
$actionArgs = [
'submissionId' => $reviewAssignment->getSubmissionId(),
'reviewAssignmentId' => $reviewAssignment->getId(),
'stageId' => $reviewAssignment->getStageId(),
];
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
// Only attach actions to the actions column. The actions and status
// columns share state values.
$columnId = $column->getId();
if ($columnId == 'actions') {
switch ($this->getCellState($row, $column)) {
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_COMPLETE:
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_THANKED:
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RECEIVED:
$user = $request->getUser();
return [new ReviewNotesLinkAction($request, $reviewAssignment, $submission, $user, 'grid.users.reviewer.AuthorReviewerGridHandler', true)];
default:
return null;
}
}
return parent::getCellActions($request, $row, $column, $position);
}
/**
* Provide meaningful locale keys for the various grid status states.
*
* @param string $state
* @param \PKP\controllers\grid\GridRow $row
*
* @return string
*/
public function _getStatusText($state, $row)
{
$reviewAssignment = $row->getData();
switch ($state) {
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE:
return '<span class="state">' . __('editor.review.requestSent') . '</span><span class="details">' . __('editor.review.responseDue', ['date' => substr($reviewAssignment->getDateResponseDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_ACCEPTED:
return '<span class="state">' . __('editor.review.requestAccepted') . '</span><span class="details">' . __('editor.review.reviewDue', ['date' => substr($reviewAssignment->getDateDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_COMPLETE:
return $this->_getStatusWithRecommendation('common.complete', $reviewAssignment);
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE:
return '<span class="state overdue">' . __('common.overdue') . '</span><span class="details">' . __('editor.review.reviewDue', ['date' => substr($reviewAssignment->getDateDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE:
return '<span class="state overdue">' . __('common.overdue') . '</span><span class="details">' . __('editor.review.responseDue', ['date' => substr($reviewAssignment->getDateResponseDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_DECLINED:
return '<span class="state declined">' . __('common.declined') . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_CANCELLED:
return '<span class="state cancelled">' . __('common.cancelled') . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RECEIVED:
return $this->_getStatusWithRecommendation('editor.review.reviewSubmitted', $reviewAssignment);
default:
return '';
}
}
/**
* Retrieve a formatted HTML string that displays the state of the review
* with the review recommendation if one exists. Or return just the state.
* Only works with some states.
*
* @param string $statusKey Locale key for status text
* @param \PKP\submission\reviewAssignment\ReviewAssignment $reviewAssignment
*
* @return string
*/
public function _getStatusWithRecommendation($statusKey, $reviewAssignment)
{
if (!$reviewAssignment->getRecommendation()) {
return __($statusKey);
}
return '<span class="state">' . __($statusKey) . '</span><span class="details">' . __('submission.recommendation', ['recommendation' => $reviewAssignment->getLocalizedRecommendation()]) . '</span>';
}
}
@@ -0,0 +1,213 @@
<?php
/**
* @file controllers/grid/users/reviewer/AuthorReviewerGridHandler.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class AuthorReviewerGridHandler
*
* @ingroup controllers_grid_users_reviewer
*
* @brief Handle reviewer grid requests from author workflow in open reviews
*/
namespace PKP\controllers\grid\users\reviewer;
use APP\core\Application;
use APP\template\TemplateManager;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\GridHandler;
use PKP\core\JSONMessage;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\reviewForm\ReviewFormDAO;
use PKP\reviewForm\ReviewFormElementDAO;
use PKP\reviewForm\ReviewFormResponseDAO;
use PKP\security\authorization\internal\ReviewAssignmentRequiredPolicy;
use PKP\security\authorization\internal\ReviewRoundRequiredPolicy;
use PKP\security\authorization\WorkflowStageAccessPolicy;
use PKP\security\Role;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\SubmissionCommentDAO;
class AuthorReviewerGridHandler extends PKPReviewerGridHandler
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->addRoleAssignment(
[Role::ROLE_ID_AUTHOR],
['fetchGrid', 'fetchRow', 'readReview', 'reviewRead']
);
}
//
// Overridden methods from PKPHandler
//
/**
* @see GridHandler::getRowInstance()
*
* @return AuthorReviewerGridRow
*/
protected function getRowInstance()
{
return new AuthorReviewerGridRow();
}
/**
* @copydoc GridHandler::initialize()
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
// Reset actions
unset($this->_actions[GridHandler::GRID_ACTION_POSITION_ABOVE]);
// Columns
$cellProvider = new AuthorReviewerGridCellProvider();
$this->addColumn(
new GridColumn(
'name',
'user.name',
null,
null,
$cellProvider
)
);
// Add a column for the status of the review.
$this->addColumn(
new GridColumn(
'considered',
'common.status',
null,
null,
$cellProvider,
['anyhtml' => true]
)
);
// Add a column for the review method
$this->addColumn(
new GridColumn(
'method',
'common.type',
null,
null,
$cellProvider
)
);
// Add a column for the status of the review.
$this->addColumn(
new GridColumn(
'actions',
'grid.columns.actions',
null,
null,
$cellProvider
)
);
}
/**
* @copydoc PKPHandler::authorize()
*/
public function authorize($request, &$args, $roleAssignments)
{
// Bypass the parent authorization checks
$this->isAuthorGrid = true;
$stageId = $request->getUserVar('stageId'); // This is being validated in WorkflowStageAccessPolicy
// Not all actions need a stageId. Some work off the reviewAssignment which has the type and round.
$this->_stageId = (int)$stageId;
// Get the stage access policy
$workflowStageAccessPolicy = new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId);
// Add policy to ensure there is a review round id.
$workflowStageAccessPolicy->addPolicy(new ReviewRoundRequiredPolicy($request, $args, 'reviewRoundId', ['fetchGrid', 'fetchRow']));
// Add policy to ensure there is a review assignment for certain operations.
$workflowStageAccessPolicy->addPolicy(new ReviewAssignmentRequiredPolicy($request, $args, 'reviewAssignmentId', ['readReview', 'reviewRead'], [ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN]));
$this->addPolicy($workflowStageAccessPolicy);
return parent::authorize($request, $args, $roleAssignments);
}
//
// Overridden methods from GridHandler
//
/**
* @see GridHandler::loadData()
*/
protected function loadData($request, $filter)
{
// Get the existing review assignments for this submission
// Only show open requests that have been accepted
$reviewRound = $this->getReviewRound();
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
return $reviewAssignmentDao->getOpenReviewsByReviewRoundId($reviewRound->getId());
}
/**
* Open a modal to read the reviewer's review and
* download any files they may have uploaded
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function readReview($args, $request)
{
$templateMgr = TemplateManager::getManager($request);
$reviewAssignment = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT);
$templateMgr->assign([
'submission' => $this->getSubmission(),
'reviewAssignment' => $reviewAssignment,
'reviewerRecommendationOptions' => ReviewAssignment::getReviewerRecommendationOptions(),
]);
if ($reviewAssignment->getReviewFormId()) {
// Retrieve review form
$context = $request->getContext();
$reviewFormElementDao = DAORegistry::getDAO('ReviewFormElementDAO'); /** @var ReviewFormElementDAO $reviewFormElementDao */
// Get review form elements visible for authors
$reviewFormElements = $reviewFormElementDao->getByReviewFormId($reviewAssignment->getReviewFormId(), null, true);
$reviewFormResponseDao = DAORegistry::getDAO('ReviewFormResponseDAO'); /** @var ReviewFormResponseDAO $reviewFormResponseDao */
$reviewFormResponses = $reviewFormResponseDao->getReviewReviewFormResponseValues($reviewAssignment->getId());
$reviewFormDao = DAORegistry::getDAO('ReviewFormDAO'); /** @var ReviewFormDAO $reviewFormDao */
$reviewformid = $reviewAssignment->getReviewFormId();
$reviewForm = $reviewFormDao->getById($reviewAssignment->getReviewFormId(), Application::getContextAssocType(), $context->getId());
$templateMgr->assign([
'reviewForm' => $reviewForm,
'reviewFormElements' => $reviewFormElements,
'reviewFormResponses' => $reviewFormResponses,
'disabled' => true,
]);
} else {
// Retrieve reviewer comments. Skip private comments.
$submissionCommentDao = DAORegistry::getDAO('SubmissionCommentDAO'); /** @var SubmissionCommentDAO $submissionCommentDao */
$templateMgr->assign([
'comments' => $submissionCommentDao->getReviewerCommentsByReviewerId($reviewAssignment->getSubmissionId(), null, $reviewAssignment->getId(), true),
]);
}
// Render the response.
return $templateMgr->fetchJson('controllers/grid/users/reviewer/authorReadReview.tpl');
}
}
@@ -0,0 +1,23 @@
<?php
/**
* @file controllers/grid/users/reviewer/AuthorReviewerGridRow.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class AuthorReviewerGridRow
*
* @ingroup controllers_grid_users_reviewer
*
* @brief Reviewer grid row definition
*/
namespace PKP\controllers\grid\users\reviewer;
use PKP\controllers\grid\GridRow;
class AuthorReviewerGridRow extends GridRow
{
}
@@ -0,0 +1,221 @@
<?php
/**
* @file controllers/grid/users/reviewer/ReviewerGridCellProvider.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ReviewerGridCellProvider
*
* @ingroup controllers_grid_users_reviewer
*
* @brief Base class for a cell provider that can retrieve labels for reviewer grid rows
*/
namespace PKP\controllers\grid\users\reviewer;
use APP\facades\Repo;
use PKP\controllers\api\task\SendReminderLinkAction;
use PKP\controllers\api\task\SendThankYouLinkAction;
use PKP\controllers\grid\DataObjectGridCellProvider;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\GridHandler;
use PKP\controllers\review\linkAction\ReviewNotesLinkAction;
use PKP\controllers\review\linkAction\UnconsiderReviewLinkAction;
use PKP\submission\reviewAssignment\ReviewAssignment;
class ReviewerGridCellProvider extends DataObjectGridCellProvider
{
/** @var bool Is the current user assigned as an author to this submission */
public $_isCurrentUserAssignedAuthor;
/**
* Constructor
*
* @param bool $isCurrentUserAssignedAuthor Is the current user assigned
* as an author to this submission?
*/
public function __construct($isCurrentUserAssignedAuthor)
{
parent::__construct();
$this->_isCurrentUserAssignedAuthor = $isCurrentUserAssignedAuthor;
}
//
// Template methods from GridCellProvider
//
/**
* Gathers the state of a given cell given a $row/$column combination
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return string
*/
public function getCellState($row, $column)
{
$reviewAssignment = $row->getData();
$columnId = $column->getId();
assert($reviewAssignment instanceof \PKP\core\DataObject && !empty($columnId));
/** @var ReviewAssignment $reviewAssignment */
switch ($columnId) {
case 'name':
case 'method':
return '';
case 'considered':
case 'actions':
return $reviewAssignment->getStatus();
}
}
/**
* Extracts variables for a given column from a data element
* so that they may be assigned to template before rendering.
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array
*/
public function getTemplateVarsFromRowColumn($row, $column)
{
$element = $row->getData();
$columnId = $column->getId();
assert($element instanceof \PKP\core\DataObject && !empty($columnId));
/** @var ReviewAssignment $element */
switch ($columnId) {
case 'name':
$isReviewAnonymous = in_array($element->getReviewMethod(), [ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS, ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS]);
if ($this->_isCurrentUserAssignedAuthor && $isReviewAnonymous) {
return ['label' => __('editor.review.anonymousReviewer')];
}
return ['label' => $element->getReviewerFullName()];
case 'method':
return ['label' => __($element->getReviewMethodKey())];
case 'considered':
$statusText = $this->_getStatusText($this->getCellState($row, $column), $row);
$reviewAssignment = $row->getData();
$competingInterests = $reviewAssignment->getCompetingInterests();
if ($competingInterests) {
$statusText .= '<span class="details">' . __('reviewer.competingInterests') . '</span>';
}
return ['label' => $statusText];
case 'actions':
// Only attach actions to this column. See self::getCellActions()
return ['label' => ''];
}
return parent::getTemplateVarsFromRowColumn($row, $column);
}
/**
* Get cell actions associated with this row/column combination
*
* @param \PKP\controllers\grid\GridRow $row
* @param GridColumn $column
*
* @return array an array of LinkAction instances
*/
public function getCellActions($request, $row, $column, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
{
$reviewAssignment = $row->getData(); /** @var ReviewAssignment $reviewAssignment */
// Authors can't perform action on reviews
if ($this->_isCurrentUserAssignedAuthor) {
return [];
}
$actionArgs = [
'submissionId' => $reviewAssignment->getSubmissionId(),
'reviewAssignmentId' => $reviewAssignment->getId(),
'stageId' => $reviewAssignment->getStageId()
];
$router = $request->getRouter();
$action = false;
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
// Only attach actions to the actions column. The actions and status
// columns share state values.
$columnId = $column->getId();
if ($columnId == 'actions') {
switch ($this->getCellState($row, $column)) {
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE:
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE:
return [new SendReminderLinkAction($request, 'editor.review.reminder', $actionArgs)];
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_COMPLETE:
return [
new SendThankYouLinkAction($request, 'editor.review.thankReviewer', $actionArgs),
new UnconsiderReviewLinkAction($request, $reviewAssignment, $submission),
];
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_THANKED:
return [new UnconsiderReviewLinkAction($request, $reviewAssignment, $submission)];
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RECEIVED:
$user = $request->getUser();
return [new ReviewNotesLinkAction($request, $reviewAssignment, $submission, $user, 'grid.users.reviewer.ReviewerGridHandler', true)];
}
}
return parent::getCellActions($request, $row, $column, $position);
}
/**
* Provide meaningful locale keys for the various grid status states.
*
* @param string $state
* @param \PKP\controllers\grid\GridRow $row
*
* @return string
*/
public function _getStatusText($state, $row)
{
$reviewAssignment = $row->getData();
switch ($state) {
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE:
return '<span class="state">' . __('editor.review.requestSent') . '</span><span class="details">' . __('editor.review.responseDue', ['date' => substr($reviewAssignment->getDateResponseDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_ACCEPTED:
return '<span class="state">' . __('editor.review.requestAccepted') . '</span><span class="details">' . __('editor.review.reviewDue', ['date' => substr($reviewAssignment->getDateDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_COMPLETE:
return $this->_getStatusWithRecommendation('common.complete', $reviewAssignment);
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE:
return '<span class="state overdue">' . __('common.overdue') . '</span><span class="details">' . __('editor.review.reviewDue', ['date' => substr($reviewAssignment->getDateDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE:
return '<span class="state overdue">' . __('common.overdue') . '</span><span class="details">' . __('editor.review.responseDue', ['date' => substr($reviewAssignment->getDateResponseDue(), 0, 10)]) . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_DECLINED:
return '<span class="state declined" title="' . __('editor.review.requestDeclined.tooltip') . '">' . __('editor.review.requestDeclined') . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_CANCELLED:
return '<span class="state declined" title="' . __('editor.review.requestCancelled.tooltip') . '">' . __('editor.review.requestCancelled') . '</span>';
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RECEIVED:
return $this->_getStatusWithRecommendation('editor.review.reviewSubmitted', $reviewAssignment);
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_THANKED:
return $this->_getStatusWithRecommendation('editor.review.reviewerThanked', $reviewAssignment);
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REQUEST_RESEND:
return '<span class="state reconsider">' . __('editor.review.ReviewerResendRequest') . '</span><span class="details">' . __('editor.review.responseDue', ['date' => substr($reviewAssignment->getDateDue(), 0, 10)]) . '</span>';
default:
return '';
}
}
/**
* Retrieve a formatted HTML string that displays the state of the review
* with the review recommendation if one exists. Or return just the state.
* Only works with some states.
*
* @param string $statusKey Locale key for status text
* @param \PKP\submission\reviewAssignment\ReviewAssignment $reviewAssignment
*
* @return string
*/
public function _getStatusWithRecommendation($statusKey, $reviewAssignment)
{
if (!$reviewAssignment->getRecommendation()) {
return __($statusKey);
}
return '<span class="state">' . __($statusKey) . '</span><span class="details">' . __('submission.recommendation', ['recommendation' => $reviewAssignment->getLocalizedRecommendation()]) . '</span>';
}
}
@@ -0,0 +1,220 @@
<?php
/**
* @file controllers/grid/users/reviewer/ReviewerGridRow.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ReviewerGridRow
*
* @ingroup controllers_grid_users_reviewer
*
* @brief Reviewer grid row definition
*/
namespace PKP\controllers\grid\users\reviewer;
use APP\facades\Repo;
use PKP\controllers\grid\GridRow;
use PKP\core\PKPApplication;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\linkAction\request\RedirectConfirmationModal;
use PKP\security\Validation;
use PKP\submission\reviewAssignment\ReviewAssignment;
class ReviewerGridRow extends GridRow
{
/** @var bool Is the current user assigned as an author to this submission */
public $_isCurrentUserAssignedAuthor;
/**
* Constructor
*
* @param bool $isCurrentUserAssignedAuthor Is the current user assigned as an
* author to this submission?
*/
public function __construct($isCurrentUserAssignedAuthor)
{
parent::__construct();
$this->_isCurrentUserAssignedAuthor = $isCurrentUserAssignedAuthor;
}
//
// Overridden methods from GridRow
//
/**
* @copydoc GridRow::initialize()
*
* @param null|mixed $template
*/
public function initialize($request, $template = null)
{
parent::initialize($request, $template);
// Retrieve the submission id from the request
// These parameters need not be validated as we're just
// passing them along to another request, where they will be
// checked before they're used.
$submissionId = (int) $request->getUserVar('submissionId');
$stageId = (int) $request->getUserVar('stageId');
$round = (int) $request->getUserVar('round');
// Authors can't perform any actions on anonymous reviews
$reviewAssignment = $this->getData(); /** @var ReviewAssignment $reviewAssignment */
$isReviewAnonymous = in_array($reviewAssignment->getReviewMethod(), [ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS, ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS]);
if ($this->_isCurrentUserAssignedAuthor && $isReviewAnonymous) {
return;
}
// Is this a new row or an existing row?
$rowId = $this->getId();
if (!empty($rowId) && is_numeric($rowId)) {
// Only add row actions if this is an existing row
$router = $request->getRouter();
$actionArgs = [
'submissionId' => $submissionId,
'reviewAssignmentId' => $rowId,
'stageId' => $stageId,
'round' => $round
];
// read or upload a review
$submission = Repo::submission()->get($submissionId);
if (!$reviewAssignment->getCancelled()) {
$this->addAction(
new LinkAction(
'readReview',
new AjaxModal(
$router->url($request, null, null, 'readReview', null, $actionArgs),
__('editor.review.reviewDetails') . ': ' . $submission->getCurrentPublication()->getLocalizedTitle(null, 'html'),
'modal_information'
),
__('editor.review.reviewDetails'),
'more_info'
)
);
}
$this->addAction(
new LinkAction(
'email',
new AjaxModal(
$router->url($request, null, null, 'sendEmail', null, $actionArgs),
__('editor.review.emailReviewer'),
'modal_email'
),
__('editor.review.emailReviewer'),
'notify'
)
);
if (!$this->_isCurrentUserAssignedAuthor) {
if ($reviewAssignment->canResendReviewRequest()) {
$this->addAction(
new LinkAction(
'resendRequestReviewer',
new AjaxModal(
$router->url($request, null, null, 'resendRequestReviewer', null, $actionArgs),
__('editor.review.resendRequestReviewer'),
'modal_add'
),
__('editor.review.resendRequestReviewer'),
'add'
)
);
}
if (!$reviewAssignment->getCancelled()) {
$this->addAction(new LinkAction(
'manageAccess',
new AjaxModal(
$router->url($request, null, null, 'editReview', null, $actionArgs),
__('editor.submissionReview.editReview'),
'modal_add_file'
),
__('common.edit'),
'edit'
));
$this->addAction(new LinkAction(
'unassignReviewer',
new AjaxModal(
$router->url($request, null, null, 'unassignReviewer', null, $actionArgs),
$reviewAssignment->getDateConfirmed() ? __('editor.review.cancelReviewer') : __('editor.review.unassignReviewer'),
'modal_delete'
),
$reviewAssignment->getDateConfirmed() ? __('editor.review.cancelReviewer') : __('editor.review.unassignReviewer'),
'delete'
));
} else {
$this->addAction(
new LinkAction(
'reinstateReviewer',
new AjaxModal(
$router->url($request, null, null, 'reinstateReviewer', null, $actionArgs),
__('editor.review.reinstateReviewer'),
'modal_add'
),
__('editor.review.reinstateReviewer'),
'add'
)
);
}
}
$this->addAction(
new LinkAction(
'history',
new AjaxModal(
$router->url($request, null, null, 'reviewHistory', null, $actionArgs),
__('submission.history'),
'modal_information'
),
__('submission.history'),
'more_info'
)
);
$user = $request->getUser();
if (
!Validation::loggedInAs() &&
$user->getId() != $reviewAssignment->getReviewerId() &&
Validation::getAdministrationLevel($reviewAssignment->getReviewerId(), $user->getId()) === Validation::ADMINISTRATION_FULL &&
!$reviewAssignment->getCancelled()
) {
$dispatcher = $router->getDispatcher();
$this->addAction(
new LinkAction(
'logInAs',
new RedirectConfirmationModal(
__('grid.user.confirmLogInAs'),
__('grid.action.logInAs'),
$dispatcher->url($request, PKPApplication::ROUTE_PAGE, null, 'login', 'signInAsUser', $reviewAssignment->getReviewerId())
),
__('grid.action.logInAs'),
'enroll_user'
)
);
}
// Add gossip action when appropriate
$canCurrentUserGossip = Repo::user()->canCurrentUserGossip($reviewAssignment->getReviewerId());
if ($canCurrentUserGossip) {
$this->addAction(
new LinkAction(
'gossip',
new AjaxModal(
$router->url($request, null, null, 'gossip', null, $actionArgs),
__('user.gossip'),
'modal_information'
),
__('user.gossip'),
'more_info'
)
);
}
}
}
}
@@ -0,0 +1,263 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/AdvancedSearchReviewerForm.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 AdvancedSearchReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Form for an advanced search and for adding a reviewer to a submission.
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\core\Request;
use APP\core\Services;
use APP\facades\Repo;
use APP\submission\Submission;
use APP\template\TemplateManager;
use Illuminate\Support\Facades\Mail;
use PKP\controllers\grid\users\reviewer\PKPReviewerGridHandler;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\emailTemplate\EmailTemplate;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxAction;
use PKP\mail\mailables\ReviewRequest;
use PKP\mail\mailables\ReviewRequestSubsequent;
use PKP\security\Role;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\reviewRound\ReviewRound;
use PKP\submission\reviewRound\ReviewRoundDAO;
class AdvancedSearchReviewerForm extends ReviewerForm
{
/**
* Constructor.
*
* @param Submission $submission
* @param ReviewRound $reviewRound
*/
public function __construct($submission, $reviewRound)
{
parent::__construct($submission, $reviewRound);
$this->setTemplate('controllers/grid/users/reviewer/form/advancedSearchReviewerForm.tpl');
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'reviewerId', 'required', 'editor.review.mustSelect'));
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
parent::readInputData();
$this->readUserVars(['reviewerId']);
}
/**
* @copydoc Form::initData()
*/
public function initData()
{
parent::initData();
$request = Application::get()->getRequest();
$context = $request->getContext();
$mailable = $this->getMailable();
$templates = Repo::emailTemplate()->getCollector($context->getId())
->filterByKeys([ReviewRequest::getEmailTemplateKey(), ReviewRequestSubsequent::getEmailTemplateKey()])
->getMany()
->mapWithKeys(function (EmailTemplate $item, int $key) use ($mailable) {
return [$item->getData('key') => Mail::compileParams($item->getLocalizedData('body'), $mailable->viewData)];
});
$this->setData('personalMessage', '');
$this->setData('reviewerMessages', $templates->toArray());
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
// Get submission context
$submissionContext = Services::get('context')->get($this->getSubmission()->getContextId());
// Pass along the request vars
$actionArgs = $request->getUserVars();
$reviewRound = $this->getReviewRound();
$actionArgs['reviewRoundId'] = $reviewRound->getId();
$actionArgs['selectionType'] = PKPReviewerGridHandler::REVIEWER_SELECT_ADVANCED_SEARCH;
// but change the selectionType for each action
$advancedSearchAction = new LinkAction(
'advancedSearch',
new AjaxAction($request->url(null, null, 'reloadReviewerForm', null, $actionArgs)),
__('manager.reviewerSearch.change'),
'user_search'
);
$this->setReviewerFormAction($advancedSearchAction);
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
// get reviewer IDs already assign to this submission and this round
$reviewAssignments = $reviewAssignmentDao->getBySubmissionId($this->getSubmissionId(), $this->getReviewRound()->getId());
$currentlyAssigned = [];
if (!empty($reviewAssignments)) {
foreach ($reviewAssignments as $reviewAssignment) {
$currentlyAssigned[] = (int) $reviewAssignment->getReviewerId();
}
}
// Get user IDs already assigned to this submission, and admins and
// managers who may have access to author identities and can not guarantee
// anonymous reviews
$warnOnAssignment = [];
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignmentResults = $stageAssignmentDao->getBySubmissionAndStageId($this->getSubmissionId());
while ($stageAssignment = $stageAssignmentResults->next()) {
$warnOnAssignment[] = $stageAssignment->getUserId();
}
// Get a list of users in the managerial and admin user groups
// Managers are assigned only to contexts; site admins are assigned only to site.
// Therefore filtering by both context IDs and role IDs will not cause problems.
$userIds = Repo::user()->getCollector()
->filterByRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN])
->filterByContextIds([$submissionContext->getId(), PKPApplication::CONTEXT_SITE])
->getIds()
->toArray();
$warnOnAssignment = array_merge($warnOnAssignment, $userIds);
$warnOnAssignment = array_values(array_unique(array_map('intval', $warnOnAssignment)));
// Get reviewers list
$selectReviewerListPanel = new \PKP\components\listPanels\PKPSelectReviewerListPanel(
'selectReviewer',
__('editor.submission.findAndSelectReviewer'),
[
'apiUrl' => $request->getDispatcher()->url(
$request,
PKPApplication::ROUTE_API,
$submissionContext->getPath(),
'users/reviewers'
),
'currentlyAssigned' => $currentlyAssigned,
'getParams' => [
'contextId' => $submissionContext->getId(),
'reviewStage' => $reviewRound->getStageId(),
],
'selectorName' => 'reviewerId',
'warnOnAssignment' => $warnOnAssignment,
]
);
// Get reviewers who completed a review in the last round
$lastRoundReviewerIds = [];
if ($this->getReviewRound()->getRound() > 1) {
/** @var ReviewRoundDAO */
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO');
$previousRound = $this->getReviewRound()->getRound() - 1;
$lastReviewRound = $reviewRoundDao->getReviewRound($this->getSubmissionId(), $this->getReviewRound()->getStageId(), $previousRound);
if ($lastReviewRound) {
$lastReviewAssignments = $reviewAssignmentDao->getByReviewRoundId($lastReviewRound->getId());
foreach ($lastReviewAssignments as $reviewAssignment) {
if (in_array($reviewAssignment->getStatus(), [ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_THANKED, ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_COMPLETE])) {
$lastRoundReviewerIds[] = (int) $reviewAssignment->getReviewerId();
}
}
$lastRoundReviewers = Repo::user()->getCollector()
->filterByContextIds([$submissionContext->getId()])
->filterByRoleIds([Role::ROLE_ID_REVIEWER])
->filterByUserIds($lastRoundReviewerIds)
->includeReviewerData()
->getMany();
if (count($lastRoundReviewers)) {
$selectReviewerListPanel->set([
'lastRoundReviewers' => $lastRoundReviewers,
]);
}
}
}
$templateMgr = TemplateManager::getManager($request);
// Used to determine the right email template
$templateMgr->assign('lastRoundReviewerIds', $lastRoundReviewerIds);
$selectReviewerListPanel->set([
'items' => $selectReviewerListPanel->getItems($request),
'itemsMax' => $selectReviewerListPanel->getItemsMax(),
]);
$templateMgr->assign('selectReviewerListData', [
'components' => [
'selectReviewer' => $selectReviewerListPanel->getConfig(),
]
]);
// Only add actions to forms where user can operate.
if (array_intersect($this->getUserRoles(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])) {
$actionArgs['selectionType'] = PKPReviewerGridHandler::REVIEWER_SELECT_CREATE;
// but change the selectionType for each action
$advancedSearchAction = new LinkAction(
'selectCreate',
new AjaxAction($request->url(null, null, 'reloadReviewerForm', null, $actionArgs)),
__('editor.review.createReviewer'),
'add_user'
);
$this->setReviewerFormAction($advancedSearchAction);
$actionArgs['selectionType'] = PKPReviewerGridHandler::REVIEWER_SELECT_ENROLL_EXISTING;
// but change the selectionType for each action
$advancedSearchAction = new LinkAction(
'enrolExisting',
new AjaxAction($request->url(null, null, 'reloadReviewerForm', null, $actionArgs)),
__('editor.review.enrollReviewer.short'),
'enroll_user'
);
$this->setReviewerFormAction($advancedSearchAction);
}
return parent::fetch($request, $template, $display);
}
protected function getEmailTemplates(): array
{
$subsequentTemplate = Repo::emailTemplate()->getByKey(
Application::get()->getRequest()->getContext()->getId(),
ReviewRequestSubsequent::getEmailTemplateKey()
);
$alternateTemplates = Repo::emailTemplate()->getCollector(Application::get()->getRequest()->getContext()->getId())
->alternateTo([ReviewRequestSubsequent::getEmailTemplateKey()])
->getMany();
$templateKeys = array_merge(
parent::getEmailTemplates(),
[ReviewRequestSubsequent::getEmailTemplateKey() => $subsequentTemplate->getLocalizedData('name')]
);
foreach ($alternateTemplates as $alternateTemplate) {
$templateKeys[$alternateTemplate->getData('key')] = $alternateTemplate->getLocalizedData('name');
}
return $templateKeys;
}
}
@@ -0,0 +1,180 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/CreateReviewerForm.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 CreateReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Form for creating and subsequently adding a reviewer to a submission.
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use APP\template\TemplateManager;
use Illuminate\Support\Facades\Mail;
use PKP\core\Core;
use PKP\mail\mailables\ReviewerRegister;
use PKP\notification\PKPNotification;
use PKP\security\Validation;
use PKP\submission\reviewRound\ReviewRound;
use PKP\user\InterestManager;
use Symfony\Component\Mailer\Exception\TransportException;
class CreateReviewerForm extends ReviewerForm
{
/**
* Constructor.
*
* @param Submission $submission
* @param ReviewRound $reviewRound
*/
public function __construct($submission, $reviewRound)
{
parent::__construct($submission, $reviewRound);
$this->setTemplate('controllers/grid/users/reviewer/form/createReviewerForm.tpl');
// the users register for the site, thus
// the site primary locale is the required default locale
$site = Application::get()->getRequest()->getSite();
$this->addSupportedFormLocale($site->getPrimaryLocale());
$form = $this;
$this->addCheck(new \PKP\form\validation\FormValidatorLocale($this, 'givenName', 'required', 'user.profile.form.givenNameRequired', $site->getPrimaryLocale()));
$this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'familyName', 'optional', 'user.profile.form.givenNameRequired.locale', function ($familyName) use ($form) {
$givenNames = $form->getData('givenName');
foreach ($familyName as $locale => $value) {
if (!empty($value) && empty($givenNames[$locale])) {
return false;
}
}
return true;
}));
$this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'username', 'required', 'user.register.form.usernameExists', [Repo::user(), 'getByUsername'], [true], true));
$this->addCheck(new \PKP\form\validation\FormValidatorUsername($this, 'username', 'required', 'user.register.form.usernameAlphaNumeric'));
$this->addCheck(new \PKP\form\validation\FormValidatorEmail($this, 'email', 'required', 'user.profile.form.emailRequired'));
$this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'email', 'required', 'user.register.form.emailExists', function ($email) {
return !Repo::user()->getByEmail($email, true);
}));
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'userGroupId', 'required', 'user.profile.form.usergroupRequired'));
}
/**
* @copydoc Form::init()
*/
public function initData()
{
parent::initData();
$mailable = $this->getMailable();
$context = Application::get()->getRequest()->getContext();
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
$this->setData('personalMessage', Mail::compileParams($template->getLocalizedData('body'), $mailable->viewData));
}
/**
* @copydoc ReviewerForm::fetch
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$advancedSearchAction = $this->getAdvancedSearchAction($request);
$this->setReviewerFormAction($advancedSearchAction);
$site = $request->getSite();
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign('sitePrimaryLocale', $site->getPrimaryLocale());
return parent::fetch($request, $template, $display);
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
parent::readInputData();
$this->readUserVars([
'givenName',
'familyName',
'affiliation',
'interests',
'username',
'email',
'skipEmail',
'userGroupId',
]);
}
/**
* @copydoc Form::execute()
*/
public function execute(...$functionArgs)
{
$user = Repo::user()->newDataObject();
$user->setGivenName($this->getData('givenName'), null);
$user->setFamilyName($this->getData('familyName'), null);
$user->setEmail($this->getData('email'));
$user->setAffiliation($this->getData('affiliation'), null); // Localized
$user->setInlineHelp(1); // default new reviewers to having inline help visible
$user->setUsername($this->getData('username'));
$password = Validation::generatePassword();
$user->setPassword(Validation::encryptCredentials($this->getData('username'), $password));
$user->setMustChangePassword(true); // Emailed P/W not safe
$user->setDateRegistered(Core::getCurrentDate());
$reviewerId = Repo::user()->add($user);
// Set the reviewerId in the Form for the parent class to use
$this->setData('reviewerId', $reviewerId);
// Insert the user interests
$interestManager = new InterestManager();
$interestManager->setInterestsForUser($user, $this->getData('interests'));
// Assign the selected user group ID to the user
$userGroupId = (int) $this->getData('userGroupId');
Repo::userGroup()->assignUserToGroup($reviewerId, $userGroupId);
if (!$this->getData('skipEmail')) {
// Send welcome email to user
$request = Application::get()->getRequest();
$context = $request->getContext();
$mailable = new ReviewerRegister($context, $password);
$mailable->recipients($user);
$mailable->sender($request->getUser());
$mailable->replyTo($context->getData('contactEmail'), $context->getData('contactName'));
$template = Repo::emailTemplate()->getByKey($context->getId(), ReviewerRegister::getEmailTemplateKey());
$mailable->subject($template->getLocalizedData('subject'));
$mailable->body($template->getLocalizedData('body'));
try {
Mail::send($mailable);
} catch (TransportException $e) {
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification(
$request->getUser()->getId(),
PKPNotification::NOTIFICATION_TYPE_ERROR,
['contents' => __('email.compose.error')]
);
error_log($e->getMessage());
}
}
return parent::execute(...$functionArgs);
}
}
@@ -0,0 +1,235 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/EditReviewForm.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 EditReviewForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Allow the editor to limit the available files to an assigned
* reviewer after the assignment has taken place.
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\Notification;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use APP\template\TemplateManager;
use Illuminate\Support\Facades\Mail;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\form\Form;
use PKP\mail\mailables\EditReviewNotify;
use PKP\notification\NotificationSubscriptionSettingsDAO;
use PKP\notification\PKPNotification;
use PKP\reviewForm\ReviewFormDAO;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\ReviewFilesDAO;
use PKP\submission\reviewRound\ReviewRound;
use PKP\submission\reviewRound\ReviewRoundDAO;
use PKP\submissionFile\SubmissionFile;
class EditReviewForm extends Form
{
/** @var ReviewAssignment */
public $_reviewAssignment;
/** @var ReviewRound */
public $_reviewRound;
protected Submission $submission;
public function __construct(ReviewAssignment $reviewAssignment, Submission $submission)
{
$this->_reviewAssignment = $reviewAssignment;
$this->submission = $submission;
assert($this->_reviewAssignment instanceof ReviewAssignment);
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$this->_reviewRound = $reviewRoundDao->getById($reviewAssignment->getReviewRoundId());
assert(is_a($this->_reviewRound, 'ReviewRound'));
parent::__construct('controllers/grid/users/reviewer/form/editReviewForm.tpl');
// 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\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
//
// Overridden template methods
//
/**
* Initialize form data from the associated author.
*/
public function initData()
{
$this->setData('responseDueDate', $this->_reviewAssignment->getDateResponseDue());
$this->setData('reviewDueDate', $this->_reviewAssignment->getDateDue());
return parent::initData();
}
/**
* Fetch the Edit Review Form form
*
* @see Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$templateMgr = TemplateManager::getManager($request);
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$context = $request->getContext();
if (!$this->_reviewAssignment->getDateCompleted()) {
$reviewFormDao = DAORegistry::getDAO('ReviewFormDAO'); /** @var ReviewFormDAO $reviewFormDao */
$reviewFormsIterator = $reviewFormDao->getActiveByAssocId(Application::getContextAssocType(), $context->getId());
$reviewForms = [];
while ($reviewForm = $reviewFormsIterator->next()) {
$reviewForms[$reviewForm->getId()] = $reviewForm->getLocalizedTitle();
}
$templateMgr->assign([
'reviewForms' => $reviewForms,
'reviewFormId' => $this->_reviewAssignment->getReviewFormId(),
]);
}
$templateMgr->assign([
'stageId' => $this->_reviewAssignment->getStageId(),
'reviewRoundId' => $this->_reviewRound->getId(),
'submissionId' => $this->_reviewAssignment->getSubmissionId(),
'reviewAssignmentId' => $this->_reviewAssignment->getId(),
'reviewMethod' => $this->_reviewAssignment->getReviewMethod(),
'reviewMethods' => $reviewAssignmentDao->getReviewMethodsTranslationKeys(),
]);
return parent::fetch($request, $template, $display);
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'selectedFiles',
'responseDueDate',
'reviewDueDate',
'reviewMethod',
'reviewFormId',
]);
}
/**
* @copydoc Form::execute()
*/
public function execute(...$functionArgs)
{
$request = Application::get()->getRequest();
$context = $request->getContext();
// Revoke all, then grant selected.
$reviewFilesDao = DAORegistry::getDAO('ReviewFilesDAO'); /** @var ReviewFilesDAO $reviewFilesDao */
$reviewFilesDao->revokeByReviewId($this->_reviewAssignment->getId());
$fileStages = [$this->_reviewRound->getStageId() == WORKFLOW_STAGE_ID_INTERNAL_REVIEW ? SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE : SubmissionFile::SUBMISSION_FILE_REVIEW_FILE];
$submissionFiles = Repo::submissionFile()
->getCollector()
->filterBySubmissionIds([$this->_reviewAssignment->getSubmissionId()])
->filterByReviewRoundIds([$this->_reviewRound->getId()])
->filterByFileStages($fileStages)
->getMany();
$selectedFiles = array_map(function ($id) {
return (int) $id;
}, (array) $this->getData('selectedFiles'));
foreach ($submissionFiles as $submissionFile) {
if (in_array($submissionFile->getId(), $selectedFiles)) {
$reviewFilesDao->grant($this->_reviewAssignment->getId(), $submissionFile->getId());
}
}
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignment = $reviewAssignmentDao->getReviewAssignment($this->_reviewRound->getId(), $this->_reviewAssignment->getReviewerId());
// Send notification to reviewer if details have changed.
if (strtotime($reviewAssignment->getDateDue()) != strtotime($this->getData('reviewDueDate')) || strtotime($reviewAssignment->getDateResponseDue()) != strtotime($this->getData('responseDueDate')) || $reviewAssignment->getReviewMethod() != $this->getData('reviewMethod')) {
$notificationManager = new NotificationManager();
$request = Application::get()->getRequest();
$context = $request->getContext();
$notification = $notificationManager->createNotification(
$request,
$reviewAssignment->getReviewerId(),
PKPNotification::NOTIFICATION_TYPE_REVIEW_ASSIGNMENT_UPDATED,
$context->getId(),
PKPApplication::ASSOC_TYPE_REVIEW_ASSIGNMENT,
$reviewAssignment->getId(),
Notification::NOTIFICATION_LEVEL_TASK
);
// Check if user is subscribed to this type of notification emails
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId());
/** @var NotificationSubscriptionSettingsDAO */
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO');
if ($notification && !in_array(
PKPNotification::NOTIFICATION_TYPE_REVIEW_ASSIGNMENT_UPDATED,
$notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings(
NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY,
$reviewer->getId(),
(int) $context->getId()
)
)
) {
$mailable = new EditReviewNotify($context, $this->submission, $reviewAssignment);
$template = Repo::emailTemplate()->getByKey($context->getId(), EditReviewNotify::getEmailTemplateKey());
// The template may not exist, see pkp/pkp-lib#9109
if (!$template) {
$template = Repo::emailTemplate()->getByKey($context->getId(), 'NOTIFICATION');
$mailable->addData([
'notificationContents' => $notificationManager->getNotificationContents($request, $notification),
'notificationUrl' => $notificationManager->getNotificationUrl($request, $notification),
]);
}
$mailable
->sender($request->getUser())
->recipients([$reviewer])
->subject($template->getLocalizedData('subject'))
->body($template->getLocalizedData('body'))
->allowUnsubscribe($notification);
Mail::send($mailable);
}
}
$reviewAssignment->setDateDue($this->getData('reviewDueDate'));
$reviewAssignment->setDateResponseDue($this->getData('responseDueDate'));
$reviewAssignment->setReviewMethod($this->getData('reviewMethod'));
if (!$reviewAssignment->getDateCompleted()) {
// Ensure that the review form ID is valid, if specified
$reviewFormId = (int) $this->getData('reviewFormId');
$reviewFormDao = DAORegistry::getDAO('ReviewFormDAO'); /** @var ReviewFormDAO $reviewFormDao */
$reviewForm = $reviewFormDao->getById($reviewFormId, Application::getContextAssocType(), $context->getId());
$reviewAssignment->setReviewFormId($reviewForm ? $reviewFormId : null);
}
$reviewAssignmentDao->updateObject($reviewAssignment);
parent::execute(...$functionArgs);
}
}
@@ -0,0 +1,130 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/EmailReviewerForm.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 EmailReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Form for sending an email to a user
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use APP\template\TemplateManager;
use Illuminate\Support\Facades\Mail;
use PKP\db\DAORegistry;
use PKP\form\Form;
use PKP\log\SubmissionEmailLogDAO;
use PKP\log\SubmissionEmailLogEntry;
use PKP\mail\Mailable;
use PKP\notification\PKPNotification;
use PKP\submission\reviewAssignment\ReviewAssignment;
use Symfony\Component\Mailer\Exception\TransportException;
class EmailReviewerForm extends Form
{
/** @var ReviewAssignment The review assignment to use for this contact */
public $_reviewAssignment;
protected Submission $submission;
/**
* Constructor.
*
* @param ReviewAssignment $reviewAssignment The review assignment to use for this contact.
* @param Submission $submission
*/
public function __construct($reviewAssignment, $submission)
{
parent::__construct('controllers/grid/users/reviewer/form/emailReviewerForm.tpl');
$this->_reviewAssignment = $reviewAssignment;
$this->submission = $submission;
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'subject', 'required', 'email.subjectRequired'));
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'message', 'required', 'email.bodyRequired'));
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'subject',
'message',
]);
}
/**
* Display the form.
*
* @param array $requestArgs Request parameters to bounce back with the form submission.
* @param null|mixed $template
*
* @see Form::fetch
*/
public function fetch($request, $template = null, $display = false, $requestArgs = [])
{
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'userFullName' => $this->_reviewAssignment->getReviewerFullName(),
'requestArgs' => $requestArgs,
'reviewAssignmentId' => $this->_reviewAssignment->getId(),
]);
return parent::fetch($request, $template, $display);
}
/**
* Send the email
*/
public function execute(...$functionArgs)
{
$toUser = Repo::user()->get($this->_reviewAssignment->getReviewerId());
$request = Application::get()->getRequest();
$fromUser = $request->getUser();
$mailable = new Mailable([$request->getContext(), $this->submission]);
$mailable->to($toUser->getEmail(), $toUser->getFullName());
$mailable->from($fromUser->getEmail(), $fromUser->getFullName());
$mailable->replyTo($fromUser->getEmail(), $fromUser->getFullName());
$mailable->subject($this->getData('subject'));
$mailable->body($this->getData('message'));
try {
Mail::send($mailable);
$submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $submissionEmailLogDao */
$submissionEmailLogDao->logMailable(
SubmissionEmailLogEntry::SUBMISSION_EMAIL_REVIEW_NOTIFY_REVIEWER,
$mailable,
$this->submission,
$fromUser,
);
} catch (TransportException $e) {
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification(
$fromUser->getId(),
PKPNotification::NOTIFICATION_TYPE_ERROR,
['contents' => __('email.compose.error')]
);
trigger_error($e->getMessage(), E_USER_WARNING);
}
parent::execute(...$functionArgs);
}
}
@@ -0,0 +1,137 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/EnrollExistingReviewerForm.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 EnrollExistingReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Form for enrolling an existing reviewer and adding them to a submission.
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use Illuminate\Support\Facades\Mail;
use PKP\db\DAORegistry;
use PKP\security\Role;
use PKP\security\RoleDAO;
class EnrollExistingReviewerForm extends ReviewerForm
{
/**
* Constructor.
*/
public function __construct($submission, $reviewRound)
{
parent::__construct($submission, $reviewRound);
$this->setTemplate('controllers/grid/users/reviewer/form/enrollExistingReviewerForm.tpl');
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'userGroupId', 'required', 'user.profile.form.usergroupRequired'));
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'userId', 'required', 'manager.people.existingUserRequired'));
}
/**
* @copydoc Form::init()
*/
public function initData()
{
parent::initData();
$mailable = $this->getMailable();
$context = Application::get()->getRequest()->getContext();
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
$this->setData('personalMessage', Mail::compileParams($template->getLocalizedData('body'), $mailable->viewData));
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$advancedSearchAction = $this->getAdvancedSearchAction($request);
$this->setReviewerFormAction($advancedSearchAction);
return parent::fetch($request, $template, $display);
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
parent::readInputData();
$this->readUserVars(['userId', 'userGroupId']);
}
/**
* @copydoc Form::execute()
*/
public function execute(...$functionArgs)
{
// Assign a reviewer user group to an existing non-reviewer
$userId = (int) $this->getData('userId');
$userGroupId = (int) $this->getData('userGroupId');
if (!$this->isValidUserAndGroup($userId, $userGroupId)) {
fatalError('invalid user or userGroup ID');
}
Repo::userGroup()->assignUserToGroup($userId, $userGroupId);
// Set the reviewerId in the Form for the parent class to use
$this->setData('reviewerId', $userId);
return parent::execute(...$functionArgs);
}
/**
* Checks if the user group is valid and the user exists and doesn't already have a reviewer role
*/
protected function isValidUserAndGroup(int $userId, int $userGroupId): bool
{
$context = Application::get()->getRequest()->getContext();
// User exists
$user = Repo::user()->get($userId);
if (!$user) {
return false;
}
// Ensure user doesn't have a reviewer role
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
if ($roleDao->userHasRole($context->getId(), $user->getId(), Role::ROLE_ID_REVIEWER)) {
return false;
}
// User Group exists
$userGroup = Repo::userGroup()->get($userGroupId);
if (!$userGroup) {
return false;
}
// User Group refers to the reviewer role
if ($userGroup->getData('roleId') != Role::ROLE_ID_REVIEWER) {
return false;
}
// User group refers to the right context
if (intval($userGroup->getData('contextId')) != $context->getId()) {
return false;
}
return true;
}
}
@@ -0,0 +1,112 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/ReinstateReviewerForm.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 ReinstateReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Allow the editor to reinstate a cancelled review assignment
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use PKP\context\Context;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\log\event\PKPSubmissionEventLogEntry;
use PKP\mail\Mailable;
use PKP\mail\mailables\ReviewerReinstate;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\security\Validation;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\reviewRound\ReviewRound;
class ReinstateReviewerForm extends ReviewerNotifyActionForm
{
/**
* Constructor
*
* @param ReviewAssignment $reviewAssignment
* @param ReviewRound $reviewRound
* @param Submission $submission
*/
public function __construct($reviewAssignment, $reviewRound, $submission)
{
parent::__construct($reviewAssignment, $reviewRound, $submission, 'controllers/grid/users/reviewer/form/reinstateReviewerForm.tpl');
}
/**
* @copydoc ReviewerNotifyActionForm::getMailable()
*/
protected function getMailable(Context $context, Submission $submission, ReviewAssignment $reviewAssignment): Mailable
{
return new ReviewerReinstate($context, $submission, $reviewAssignment);
}
/**
* @copydoc Form::execute()
*
* @return bool whether or not the review assignment was deleted successfully
*/
public function execute(...$functionArgs)
{
parent::execute(...$functionArgs);
$request = Application::get()->getRequest();
$submission = $this->getSubmission(); /** @var Submission $submission */
$reviewAssignment = $this->getReviewAssignment();
// Reinstate the review assignment.
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
if (isset($reviewAssignment) && $reviewAssignment->getSubmissionId() == $submission->getId() && !Hook::call('EditorAction::reinstateReview', [&$submission, $reviewAssignment])) {
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId());
if (!isset($reviewer)) {
return false;
}
$reviewAssignment->setCancelled(false);
$reviewAssignmentDao->updateObject($reviewAssignment);
// Stamp the modification date
$submission->stampModified();
Repo::submission()->dao->update($submission);
// Insert a trivial notification to indicate the reviewer was reinstated successfully.
$currentUser = $request->getUser();
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification($currentUser->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS, ['contents' => __('notification.reinstatedReviewer')]);
// Add log
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_REINSTATED,
'userId' => Validation::loggedInAs() ?? $currentUser->getId(),
'message' => 'log.review.reviewReinstated',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
'reviewAssignmentId' => $reviewAssignment->getId(),
'reviewerName' => $reviewer->getFullName(),
'submissionId' => $submission->getId(),
'stageId' => $reviewAssignment->getStageId(),
'round' => $reviewAssignment->getRound()
]);
Repo::eventLog()->add($eventLog);
return true;
}
return false;
}
}
@@ -0,0 +1,125 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/ResendRequestReviewerForm.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2003-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ResendRequestReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Allow the editor to resend request to reconsider a declined review assignment invitation
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\core\Request;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use PKP\context\Context;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\log\event\PKPSubmissionEventLogEntry;
use PKP\mail\Mailable;
use PKP\mail\mailables\ReviewerResendRequest;
use PKP\notification\PKPNotification;
use PKP\security\Validation;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\reviewRound\ReviewRound;
class ResendRequestReviewerForm extends ReviewerNotifyActionForm
{
/**
* Constructor
*
*/
public function __construct(ReviewAssignment $reviewAssignment, ReviewRound $reviewRound, Submission $submission)
{
parent::__construct(
$reviewAssignment,
$reviewRound,
$submission,
'controllers/grid/users/reviewer/form/resendRequestReviewerForm.tpl'
);
}
protected function getMailable(Context $context, Submission $submission, ReviewAssignment $reviewAssignment): Mailable
{
return new ReviewerResendRequest($context, $submission, $reviewAssignment);
}
/**
* @copydoc ReviewerNotifyActionForm::getEmailKey()
*/
protected function getEmailKey()
{
return 'REVIEW_RESEND_REQUEST';
}
/**
* @copydoc Form::execute()
*
* @return bool whether or not the review assignment was deleted successfully
*/
public function execute(...$functionArgs)
{
parent::execute(...$functionArgs);
$request = Application::get()->getRequest(); /** @var Request $request */
$submission = $this->getSubmission(); /** @var Submission $submission */
$reviewAssignment = $this->getReviewAssignment(); /** @var ReviewAssignment $reviewAssignment */
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
if (isset($reviewAssignment) && $reviewAssignment->getSubmissionId() == $submission->getId()) {
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId());
if (!isset($reviewer)) {
return false;
}
// $reviewAssignment->setCancelled(false);
$reviewAssignment->setDeclined(false);
$reviewAssignment->setRequestResent(true);
$reviewAssignment->setDateConfirmed(null);
$reviewAssignmentDao->updateObject($reviewAssignment);
// Stamp the modification date
$submission->stampLastActivity();
Repo::submission()->dao->update($submission);
// Insert a trivial notification to indicate the reviewer requested to reconsider successfully.
$currentUser = $request->getUser();
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification(
$currentUser->getId(),
PKPNotification::NOTIFICATION_TYPE_SUCCESS,
['contents' => __('notification.reviewerResendRequest')]
);
// Add log
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_ASSIGN,
'userId' => Validation::loggedInAs() ?? $currentUser->getId(),
'message' => 'log.review.reviewerResendRequest',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
'reviewAssignmentId' => $reviewAssignment->getId(),
'reviewerName' => $reviewer->getFullName(),
'submissionId' => $submission->getId(),
'stageId' => $reviewAssignment->getStageId(),
'round' => $reviewAssignment->getRound(),
]);
Repo::eventLog()->add($eventLog);
return true;
}
return false;
}
}
@@ -0,0 +1,188 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/ReviewReminderForm.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 ReviewReminderForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Form for sending a review reminder to a reviewer
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\template\TemplateManager;
use Illuminate\Support\Facades\Mail;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\form\Form;
use PKP\log\event\PKPSubmissionEventLogEntry;
use PKP\mail\mailables\ReviewRemind;
use PKP\mail\variables\ReviewAssignmentEmailVariable;
use PKP\notification\PKPNotification;
use PKP\security\Validation;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use Symfony\Component\Mailer\Exception\TransportException;
class ReviewReminderForm extends Form
{
/** @var ReviewAssignment The review assignment associated with the reviewer */
public $_reviewAssignment;
/**
* Constructor.
*/
public function __construct($reviewAssignment)
{
parent::__construct('controllers/grid/users/reviewer/form/reviewReminderForm.tpl');
$this->_reviewAssignment = $reviewAssignment;
// Validation checks for this form
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
//
// Getters and Setters
//
/**
* Get the review assignment
*
* @return ReviewAssignment
*/
public function getReviewAssignment()
{
return $this->_reviewAssignment;
}
//
// Overridden template methods
//
/**
* @copydoc Form::initData
*/
public function initData()
{
$request = Application::get()->getRequest();
$user = $request->getUser();
$context = $request->getContext();
$reviewAssignment = $this->getReviewAssignment();
$reviewerId = $reviewAssignment->getReviewerId();
$reviewer = Repo::user()->get($reviewerId);
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
$mailable = new ReviewRemind($context, $submission, $reviewAssignment);
$mailable->sender($user)->recipients([$reviewer]);
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
$data = $mailable->getData(Locale::getLocale());
// Don't expose the reviewer's one-click access URL to editors
$data[ReviewAssignmentEmailVariable::REVIEW_ASSIGNMENT_URL] = '{$' . ReviewAssignmentEmailVariable::REVIEW_ASSIGNMENT_URL . '}';
$body = Mail::compileParams($template->getLocalizedData('body'), $data);
$this->setData('stageId', $reviewAssignment->getStageId());
$this->setData('reviewAssignmentId', $reviewAssignment->getId());
$this->setData('submissionId', $submission->getId());
$this->setData('reviewAssignment', $reviewAssignment);
$this->setData('reviewerName', $reviewer->getFullName() . ' <' . $reviewer->getEmail() . '>');
$this->setData('message', $body);
$this->setData('reviewDueDate', $mailable->viewData[ReviewAssignmentEmailVariable::REVIEW_DUE_DATE]);
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign('emailVariables', [
'passwordResetUrl' => __('common.url'),
]);
return parent::fetch($request, $template, $display);
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'message',
'reviewDueDate',
]);
}
/**
* @copydoc Form::execute()
*/
public function execute(...$functionArgs)
{
$request = Application::get()->getRequest();
$reviewAssignment = $this->getReviewAssignment();
$reviewerId = $reviewAssignment->getReviewerId();
$reviewer = Repo::user()->get($reviewerId);
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
$user = $request->getUser();
$context = $request->getContext();
// Create ReviewRemind email and populate with data
$mailable = new ReviewRemind($context, $submission, $reviewAssignment);
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
$mailable
->subject($template->getLocalizedData('subject'))
->body($this->getData('message'))
->sender($user)
->recipients([$reviewer]);
// Finally, send email and handle Symfony transport exceptions
try {
Mail::send($mailable);
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_REMIND,
'userId' => Validation::loggedInAs() ?? $user->getId(),
'message' => 'submission.event.reviewer.reviewerReminded',
'isTranslate' => 0,
'dateLogged' => Core::getCurrentDate(),
'recipientId' => $reviewer->getId(),
'recipientName' => $reviewer->getFullName(),
'senderId' => $user->getId(),
'senderName' => $user->getFullName(),
]);
Repo::eventLog()->add($eventLog);
$reviewAssignment->setDateReminded(Core::getCurrentDate());
$reviewAssignment->stampModified();
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignmentDao->updateObject($reviewAssignment);
} catch (TransportException $e) {
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification(
$request->getUser()->getId(),
PKPNotification::NOTIFICATION_TYPE_ERROR,
['contents' => __('email.compose.error')]
);
trigger_error($e->getMessage(), E_USER_WARNING);
}
parent::execute(...$functionArgs);
}
}
@@ -0,0 +1,494 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/ReviewerForm.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 ReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Base Form for adding a reviewer to a submission.
* N.B. Requires a subclass to implement the "reviewerId" to be added.
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\core\Request;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use APP\template\TemplateManager;
use PKP\context\Context;
use PKP\controllers\grid\users\reviewer\PKPReviewerGridHandler;
use PKP\core\Core;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\form\Form;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxAction;
use PKP\mail\mailables\ReviewRequest;
use PKP\mail\variables\ReviewAssignmentEmailVariable;
use PKP\notification\PKPNotification;
use PKP\reviewForm\ReviewFormDAO;
use PKP\security\Role;
use PKP\security\RoleDAO;
use PKP\submission\action\EditorAction;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\ReviewFilesDAO;
use PKP\submission\reviewRound\ReviewRound;
use PKP\submissionFile\SubmissionFile;
class ReviewerForm extends Form
{
/** @var Submission The submission associated with the review assignment */
public $_submission;
/** @var ReviewRound The review round associated with the review assignment */
public $_reviewRound;
/** @var array An array of actions for the other reviewer forms */
public $_reviewerFormActions;
/** @var array An array with all current user roles */
public $_userRoles;
/**
* Constructor.
*
* @param Submission $submission
* @param ReviewRound $reviewRound
*/
public function __construct($submission, $reviewRound)
{
parent::__construct('controllers/grid/users/reviewer/form/defaultReviewerForm.tpl');
$this->setSubmission($submission);
$this->setReviewRound($reviewRound);
// 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\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
//
// Getters and Setters
//
/**
* Get the submission Id
*
* @return int submissionId
*/
public function getSubmissionId()
{
$submission = $this->getSubmission();
return $submission->getId();
}
/**
* Get the submission
*
* @return Submission
*/
public function getSubmission()
{
return $this->_submission;
}
/**
* Get the ReviewRound
*
* @return ReviewRound
*/
public function getReviewRound()
{
return $this->_reviewRound;
}
/**
* Set the submission
*
* @param Submission $submission
*/
public function setSubmission($submission)
{
$this->_submission = $submission;
}
/**
* Set the ReviewRound
*
* @param ReviewRound $reviewRound
*/
public function setReviewRound($reviewRound)
{
$this->_reviewRound = $reviewRound;
}
/**
* Set a reviewer form action
*
* @param LinkAction $action
*/
public function setReviewerFormAction($action)
{
$this->_reviewerFormActions[$action->getId()] = $action;
}
/**
* Set current user roles.
*
* @param array $userRoles
*/
public function setUserRoles($userRoles)
{
$this->_userRoles = $userRoles;
}
/**
* Get current user roles.
*
* @return array $userRoles
*/
public function getUserRoles()
{
return $this->_userRoles;
}
/**
* Get all of the reviewer form actions
*
* @return array
*/
public function getReviewerFormActions()
{
return $this->_reviewerFormActions;
}
//
// Overridden template methods
//
/**
* @copydoc Form::initData
*/
public function initData()
{
$request = Application::get()->getRequest();
$context = $request->getContext();
$reviewRound = $this->getReviewRound();
$submission = $this->getSubmission();
// Set default review method.
$reviewMethod = $context->getData('defaultReviewMode');
if (!$reviewMethod) {
$reviewMethod = ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS;
}
// If there is a section/series and it has a default
// review form designated, use it.
$sectionId = $submission->getCurrentPublication()->getData(Application::getSectionIdPropName());
$section = $sectionId ? Repo::section()->get($sectionId, $context->getId()) : null;
if ($section) {
$reviewFormId = $section->getReviewFormId();
} else {
$reviewFormId = null;
}
$numWeeks = (int) $context->getData('numWeeksPerReview');
if ($numWeeks <= 0) {
$numWeeks = 4;
}
$reviewDueDate = strtotime('+' . $numWeeks . ' week');
$numWeeks = (int) $context->getData('numWeeksPerResponse');
if ($numWeeks <= 0) {
$numWeeks = 3;
}
$responseDueDate = strtotime('+' . $numWeeks . ' week');
// Get the currently selected reviewer selection type to show the correct tab if we're re-displaying the form
$selectionType = (int) $request->getUserVar('selectionType');
$stageId = $reviewRound->getStageId();
$this->setData('submissionId', $this->getSubmissionId());
$this->setData('stageId', $stageId);
$this->setData('reviewMethod', $reviewMethod);
$this->setData('reviewFormId', $reviewFormId);
$this->setData('reviewRoundId', $reviewRound->getId());
$this->setData('responseDueDate', $responseDueDate);
$this->setData('reviewDueDate', $reviewDueDate);
$this->setData('selectionType', $selectionType);
$this->setData('considered', ReviewAssignment::REVIEW_ASSIGNMENT_NEW);
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$context = $request->getContext();
// Get the review method options.
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewMethods = $reviewAssignmentDao->getReviewMethodsTranslationKeys();
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign('reviewMethods', $reviewMethods);
$templateMgr->assign('reviewerActions', $this->getReviewerFormActions());
$reviewFormDao = DAORegistry::getDAO('ReviewFormDAO'); /** @var ReviewFormDAO $reviewFormDao */
$reviewFormsIterator = $reviewFormDao->getActiveByAssocId(Application::getContextAssocType(), $context->getId());
$reviewForms = [];
while ($reviewForm = $reviewFormsIterator->next()) {
$reviewForms[$reviewForm->getId()] = $reviewForm->getLocalizedTitle();
}
$templateMgr->assign('reviewForms', $reviewForms);
$templateMgr->assign('emailVariables', [
'recipientName' => __('user.name'),
'responseDueDate' => __('reviewer.submission.responseDueDate'),
'reviewDueDate' => __('reviewer.submission.reviewDueDate'),
'reviewAssignmentUrl' => __('common.url'),
'recipientUsername' => __('user.username'),
]);
$templates = $this->getEmailTemplates();
$templateMgr->assign([
'hasCustomTemplates' => (count($templates) > 1),
'templates' => $templates,
]);
// Get the reviewer user groups for the create new reviewer/enroll existing user tabs
$reviewRound = $this->getReviewRound();
$reviewerUserGroups = Repo::userGroup()->getUserGroupsByStage(
$context->getId(),
$reviewRound->getStageId(),
Role::ROLE_ID_REVIEWER
);
$userGroups = [];
foreach ($reviewerUserGroups as $userGroup) {
$userGroups[$userGroup->getId()] = $userGroup->getLocalizedName();
}
$this->setData('userGroups', $userGroups);
return parent::fetch($request, $template, $display);
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'selectionType',
'submissionId',
'template',
'personalMessage',
'responseDueDate',
'reviewDueDate',
'reviewMethod',
'skipEmail',
'keywords',
'interests',
'reviewRoundId',
'stageId',
'selectedFiles',
'reviewFormId',
]);
}
/**
* Save review assignment
*/
public function execute(...$functionParams)
{
parent::execute(...$functionParams);
$submission = $this->getSubmission();
$request = Application::get()->getRequest();
$context = $request->getContext();
$currentReviewRound = $this->getReviewRound();
$stageId = $currentReviewRound->getStageId();
$reviewDueDate = $this->getData('reviewDueDate');
$responseDueDate = $this->getData('responseDueDate');
// Get reviewer id and validate it.
$reviewerId = (int) $this->getData('reviewerId');
if (!$this->_isValidReviewer($context, $submission, $currentReviewRound, $reviewerId)) {
fatalError('Invalid reviewer id.');
}
$reviewMethod = (int) $this->getData('reviewMethod');
$editorAction = new EditorAction();
$editorAction->addReviewer($request, $submission, $reviewerId, $currentReviewRound, $reviewDueDate, $responseDueDate, $reviewMethod);
// Get the reviewAssignment object now that it has been added.
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignment = $reviewAssignmentDao->getReviewAssignment($currentReviewRound->getId(), $reviewerId);
$reviewAssignment->setDateNotified(Core::getCurrentDate());
$reviewAssignment->stampModified();
// Ensure that the review form ID is valid, if specified
$reviewFormId = (int) $this->getData('reviewFormId');
$reviewFormDao = DAORegistry::getDAO('ReviewFormDAO'); /** @var ReviewFormDAO $reviewFormDao */
$reviewForm = $reviewFormDao->getById($reviewFormId, Application::getContextAssocType(), $context->getId());
$reviewAssignment->setReviewFormId($reviewForm ? $reviewFormId : null);
$reviewAssignmentDao->updateObject($reviewAssignment);
$fileStages = [$stageId == WORKFLOW_STAGE_ID_INTERNAL_REVIEW ? SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE : SubmissionFile::SUBMISSION_FILE_REVIEW_FILE];
// Grant access for this review to all selected files.
$submissionFiles = Repo::submissionFile()
->getCollector()
->filterBySubmissionIds([$submission->getId()])
->filterByReviewRoundIds([$currentReviewRound->getId()])
->filterByFileStages($fileStages)
->getMany();
$selectedFiles = array_map(function ($id) {
return (int) $id;
}, (array) $this->getData('selectedFiles'));
$reviewFilesDao = DAORegistry::getDAO('ReviewFilesDAO'); /** @var ReviewFilesDAO $reviewFilesDao */
foreach ($submissionFiles as $submissionFile) {
if (in_array($submissionFile->getId(), $selectedFiles)) {
$reviewFilesDao->grant($reviewAssignment->getId(), $submissionFile->getId());
}
}
// Insert a trivial notification to indicate the reviewer was added successfully.
$reviewer = Repo::user()->get($reviewerId);
$currentUser = $request->getUser();
$notificationMgr = new NotificationManager();
$msgKey = $this->getData('skipEmail') ? 'notification.addedReviewerNoEmail' : 'notification.addedReviewer';
$notificationMgr->createTrivialNotification(
$currentUser->getId(),
PKPNotification::NOTIFICATION_TYPE_SUCCESS,
['contents' => __($msgKey, ['reviewerName' => $reviewer->getFullName()])]
);
return $reviewAssignment;
}
//
// Protected methods.
//
/**
* Get the link action that fetchs the advanced search form content
*
* @param Request $request
*
* @return LinkAction
*/
public function getAdvancedSearchAction($request)
{
$reviewRound = $this->getReviewRound();
return new LinkAction(
'addReviewer',
new AjaxAction($request->url(null, null, 'reloadReviewerForm', null, [
'submissionId' => $this->getSubmissionId(),
'stageId' => $reviewRound->getStageId(),
'reviewRoundId' => $reviewRound->getId(),
'selectionType' => PKPReviewerGridHandler::REVIEWER_SELECT_ADVANCED_SEARCH,
])),
__('editor.submission.backToSearch'),
'return'
);
}
//
// Private helper methods
//
/**
* Check if a given user id is enrolled in reviewer user group.
*
* @param Context $context
* @param Submission $submission
* @param ReviewRound $reviewRound
* @param int $reviewerId
*
* @return bool
*/
public function _isValidReviewer($context, $submission, $reviewRound, $reviewerId)
{
// Ensure the user isn't already assigned to the current submission
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignments = $reviewAssignmentDao->getBySubmissionId($submission->getId(), $reviewRound->getId());
foreach ($reviewAssignments as $reviewAssignment) {
if ($reviewerId == $reviewAssignment->getReviewerId()) {
return false;
}
}
// Ensure that they are a reviewer
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
return $roleDao->userHasRole($context->getId(), $reviewerId, Role::ROLE_ID_REVIEWER);
}
/**
* Get the Mailable and populate with data
*/
protected function getMailable(): ReviewRequest
{
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignment = $reviewAssignmentDao->newDataObject(); /** @var ReviewAssignment $reviewAssignment */
$reviewAssignment->setSubmissionId($this->getSubmissionId());
$submission = $this->getSubmission();
$request = Application::get()->getRequest();
$mailable = new ReviewRequest($request->getContext(), $submission, $reviewAssignment);
$mailable->sender($request->getUser());
$mailable->addData([
'messageToReviewer' => __('reviewer.step1.requestBoilerplate'),
'abstractTermIfEnabled' => ($submission->getLocalizedAbstract() == '' ? '' : __('common.abstract')), // Deprecated; for OJS 2.x templates
]);
// Remove template variables that haven't been set yet during form initialization
$mailable->setData(Locale::getLocale());
unset($mailable->viewData[ReviewAssignmentEmailVariable::REVIEW_DUE_DATE]);
unset($mailable->viewData[ReviewAssignmentEmailVariable::RESPONSE_DUE_DATE]);
unset($mailable->viewData[ReviewAssignmentEmailVariable::REVIEW_ASSIGNMENT_URL]);
return $mailable;
}
/**
* Get email templates associated with the form
*
* @return array [key => name]
*/
protected function getEmailTemplates(): array
{
$defaultTemplate = Repo::emailTemplate()->getByKey(
Application::get()->getRequest()->getContext()->getId(),
ReviewRequest::getEmailTemplateKey()
);
$templateKeys = [ReviewRequest::getEmailTemplateKey() => $defaultTemplate->getLocalizedData('name')];
$alternateTemplates = Repo::emailTemplate()->getCollector(Application::get()->getRequest()->getContext()->getId())
->alternateTo([ReviewRequest::getEmailTemplateKey()])
->getMany();
foreach ($alternateTemplates as $alternateTemplate) {
$templateKeys[$alternateTemplate->getData('key')] = $alternateTemplate->getLocalizedData('name');
}
return $templateKeys;
}
}
@@ -0,0 +1,83 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/ReviewerGossipForm.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ReviewerGossipForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Form for viewing and editing gossip about a reviewer
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\facades\Repo;
use APP\template\TemplateManager;
use PKP\form\Form;
use PKP\user\User;
class ReviewerGossipForm extends Form
{
/** @var User The user to gossip about */
public $_user;
/** @var array Arguments used to route the form op */
public $_requestArgs;
/**
* Constructor.
*
* @param User $user The user to gossip about
* @param array $requestArgs Arguments used to route the form op to the
* correct submission, stage and review round
*/
public function __construct($user, $requestArgs)
{
parent::__construct('controllers/grid/users/reviewer/form/reviewerGossipForm.tpl');
$this->_user = $user;
$this->_requestArgs = $requestArgs;
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
/**
* @copydoc Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'gossip',
]);
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'requestArgs' => $this->_requestArgs,
'gossip' => $this->_user->getGossip(),
]);
return parent::fetch($request, $template, $display);
}
/**
* @copydoc Form::execute()
*/
public function execute(...$functionArgs)
{
$this->_user->setGossip($this->getData('gossip'));
Repo::user()->edit($this->_user);
parent::execute(...$functionArgs);
}
}
@@ -0,0 +1,170 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/ReviewerNotifyActionForm.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 ReviewerNotifyActionForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Perform an action on a review including a reviewer notification email.
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\submission\Submission;
use Illuminate\Support\Facades\Mail;
use PKP\context\Context;
use PKP\form\Form;
use PKP\mail\Mailable;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewRound\ReviewRound;
abstract class ReviewerNotifyActionForm extends Form
{
/** @var ReviewAssignment The review assignment to alter */
public $_reviewAssignment;
/** @var Submission The submission associated with the review assignment */
public $_submission;
/** @var ReviewRound The review round associated with the review assignment */
public $_reviewRound;
/**
* Constructor
*
* @param ReviewAssignment $reviewAssignment
* @param ReviewRound $reviewRound
* @param Submission $submission
* @param string $template
*/
public function __construct($reviewAssignment, $reviewRound, $submission, $template)
{
$this->setReviewAssignment($reviewAssignment);
$this->setReviewRound($reviewRound);
$this->setSubmission($submission);
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
parent::__construct($template);
}
abstract protected function getMailable(Context $context, Submission $submission, ReviewAssignment $reviewAssignment): Mailable;
//
// Overridden template methods
//
/**
* @copydoc Form::initData
*/
public function initData()
{
$request = Application::get()->getRequest();
$submission = $this->getSubmission();
$reviewAssignment = $this->getReviewAssignment();
$reviewRound = $this->getReviewRound();
$reviewerId = $reviewAssignment->getReviewerId();
$this->setData([
'submissionId' => $submission->getId(),
'stageId' => $reviewRound->getStageId(),
'reviewRoundId' => $reviewRound->getId(),
'reviewAssignmentId' => $reviewAssignment->getId(),
'dateConfirmed' => $reviewAssignment->getDateConfirmed(),
'reviewerId' => $reviewerId,
]);
$context = $request->getContext();
$mailable = $this->getMailable($context, $submission, $reviewAssignment);
$mailable->sender($request->getUser());
$mailable->recipients([Repo::user()->get($reviewerId)]);
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
$this->setData('personalMessage', Mail::compileParams($template->getLocalizedData('body'), $mailable->getData()));
}
/**
* @copydoc Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'personalMessage',
'reviewAssignmentId',
'reviewRoundId',
'reviewerId',
'skipEmail',
'stageId',
'submissionId',
]);
}
//
// Getters and Setters
//
/**
* Set the ReviewAssignment
*
* @param mixed $reviewAssignment ReviewAssignment
*/
public function setReviewAssignment($reviewAssignment)
{
$this->_reviewAssignment = $reviewAssignment;
}
/**
* Get the ReviewAssignment
*
* @return ReviewAssignment
*/
public function getReviewAssignment()
{
return $this->_reviewAssignment;
}
/**
* Set the ReviewRound
*
* @param mixed $reviewRound ReviewRound
*/
public function setReviewRound($reviewRound)
{
$this->_reviewRound = $reviewRound;
}
/**
* Get the ReviewRound
*
* @return ReviewRound
*/
public function getReviewRound()
{
return $this->_reviewRound;
}
/**
* Set the submission
*
* @param Submission $submission
*/
public function setSubmission($submission)
{
$this->_submission = $submission;
}
/**
* Get the submission
*
* @return Submission
*/
public function getSubmission()
{
return $this->_submission;
}
}
@@ -0,0 +1,164 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/ThankReviewerForm.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 ThankReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Form for sending a thank you to a reviewer
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use Illuminate\Support\Facades\Mail;
use PKP\core\Core;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\form\Form;
use PKP\log\SubmissionEmailLogDAO;
use PKP\log\SubmissionEmailLogEntry;
use PKP\mail\mailables\ReviewAcknowledgement;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use Symfony\Component\Mailer\Exception\TransportException;
class ThankReviewerForm extends Form
{
/** @var ReviewAssignment The review assignment associated with the reviewer */
public $_reviewAssignment;
/**
* Constructor.
*/
public function __construct($reviewAssignment)
{
parent::__construct('controllers/grid/users/reviewer/form/thankReviewerForm.tpl');
$this->_reviewAssignment = $reviewAssignment;
// Validation checks for this form
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
//
// Getters and Setters
//
/**
* Get the review assignment
*
* @return ReviewAssignment
*/
public function getReviewAssignment()
{
return $this->_reviewAssignment;
}
//
// Overridden template methods
//
/**
* @copydoc Form::initData
*/
public function initData()
{
$request = Application::get()->getRequest();
$user = $request->getUser();
$reviewAssignment = $this->getReviewAssignment();
$reviewerId = $reviewAssignment->getReviewerId();
$reviewer = Repo::user()->get($reviewerId);
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
$contextDao = Application::getContextDAO();
$context = $contextDao->getById($submission->getData('contextId'));
$mailable = new ReviewAcknowledgement($context, $submission, $reviewAssignment);
$mailable->sender($user)->recipients([$reviewer]);
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable->getEmailTemplateKey());
$this->setData('submissionId', $submission->getId());
$this->setData('stageId', $reviewAssignment->getStageId());
$this->setData('reviewAssignmentId', $reviewAssignment->getId());
$this->setData('reviewAssignment', $reviewAssignment);
$this->setData('reviewerName', $reviewer->getFullName() . ' <' . $reviewer->getEmail() . '>');
$this->setData('message', Mail::compileParams($template->getLocalizedData('body'), $mailable->getData(Locale::getLocale())));
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars(['message', 'skipEmail']);
}
/**
* @copydoc Form::execute()
*/
public function execute(...$functionArgs)
{
$request = Application::get()->getRequest();
$reviewAssignment = $this->getReviewAssignment();
$reviewerId = $reviewAssignment->getReviewerId();
$reviewer = Repo::user()->get($reviewerId);
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
$contextDao = Application::getContextDAO();
$context = $contextDao->getById($submission->getData('contextId'));
$user = $request->getUser();
// Create mailable and populate with data
$mailable = new ReviewAcknowledgement($context, $submission, $reviewAssignment);
$mailable->sender($user)->recipients([$reviewer]);
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable->getEmailTemplateKey());
$mailable->body($this->getData('message'))->subject($template->getLocalizedData('subject'));
Hook::call('ThankReviewerForm::thankReviewer', [$submission, $reviewAssignment, $mailable]);
if (!$this->getData('skipEmail')) {
$mailable->setData(Locale::getLocale());
try {
Mail::send($mailable);
$submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $submissionEmailLogDao */
$submissionEmailLogDao->logMailable(
SubmissionEmailLogEntry::SUBMISSION_EMAIL_REVIEW_THANK_REVIEWER,
$mailable,
$submission,
$user,
);
} catch (TransportException $e) {
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification(
$request->getUser()->getId(),
PKPNotification::NOTIFICATION_TYPE_ERROR,
['contents' => __('email.compose.error')]
);
trigger_error($e->getMessage(), E_USER_WARNING);
}
}
// update the ReviewAssignment with the acknowledged date
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignment->setDateAcknowledged(Core::getCurrentDate());
$reviewAssignment->stampModified();
if (!in_array($reviewAssignment->getConsidered(), [ReviewAssignment::REVIEW_ASSIGNMENT_CONSIDERED, ReviewAssignment::REVIEW_ASSIGNMENT_RECONSIDERED])) {
$reviewAssignment->setConsidered(
$reviewAssignment->getConsidered() === ReviewAssignment::REVIEW_ASSIGNMENT_NEW
? ReviewAssignment::REVIEW_ASSIGNMENT_CONSIDERED
: ReviewAssignment::REVIEW_ASSIGNMENT_RECONSIDERED
);
}
$reviewAssignmentDao->updateObject($reviewAssignment);
parent::execute(...$functionArgs);
}
}
@@ -0,0 +1,126 @@
<?php
/**
* @file controllers/grid/users/reviewer/form/UnassignReviewerForm.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 UnassignReviewerForm
*
* @ingroup controllers_grid_users_reviewer_form
*
* @brief Allow the editor to remove a review assignment
*/
namespace PKP\controllers\grid\users\reviewer\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use PKP\context\Context;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\log\event\PKPSubmissionEventLogEntry;
use PKP\mail\Mailable;
use PKP\mail\mailables\ReviewerUnassign;
use PKP\notification\NotificationDAO;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\security\Validation;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
class UnassignReviewerForm extends ReviewerNotifyActionForm
{
/**
* Constructor
*
* @param mixed $reviewAssignment ReviewAssignment
* @param mixed $reviewRound ReviewRound
* @param mixed $submission Submission
*/
public function __construct($reviewAssignment, $reviewRound, $submission)
{
parent::__construct($reviewAssignment, $reviewRound, $submission, 'controllers/grid/users/reviewer/form/unassignReviewerForm.tpl');
}
/**
* @copydoc ReviewerNotifyActionForm::getMailable()
*/
protected function getMailable(Context $context, Submission $submission, ReviewAssignment $reviewAssignment): Mailable
{
return new ReviewerUnassign($context, $submission, $reviewAssignment);
}
/**
* @copydoc Form::execute()
*
* @return bool whether or not the review assignment was deleted successfully
*/
public function execute(...$functionArgs)
{
parent::execute(...$functionArgs);
$request = Application::get()->getRequest();
$submission = $this->getSubmission();
$reviewAssignment = $this->getReviewAssignment();
// Delete or cancel the review assignment.
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
if (isset($reviewAssignment) && $reviewAssignment->getSubmissionId() == $submission->getId() && !Hook::call('EditorAction::clearReview', [&$submission, $reviewAssignment])) {
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId(), true);
if (!isset($reviewer)) {
return false;
}
if ($reviewAssignment->getDateConfirmed()) {
// The review has been confirmed but not completed. Flag it as cancelled.
$reviewAssignment->setCancelled(true);
$reviewAssignmentDao->updateObject($reviewAssignment);
} else {
// The review had not been confirmed yet. Delete the assignment.
$reviewAssignmentDao->deleteById($reviewAssignment->getId());
}
// Stamp the modification date
$submission->stampModified();
Repo::submission()->dao->update($submission);
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
$notificationDao->deleteByAssoc(
Application::ASSOC_TYPE_REVIEW_ASSIGNMENT,
$reviewAssignment->getId(),
$reviewAssignment->getReviewerId(),
PKPNotification::NOTIFICATION_TYPE_REVIEW_ASSIGNMENT
);
// Insert a trivial notification to indicate the reviewer was removed successfully.
$currentUser = $request->getUser();
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification($currentUser->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS, ['contents' => $reviewAssignment->getDateConfirmed() ? __('notification.cancelledReviewer') : __('notification.removedReviewer')]);
// Add log
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_CLEAR,
'userId' => Validation::loggedInAs() ?? $currentUser->getId(),
'message' => 'log.review.reviewCleared',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
'reviewAssignmentId' => $reviewAssignment->getId(),
'reviewerName' => $reviewer->getFullName(),
'submissionId' => $submission->getId(),
'stageId' => $reviewAssignment->getStageId(),
'round' => $reviewAssignment->getRound()
]);
Repo::eventLog()->add($eventLog);
return true;
}
return false;
}
}