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,476 @@
<?php
/**
* @file controllers/grid/users/author/AuthorGridHandler.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 AuthorGridHandler
*
* @ingroup controllers_grid_users_author
*
* @deprecated 3.4
*
* @brief base PKP class to handle author grid requests.
*/
namespace PKP\controllers\grid\users\author;
use APP\controllers\grid\users\author\form\AuthorForm;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use PKP\controllers\grid\feature\OrderGridItemsFeature;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\GridHandler;
use PKP\controllers\grid\settings\user\form\UserDetailsForm;
use PKP\core\JSONMessage;
use PKP\core\PKPRequest;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\notification\PKPNotification;
use PKP\security\authorization\PublicationAccessPolicy;
use PKP\security\Role;
use PKP\submission\PKPSubmission;
use PKP\user\User;
class AuthorGridHandler extends GridHandler
{
/** @var bool */
public $_readOnly;
/** @var int */
public $_version;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->addRoleAssignment(
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR],
['fetchGrid', 'fetchRow', 'addAuthor', 'editAuthor',
'updateAuthor', 'deleteAuthor', 'saveSequence']
);
$this->addRoleAssignment(Role::ROLE_ID_REVIEWER, ['fetchGrid', 'fetchRow']);
$this->addRoleAssignment([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT], ['addUser']);
}
//
// Getters/Setters
//
/**
* Get the submission associated with this author grid.
*
* @return Submission
*/
public function getSubmission()
{
return $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
}
/**
* Get the publication associated with this author grid.
*
* @return Submission
*/
public function getPublication()
{
return $this->getAuthorizedContextObject(Application::ASSOC_TYPE_PUBLICATION);
}
/**
* Get whether or not this grid should be 'read only'
*
* @return bool
*/
public function getReadOnly()
{
return $this->_readOnly;
}
/**
* Set the boolean for 'read only' status
*
* @param bool $readOnly
*/
public function setReadOnly($readOnly)
{
$this->_readOnly = $readOnly;
}
//
// Overridden methods from PKPHandler.
//
/**
* @copydoc PKPHandler::authorize()
*/
public function authorize($request, &$args, $roleAssignments)
{
$this->addPolicy(new PublicationAccessPolicy($request, $args, $roleAssignments));
return parent::authorize($request, $args, $roleAssignments);
}
/**
* @copydoc GridHandler::initialize()
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
$this->setTitle('submission.contributors');
if ($this->getSubmission()->getData('submissionProgress') || $this->canAdminister($request->getUser())) {
$this->setReadOnly(false);
// Grid actions
$router = $request->getRouter();
$actionArgs = $this->getRequestArgs();
$this->addAction(
new LinkAction(
'addAuthor',
new AjaxModal(
$router->url($request, null, null, 'addAuthor', null, $actionArgs),
__('grid.action.addContributor'),
'modal_add_user'
),
__('grid.action.addContributor'),
'add_user'
)
);
} else {
$this->setReadOnly(true);
}
// Columns
$cellProvider = new PKPAuthorGridCellProvider($this->getPublication());
$this->addColumn(
new GridColumn(
'name',
'author.users.contributor.name',
null,
null,
$cellProvider,
['width' => 40, 'alignment' => GridColumn::COLUMN_ALIGNMENT_LEFT]
)
);
$this->addColumn(
new GridColumn(
'email',
'author.users.contributor.email',
null,
null,
$cellProvider
)
);
$this->addColumn(
new GridColumn(
'role',
'author.users.contributor.role',
null,
null,
$cellProvider
)
);
$this->addColumn(
new GridColumn(
'principalContact',
'author.users.contributor.principalContact',
null,
'controllers/grid/users/author/primaryContact.tpl',
$cellProvider
)
);
$this->addColumn(
new GridColumn(
'includeInBrowse',
'author.users.contributor.includeInBrowse',
null,
'controllers/grid/users/author/includeInBrowse.tpl',
$cellProvider
)
);
}
//
// Overridden methods from GridHandler
//
/**
* @see GridHandler::initFeatures()
*/
public function initFeatures($request, $args)
{
$features = parent::initFeatures($request, $args);
if ($this->canAdminister($request->getUser())) {
$features[] = new OrderGridItemsFeature();
}
return $features;
}
/**
* @copydoc GridHandler::getDataElementSequence()
*/
public function getDataElementSequence($gridDataElement)
{
return $gridDataElement->getSequence();
}
/**
* @copydoc GridHandler::setDataElementSequence()
*/
public function setDataElementSequence($request, $rowId, $gridDataElement, $newSequence)
{
if (!$this->canAdminister($request->getUser())) {
return;
}
$author = Repo::author()->get((int) $rowId, $this->getPublication()->getId());
Repo::author()->edit($author, ['seq' => $newSequence]);
}
/**
* @copydoc GridHandler::getRowInstance()
*
* @return AuthorGridRow
*/
protected function getRowInstance()
{
return new AuthorGridRow($this->getSubmission(), $this->getPublication(), $this->getReadOnly());
}
/**
* Get the arguments that will identify the data in the grid.
* Overridden by child grids.
*
* @return array
*/
public function getRequestArgs()
{
$submission = $this->getSubmission();
$publication = $this->getPublication();
return [
'submissionId' => $submission->getId(),
'publicationId' => $publication->getId()
];
}
/**
* Determines if there should be add/edit actions on this grid.
*
* @param User $user
*
* @return bool
*/
public function canAdminister($user)
{
$publication = $this->getPublication();
$submission = $this->getSubmission();
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return false;
}
if (in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles)) {
return true;
}
// Incomplete submissions can be edited. (Presumably author.)
if ($submission->getDateSubmitted() == null) {
return true;
}
// The user may not be allowed to edit the metadata
if (Repo::submission()->canEditPublication($submission->getId(), $user->getId())) {
return true;
}
// Default: Read-only.
return false;
}
/**
* @copydoc GridHandler::loadData()
*
* @param null|mixed $filter
*/
protected function loadData($request, $filter = null)
{
return Repo::author()
->getCollector()
->filterByPublicationIds([$this->getPublication()->getId()])
->orderBy(Repo::author()->getCollector()::ORDERBY_SEQUENCE)
->getMany();
}
//
// Public Author Grid Actions
//
/**
* An action to manually add a new author
*
* @param array $args
* @param PKPRequest $request
*/
public function addAuthor($args, $request)
{
if (!$this->canAdminister($request->getUser())) {
return new JSONMessage(false);
}
// Calling editAuthor() with an empty row id will add
// a new author.
return $this->editAuthor($args, $request);
}
/**
* Edit an author
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function editAuthor($args, $request)
{
if (!$this->canAdminister($request->getUser())) {
return new JSONMessage(false);
}
// Identify the author to be updated
$authorId = (int) $request->getUserVar('authorId');
$author = Repo::author()->get($authorId, $this->getPublication()->getId());
// Form handling
$authorForm = new AuthorForm($this->getPublication(), $author);
$authorForm->initData();
return new JSONMessage(true, $authorForm->fetch($request));
}
/**
* Update an author
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function updateAuthor($args, $request)
{
if (!$this->canAdminister($request->getUser())) {
return new JSONMessage(false);
}
// Identify the author to be updated
$authorId = (int) $request->getUserVar('authorId');
$publication = $this->getPublication();
$author = Repo::author()->get($authorId, $publication->getId());
// Form handling
$authorForm = new AuthorForm($publication, $author);
$authorForm->readInputData();
if ($authorForm->validate()) {
$authorId = $authorForm->execute();
if (!isset($author)) {
// This is a new contributor
$author = Repo::author()->get($authorId, $publication->getId());
// New added author action notification content.
$notificationContent = __('notification.addedAuthor');
} else {
// Author edition action notification content.
$notificationContent = __('notification.editedAuthor');
}
// Create trivial notification.
$currentUser = $request->getUser();
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification($currentUser->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS, ['contents' => $notificationContent]);
// Prepare the grid row data
$row = $this->getRowInstance();
$row->setGridId($this->getId());
$row->setId($authorId);
$row->setData($author);
$row->initialize($request);
// Render the row into a JSON response
if ($author->getPrimaryContact()) {
// If this is the primary contact, redraw the whole grid
// so that it takes the checkbox off other rows.
$json = \PKP\db\DAO::getDataChangedEvent();
} else {
$json = \PKP\db\DAO::getDataChangedEvent($authorId);
}
$json->setGlobalEvent('authorsUpdated');
return $json;
} else {
return new JSONMessage(true, $authorForm->fetch($request));
}
}
/**
* Delete a author
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function deleteAuthor($args, $request)
{
if (!$request->checkCSRF()) {
return new JSONMessage(false);
}
if (!$this->canAdminister($request->getUser())) {
return new JSONMessage(false);
}
$authorId = (int) $request->getUserVar('authorId');
$author = Repo::author()->get($authorId, $this->getPublication()->getId());
if (!$author) {
return new JSONMessage(false);
}
Repo::author()->delete($author);
$json = \PKP\db\DAO::getDataChangedEvent($authorId);
$json->setGlobalEvent('authorsUpdated');
return $json;
}
/**
* Add a user with data initialized from an existing author.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function addUser($args, $request)
{
// Identify the author Id.
$authorId = (int) $request->getUserVar('authorId');
$author = Repo::author()->get($authorId, $this->getPublication()->getId());
if ($author !== null && Repo::user()->getByEmail($author->getEmail(), true)) {
// We don't have administrative rights over this user.
return new JSONMessage(false, __('grid.user.cannotAdminister'));
}
// Form handling.
$userForm = new UserDetailsForm($request, null, $author);
$userForm->attachValidationChecks($request)->initData();
return new JSONMessage(true, $userForm->display($request));
}
}
@@ -0,0 +1,185 @@
<?php
/**
* @file controllers/grid/users/author/AuthorGridRow.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 AuthorGridRow
*
* @ingroup controllers_grid_users_author
*
* @deprecated 3.4
*
* @brief Base class for author grid row definition
*/
namespace PKP\controllers\grid\users\author;
use APP\facades\Repo;
use APP\publication\Publication;
use APP\submission\Submission;
use PKP\controllers\grid\GridRow;
use PKP\core\PKPRequest;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\linkAction\request\RemoteActionConfirmationModal;
class AuthorGridRow extends GridRow
{
/** @var Submission */
public $_submission;
/** @var Publication */
public $_publication;
/** @var bool */
public $_readOnly;
/** @var int */
public $_version;
/**
* Constructor
*/
public function __construct($submission, $publication, $readOnly = false)
{
$this->_submission = $submission;
$this->_publication = $publication;
$this->_readOnly = $readOnly;
parent::__construct();
}
//
// Overridden methods from GridRow
//
/**
* @copydoc GridRow::initialize()
*
* @param null|mixed $template
*/
public function initialize($request, $template = null)
{
// Do the default initialization
parent::initialize($request, $template);
// 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 = $this->getRequestArgs();
$actionArgs['authorId'] = $rowId;
if (!$this->isReadOnly()) {
// Add row-level actions
$this->addAction(
new LinkAction(
'editAuthor',
new AjaxModal(
$router->url($request, null, null, 'editAuthor', null, $actionArgs),
__('grid.action.editContributor'),
'modal_edit'
),
__('grid.action.edit'),
'edit'
)
);
$this->addAction(
new LinkAction(
'deleteAuthor',
new RemoteActionConfirmationModal(
$request->getSession(),
__('common.confirmDelete'),
__('common.delete'),
$router->url($request, null, null, 'deleteAuthor', null, $actionArgs),
'modal_delete'
),
__('grid.action.delete'),
'delete'
)
);
$author = Repo::author()->get((int) $rowId, $this->getPublication()->getId());
if ($author && !Repo::user()->getByEmail($author->getEmail(), true)) {
$this->addAction(
new LinkAction(
'addUser',
new AjaxModal(
$router->url($request, null, null, 'addUser', null, $actionArgs),
__('grid.user.add'),
'modal_add_user',
true
),
__('grid.user.add'),
'add_user'
)
);
}
}
}
}
/**
* Get the submission for this row (already authorized)
*
* @return Submission
*/
public function getSubmission()
{
return $this->_submission;
}
/**
* Get the publication for this row (already authorized)
*
* @return Publication
*/
public function getPublication()
{
return $this->_publication;
}
/**
* Get the base arguments that will identify the data in the grid.
*
* @return array
*/
public function getRequestArgs()
{
$submission = $this->getSubmission();
$publication = $this->getPublication();
return [
'submissionId' => $submission->getId(),
'publicationId' => $publication->getId()
];
}
/**
* Determines whether the current user can create user accounts from authors present
* in the grid.
* Overridden by child grid rows.
*
* @param PKPRequest $request
*
* @return bool
*/
public function allowedToCreateUser($request)
{
return false;
}
/**
* Determine if this grid row should be read only.
*
* @return bool
*/
public function isReadOnly()
{
return $this->_readOnly;
}
}
@@ -0,0 +1,72 @@
<?php
/**
* @file controllers/grid/users/author/PKPAuthorGridCellProvider.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 PKPAuthorGridCellProvider
*
* @ingroup controllers_grid_users_author
*
* @deprecated 3.4
*
* @brief Base class for a cell provider that can retrieve labels for submission contributors
*/
namespace PKP\controllers\grid\users\author;
use APP\author\Author;
use APP\publication\Publication;
use PKP\controllers\grid\DataObjectGridCellProvider;
use PKP\controllers\grid\GridColumn;
class PKPAuthorGridCellProvider extends DataObjectGridCellProvider
{
/** @var Publication The publication this author is related to */
private $_publication;
/**
* Constructor
*
* @param Publication $publication
*/
public function __construct($publication)
{
$this->_publication = $publication;
}
//
// Template methods from GridCellProvider
//
/**
* 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 Author $element */
switch ($columnId) {
case 'name':
return ['label' => $element->getFullName()];
case 'role':
return ['label' => $element->getLocalizedUserGroupName()];
case 'email':
return parent::getTemplateVarsFromRowColumn($row, $column);
case 'principalContact':
return ['isPrincipalContact' => $this->_publication->getData('primaryContactId') === $element->getId()];
case 'includeInBrowse':
return ['includeInBrowse' => $element->getIncludeInBrowse()];
}
}
}
@@ -0,0 +1,271 @@
<?php
/**
* @file controllers/grid/users/author/form/PKPAuthorForm.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 PKPAuthorForm
*
* @ingroup controllers_grid_users_author_form
*
* @deprecated 3.4
*
* @brief Form for adding/editing a author
*/
namespace PKP\controllers\grid\users\author\form;
use APP\author\Author;
use APP\core\Services;
use APP\facades\Repo;
use APP\publication\Publication;
use APP\template\TemplateManager;
use Exception;
use PKP\facades\Locale;
use PKP\form\Form;
use PKP\security\Role;
class PKPAuthorForm extends Form
{
/** @var Publication publication associated with the contributor being edited */
public $_publication;
/** @var Author the author being edited */
public $_author;
/**
* Constructor.
*
* @param Publication $publication
*/
public function __construct($publication, $author)
{
parent::__construct('controllers/grid/users/author/form/authorForm.tpl');
$this->setPublication($publication);
$this->setAuthor($author);
// the publication locale should be the default/required locale
$this->setDefaultFormLocale($publication->getData('locale'));
// Validation checks for this form
$form = $this;
$this->addCheck(new \PKP\form\validation\FormValidatorLocale($this, 'givenName', 'required', 'user.profile.form.givenNameRequired', $this->defaultLocale));
$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\FormValidatorEmail($this, 'email', 'required', 'form.emailRequired'));
$this->addCheck(new \PKP\form\validation\FormValidatorUrl($this, 'userUrl', 'optional', 'user.profile.form.urlInvalid'));
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'userGroupId', 'required', 'submission.submit.form.contributorRoleRequired'));
$this->addCheck(new \PKP\form\validation\FormValidatorORCID($this, 'orcid', 'optional', 'user.orcid.orcidInvalid'));
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
//
// Getters and Setters
//
/**
* Get the author
*
* @return Author
*/
public function getAuthor(): ?Author
{
return $this->_author;
}
/**
* Set the author
*
* @param Author $author
*/
public function setAuthor($author)
{
$this->_author = $author;
}
/**
* Get the Publication
*
* @return Publication
*/
public function getPublication()
{
return $this->_publication;
}
/**
* Set the Publication
*
* @param Publication $publication
*/
public function setPublication($publication)
{
$this->_publication = $publication;
}
//
// Overridden template methods
//
/**
* Initialize form data from the associated author.
*/
public function initData()
{
$author = $this->getAuthor();
if ($author) {
$this->_data = [
'authorId' => $author->getId(),
'givenName' => $author->getGivenName(null),
'familyName' => $author->getFamilyName(null),
'preferredPublicName' => $author->getPreferredPublicName(null),
'affiliation' => $author->getAffiliation(null),
'country' => $author->getCountry(),
'email' => $author->getEmail(),
'userUrl' => $author->getUrl(),
'orcid' => $author->getOrcid(),
'competingInterests' => $author->getCompetingInterests(null),
'userGroupId' => $author->getUserGroupId(),
'biography' => $author->getBiography(null),
'primaryContact' => $this->getPublication()->getData('primaryContactId') === $author->getId(),
'includeInBrowse' => $author->getIncludeInBrowse(),
];
} else {
// assume authors should be listed unless otherwise specified.
$this->_data = ['includeInBrowse' => true];
}
// in order to be able to use the hook
return parent::initData();
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$context = $request->getContext();
$authorUserGroups = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_AUTHOR], $context->getId());
$publication = $this->getPublication();
$countries = [];
foreach (Locale::getCountries() as $country) {
$countries[$country->getAlpha2()] = $country->getLocalName();
}
asort($countries);
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'submissionId' => $publication->getData('submissionId'),
'publicationId' => $publication->getId(),
'countries' => $countries,
'authorUserGroups' => $authorUserGroups,
'requireAuthorCompetingInterests' => $context->getData('requireAuthorCompetingInterests'),
]);
return parent::fetch($request, $template, $display);
}
/**
* Assign form data to user-submitted data.
*
* @see Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'authorId',
'givenName',
'familyName',
'preferredPublicName',
'affiliation',
'country',
'email',
'userUrl',
'orcid',
'competingInterests',
'userGroupId',
'biography',
'primaryContact',
'includeInBrowse',
]);
}
/**
* Save author
*
* @see Form::execute()
*/
public function execute(...$functionParams)
{
$publication = $this->getPublication(); /** @var Publication $publication */
$submission = Repo::submission()->get($publication->getData('submissionId'));
$context = Services::get('context')->get($submission->getData('contextId'));
$author = $this->getAuthor();
if (!$author) {
// this is a new submission contributor
$this->_author = Repo::author()->newDataObject();
$author = $this->getAuthor();
$author->setData('publicationId', $publication->getId());
$author->setData('seq', count($publication->getData('authors')));
$existingAuthor = false;
} else {
$existingAuthor = true;
if ($publication->getId() !== $author->getData('publicationId')) {
fatalError('Invalid author!');
}
}
$author->setGivenName(array_map('trim', $this->getData('givenName')), null);
$author->setFamilyName($this->getData('familyName'), null);
$author->setPreferredPublicName($this->getData('preferredPublicName'), null);
$author->setAffiliation($this->getData('affiliation'), null); // localized
$author->setCountry($this->getData('country'));
$author->setEmail($this->getData('email'));
$author->setUrl($this->getData('userUrl'));
$author->setOrcid($this->getData('orcid'));
if ($context->getData('requireAuthorCompetingInterests')) {
$author->setCompetingInterests($this->getData('competingInterests'), null);
}
$author->setUserGroupId($this->getData('userGroupId'));
$author->setBiography($this->getData('biography'), null); // localized
$author->setIncludeInBrowse(($this->getData('includeInBrowse') ? true : false));
// in order to be able to use the hook
parent::execute(...$functionParams);
if ($existingAuthor) {
Repo::author()->edit($author, []);
$authorId = $author->getId();
} else {
$authorId = Repo::author()->add($author);
}
if ($this->getData('primaryContact')) {
$params = ['primaryContactId' => $authorId];
$errors = Repo::publication()->validate(
$publication,
$params,
$submission,
$context
);
if (!empty($errors)) {
throw new Exception('Invalid primary contact ID. This author can not be a primary contact.');
}
Repo::publication()->edit($publication, $params);
}
return $authorId;
}
}
@@ -0,0 +1,289 @@
<?php
/**
* @file controllers/grid/users/exportableUsers/ExportableUsersGridHandler.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 ExportableUsersGridHandler
*
* @ingroup controllers_grid_users_exportableUsers
*
* @brief Handle exportable user grid requests.
*/
namespace PKP\controllers\grid\users\exportableUsers;
use APP\facades\Repo;
use PKP\controllers\grid\DataObjectGridCellProvider;
use PKP\controllers\grid\feature\PagingFeature;
use PKP\controllers\grid\feature\selectableItems\SelectableItemsFeature;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\GridHandler;
use PKP\core\PKPApplication;
use PKP\identity\Identity;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\RedirectConfirmationModal;
use PKP\security\authorization\ContextAccessPolicy;
use PKP\security\Role;
class ExportableUsersGridHandler extends GridHandler
{
public $_pluginName;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->addRoleAssignment(
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN],
['fetchGrid', 'fetchRow']
);
}
//
// Implement template methods from PKPHandler.
//
/**
* @copydoc PKPHandler::authorize()
*/
public function authorize($request, &$args, $roleAssignments)
{
$this->addPolicy(new ContextAccessPolicy($request, $roleAssignments));
return parent::authorize($request, $args, $roleAssignments);
}
/**
* @copydoc GridHandler::initialize()
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
// Basic grid configuration.
$this->setTitle('grid.user.currentUsers');
// Grid actions.
$router = $request->getRouter();
$pluginName = $request->getUserVar('pluginName');
assert(!empty($pluginName));
$this->_pluginName = $pluginName;
$dispatcher = $request->getDispatcher();
$url = $dispatcher->url($request, PKPApplication::ROUTE_PAGE, null, 'management', 'importexport', ['plugin', $pluginName, 'exportAllUsers']);
$this->addAction(
new LinkAction(
'exportAllUsers',
new RedirectConfirmationModal(
__('grid.users.confirmExportAllUsers'),
null,
$url
),
__('grid.action.exportAllUsers'),
'export_users'
)
);
//
// Grid columns.
//
// First Name.
$cellProvider = new DataObjectGridCellProvider();
$this->addColumn(
new GridColumn(
'givenName',
'user.givenName',
null,
null,
$cellProvider
)
);
// Last Name.
$cellProvider = new DataObjectGridCellProvider();
$this->addColumn(
new GridColumn(
'familyName',
'user.familyName',
null,
null,
$cellProvider
)
);
// User name.
$cellProvider = new DataObjectGridCellProvider();
$this->addColumn(
new GridColumn(
'username',
'user.username',
null,
null,
$cellProvider
)
);
// Email.
$cellProvider = new DataObjectGridCellProvider();
$this->addColumn(
new GridColumn(
'email',
'user.email',
null,
null,
$cellProvider
)
);
}
//
// Implement methods from GridHandler.
//
/**
* @copydoc GridHandler::initFeatures()
*/
public function initFeatures($request, $args)
{
return [new SelectableItemsFeature(), new PagingFeature()];
}
/**
* @copydoc GridHandler::getSelectName()
*/
public function getSelectName()
{
return 'selectedUsers';
}
//
// Implemented methods from GridHandler.
//
/**
* @copydoc GridHandler::isDataElementSelected()
*/
public function isDataElementSelected($gridDataElement)
{
return false; // Nothing is selected by default
}
/**
* @copydoc GridHandler::loadData()
*/
protected function loadData($request, $filter)
{
// Get the context.
$context = $request->getContext();
// The user interface uses filter['userGroup'] and $filter['search']
$userGroupSearchTerm = $filter['userGroup'] ? [$filter['userGroup']] : null;
$userCollector = Repo::user()->getCollector()
->filterByContextIds([$context->getId()])
->searchPhrase($filter['search'])
->filterByUserGroupIds($userGroupSearchTerm);
// Get all users for this context that match search criteria.
$rangeInfo = $this->getGridRangeInfo($request, $this->getId());
$totalCount = $userCollector->getCount();
$userCollector->limit($rangeInfo->getCount());
$userCollector->offset($rangeInfo->getOffset() + max(0, $rangeInfo->getPage() - 1) * $rangeInfo->getCount());
$iterator = $userCollector->getMany();
return new \PKP\core\VirtualArrayIterator(iterator_to_array($iterator, true), $totalCount, $rangeInfo->getPage(), $rangeInfo->getCount());
}
/**
* @copydoc GridHandler::renderFilter()
*/
public function renderFilter($request, $filterData = [])
{
$context = $request->getContext();
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
$userGroupOptions = ['' => __('grid.user.allRoles')];
foreach ($userGroups as $userGroup) {
$userGroupOptions[$userGroup->getId()] = $userGroup->getLocalizedName();
}
$userDao = Repo::user()->dao;
$fieldOptions = [
Identity::IDENTITY_SETTING_GIVENNAME => 'user.givenName',
Identity::IDENTITY_SETTING_FAMILYNAME => 'user.familyName',
$userDao::USER_FIELD_USERNAME => 'user.username',
$userDao::USER_FIELD_EMAIL => 'user.email'
];
$matchOptions = [
'contains' => 'form.contains',
'is' => 'form.is'
];
$filterData = [
'userGroupOptions' => $userGroupOptions,
'fieldOptions' => $fieldOptions,
'matchOptions' => $matchOptions
];
return parent::renderFilter($request, $filterData);
}
/**
* @copydoc GridHandler::getFilterSelectionData()
*
* @return array Filter selection data.
*/
public function getFilterSelectionData($request)
{
// Get the search terms.
$userGroup = $request->getUserVar('userGroup') ? (int)$request->getUserVar('userGroup') : null;
$searchField = $request->getUserVar('searchField');
$searchMatch = $request->getUserVar('searchMatch');
$search = $request->getUserVar('search');
return $filterSelectionData = [
'userGroup' => $userGroup,
'searchField' => $searchField,
'searchMatch' => $searchMatch,
'search' => $search ? $search : ''
];
}
/**
* @copydoc GridHandler::getFilterForm()
*
* @return string Filter template.
*/
protected function getFilterForm()
{
return 'controllers/grid/users/exportableUsers/userGridFilter.tpl';
}
/**
* @see GridHandler::getRequestArgs()
*/
public function getRequestArgs()
{
return array_merge(parent::getRequestArgs(), ['pluginName' => $this->_getPluginName()]);
}
/**
* Fetch the name of the plugin for this grid's calling context.
*
* @return string
*/
public function _getPluginName()
{
return $this->_pluginName;
}
}
@@ -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;
}
}
@@ -0,0 +1,76 @@
<?php
/**
* @file controllers/grid/users/stageParticipant/StageParticipantGridCategoryRow.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 StageParticipantGridCategoryRow
*
* @ingroup controllers_grid_users_stageParticipant
*
* @brief Stage participant grid category row definition
*/
namespace PKP\controllers\grid\users\stageParticipant;
use APP\submission\Submission;
use PKP\controllers\grid\GridCategoryRow;
// Link actions
class StageParticipantGridCategoryRow extends GridCategoryRow
{
/** @var Submission */
public $_submission;
/** @var int */
public $_stageId;
/**
* Constructor
*/
public function __construct($submission, $stageId)
{
$this->_submission = $submission;
$this->_stageId = $stageId;
parent::__construct();
}
//
// Overridden methods from GridCategoryRow
//
/**
* @copydoc GridCategoryRow::getCategoryLabel()
*/
public function getCategoryLabel()
{
$userGroup = $this->getData();
return $userGroup->getLocalizedName();
}
//
// Private methods
//
/**
* Get the submission for this row (already authorized)
*
* @return Submission
*/
public function getSubmission()
{
return $this->_submission;
}
/**
* Get the stage ID for this grid.
*
* @return int
*/
public function getStageId()
{
return $this->_stageId;
}
}
@@ -0,0 +1,49 @@
<?php
/**
* @file controllers/grid/users/stageParticipant/StageParticipantGridCellProvider.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 StageParticipantGridCellProvider
*
* @ingroup controllers_grid_users_submissionContributor
*
* @brief Cell provider to retrieve the user's name from the stage assignment
*/
namespace PKP\controllers\grid\users\stageParticipant;
use APP\facades\Repo;
use PKP\controllers\grid\DataObjectGridCellProvider;
use PKP\controllers\grid\GridColumn;
class StageParticipantGridCellProvider extends DataObjectGridCellProvider
{
//
// Template methods from GridCellProvider
//
/**
* 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)
{
switch ($column->getId()) {
case 'participants':
$stageAssignment = $row->getData();
$user = Repo::user()->get($stageAssignment->getUserId(), true);
assert($user);
return ['label' => $user ? $user->getFullName() : ''];
default:
assert(false);
}
}
}
@@ -0,0 +1,614 @@
<?php
/**
* @file controllers/grid/users/stageParticipant/StageParticipantGridHandler.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 StageParticipantGridHandler
*
* @ingroup controllers_grid_users_stageParticipant
*
* @brief Handle stageParticipant grid requests.
*/
namespace PKP\controllers\grid\users\stageParticipant;
use APP\core\Application;
use APP\core\Request;
use APP\facades\Repo;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use Illuminate\Support\Facades\Mail;
use PKP\controllers\grid\CategoryGridHandler;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\queries\traits\StageMailable;
use PKP\controllers\grid\users\stageParticipant\form\AddParticipantForm;
use PKP\controllers\grid\users\stageParticipant\form\PKPStageParticipantNotifyForm;
use PKP\core\Core;
use PKP\core\JSONMessage;
use PKP\core\PKPApplication;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\linkAction\request\RedirectAction;
use PKP\notification\NotificationDAO;
use PKP\log\event\PKPSubmissionEventLogEntry;
use PKP\notification\PKPNotification;
use PKP\security\authorization\WorkflowStageAccessPolicy;
use PKP\security\Role;
use PKP\security\Validation;
use PKP\stageAssignment\StageAssignmentDAO;
class StageParticipantGridHandler extends CategoryGridHandler
{
use StageMailable;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
// Assistants get read-only access
$this->addRoleAssignment(
[Role::ROLE_ID_ASSISTANT],
$peOps = ['fetchGrid', 'fetchCategory', 'fetchRow', 'viewNotify', 'fetchTemplateBody', 'sendNotification']
);
// Managers and Editors additionally get administrative access
$this->addRoleAssignment(
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR],
array_merge($peOps, ['addParticipant', 'deleteParticipant', 'saveParticipant', 'fetchUserList'])
);
$this->setTitle('editor.submission.stageParticipants');
}
//
// Getters/Setters
//
/**
* Get the authorized submission.
*
* @return Submission
*/
public function getSubmission()
{
return $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
}
/**
* Get the authorized workflow stage.
*
* @return int
*/
public function getStageId()
{
return $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
}
//
// Overridden methods from PKPHandler
//
/**
* @copydoc PKPHandler::authorize()
*/
public function authorize($request, &$args, $roleAssignments)
{
$stageId = (int) $request->getUserVar('stageId');
$this->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
return parent::authorize($request, $args, $roleAssignments);
}
/**
* Determine whether the current user has admin privileges for this
* grid.
*
* @return bool
*/
protected function _canAdminister()
{
// If the current role set includes Manager or Editor, grant.
return (bool) array_intersect(
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR],
$this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES)
);
}
/**
* @copydoc CategoryGridHandler::initialize()
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
// Columns
$cellProvider = new StageParticipantGridCellProvider();
$this->addColumn(new GridColumn(
'participants',
null,
null,
null,
$cellProvider
));
$submission = $this->getSubmission();
$submissionId = $submission->getId();
if (Validation::loggedInAs()) {
$router = $request->getRouter();
$dispatcher = $router->getDispatcher();
$user = $request->getUser();
$redirectUrl = $dispatcher->url(
$request,
PKPApplication::ROUTE_PAGE,
null,
'workflow',
'access',
$submissionId
);
$this->addAction(
new LinkAction(
'signOutAsUser',
new RedirectAction(
$dispatcher->url($request, PKPApplication::ROUTE_PAGE, null, 'login', 'signOutAsUser', null, ['redirectUrl' => $redirectUrl])
),
__('user.logOutAs', ['username' => $user->getUsername()]),
null,
__('user.logOutAs', ['username' => $user->getUsername()])
)
);
}
// The "Add stage participant" grid action is available to
// Editors and Managers only
if ($this->_canAdminister()) {
$router = $request->getRouter();
$this->addAction(
new LinkAction(
'requestAccount',
new AjaxModal(
$router->url($request, null, null, 'addParticipant', null, $this->getRequestArgs()),
__('editor.submission.addStageParticipant'),
'modal_add_user'
),
__('common.assign'),
'add_user'
)
);
}
$this->setEmptyCategoryRowText('editor.submission.noneAssigned');
}
//
// Overridden methods from [Category]GridHandler
//
/**
* @copydoc CategoryGridHandler::loadCategoryData()
*
* @param null|mixed $filter
*/
public function loadCategoryData($request, &$userGroup, $filter = null)
{
// Retrieve useful objects.
$submission = $this->getSubmission();
$stageId = $this->getStageId();
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignments = $stageAssignmentDao->getBySubmissionAndStageId(
$submission->getId(),
$stageId,
$userGroup->getId()
);
return $stageAssignments->toAssociativeArray();
}
/**
* @copydoc GridHandler::isSubComponent()
*/
public function getIsSubcomponent()
{
return true;
}
/**
* @copydoc GridHandler::getRowInstance()
*/
protected function getRowInstance()
{
return new StageParticipantGridRow($this->getSubmission(), $this->getStageId(), $this->_canAdminister());
}
/**
* @copydoc CategoryGridHandler::getCategoryRowInstance()
*/
protected function getCategoryRowInstance()
{
$submission = $this->getSubmission();
return new StageParticipantGridCategoryRow($submission, $this->getStageId());
}
/**
* @copydoc CategoryGridHandler::getCategoryRowIdParameterName()
*/
public function getCategoryRowIdParameterName()
{
return 'userGroupId';
}
/**
* @copydoc GridHandler::getRequestArgs()
*/
public function getRequestArgs()
{
$submission = $this->getSubmission();
return array_merge(
parent::getRequestArgs(),
[
'submissionId' => $submission->getId(),
'stageId' => $this->getStageId(),
]
);
}
/**
* @copydoc GridHandler::loadData()
*/
protected function loadData($request, $filter)
{
$submission = $this->getSubmission();
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignments = $stageAssignmentDao->getBySubmissionAndStageId(
$this->getSubmission()->getId(),
$this->getStageId()
);
// Make a list of the active (non-reviewer) user groups.
$userGroupIds = [];
while ($stageAssignment = $stageAssignments->next()) {
$userGroupIds[] = $stageAssignment->getUserGroupId();
}
// Fetch the desired user groups as objects.
$result = [];
$userGroups = Repo::userGroup()->getUserGroupsByStage(
$request->getContext()->getId(),
$this->getStageId()
);
foreach ($userGroups as $userGroup) {
if ($userGroup->getRoleId() == Role::ROLE_ID_REVIEWER) {
continue;
}
if (!in_array($userGroup->getId(), $userGroupIds)) {
continue;
}
$result[$userGroup->getId()] = $userGroup;
}
return $result;
}
//
// Public actions
//
/**
* Add a participant to the stages
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function addParticipant($args, $request)
{
$submission = $this->getSubmission();
$stageId = $this->getStageId();
$assignmentId = null;
if (array_key_exists('assignmentId', $args)) {
$assignmentId = $args['assignmentId'];
}
$userGroups = $this->getGridDataElements($request);
$form = new AddParticipantForm($submission, $stageId, $assignmentId);
$form->initData();
return new JSONMessage(true, $form->fetch($request));
}
/**
* Update the row for the current userGroup's stage participant list.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function saveParticipant($args, $request)
{
$submission = $this->getSubmission();
$stageId = $this->getStageId();
$assignmentId = $args['assignmentId'];
$userGroups = $this->getGridDataElements($request);
$form = new AddParticipantForm($submission, $stageId, $assignmentId);
$form->readInputData();
if ($form->validate()) {
[$userGroupId, $userId, $stageAssignmentId] = $form->execute();
$notificationMgr = new NotificationManager();
// Check user group role id.
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$userGroup = Repo::userGroup()->get($userGroupId);
if ($userGroup->getRoleId() == Role::ROLE_ID_MANAGER) {
$notificationMgr->updateNotification(
$request,
$notificationMgr->getDecisionStageNotifications(),
null,
Application::ASSOC_TYPE_SUBMISSION,
$submission->getId()
);
}
$stages = Application::getApplicationStages();
foreach ($stages as $workingStageId) {
// remove the 'editor required' task if we now have an editor assigned
if ($stageAssignmentDao->editorAssignedToStage($submission->getId(), $workingStageId)) {
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
$notificationDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $submission->getId(), null, PKPNotification::NOTIFICATION_TYPE_EDITOR_ASSIGNMENT_REQUIRED);
}
}
// Create trivial notification.
$user = $request->getUser();
if ($stageAssignmentId != $assignmentId) { // New assignment added
$notificationMgr->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS, ['contents' => __('notification.addedStageParticipant')]);
} else {
$notificationMgr->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS, ['contents' => __('notification.editStageParticipant')]);
}
// Log addition.
$assignedUser = Repo::user()->get($userId, true);
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_ADD_PARTICIPANT,
'userId' => Validation::loggedInAs() ?? $user->getId(),
'message' => 'submission.event.participantAdded',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
'userFullName' => $assignedUser->getFullName(),
'username' => $assignedUser->getUsername(),
'userGroupName' => $userGroup->getData('name')
]);
Repo::eventLog()->add($eventLog);
return \PKP\db\DAO::getDataChangedEvent($userGroupId);
} else {
return new JSONMessage(true, $form->fetch($request));
}
}
/**
* Delete the participant from the user groups
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function deleteParticipant($args, $request)
{
$submission = $this->getSubmission();
$stageId = $this->getStageId();
$assignmentId = (int) $request->getUserVar('assignmentId');
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignment = $stageAssignmentDao->getById($assignmentId);
if (!$request->checkCSRF() || !$stageAssignment || $stageAssignment->getSubmissionId() != $submission->getId()) {
return new JSONMessage(false);
}
// Delete the assignment
$stageAssignmentDao->deleteObject($stageAssignment);
// FIXME: perhaps we can just insert the notification on page load
// instead of having it there all the time?
$notificationMgr = new NotificationManager();
$notificationMgr->updateNotification(
$request,
$notificationMgr->getDecisionStageNotifications(),
null,
Application::ASSOC_TYPE_SUBMISSION,
$submission->getId()
);
if ($stageId == WORKFLOW_STAGE_ID_EDITING ||
$stageId == WORKFLOW_STAGE_ID_PRODUCTION) {
// Update submission notifications
$notificationMgr->updateNotification(
$request,
[
PKPNotification::NOTIFICATION_TYPE_ASSIGN_COPYEDITOR,
PKPNotification::NOTIFICATION_TYPE_AWAITING_COPYEDITS,
PKPNotification::NOTIFICATION_TYPE_ASSIGN_PRODUCTIONUSER,
PKPNotification::NOTIFICATION_TYPE_AWAITING_REPRESENTATIONS,
],
null,
Application::ASSOC_TYPE_SUBMISSION,
$submission->getId()
);
}
// Log removal.
$assignedUser = Repo::user()->get($stageAssignment->getUserId(), true);
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REMOVE_PARTICIPANT,
'userId' => Validation::loggedInAs() ?? $request->getUser()->getId(),
'message' => 'submission.event.participantRemoved',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
'userFullName' => $assignedUser->getFullName(),
'username' => $assignedUser->getUsername(),
'userGroupName' => $userGroup->getData('name')
]);
Repo::eventLog()->add($eventLog);
// Redraw the category
return \PKP\db\DAO::getDataChangedEvent($stageAssignment->getUserGroupId());
}
/**
* Get the list of users for the specified user group
*
* @param array $args
* @param Request $request
*
* @return JSONMessage JSON object
*/
public function fetchUserList($args, $request)
{
$submission = $this->getSubmission();
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
$userGroupId = (int) $request->getUserVar('userGroupId');
$collector = Repo::user()->getCollector();
$collector->filterExcludeSubmissionStage($submission->getId(), $stageId, $userGroupId);
$users = $collector->getMany();
$userGroup = Repo::userGroup()->get($userGroupId);
$roleId = $userGroup->getRoleId();
$sectionId = $submission->getSectionId();
$contextId = $submission->getContextId();
$userList = [];
foreach ($users as $user) {
$userList[$user->getId()] = $user->getFullName();
}
if (count($userList) == 0) {
$userList[0] = __('common.noMatches');
}
return new JSONMessage(true, $userList);
}
/**
* Display the notify tab.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function viewNotify($args, $request)
{
$this->setupTemplate($request);
$notifyForm = new PKPStageParticipantNotifyForm($this->getSubmission()->getId(), Application::ASSOC_TYPE_SUBMISSION, $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE));
$notifyForm->initData();
return new JSONMessage(true, $notifyForm->fetch($request));
}
/**
* Send a notification from the notify tab.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function sendNotification($args, $request)
{
$this->setupTemplate($request);
$notifyForm = new PKPStageParticipantNotifyForm($this->getSubmission()->getId(), Application::ASSOC_TYPE_SUBMISSION, $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE));
$notifyForm->readInputData();
if ($notifyForm->validate()) {
$noteId = $notifyForm->execute();
if ($this->getStageId() == WORKFLOW_STAGE_ID_EDITING ||
$this->getStageId() == WORKFLOW_STAGE_ID_PRODUCTION) {
// Update submission notifications
$notificationMgr = new NotificationManager();
$notificationMgr->updateNotification(
$request,
[
PKPNotification::NOTIFICATION_TYPE_ASSIGN_COPYEDITOR,
PKPNotification::NOTIFICATION_TYPE_AWAITING_COPYEDITS,
PKPNotification::NOTIFICATION_TYPE_ASSIGN_PRODUCTIONUSER,
PKPNotification::NOTIFICATION_TYPE_AWAITING_REPRESENTATIONS,
],
null,
Application::ASSOC_TYPE_SUBMISSION,
$this->getSubmission()->getId()
);
}
$json = new JSONMessage(true);
$json->setGlobalEvent('stageStatusUpdated');
return $json;
} else {
// Return a JSON string indicating failure
return new JSONMessage(false);
}
}
/**
* Fetches an email template's message body and returns it via AJAX.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage JSON object
*/
public function fetchTemplateBody($args, $request)
{
$templateKey = $request->getUserVar('template');
$context = $request->getContext();
$template = Repo::emailTemplate()->getByKey($context->getId(), $templateKey);
if ($template) {
$submission = $this->getSubmission();
$mailable = $this->getStageMailable($context, $submission);
$mailable->sender($request->getUser());
$data = $mailable->getData();
$notifyForm = new PKPStageParticipantNotifyForm($submission->getId(), Application::ASSOC_TYPE_SUBMISSION, $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE));
return new JSONMessage(
true,
[
'body' => Mail::compileParams($template->getLocalizedData('body'), $data),
'variables' => $notifyForm->getEmailVariableNames($templateKey),
]
);
}
}
/**
* Get the js handler for this component.
*
* @return string
*/
public function getJSHandler()
{
return '$.pkp.controllers.grid.users.stageParticipant.StageParticipantGridHandler';
}
}
@@ -0,0 +1,186 @@
<?php
/**
* @file controllers/grid/users/stageParticipant/StageParticipantGridRow.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 StageParticipantGridRow
*
* @ingroup controllers_grid_users_stageParticipant
*
* @brief StageParticipant grid row definition
*/
namespace PKP\controllers\grid\users\stageParticipant;
use APP\facades\Repo;
use APP\submission\Submission;
use PKP\controllers\grid\GridRow;
use PKP\controllers\grid\users\stageParticipant\linkAction\NotifyLinkAction;
use PKP\core\PKPApplication;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\linkAction\request\RedirectConfirmationModal;
use PKP\linkAction\request\RemoteActionConfirmationModal;
use PKP\security\Role;
use PKP\security\Validation;
class StageParticipantGridRow extends GridRow
{
/** @var Submission */
public $_submission;
/** @var int */
public $_stageId;
/** @var bool Whether the user can admin this row */
public $_canAdminister;
/**
* Constructor
*/
public function __construct($submission, $stageId, $canAdminister = false)
{
$this->_submission = $submission;
$this->_stageId = $stageId;
$this->_canAdminister = $canAdminister;
parent::__construct();
}
//
// Overridden methods from GridRow
//
/**
* @copydoc GridRow::initialize()
*
* @param null|mixed $template
*/
public function initialize($request, $template = null)
{
// Do the default initialization
parent::initialize($request, $template);
// 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();
if ($this->_canAdminister) {
$this->addAction(
new LinkAction(
'delete',
new RemoteActionConfirmationModal(
$request->getSession(),
__('editor.submission.removeStageParticipant.description'),
__('editor.submission.removeStageParticipant'),
$router->url($request, null, null, 'deleteParticipant', null, $this->getRequestArgs()),
'modal_delete'
),
__('grid.action.remove'),
'delete'
)
);
$this->addAction(
new LinkAction(
'requestAccount',
new AjaxModal(
$router->url($request, null, null, 'addParticipant', null, $this->getRequestArgs()),
__('editor.submission.editStageParticipant'),
'modal_edit_user'
),
__('common.edit'),
'edit_user'
)
);
}
$submission = $this->getSubmission();
$stageId = $this->getStageId();
$stageAssignment = $this->getData();
$userId = $stageAssignment->getUserId();
$userGroupId = $stageAssignment->getUserGroupId();
$context = $request->getContext();
$this->addAction(new NotifyLinkAction($request, $submission, $stageId, $userId));
$user = $request->getUser();
if (
!Validation::loggedInAs() &&
$user->getId() != $userId &&
Validation::getAdministrationLevel($userId, $user->getId()) === Validation::ADMINISTRATION_FULL
) {
$dispatcher = $router->getDispatcher();
$userGroup = Repo::userGroup()->get($userGroupId);
if ($userGroup->getRoleId() == Role::ROLE_ID_AUTHOR) {
$handler = 'authorDashboard';
$op = 'submission';
} else {
$handler = 'workflow';
$op = 'access';
}
$redirectUrl = $dispatcher->url(
$request,
PKPApplication::ROUTE_PAGE,
$context->getPath(),
$handler,
$op,
$submission->getId()
);
$this->addAction(
new LinkAction(
'logInAs',
new RedirectConfirmationModal(
__('grid.user.confirmLogInAs'),
__('grid.action.logInAs'),
$dispatcher->url($request, PKPApplication::ROUTE_PAGE, null, 'login', 'signInAsUser', $userId, ['redirectUrl' => $redirectUrl])
),
__('grid.action.logInAs'),
'enroll_user'
)
);
}
}
}
//
// Getters/Setters
//
/**
* Get the submission for this row (already authorized)
*
* @return Submission
*/
public function getSubmission()
{
return $this->_submission;
}
/**
* Get the stage id for this row
*
* @return int
*/
public function getStageId()
{
return $this->_stageId;
}
/**
* @copydoc GridHandler::getRequestArgs()
*/
public function getRequestArgs()
{
return [
'submissionId' => $this->getSubmission()->getId(),
'stageId' => $this->_stageId,
'assignmentId' => $this->getId()
];
}
}
@@ -0,0 +1,299 @@
<?php
/**
* @file controllers/grid/users/stageParticipant/form/AddParticipantForm.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 AddParticipantForm
*
* @ingroup controllers_grid_users_stageParticipant_form
*
* @brief Form for adding a stage participant
*/
namespace PKP\controllers\grid\users\stageParticipant\form;
use APP\core\Application;
use APP\facades\Repo;
use APP\submission\Submission;
use APP\template\TemplateManager;
use PKP\db\DAORegistry;
use PKP\security\Role;
use PKP\stageAssignment\StageAssignment;
use PKP\stageAssignment\StageAssignmentDAO;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\userGroup\relationships\UserGroupStage;
use PKP\userGroup\UserGroup;
class AddParticipantForm extends PKPStageParticipantNotifyForm
{
/** @var Submission The submission associated with the submission contributor being edited */
public $_submission;
/** @var int $_assignmentId Used for edit the assignment */
public $_assignmentId;
/** @var array $_managerGroupIds Contains all manager group_ids */
public $_managerGroupIds;
/** @var array $_possibleRecommendOnlyUserGroupIds Contains all group_ids that can have the recommendOnly field available for change */
public $_possibleRecommendOnlyUserGroupIds;
/** @var int $_contextId the current Context Id */
public $_contextId;
/**
* Constructor.
*
* @param Submission $submission
* @param int $stageId STAGE_ID_...
* @param int $assignmentId Optional - Used for edit the assignment
*/
public function __construct($submission, $stageId, $assignmentId = null)
{
parent::__construct($submission->getId(), Application::ASSOC_TYPE_SUBMISSION, $stageId, 'controllers/grid/users/stageParticipant/addParticipantForm.tpl');
$this->_submission = $submission;
$this->_stageId = $stageId;
$this->_assignmentId = $assignmentId;
$this->_contextId = $submission->getContextId();
// add checks in addition to anything that the Notification form may apply.
// FIXME: should use a custom validator to check that the userId belongs to this group.
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'userGroupId', 'required', 'editor.submission.addStageParticipant.form.userGroupRequired'));
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
$this->initialize();
}
//
// Getters and Setters
//
/**
* Get the Submission
*
* @return Submission
*/
public function getSubmission()
{
return $this->_submission;
}
/**
* Initialize private attributes that need to be used through all functions.
*/
public function initialize()
{
// assign all user group IDs with ROLE_ID_MANAGER or ROLE_ID_SUB_EDITOR
$this->_managerGroupIds = Repo::userGroup()->getArrayIdByRoleId(Role::ROLE_ID_MANAGER, $this->_contextId);
$subEditorGroupIds = Repo::userGroup()->getArrayIdByRoleId(Role::ROLE_ID_SUB_EDITOR, $this->_contextId);
$this->_possibleRecommendOnlyUserGroupIds = array_merge($this->_managerGroupIds, $subEditorGroupIds);
}
/**
* Determine whether the specified user group is potentially restricted from editing metadata.
*
* Subeditors can not change their own permissions.
*/
protected function _isChangePermitMetadataAllowed(UserGroup $userGroup, int $userId): bool
{
$currentUser = Application::get()->getRequest()->getUser();
if ($currentUser->getId() === $userId && $userGroup->getRoleId() === Role::ROLE_ID_SUB_EDITOR) {
return false;
}
return $userGroup->getRoleId() !== Role::ROLE_ID_MANAGER;
}
/**
* Determine whether the specified group is potentially required to make recommendations rather than decisions.
*
* Subeditors can not change their own permissions.
*/
protected function _isChangeRecommendOnlyAllowed(UserGroup $userGroup, int $userId): bool
{
$currentUser = Application::get()->getRequest()->getUser();
if ($currentUser->getId() === $userId && $userGroup->getRoleId() === Role::ROLE_ID_SUB_EDITOR) {
return false;
}
return in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]);
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$userGroups = Repo::userGroup()->getUserGroupsByStage(
$request->getContext()->getId(),
$this->getStageId()
);
$userGroupOptions = [];
foreach ($userGroups as $userGroup) {
// Exclude reviewers.
if ($userGroup->getRoleId() == Role::ROLE_ID_REVIEWER) {
continue;
}
$userGroupOptions[$userGroup->getId()] = $userGroup->getLocalizedName();
}
$templateMgr = TemplateManager::getManager($request);
$keys = array_keys($userGroupOptions);
$templateMgr->assign([
'userGroupOptions' => $userGroupOptions,
'selectedUserGroupId' => array_shift($keys), // assign the first element as selected
'possibleRecommendOnlyUserGroupIds' => $this->_possibleRecommendOnlyUserGroupIds,
'recommendOnlyUserGroupIds' => Repo::userGroup()->getCollector()
->filterByContextIds([$request->getContext()->getId()])
->filterByIsRecommendOnly()
->getMany()
->toArray(),
'notPossibleEditSubmissionMetadataPermissionChange' => $this->_managerGroupIds,
'permitMetadataEditUserGroupIds' => Repo::userGroup()->getCollector()
->filterByContextIds([$request->getContext()->getId()])
->filterByPermitMetadataEdit(true)
->getMany()
->toArray(),
'submissionId' => $this->getSubmission()->getId(),
'userGroupId' => '',
'userIdSelected' => '',
]);
if ($this->_assignmentId) {
/** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
/** @var StageAssignment $stageAssignment */
$stageAssignment = $stageAssignmentDao->getById($this->_assignmentId);
$currentUser = Repo::user()->get($stageAssignment->getUserId());
/** @var UserGroup $userGroup */
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
$templateMgr->assign([
'assignmentId' => $this->_assignmentId,
'currentUserName' => $currentUser->getFullName(),
'currentUserGroup' => $userGroup->getLocalizedName(),
'userGroupId' => $stageAssignment->getUserGroupId(),
'userIdSelected' => $stageAssignment->getUserId(),
'currentAssignmentRecommendOnly' => $stageAssignment->getRecommendOnly(),
'currentAssignmentPermitMetadataEdit' => $stageAssignment->getCanChangeMetadata(),
'isChangePermitMetadataAllowed' => $this->_isChangePermitMetadataAllowed($userGroup, $stageAssignment->getUserId()),
'isChangeRecommendOnlyAllowed' => $this->_isChangeRecommendOnlyAllowed($userGroup, $stageAssignment->getUserId()),
]);
}
// If submission is in review, add a list of reviewer Ids that should not be
// assigned as participants because they have anonymous peer reviews in progress
$anonymousReviewerIds = [];
if (in_array($this->getSubmission()->getStageId(), [WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW])) {
$anonymousReviewMethods = [
\PKP\submission\reviewAssignment\ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS,
\PKP\submission\reviewAssignment\ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS
];
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignments = $reviewAssignmentDao->getBySubmissionId($this->getSubmission()->getId());
$anonymousReviews = array_filter($reviewAssignments, function ($reviewAssignment) use ($anonymousReviewMethods) {
return in_array($reviewAssignment->getReviewMethod(), $anonymousReviewMethods) && !$reviewAssignment->getDeclined();
});
$anonymousReviewerIds = array_map(function ($reviewAssignment) {
return $reviewAssignment->getReviewerId();
}, $anonymousReviews);
}
$templateMgr->assign([
'anonymousReviewerIds' => array_values(array_unique($anonymousReviewerIds)),
'anonymousReviewerWarning' => __('editor.submission.addStageParticipant.form.reviewerWarning'),
'anonymousReviewerWarningOk' => __('common.ok'),
]);
return parent::fetch($request, $template, $display);
}
/**
* @copydoc Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars([
'userGroupId',
'userId',
'message',
'template',
'recommendOnly',
'canChangeMetadata',
]);
}
/**
* @copydoc Form::validate()
*/
public function validate($callHooks = true)
{
$userGroupId = (int) $this->getData('userGroupId');
$userId = (int) $this->getData('userId');
return Repo::userGroup()->userInGroup($userId, $userGroupId) && Repo::userGroup()->get($userGroupId) && parent::validate($callHooks);
}
/**
* @see Form::execute()
*
* @return array ($userGroupId, $userId)
*/
public function execute(...$functionParams)
{
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$submission = $this->getSubmission();
$userGroup = Repo::userGroup()->get((int) $this->getData('userGroupId'));
$userId = (int) $this->getData('userId');
$recommendOnly = $this->_isChangeRecommendOnlyAllowed($userGroup, $userId) ? (bool) $this->getData('recommendOnly') : false;
$canChangeMetadata = $this->_isChangePermitMetadataAllowed($userGroup, $userId) ? (bool) $this->getData('canChangeMetadata') : true;
// sanity check
if (UserGroupStage::withStageId($this->getStageId())->withUserGroupId($userGroup->getId())->get()->isNotEmpty()) {
$updated = false;
if ($this->_assignmentId) {
/** @var StageAssignment $stageAssignment */
$stageAssignment = $stageAssignmentDao->getById($this->_assignmentId);
if ($stageAssignment) {
$stageAssignment->setRecommendOnly($recommendOnly);
$stageAssignment->setCanChangeMetadata($canChangeMetadata);
$stageAssignmentDao->updateObject($stageAssignment);
$updated = true;
}
}
if (!$updated) {
// insert the assignment
$stageAssignment = $stageAssignmentDao->build($submission->getId(), $userGroup->getId(), $userId, $recommendOnly, $canChangeMetadata);
}
}
parent::execute(...$functionParams);
return [$userGroup->getId(), $userId, $stageAssignment->getId()];
}
/**
* whether or not to require a message field
*
* @return bool
*/
public function isMessageRequired()
{
return false;
}
}
@@ -0,0 +1,406 @@
<?php
/**
* @file controllers/grid/users/stageParticipant/form/PKPStageParticipantNotifyForm.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 PKPStageParticipantNotifyForm
*
* @ingroup controllers_grid_users_stageParticipant_form
*
* @brief Form to notify a user regarding a file
*/
namespace PKP\controllers\grid\users\stageParticipant\form;
use APP\core\Application;
use APP\core\Request;
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\controllers\grid\queries\traits\StageMailable;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\form\Form;
use PKP\log\event\EventLogEntry;
use PKP\log\SubmissionEmailLogDAO;
use PKP\log\SubmissionEmailLogEntry;
use PKP\note\NoteDAO;
use PKP\notification\NotificationDAO;
use PKP\notification\PKPNotification;
use PKP\query\QueryDAO;
use PKP\security\Role;
use PKP\security\Validation;
use Symfony\Component\Mailer\Exception\TransportException;
class PKPStageParticipantNotifyForm extends Form
{
use StageMailable;
/** @var int The file/submission ID this form is for */
public $_itemId;
/** @var int The type of item the form is for (used to determine which email template to use) */
public $_itemType;
/** @var int The stage Id */
public $_stageId;
/** @var int the Submission id */
public $_submissionId;
/**
* Constructor.
*
* @param null|mixed $template
*/
public function __construct($itemId, $itemType, $stageId, $template = null)
{
$template = ($template != null) ? $template : 'controllers/grid/users/stageParticipant/form/notify.tpl';
parent::__construct($template);
$this->_itemId = $itemId;
$this->_itemType = $itemType;
$this->_stageId = $stageId;
if ($itemType == Application::ASSOC_TYPE_SUBMISSION) {
$this->_submissionId = $itemId;
} else {
$submissionFile = Repo::submissionFile()->get($itemId);
$this->_submissionId = $submissionFile->getData('submissionId');
}
// Some other forms (e.g. the Add Participant form) subclass this form and
// may not enforce the sending of an email.
if ($this->isMessageRequired()) {
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'message', 'required', 'stageParticipants.notify.warning'));
}
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'userId', 'required', 'stageParticipants.notify.warning'));
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
}
/**
* @copydoc Form::fetch()
*
* @param null|mixed $template
*/
public function fetch($request, $template = null, $display = false)
{
$submission = Repo::submission()->get($this->_submissionId);
$context = $request->getContext();
$user = $request->getUser();
// Add the templates that can be used for this message
if ($user->hasRole([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT], $context->getId())) {
$mailable = $this->getStageMailable($context, $submission);
$data = $mailable->getData();
$defaultTemplate = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
$templates = [$mailable::getEmailTemplateKey() => $defaultTemplate->getLocalizedData('name')];
$alternateTemplates = Repo::emailTemplate()->getCollector($context->getId())
->alternateTo([$mailable::getEmailTemplateKey()])
->getMany();
foreach ($alternateTemplates as $alternateTemplate) {
$templates[$alternateTemplate->getData('key')] = Mail::compileParams(
$alternateTemplate->getLocalizedData('name'),
$data
);
}
}
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'templates' => $templates,
'stageId' => $this->getStageId(),
'submissionId' => $this->_submissionId,
'itemId' => $this->_itemId,
]);
if ($request->getUserVar('userId')) {
$user = Repo::user()->get($request->getUserVar('userId'));
if ($user) {
$templateMgr->assign([
'userId' => $user->getId(),
'userFullName' => $user->getFullName(),
]);
}
}
return parent::fetch($request, $template, $display);
}
/**
* @copydoc Form::readInputData()
*/
public function readInputData()
{
$this->readUserVars(['message', 'userId', 'template']);
}
/**
* @copydoc Form::execute()
*/
public function execute(...$functionParams)
{
$submission = Repo::submission()->get($this->_submissionId);
if ($this->getData('message')) {
$request = Application::get()->getRequest();
$this->sendMessage((int) $this->getData('userId'), $submission, $request);
$this->_logEventAndCreateNotification($request, $submission);
}
return parent::execute(...$functionParams);
}
/**
* Send a message to a user.
*/
public function sendMessage(int $userId, Submission $submission, Request $request)
{
$user = Repo::user()->get($userId);
if (!isset($user)) {
return;
}
$contextDao = Application::getContextDAO();
$context = $contextDao->getById($submission->getData('contextId'));
$mailable = $this->getStageMailable($context, $submission);
$templateKey = $this->getData('template');
$template = Repo::emailTemplate()->getByKey($context->getId(), $templateKey);
if (!$template) {
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
}
// Create a query
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
$query = $queryDao->newDataObject();
$query->setAssocType(PKPApplication::ASSOC_TYPE_SUBMISSION);
$query->setAssocId($submission->getId());
$query->setStageId($this->_stageId);
$query->setSequence(REALLY_BIG_NUMBER);
$queryDao->insertObject($query);
$queryDao->resequence(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId());
// Add the current user and message recipient as participants.
$queryDao->insertParticipant($query->getId(), $user->getId());
if ($user->getId() != $request->getUser()->getId()) {
$queryDao->insertParticipant($query->getId(), $request->getUser()->getId());
}
// Create a head note
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
$headNote = $noteDao->newDataObject();
$headNote->setUserId($request->getUser()->getId());
$headNote->setAssocType(PKPApplication::ASSOC_TYPE_QUERY);
$headNote->setAssocId($query->getId());
$headNote->setDateCreated(Core::getCurrentDate());
// Populate mailable with data before compiling headNote title and content
$mailable
->addData(['authorName' => $user->getFullName()]) // For compatibility with removed AUTHOR_ASSIGN and AUTHOR_NOTIFY
->sender($request->getUser())
->recipients([$user])
->body($this->getData('message'))
->subject($template->getLocalizedData('subject'));
// Compile and insert note
$headNote->setTitle(Mail::compileParams(
$template->getLocalizedData('subject'),
$mailable->getData()
));
//Substitute email template variables not available before form being executed
$additionalVariables = $this->getEmailVariableNames($template->getData('key'));
$headNote->setContents(Mail::compileParams(
$this->getData('message'),
array_intersect_key($mailable->getData(), $additionalVariables)
));
$noteDao->insertObject($headNote);
// Send the email
$notificationMgr = new NotificationManager();
$notification = $notificationMgr->createNotification(
$request,
$userId,
Notification::NOTIFICATION_TYPE_NEW_QUERY,
$request->getContext()->getId(),
PKPApplication::ASSOC_TYPE_QUERY,
$query->getId(),
Notification::NOTIFICATION_LEVEL_TASK
);
$mailable->allowUnsubscribe($notification);
$logDao = null;
try {
Mail::send($mailable);
$logDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $logDao */
} catch (TransportException $e) {
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification(
$request->getUser()->getId(),
PKPNotification::NOTIFICATION_TYPE_ERROR,
['contents' => __('email.compose.error')]
);
error_log($e->getMessage());
}
// remove the INDEX_ and LAYOUT_ tasks if a user has sent the appropriate _COMPLETE email
switch ($templateKey) {
case 'EDITOR_ASSIGN':
$this->_addAssignmentTaskNotification($request, PKPNotification::NOTIFICATION_TYPE_EDITOR_ASSIGN, $user->getId(), $submission->getId());
!$logDao ?: $logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_EDITOR_ASSIGN, $mailable, $submission);
break;
case 'COPYEDIT_REQUEST':
$this->_addAssignmentTaskNotification($request, PKPNotification::NOTIFICATION_TYPE_COPYEDIT_ASSIGNMENT, $user->getId(), $submission->getId());
!$logDao ?: $logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_COPYEDIT_NOTIFY_COPYEDITOR, $mailable, $submission);
break;
case 'LAYOUT_REQUEST':
$this->_addAssignmentTaskNotification($request, PKPNotification::NOTIFICATION_TYPE_LAYOUT_ASSIGNMENT, $user->getId(), $submission->getId());
!$logDao ?: $logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_LAYOUT_NOTIFY_EDITOR, $mailable, $submission);
break;
case 'INDEX_REQUEST':
$this->_addAssignmentTaskNotification($request, PKPNotification::NOTIFICATION_TYPE_INDEX_ASSIGNMENT, $user->getId(), $submission->getId());
!$logDao ?: $logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_INDEX_NOTIFY_INDEXER, $mailable, $submission);
break;
case 'LAYOUT_COMPLETE':
!$logDao ?: $logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_LAYOUT_NOTIFY_COMPLETE, $mailable, $submission);
break;
case 'INDEX_COMPLETE':
!$logDao ?: $logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_INDEX_NOTIFY_COMPLETE, $mailable, $submission);
break;
default:
!$logDao ?: $logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_DISCUSSION_NOTIFY, $mailable, $submission);
break;
}
if ($submission->getStageId() == WORKFLOW_STAGE_ID_EDITING ||
$submission->getStageId() == WORKFLOW_STAGE_ID_PRODUCTION) {
$notificationMgr = new NotificationManager();
$notificationMgr->updateNotification(
$request,
[
PKPNotification::NOTIFICATION_TYPE_ASSIGN_COPYEDITOR,
PKPNotification::NOTIFICATION_TYPE_AWAITING_COPYEDITS,
PKPNotification::NOTIFICATION_TYPE_ASSIGN_PRODUCTIONUSER,
PKPNotification::NOTIFICATION_TYPE_AWAITING_REPRESENTATIONS,
],
null,
PKPApplication::ASSOC_TYPE_SUBMISSION,
$submission->getId()
);
}
}
/**
* Get the available email template variable names for the given template name.
*/
public function getEmailVariableNames(string $emailKey): array
{
switch ($emailKey) {
case 'COPYEDIT_REQUEST':
case 'LAYOUT_REQUEST':
case 'INDEX_REQUEST': return [
'recipientName' => __('user.name'),
'recipientUsername' => __('user.username'),
'submissionUrl' => __('common.url'),
];
case 'LAYOUT_COMPLETE':
case 'INDEX_COMPLETE': return [
'recipientName' => __('user.role.editor'),
];
case 'EDITOR_ASSIGN_SUBMISSION':
case 'EDITOR_ASSIGN_REVIEW':
case 'EDITOR_ASSIGN_PRODUCTION':
case 'EDITOR_ASSIGN': return [
'recipientName' => __('user.name'),
'recipientUsername' => __('user.username'),
'signature' => __('user.role.editor'),
'submissionUrl' => __('common.url'),
];
}
return [];
}
/**
* Get the stage ID
*
* @return int
*/
public function getStageId()
{
return $this->_stageId;
}
/**
* Add upload task notifications.
*
* @param PKPRequest $request
* @param int $type NOTIFICATION_TYPE_...
* @param int $userId User ID
* @param int $submissionId Submission ID
*/
private function _addAssignmentTaskNotification($request, $type, $userId, $submissionId)
{
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
$notificationFactory = $notificationDao->getByAssoc(
Application::ASSOC_TYPE_SUBMISSION,
$submissionId,
$userId,
$type
);
if (!$notificationFactory->next()) {
$context = $request->getContext();
$notificationMgr = new NotificationManager();
$notificationMgr->createNotification(
$request,
$userId,
$type,
$context->getId(),
PKPApplication::ASSOC_TYPE_SUBMISSION,
$submissionId,
Notification::NOTIFICATION_LEVEL_TASK
);
}
}
/**
* Convenience function for logging the message sent event and creating the notification.
*
* @param PKPRequest $request
* @param Submission $submission
*/
public function _logEventAndCreateNotification($request, $submission)
{
$currentUser = $request->getUser();
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => EventLogEntry::SUBMISSION_LOG_MESSAGE_SENT,
'userId' => Validation::loggedInAs() ?? $currentUser->getId(),
'message' => 'informationCenter.history.messageSent',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate()
]);
Repo::eventLog()->add($eventLog);
// Create trivial notification.
$notificationMgr = new NotificationManager();
$notificationMgr->createTrivialNotification($currentUser->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS, ['contents' => __('stageParticipants.history.messageSent')]);
}
/**
* whether or not to include the Notify Users listbuilder true, by default.
*
* @return bool
*/
public function isMessageRequired()
{
return true;
}
}
@@ -0,0 +1,65 @@
<?php
/**
* @file controllers/grid/users/stageParticipant/linkAction/NotifyLinkAction.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 NotifyLinkAction
*
* @ingroup controllers_grid_users_stageParticipant
*
* @brief An action to open up the notify part of the stage participants grid.
*/
namespace PKP\controllers\grid\users\stageParticipant\linkAction;
use APP\core\Request;
use APP\submission\Submission;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
class NotifyLinkAction extends LinkAction
{
/**
* Constructor
*
* @param Request $request
* @param Submission $submission The submission
* @param int $stageId
* @param int $userId optional
* to show information about.
*/
public function __construct($request, &$submission, $stageId, $userId = null)
{
// Prepare request arguments
$requestArgs['submissionId'] = $submission->getId();
$requestArgs['stageId'] = $stageId;
if ($userId) {
$requestArgs['userId'] = $userId;
}
$router = $request->getRouter();
$ajaxModal = new AjaxModal(
$router->url(
$request,
null,
'grid.users.stageParticipant.StageParticipantGridHandler',
'viewNotify',
null,
$requestArgs
),
__('submission.stageParticipants.notify'),
'modal_email'
);
// Configure the file link action.
parent::__construct(
'notify',
$ajaxModal,
__('submission.stageParticipants.notify'),
'notify'
);
}
}
@@ -0,0 +1,62 @@
<?php
/**
* @file controllers/grid/users/userSelect/UserSelectGridCellProvider.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 UserSelectGridCellProvider
*
* @ingroup controllers_grid_users_userSelect
*
* @brief Base class for a cell provider that retrieves data for selecting a user
*/
namespace PKP\controllers\grid\users\userSelect;
use PKP\controllers\grid\DataObjectGridCellProvider;
use PKP\controllers\grid\GridColumn;
class UserSelectGridCellProvider extends DataObjectGridCellProvider
{
/** @var int User ID of already-selected user */
public $_userId;
/**
* Constructor
*
* @param int $userId ID of preselected user.
*/
public function __construct($userId = null)
{
$this->_userId = $userId;
}
//
// Template methods from GridCellProvider
//
/**
* 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();
assert(is_a($element, 'User'));
switch ($column->getId()) {
case 'select': // Displays the radio option
return ['rowId' => $row->getId(), 'userId' => $this->_userId];
case 'name': // User's name
return ['label' => $element->getFullName()];
}
assert(false);
}
}
@@ -0,0 +1,244 @@
<?php
/**
* @file controllers/grid/users/userSelect/UserSelectGridHandler.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 UserSelectGridHandler
*
* @ingroup controllers_grid_users_userSelect
*
* @brief Handle user selector grid requests.
*/
namespace PKP\controllers\grid\users\userSelect;
use APP\core\Application;
use APP\facades\Repo;
use PKP\controllers\grid\feature\CollapsibleGridFeature;
use PKP\controllers\grid\feature\InfiniteScrollingFeature;
use PKP\controllers\grid\GridColumn;
use PKP\controllers\grid\GridHandler;
use PKP\security\authorization\WorkflowStageAccessPolicy;
use PKP\security\Role;
class UserSelectGridHandler extends GridHandler
{
/** @var array (user group ID => user group name) */
public $_userGroupOptions;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->addRoleAssignment(
[Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_ASSISTANT],
['fetchGrid', 'fetchRows']
);
}
//
// Implement template methods from PKPHandler
//
/**
* @copydoc PKPHandler::authorize()
*/
public function authorize($request, &$args, $roleAssignments)
{
$stageId = (int)$request->getUserVar('stageId');
$this->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $stageId));
return parent::authorize($request, $args, $roleAssignments);
}
/**
* @copydoc GridHandler::initialize()
*
* @param null|mixed $args
*/
public function initialize($request, $args = null)
{
parent::initialize($request, $args);
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
$userGroups = Repo::userGroup()->getUserGroupsByStage(
$request->getContext()->getId(),
$stageId
);
$this->_userGroupOptions = [];
foreach ($userGroups as $userGroup) {
// Exclude reviewers.
if ($userGroup->getRoleId() == Role::ROLE_ID_REVIEWER) {
continue;
}
$this->_userGroupOptions[$userGroup->getId()] = $userGroup->getLocalizedName();
}
$this->setTitle('editor.submission.findAndSelectUser');
// Columns
$cellProvider = new UserSelectGridCellProvider();
$this->addColumn(
new GridColumn(
'select',
'',
null,
'controllers/grid/users/userSelect/userSelectRadioButton.tpl',
$cellProvider,
['width' => 5]
)
);
$this->addColumn(
new GridColumn(
'name',
'common.name',
null,
null,
$cellProvider,
['alignment' => GridColumn::COLUMN_ALIGNMENT_LEFT,
'width' => 30
]
)
);
}
//
// Overridden methods from GridHandler
//
/**
* @copydoc GridHandler::initFeatures()
*/
public function initFeatures($request, $args)
{
return [new InfiniteScrollingFeature('infiniteScrolling', $this->getItemsNumber()), new CollapsibleGridFeature()];
}
/**
* @copydoc GridHandler::loadData()
*/
protected function loadData($request, $filter)
{
[$filterUserGroupId, $name] = $this->getFilterValues($filter);
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
$rangeInfo = $this->getGridRangeInfo($request, $this->getId());
$collector = Repo::user()->getCollector()
->filterByContextIds([$submission->getContextId()])
->filterExcludeSubmissionStage($submission->getId(), $stageId, $filterUserGroupId)
->searchPhrase($name)
->limit($rangeInfo->getCount())
->offset($rangeInfo->getOffset() + max(0, $rangeInfo->getPage() - 1) * $rangeInfo->getCount());
$users = $collector->getMany()->toArray();
$totalCount = $collector->limit(null)->offset(null)->getCount();
return new \PKP\core\VirtualArrayIterator($users, $totalCount, $rangeInfo->getPage(), $rangeInfo->getCount());
}
/**
* @copydoc GridHandler::renderFilter()
*/
public function renderFilter($request, $filterData = [])
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
$keys = array_keys($this->_userGroupOptions);
$allFilterData = array_merge(
$filterData,
[
'userGroupOptions' => $this->_userGroupOptions,
'selectedUserGroupId' => reset($keys),
'gridId' => $this->getId(),
'submissionId' => $submission->getId(),
'stageId' => $stageId,
]
);
return parent::renderFilter($request, $allFilterData);
}
/**
* @copydoc GridHandler::getFilterSelectionData()
*/
public function getFilterSelectionData($request)
{
$name = (string) $request->getUserVar('name');
$filterUserGroupId = (int) $request->getUserVar('filterUserGroupId');
return [
'name' => $name,
'filterUserGroupId' => $filterUserGroupId,
];
}
/**
* @copydoc GridHandler::getFilterForm()
*/
protected function getFilterForm()
{
return 'controllers/grid/users/userSelect/searchUserFilter.tpl';
}
/**
* @copydoc GridHandler::getRequestArgs()
*/
public function getRequestArgs()
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
return [
'submissionId' => $submission->getId(),
'stageId' => $stageId,
];
}
/**
* Determine whether a filter form should be collapsible.
*
* @return bool
*/
protected function isFilterFormCollapsible()
{
return false;
}
/**
* Define how many items this grid will start loading.
*
* @return int
*/
protected function getItemsNumber()
{
return 20;
}
/**
* Process filter values, assigning default ones if
* none was set.
*
* @return array
*/
protected function getFilterValues($filter)
{
if (isset($filter['filterUserGroupId']) && $filter['filterUserGroupId']) {
$filterUserGroupId = $filter['filterUserGroupId'];
} else {
$keys = array_keys($this->_userGroupOptions);
$filterUserGroupId = reset($keys);
}
if (isset($filter['name']) && $filter['name']) {
$name = $filter['name'];
} else {
$name = null;
}
return [$filterUserGroupId, $name];
}
}