first commit
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueriesAccessHelper.php
|
||||
*
|
||||
* Copyright (c) 2016-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 QueriesAccessHelper
|
||||
*
|
||||
* @ingroup controllers_grid_query
|
||||
*
|
||||
* @brief Implements access rules for queries.
|
||||
* Permissions are intended as follows (per UI/UX group, 2015-12-01)
|
||||
* Added permissions for Reviewers, 2017-11-05
|
||||
*
|
||||
* ROLE
|
||||
* TASK MANAGER/ADMIN SUB EDITOR ASSISTANT AUTHOR REVIEWER
|
||||
* Create Q Yes Yes Yes Yes Yes
|
||||
* Edit Q All All If Creator If Creator if Creator
|
||||
* List/View All All Assigned Assigned Assigned
|
||||
* Open/close All All Assigned No No
|
||||
* Delete Q All All If Blank If Blank If Blank
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\query\Query;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\Role;
|
||||
use PKP\user\User;
|
||||
|
||||
class QueriesAccessHelper
|
||||
{
|
||||
/** @var array */
|
||||
public $_authorizedContext;
|
||||
|
||||
/** @var User */
|
||||
public $_user;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $authorizedContext
|
||||
* @param User $user
|
||||
*/
|
||||
public function __construct($authorizedContext, $user)
|
||||
{
|
||||
$this->_authorizedContext = $authorizedContext;
|
||||
$this->_user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve authorized context objects from the authorized context.
|
||||
*
|
||||
* @param int $assocType any of the Application::ASSOC_TYPE_* constants
|
||||
*/
|
||||
public function getAuthorizedContextObject($assocType)
|
||||
{
|
||||
return $this->_authorizedContext[$assocType] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current user can open/close a query.
|
||||
*
|
||||
* @param Query $query
|
||||
*
|
||||
* @return bool True if the user is allowed to open/close the query.
|
||||
*/
|
||||
public function getCanOpenClose($query)
|
||||
{
|
||||
// Managers and sub editors are always allowed
|
||||
if ($this->hasStageRole($query->getStageId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assigned assistants are allowed
|
||||
if ($this->hasStageRole($query->getStageId(), [Role::ROLE_ID_ASSISTANT]) && $this->isAssigned($this->_user->getId(), $query->getId())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can re-order the queries.
|
||||
*
|
||||
* @param int $stageId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanOrder($stageId)
|
||||
{
|
||||
return $this->hasStageRole($stageId, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create queries.
|
||||
*
|
||||
* @param int $stageId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanCreate($stageId)
|
||||
{
|
||||
return $this->hasStageRole($stageId, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR, Role::ROLE_ID_REVIEWER]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current user can edit a query.
|
||||
*
|
||||
* @param int $queryId Query ID
|
||||
*
|
||||
* @return bool True iff the user is allowed to edit the query.
|
||||
*/
|
||||
public function getCanEdit($queryId)
|
||||
{
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query = $queryDao->getById($queryId);
|
||||
if (!$query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assistants, authors and reviewers are allowed, if they created the query less than x seconds ago
|
||||
if ($this->hasStageRole($query->getStageId(), [Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR, Role::ROLE_ID_REVIEWER])) {
|
||||
$headNote = $query->getHeadNote();
|
||||
if ($headNote->getUserId() == $this->_user->getId() && (time() - strtotime($headNote->getDateCreated()) < 3600)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Managers are always allowed
|
||||
if ($this->hasStageRole($query->getStageId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current user can delete a query.
|
||||
*
|
||||
* @param int $queryId Query ID
|
||||
*
|
||||
* @return bool True iff the user is allowed to delete the query.
|
||||
*/
|
||||
public function getCanDelete($queryId)
|
||||
{
|
||||
// Users can always delete their own placeholder queries.
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query = $queryDao->getById($queryId);
|
||||
if ($query) {
|
||||
$headNote = $query->getHeadNote();
|
||||
if ($headNote?->getUserId() == $this->_user->getId() && $headNote?->getTitle() == '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Managers and site admins are always allowed
|
||||
if ($this->hasStageRole($query->getStageId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether the current user can list all queries on the submission
|
||||
*
|
||||
* @param int $stageId The stage ID to load discussions for
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanListAll($stageId)
|
||||
{
|
||||
return $this->hasStageRole($stageId, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current user is assigned to the current query.
|
||||
*
|
||||
* @param int $userId User ID
|
||||
* @param int $queryId Query ID
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAssigned($userId, $queryId)
|
||||
{
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
return (bool) $queryDao->getParticipantIds($queryId, $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current user has role(s) in the current workflow
|
||||
* stage
|
||||
*
|
||||
* @param int $stageId
|
||||
* @param array $roles [ROLE_ID_...]
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasStageRole($stageId, $roles)
|
||||
{
|
||||
$stageRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES);
|
||||
return !empty(array_intersect($stageRoles[$stageId], $roles));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueriesGridCellProvider.php
|
||||
*
|
||||
* Copyright (c) 2016-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 QueriesGridCellProvider
|
||||
*
|
||||
* @ingroup controllers_grid_queries
|
||||
*
|
||||
* @brief Base class for a cell provider that can retrieve labels for queries.
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\submission\Submission;
|
||||
use PKP\controllers\grid\DataObjectGridCellProvider;
|
||||
use PKP\controllers\grid\GridColumn;
|
||||
use PKP\controllers\grid\GridHandler;
|
||||
use PKP\core\PKPString;
|
||||
use PKP\linkAction\LinkAction;
|
||||
use PKP\linkAction\request\AjaxAction;
|
||||
use PKP\note\NoteDAO;
|
||||
use PKP\query\Query;
|
||||
|
||||
class QueriesGridCellProvider extends DataObjectGridCellProvider
|
||||
{
|
||||
/** @var Submission */
|
||||
public $_submission;
|
||||
|
||||
/** @var int */
|
||||
public $_stageId;
|
||||
|
||||
/** @var QueriesAccessHelper */
|
||||
public $_queriesAccessHelper;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Submission $submission
|
||||
* @param int $stageId
|
||||
* @param QueriesAccessHelper $queriesAccessHelper
|
||||
*/
|
||||
public function __construct($submission, $stageId, $queriesAccessHelper)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->_submission = $submission;
|
||||
$this->_stageId = $stageId;
|
||||
$this->_queriesAccessHelper = $queriesAccessHelper;
|
||||
}
|
||||
|
||||
//
|
||||
// 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 Query $element */
|
||||
$headNote = $element->getHeadNote();
|
||||
$user = $headNote ? $headNote->getUser() : null;
|
||||
$notes = $element->getReplies(null, NoteDAO::NOTE_ORDER_ID, \PKP\db\DAO::SORT_DIRECTION_DESC);
|
||||
$context = Application::get()->getRequest()->getContext();
|
||||
$datetimeFormatShort = PKPString::convertStrftimeFormat($context->getLocalizedDateTimeFormatShort());
|
||||
|
||||
switch ($columnId) {
|
||||
case 'replies':
|
||||
return ['label' => max(0, $notes->count() - 1)];
|
||||
case 'from':
|
||||
return ['label' => ($user ? $user->getUsername() : '—') . '<br />' . ($headNote ? date($datetimeFormatShort, strtotime($headNote->getDateCreated())) : '')];
|
||||
case 'lastReply':
|
||||
$latestReply = $notes->first();
|
||||
if ($latestReply && $latestReply->getId() != $headNote->getId()) {
|
||||
$repliedUser = $latestReply->getUser();
|
||||
return ['label' => ($repliedUser ? $repliedUser->getUsername() : '—') . '<br />' . date($datetimeFormatShort, strtotime($latestReply->getDateCreated()))];
|
||||
} else {
|
||||
return ['label' => '-'];
|
||||
}
|
||||
// no break
|
||||
case 'closed':
|
||||
return [
|
||||
'selected' => $element->getIsClosed(),
|
||||
'disabled' => !$this->_queriesAccessHelper->getCanOpenClose($element),
|
||||
];
|
||||
}
|
||||
return parent::getTemplateVarsFromRowColumn($row, $column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridCellProvider::getCellActions()
|
||||
*/
|
||||
public function getCellActions($request, $row, $column, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
|
||||
{
|
||||
$element = $row->getData();
|
||||
$router = $request->getRouter();
|
||||
$actionArgs = $this->getRequestArgs($row);
|
||||
switch ($column->getId()) {
|
||||
case 'closed':
|
||||
if ($this->_queriesAccessHelper->getCanOpenClose($element)) {
|
||||
$enabled = !$element->getIsClosed();
|
||||
if ($enabled) {
|
||||
return [new LinkAction(
|
||||
'close-' . $row->getId(),
|
||||
new AjaxAction($router->url($request, null, null, 'closeQuery', null, $actionArgs)),
|
||||
null,
|
||||
null
|
||||
)];
|
||||
} else {
|
||||
return [new LinkAction(
|
||||
'open-' . $row->getId(),
|
||||
new AjaxAction($router->url($request, null, null, 'openQuery', null, $actionArgs)),
|
||||
null,
|
||||
null
|
||||
)];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return parent::getCellActions($request, $row, $column, $position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request arguments.
|
||||
*
|
||||
* @param \PKP\controllers\grid\GridRow $row
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequestArgs($row)
|
||||
{
|
||||
return [
|
||||
'submissionId' => $this->_submission->getId(),
|
||||
'stageId' => $this->_stageId,
|
||||
'queryId' => $row->getId(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,791 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueriesGridHandler.php
|
||||
*
|
||||
* Copyright (c) 2016-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 QueriesGridHandler
|
||||
*
|
||||
* @ingroup controllers_grid_query
|
||||
*
|
||||
* @brief base PKP class to handle query grid requests.
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
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\controllers\grid\feature\OrderGridItemsFeature;
|
||||
use PKP\controllers\grid\GridColumn;
|
||||
use PKP\controllers\grid\GridHandler;
|
||||
use PKP\controllers\grid\queries\form\QueryForm;
|
||||
use PKP\controllers\grid\queries\traits\StageMailable;
|
||||
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\RemoteActionConfirmationModal;
|
||||
use PKP\log\SubmissionEmailLogDAO;
|
||||
use PKP\log\SubmissionEmailLogEntry;
|
||||
use PKP\notification\NotificationDAO;
|
||||
use PKP\notification\NotificationSubscriptionSettingsDAO;
|
||||
use PKP\notification\PKPNotification;
|
||||
use PKP\query\Query;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\authorization\QueryAccessPolicy;
|
||||
use PKP\security\authorization\QueryWorkflowStageAccessPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class QueriesGridHandler extends GridHandler
|
||||
{
|
||||
use StageMailable;
|
||||
|
||||
/** @var int WORKFLOW_STAGE_ID_... */
|
||||
public $_stageId;
|
||||
|
||||
/** @var PKPRequest */
|
||||
public $_request;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->addRoleAssignment(
|
||||
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR],
|
||||
['fetchGrid', 'fetchRow', 'readQuery', 'participants', 'addQuery', 'editQuery', 'updateQuery', 'deleteQuery']
|
||||
);
|
||||
$this->addRoleAssignment(
|
||||
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
|
||||
['openQuery', 'closeQuery', 'saveSequence', 'fetchTemplateBody']
|
||||
);
|
||||
$this->addRoleAssignment(
|
||||
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN],
|
||||
['leaveQuery']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Getters/Setters
|
||||
//
|
||||
/**
|
||||
* Get the authorized submission.
|
||||
*
|
||||
* @return Submission
|
||||
*/
|
||||
public function getSubmission()
|
||||
{
|
||||
return $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_SUBMISSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorized query.
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stage id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStageId()
|
||||
{
|
||||
return $this->_stageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query assoc type.
|
||||
*
|
||||
* @return int Application::ASSOC_TYPE_...
|
||||
*/
|
||||
public function getAssocType()
|
||||
{
|
||||
return PKPApplication::ASSOC_TYPE_SUBMISSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query assoc ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocId()
|
||||
{
|
||||
return $this->getSubmission()->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a data provider for this grid.
|
||||
*
|
||||
* @return QueriesGridCellProvider
|
||||
*/
|
||||
public function getCellProvider()
|
||||
{
|
||||
return new QueriesGridCellProvider(
|
||||
$this->getSubmission(),
|
||||
$this->getStageId(),
|
||||
$this->getAccessHelper()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Overridden methods from PKPHandler.
|
||||
// Note: this is subclassed in application-specific grids.
|
||||
//
|
||||
/**
|
||||
* @copydoc PKPHandler::authorize()
|
||||
*/
|
||||
public function authorize($request, &$args, $roleAssignments)
|
||||
{
|
||||
$this->_stageId = (int) $request->getUserVar('stageId'); // This is being validated in WorkflowStageAccessPolicy
|
||||
|
||||
$this->_request = $request;
|
||||
|
||||
if ($request->getUserVar('queryId')) {
|
||||
$this->addPolicy(new QueryAccessPolicy($request, $args, $roleAssignments, $this->_stageId));
|
||||
} else {
|
||||
$this->addPolicy(new QueryWorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $this->_stageId));
|
||||
}
|
||||
|
||||
return parent::authorize($request, $args, $roleAssignments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridHandler::initialize()
|
||||
*
|
||||
* @param null|mixed $args
|
||||
*/
|
||||
public function initialize($request, $args = null)
|
||||
{
|
||||
parent::initialize($request, $args);
|
||||
|
||||
switch ($this->getStageId()) {
|
||||
case WORKFLOW_STAGE_ID_SUBMISSION: $this->setTitle('submission.queries.submission');
|
||||
break;
|
||||
case WORKFLOW_STAGE_ID_EDITING: $this->setTitle('submission.queries.editorial');
|
||||
break;
|
||||
case WORKFLOW_STAGE_ID_PRODUCTION: $this->setTitle('submission.queries.production');
|
||||
break;
|
||||
case WORKFLOW_STAGE_ID_INTERNAL_REVIEW:
|
||||
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW:
|
||||
$this->setTitle('submission.queries.review');
|
||||
break;
|
||||
default: assert(false);
|
||||
}
|
||||
|
||||
// Columns
|
||||
$cellProvider = $this->getCellProvider();
|
||||
$this->addColumn(new QueryTitleGridColumn($this->getRequestArgs()));
|
||||
|
||||
$this->addColumn(new GridColumn(
|
||||
'from',
|
||||
'submission.query.from',
|
||||
null,
|
||||
null,
|
||||
$cellProvider,
|
||||
['html' => true, 'width' => 20]
|
||||
));
|
||||
$this->addColumn(new GridColumn(
|
||||
'lastReply',
|
||||
'submission.query.lastReply',
|
||||
null,
|
||||
null,
|
||||
$cellProvider,
|
||||
['html' => true, 'width' => 20]
|
||||
));
|
||||
$this->addColumn(new GridColumn(
|
||||
'replies',
|
||||
'submission.query.replies',
|
||||
null,
|
||||
null,
|
||||
$cellProvider,
|
||||
['width' => 10, 'alignment' => GridColumn::COLUMN_ALIGNMENT_CENTER]
|
||||
));
|
||||
|
||||
$this->addColumn(
|
||||
new GridColumn(
|
||||
'closed',
|
||||
'submission.query.closed',
|
||||
null,
|
||||
'controllers/grid/common/cell/selectStatusCell.tpl',
|
||||
$cellProvider,
|
||||
['width' => 10, 'alignment' => GridColumn::COLUMN_ALIGNMENT_CENTER]
|
||||
)
|
||||
);
|
||||
|
||||
$router = $request->getRouter();
|
||||
if ($this->getAccessHelper()->getCanCreate($this->getStageId())) {
|
||||
$this->addAction(new LinkAction(
|
||||
'addQuery',
|
||||
new AjaxModal(
|
||||
$router->url($request, null, null, 'addQuery', null, $this->getRequestArgs()),
|
||||
__('grid.action.addQuery'),
|
||||
'modal_add_item'
|
||||
),
|
||||
__('grid.action.addQuery'),
|
||||
'add_item'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Overridden methods from GridHandler
|
||||
//
|
||||
/**
|
||||
* @copydoc GridHandler::initFeatures()
|
||||
*/
|
||||
public function initFeatures($request, $args)
|
||||
{
|
||||
$features = parent::initFeatures($request, $args);
|
||||
if ($this->getAccessHelper()->getCanOrder($this->getStageId())) {
|
||||
$features[] = new OrderGridItemsFeature();
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridHandler::getDataElementSequence()
|
||||
*/
|
||||
public function getDataElementSequence($row)
|
||||
{
|
||||
return $row->getSequence();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridHandler::setDataElementSequence()
|
||||
*/
|
||||
public function setDataElementSequence($request, $rowId, $gridDataElement, $newSequence)
|
||||
{
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query = $queryDao->getById($rowId, $this->getAssocType(), $this->getAssocId());
|
||||
$query->setSequence($newSequence);
|
||||
$queryDao->updateObject($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridHandler::getRowInstance()
|
||||
*
|
||||
* @return QueriesGridRow
|
||||
*/
|
||||
public function getRowInstance()
|
||||
{
|
||||
return new QueriesGridRow(
|
||||
$this->getSubmission(),
|
||||
$this->getStageId(),
|
||||
$this->getAccessHelper()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the queries grid access helper
|
||||
*
|
||||
* @return QueriesAccessHelper
|
||||
*/
|
||||
public function getAccessHelper()
|
||||
{
|
||||
return new QueriesAccessHelper($this->getAuthorizedContext(), $this->_request->getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments that will identify the data in the grid.
|
||||
* Overridden by child grids.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequestArgs()
|
||||
{
|
||||
return [
|
||||
'submissionId' => $this->getSubmission()->getId(),
|
||||
'stageId' => $this->getStageId(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridHandler::loadData()
|
||||
*
|
||||
* @param null|mixed $filter
|
||||
*/
|
||||
public function loadData($request, $filter = null)
|
||||
{
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
return $queryDao->getByAssoc(
|
||||
$this->getAssocType(),
|
||||
$this->getAssocId(),
|
||||
$this->getStageId(),
|
||||
$this->getAccessHelper()->getCanListAll($this->getStageId()) ? null : $request->getUser()->getId()
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Public Query Grid Actions
|
||||
//
|
||||
/**
|
||||
* Add a query
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function addQuery($args, $request)
|
||||
{
|
||||
if (!$this->getAccessHelper()->getCanCreate($this->getStageId())) {
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
|
||||
$queryForm = new QueryForm(
|
||||
$request,
|
||||
$this->getAssocType(),
|
||||
$this->getAssocId(),
|
||||
$this->getStageId()
|
||||
);
|
||||
$queryForm->initData();
|
||||
return new JSONMessage(true, $queryForm->fetch($request, null, false, $this->getRequestArgs()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a query.
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function deleteQuery($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
if (!$request->checkCSRF() || !$query || !$this->getAccessHelper()->getCanDelete($query->getId())) {
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$queryDao->deleteObject($query);
|
||||
|
||||
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
|
||||
$notificationDao->deleteByAssoc(PKPApplication::ASSOC_TYPE_QUERY, $query->getId());
|
||||
|
||||
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,
|
||||
PKPApplication::ASSOC_TYPE_SUBMISSION,
|
||||
$this->getAssocId()
|
||||
);
|
||||
}
|
||||
|
||||
return \PKP\db\DAO::getDataChangedEvent($query->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a closed query.
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function openQuery($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
if (!$query || !$this->getAccessHelper()->getCanOpenClose($query)) {
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query->setIsClosed(false);
|
||||
$queryDao->updateObject($query);
|
||||
return \PKP\db\DAO::getDataChangedEvent($query->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Close an open query.
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function closeQuery($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
if (!$query || !$this->getAccessHelper()->getCanOpenClose($query)) {
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query->setIsClosed(true);
|
||||
$queryDao->updateObject($query);
|
||||
return \PKP\db\DAO::getDataChangedEvent($query->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the query notes grid handler.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryNotesGridHandlerName()
|
||||
{
|
||||
return 'grid.queries.QueryNotesGridHandler';
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a query
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function readQuery($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
$router = $request->getRouter();
|
||||
$user = $request->getUser();
|
||||
$context = $request->getContext();
|
||||
|
||||
$actionArgs = array_merge($this->getRequestArgs(), ['queryId' => $query->getId()]);
|
||||
|
||||
// If appropriate, create an Edit action for the participants list
|
||||
if ($this->getAccessHelper()->getCanEdit($query->getId())) {
|
||||
$editAction = new LinkAction(
|
||||
'editQuery',
|
||||
new AjaxModal(
|
||||
$router->url($request, null, null, 'editQuery', null, $actionArgs),
|
||||
__('grid.action.updateQuery'),
|
||||
'modal_edit'
|
||||
),
|
||||
__('grid.action.edit'),
|
||||
'edit'
|
||||
);
|
||||
} else {
|
||||
$editAction = null;
|
||||
}
|
||||
|
||||
$leaveQueryLinkAction = new LinkAction(
|
||||
'leaveQuery',
|
||||
new RemoteActionConfirmationModal(
|
||||
$request->getSession(),
|
||||
__('submission.query.leaveQuery.confirm'),
|
||||
__('submission.query.leaveQuery'),
|
||||
$router->url($request, null, null, 'leaveQuery', null, $actionArgs),
|
||||
'modal_delete'
|
||||
),
|
||||
__('submission.query.leaveQuery'),
|
||||
'leaveQuery'
|
||||
);
|
||||
|
||||
// Show leave query button for journal managers included in the query
|
||||
if ($user && $this->_getCurrentUserCanLeave($query->getId())) {
|
||||
$showLeaveQueryButton = true;
|
||||
} else {
|
||||
$showLeaveQueryButton = false;
|
||||
}
|
||||
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$templateMgr->assign([
|
||||
'queryNotesGridHandlerName' => $this->getQueryNotesGridHandlerName(),
|
||||
'requestArgs' => $this->getRequestArgs(),
|
||||
'query' => $query,
|
||||
'editAction' => $editAction,
|
||||
'leaveQueryLinkAction' => $leaveQueryLinkAction,
|
||||
'showLeaveQueryButton' => $showLeaveQueryButton,
|
||||
]);
|
||||
return new JSONMessage(true, $templateMgr->fetch('controllers/grid/queries/readQuery.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the list of participants for a query
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function participants($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$context = $request->getContext();
|
||||
$user = $request->getUser();
|
||||
|
||||
$participants = [];
|
||||
foreach ($queryDao->getParticipantIds($query->getId()) as $userId) {
|
||||
$user = Repo::user()->get($userId);
|
||||
if ($user) {
|
||||
$participants[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$templateMgr->assign('participants', $participants);
|
||||
|
||||
if ($user && $this->_getCurrentUserCanLeave($query->getId())) {
|
||||
$showLeaveQueryButton = true;
|
||||
} else {
|
||||
$showLeaveQueryButton = false;
|
||||
}
|
||||
$json = new JSONMessage();
|
||||
$json->setStatus(true);
|
||||
$json->setContent($templateMgr->fetch('controllers/grid/queries/participants.tpl'));
|
||||
$json->setAdditionalAttributes(['showLeaveQueryButton' => $showLeaveQueryButton]);
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a query
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function editQuery($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
if (!$this->getAccessHelper()->getCanEdit($query->getId())) {
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
|
||||
// Form handling
|
||||
$queryForm = new QueryForm(
|
||||
$request,
|
||||
$this->getAssocType(),
|
||||
$this->getAssocId(),
|
||||
$this->getStageId(),
|
||||
$query->getId()
|
||||
);
|
||||
$queryForm->initData();
|
||||
return new JSONMessage(true, $queryForm->fetch($request, null, false, $this->getRequestArgs()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a query
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function updateQuery($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
if (!$this->getAccessHelper()->getCanEdit($query->getId())) {
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
/** @var QueryDAO */
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO');
|
||||
$oldParticipantIds = $queryDao->getParticipantIds($query->getId());
|
||||
|
||||
$queryForm = new QueryForm(
|
||||
$request,
|
||||
$this->getAssocType(),
|
||||
$this->getAssocId(),
|
||||
$this->getStageId(),
|
||||
$query->getId()
|
||||
);
|
||||
$queryForm->readInputData();
|
||||
|
||||
if ($queryForm->validate()) {
|
||||
$queryForm->execute();
|
||||
$notificationMgr = new NotificationManager();
|
||||
|
||||
if ($this->getStageId() == WORKFLOW_STAGE_ID_EDITING ||
|
||||
$this->getStageId() == 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,
|
||||
PKPApplication::ASSOC_TYPE_SUBMISSION,
|
||||
$this->getAssocId()
|
||||
);
|
||||
}
|
||||
|
||||
// Send notifications
|
||||
$currentUser = $request->getUser();
|
||||
$newParticipantIds = $queryForm->getData('users');
|
||||
$added = array_diff($newParticipantIds, $oldParticipantIds);
|
||||
// Don't notify the current user
|
||||
if ($key = array_search($currentUser->getId(), $added)) {
|
||||
unset($added[$key]);
|
||||
}
|
||||
|
||||
/** @var NotificationSubscriptionSettingsDAO */
|
||||
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO');
|
||||
$note = $query->getHeadNote();
|
||||
$submission = $this->getSubmission();
|
||||
|
||||
// Find attachments if any
|
||||
$submissionFiles = Repo::submissionFile()
|
||||
->getCollector()
|
||||
->filterByAssoc(
|
||||
PKPApplication::ASSOC_TYPE_NOTE,
|
||||
[$note->getId()]
|
||||
)->filterBySubmissionIds([$submission->getId()])
|
||||
->getMany();
|
||||
|
||||
foreach ($added as $userId) {
|
||||
$user = Repo::user()->get((int) $userId);
|
||||
|
||||
$notification = $notificationMgr->createNotification(
|
||||
$request,
|
||||
$userId,
|
||||
PKPNotification::NOTIFICATION_TYPE_NEW_QUERY,
|
||||
$request->getContext()->getId(),
|
||||
PKPApplication::ASSOC_TYPE_QUERY,
|
||||
$query->getId(),
|
||||
Notification::NOTIFICATION_LEVEL_TASK
|
||||
);
|
||||
|
||||
// Check if the user is unsubscribed
|
||||
$notificationSubscriptionSettings = $notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings(
|
||||
NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY,
|
||||
$user->getId(),
|
||||
$request->getContext()->getId()
|
||||
);
|
||||
if (in_array(PKPNotification::NOTIFICATION_TYPE_NEW_QUERY, $notificationSubscriptionSettings)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mailable = $this->getStageMailable($request->getContext(), $submission)
|
||||
->sender($currentUser)
|
||||
->recipients([$user])
|
||||
->subject($note->getData('title'))
|
||||
->body($note->getData('contents'))
|
||||
->allowUnsubscribe($notification);
|
||||
|
||||
$submissionFiles->each(fn(SubmissionFile $item) => $mailable->attachSubmissionFile(
|
||||
$item->getId(),
|
||||
$item->getLocalizedData('name')
|
||||
));
|
||||
|
||||
Mail::send($mailable);
|
||||
$logDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $logDao */
|
||||
$logDao->logMailable(SubmissionEmailLogEntry::SUBMISSION_EMAIL_DISCUSSION_NOTIFY, $mailable, $submission);
|
||||
}
|
||||
|
||||
return \PKP\db\DAO::getDataChangedEvent($query->getId());
|
||||
}
|
||||
|
||||
// If this was new (placeholder) query that didn't validate, remember whether or not
|
||||
// we need to delete it on cancellation.
|
||||
if ($request->getUserVar('wasNew')) {
|
||||
$queryForm->setIsNew(true);
|
||||
}
|
||||
return new JSONMessage(
|
||||
true,
|
||||
$queryForm->fetch(
|
||||
$request,
|
||||
null,
|
||||
false,
|
||||
array_merge(
|
||||
$this->getRequestArgs(),
|
||||
['queryId' => $query->getId()]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave query
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function leaveQuery($args, $request)
|
||||
{
|
||||
$queryId = $args['queryId'];
|
||||
$user = $request->getUser();
|
||||
if ($user && $this->_getCurrentUserCanLeave($queryId)) {
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$queryDao->removeParticipant($queryId, $user->getId());
|
||||
$json = new JSONMessage();
|
||||
$json->setEvent('user-left-discussion');
|
||||
} else {
|
||||
$json = new JSONMessage(false);
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user can leave a query. Only allow if query has more than two participants.
|
||||
*
|
||||
* @param int $queryId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function _getCurrentUserCanLeave($queryId)
|
||||
{
|
||||
$userRoles = $this->getAuthorizedContextObject(PKPApplication::ASSOC_TYPE_USER_ROLES);
|
||||
if (!count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, ], $userRoles))) {
|
||||
return false;
|
||||
}
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$participantIds = $queryDao->getParticipantIds($queryId);
|
||||
if (count($participantIds) < 3) {
|
||||
return false;
|
||||
}
|
||||
$user = Application::get()->getRequest()->getUser();
|
||||
return in_array($user->getId(), $participantIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an email template's message body.
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function fetchTemplateBody(array $args, PKPRequest $request): JSONMessage
|
||||
{
|
||||
$templateId = $request->getUserVar('template');
|
||||
$context = $request->getContext();
|
||||
$template = Repo::emailTemplate()->getByKey($context->getId(), $templateId);
|
||||
if ($template) {
|
||||
$mailable = $this->getStageMailable($context, $this->getSubmission());
|
||||
$mailable->sender($request->getUser());
|
||||
$data = $mailable->getData();
|
||||
|
||||
return new JSONMessage(
|
||||
true,
|
||||
[
|
||||
'body' => Mail::compileParams($template->getLocalizedData('body'), $data),
|
||||
'subject' => Mail::compileParams($template->getLocalizedData('subject'), $data),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueriesGridRow.php
|
||||
*
|
||||
* Copyright (c) 2016-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 QueriesGridRow
|
||||
*
|
||||
* @ingroup controllers_grid_queries
|
||||
*
|
||||
* @brief Base class for query grid row definition
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
use APP\submission\Submission;
|
||||
use PKP\controllers\grid\GridRow;
|
||||
use PKP\linkAction\LinkAction;
|
||||
use PKP\linkAction\request\AjaxModal;
|
||||
use PKP\linkAction\request\RemoteActionConfirmationModal;
|
||||
|
||||
class QueriesGridRow extends GridRow
|
||||
{
|
||||
/** @var Submission */
|
||||
public $_submission;
|
||||
|
||||
/** @var int */
|
||||
public $_stageId;
|
||||
|
||||
/** @var QueriesAccessHelper */
|
||||
public $_queriesAccessHelper;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Submission $submission
|
||||
* @param int $stageId
|
||||
* @param QueriesAccessHelper $queriesAccessHelper
|
||||
*/
|
||||
public function __construct($submission, $stageId, $queriesAccessHelper)
|
||||
{
|
||||
$this->_submission = $submission;
|
||||
$this->_stageId = $stageId;
|
||||
$this->_queriesAccessHelper = $queriesAccessHelper;
|
||||
|
||||
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);
|
||||
|
||||
// Retrieve the submission from the request
|
||||
$submission = $this->getSubmission();
|
||||
|
||||
// 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['queryId'] = $rowId;
|
||||
|
||||
// Add row-level actions
|
||||
if ($this->_queriesAccessHelper->getCanEdit($rowId)) {
|
||||
$this->addAction(
|
||||
new LinkAction(
|
||||
'editQuery',
|
||||
new AjaxModal(
|
||||
$router->url($request, null, null, 'editQuery', null, $actionArgs),
|
||||
__('grid.action.updateQuery'),
|
||||
'modal_edit'
|
||||
),
|
||||
__('grid.action.edit'),
|
||||
'edit'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->_queriesAccessHelper->getCanDelete($rowId)) {
|
||||
$this->addAction(
|
||||
new LinkAction(
|
||||
'deleteQuery',
|
||||
new RemoteActionConfirmationModal(
|
||||
$request->getSession(),
|
||||
__('common.confirmDelete'),
|
||||
__('grid.action.delete'),
|
||||
$router->url($request, null, null, 'deleteQuery', null, $actionArgs),
|
||||
'modal_delete'
|
||||
),
|
||||
__('grid.action.delete'),
|
||||
'delete'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the submission for this row (already authorized)
|
||||
*
|
||||
* @return Submission
|
||||
*/
|
||||
public function getSubmission()
|
||||
{
|
||||
return $this->_submission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stageId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStageId()
|
||||
{
|
||||
return $this->_stageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base arguments that will identify the data in the grid.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequestArgs()
|
||||
{
|
||||
$submission = $this->getSubmission();
|
||||
return [
|
||||
'submissionId' => $submission->getId(),
|
||||
'stageId' => $this->getStageId(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueryNotesGridCellProvider.php
|
||||
*
|
||||
* Copyright (c) 2016-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 QueryNotesGridCellProvider
|
||||
*
|
||||
* @ingroup controllers_grid_queries
|
||||
*
|
||||
* @brief Base class for a cell provider that can retrieve query note info.
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use PKP\controllers\api\file\linkAction\DownloadFileLinkAction;
|
||||
use PKP\controllers\grid\DataObjectGridCellProvider;
|
||||
use PKP\controllers\grid\GridColumn;
|
||||
use PKP\controllers\grid\GridHandler;
|
||||
use PKP\core\PKPString;
|
||||
use PKP\note\Note;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
|
||||
class QueryNotesGridCellProvider extends DataObjectGridCellProvider
|
||||
{
|
||||
/** @var Submission */
|
||||
public $_submission;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Submission $submission
|
||||
*/
|
||||
public function __construct($submission)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->_submission = $submission;
|
||||
}
|
||||
|
||||
//
|
||||
// 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 Note $element */
|
||||
$user = $element->getUser();
|
||||
$datetimeFormatShort = PKPString::convertStrftimeFormat(Application::get()->getRequest()->getContext()->getLocalizedDateTimeFormatShort());
|
||||
|
||||
switch ($columnId) {
|
||||
case 'from':
|
||||
return ['label' => ($user ? $user->getUsername() : '—') . '<br />' . date($datetimeFormatShort, strtotime($element->getDateCreated()))];
|
||||
}
|
||||
|
||||
return parent::getTemplateVarsFromRowColumn($row, $column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridCellProvider::getCellActions()
|
||||
*/
|
||||
public function getCellActions($request, $row, $column, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
|
||||
{
|
||||
switch ($column->getId()) {
|
||||
case 'contents':
|
||||
$submissionFiles = Repo::submissionFile()
|
||||
->getCollector()
|
||||
->filterByAssoc(
|
||||
Application::ASSOC_TYPE_NOTE,
|
||||
[$row->getData()->getId()]
|
||||
)->filterBySubmissionIds([$this->_submission->getId()])
|
||||
->filterByFileStages([SubmissionFile::SUBMISSION_FILE_QUERY])
|
||||
->getMany();
|
||||
|
||||
$actions = [];
|
||||
foreach ($submissionFiles as $submissionFile) {
|
||||
$actions[] = new DownloadFileLinkAction($request, $submissionFile, $request->getUserVar('stageId'));
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
return parent::getCellActions($request, $row, $column, $position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueryNotesGridHandler.php
|
||||
*
|
||||
* Copyright (c) 2016-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 QueryNotesGridHandler
|
||||
*
|
||||
* @ingroup controllers_grid_query
|
||||
*
|
||||
* @brief base PKP class to handle query grid requests.
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\notification\Notification;
|
||||
use APP\notification\NotificationManager;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\controllers\grid\GridColumn;
|
||||
use PKP\controllers\grid\GridHandler;
|
||||
use PKP\controllers\grid\queries\form\QueryNoteForm;
|
||||
use PKP\controllers\grid\queries\traits\StageMailable;
|
||||
use PKP\core\JSONMessage;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\note\Note;
|
||||
use PKP\note\NoteDAO;
|
||||
use PKP\notification\NotificationDAO;
|
||||
use PKP\notification\NotificationSubscriptionSettingsDAO;
|
||||
use PKP\notification\PKPNotification;
|
||||
use PKP\query\Query;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\authorization\QueryAccessPolicy;
|
||||
use PKP\security\Role;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
use PKP\user\User;
|
||||
|
||||
class QueryNotesGridHandler extends GridHandler
|
||||
{
|
||||
use StageMailable;
|
||||
|
||||
/** @var User */
|
||||
public $_user;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->addRoleAssignment(
|
||||
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
|
||||
['fetchGrid', 'fetchRow', 'addNote', 'insertNote', 'deleteNote']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Getters/Setters
|
||||
//
|
||||
/**
|
||||
* Get the authorized submission.
|
||||
*/
|
||||
public function getSubmission(): Submission
|
||||
{
|
||||
return $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query.
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery(): ?Query
|
||||
{
|
||||
return $this->getAuthorizedContextObject(Application::ASSOC_TYPE_QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stage id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStageId()
|
||||
{
|
||||
return $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE);
|
||||
}
|
||||
|
||||
//
|
||||
// Overridden methods from PKPHandler.
|
||||
// Note: this is subclassed in application-specific grids.
|
||||
//
|
||||
/**
|
||||
* @copydoc PKPHandler::authorize()
|
||||
*/
|
||||
public function authorize($request, &$args, $roleAssignments)
|
||||
{
|
||||
$stageId = $request->getUserVar('stageId'); // This is being validated in WorkflowStageAccessPolicy
|
||||
|
||||
// Get the access policy
|
||||
$this->addPolicy(new QueryAccessPolicy($request, $args, $roleAssignments, $stageId));
|
||||
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.query.messages');
|
||||
|
||||
$cellProvider = new QueryNotesGridCellProvider($this->getSubmission());
|
||||
|
||||
// Columns
|
||||
$this->addColumn(
|
||||
new GridColumn(
|
||||
'contents',
|
||||
'common.note',
|
||||
null,
|
||||
null,
|
||||
$cellProvider,
|
||||
['width' => 80, 'html' => true]
|
||||
)
|
||||
);
|
||||
$this->addColumn(
|
||||
new GridColumn(
|
||||
'from',
|
||||
'submission.query.from',
|
||||
null,
|
||||
null,
|
||||
$cellProvider,
|
||||
['html' => true]
|
||||
)
|
||||
);
|
||||
|
||||
$this->_user = $request->getUser();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Overridden methods from GridHandler
|
||||
//
|
||||
/**
|
||||
* @copydoc GridHandler::getRowInstance()
|
||||
*
|
||||
* @return QueryNotesGridRow
|
||||
*/
|
||||
public function getRowInstance()
|
||||
{
|
||||
return new QueryNotesGridRow($this->getRequestArgs(), $this->getQuery(), $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments that will identify the data in the grid.
|
||||
* Overridden by child grids.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequestArgs()
|
||||
{
|
||||
return [
|
||||
'submissionId' => $this->getSubmission()->getId(),
|
||||
'stageId' => $this->getStageId(),
|
||||
'queryId' => $this->getQuery()->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc GridHandler::loadData()
|
||||
*
|
||||
* Incomplete notes are hidden from everyone except
|
||||
* the user who created them. These are considered
|
||||
* in-progress and not yet saved.
|
||||
*
|
||||
* @see https://github.com/pkp/pkp-lib/issues/1155
|
||||
*
|
||||
* @param null|mixed $filter
|
||||
*/
|
||||
public function loadData($request, $filter = null)
|
||||
{
|
||||
return $this->getQuery()
|
||||
->getReplies(null, NoteDAO::NOTE_ORDER_DATE_CREATED, \PKP\db\DAO::SORT_DIRECTION_ASC)
|
||||
->filter(function (Note $note) use ($request) {
|
||||
return (bool) $note->getContents() || (
|
||||
$note->getUserId() === $request->getUser()->getId()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Public Query Notes Grid Actions
|
||||
//
|
||||
/**
|
||||
* Present the form to add a new note.
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function addNote($args, $request)
|
||||
{
|
||||
$queryNoteForm = new QueryNoteForm($this->getRequestArgs(), $this->getQuery(), $request->getUser());
|
||||
$queryNoteForm->initData();
|
||||
return new JSONMessage(true, $queryNoteForm->fetch($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new note.
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function insertNote($args, $request)
|
||||
{
|
||||
$queryNoteForm = new QueryNoteForm($this->getRequestArgs(), $this->getQuery(), $request->getUser(), $request->getUserVar('noteId'));
|
||||
$queryNoteForm->readInputData();
|
||||
if ($queryNoteForm->validate()) {
|
||||
$note = $queryNoteForm->execute();
|
||||
$this->insertedNoteNotify($note);
|
||||
return \PKP\db\DAO::getDataChangedEvent($this->getQuery()->getId());
|
||||
} else {
|
||||
return new JSONMessage(true, $queryNoteForm->fetch($request));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current user can manage (delete) a note.
|
||||
*
|
||||
* @param Note $note optional
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanManage($note)
|
||||
{
|
||||
$isAdmin = (0 != count(array_intersect(
|
||||
$this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES),
|
||||
[Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_SUB_EDITOR]
|
||||
)));
|
||||
|
||||
if ($note === null) {
|
||||
return $isAdmin;
|
||||
} else {
|
||||
return ($note->getUserId() == $this->_user->getId() || $isAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a query note.
|
||||
*
|
||||
* @param array $args
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return JSONMessage JSON object
|
||||
*/
|
||||
public function deleteNote($args, $request)
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$note = $noteDao->getById($request->getUserVar('noteId'));
|
||||
$user = $request->getUser();
|
||||
|
||||
if (!$request->checkCSRF() || !$note || $note->getAssocType() != Application::ASSOC_TYPE_QUERY || $note->getAssocId() != $query->getId()) {
|
||||
// The note didn't exist or has the wrong assoc info.
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
|
||||
if (!$this->getCanManage($note)) {
|
||||
// The user doesn't own the note and isn't privileged enough to delete it.
|
||||
return new JSONMessage(false);
|
||||
}
|
||||
|
||||
$noteDao->deleteObject($note);
|
||||
return \PKP\db\DAO::getDataChangedEvent($note->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends notification and email to the query participants
|
||||
*/
|
||||
protected function insertedNoteNotify(Note $note): void
|
||||
{
|
||||
$notificationManager = new NotificationManager();
|
||||
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query = $queryDao->getById($note->getData('assocId'));
|
||||
$sender = Repo::user()->get($note->getData('userId'));
|
||||
$request = Application::get()->getRequest();
|
||||
$context = $request->getContext();
|
||||
$submission = $this->getSubmission();
|
||||
$title = $query->getHeadNote()->getData('title');
|
||||
|
||||
/** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */
|
||||
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO');
|
||||
|
||||
// Find attachments if any
|
||||
$submissionFiles = Repo::submissionFile()
|
||||
->getCollector()
|
||||
->filterByAssoc(
|
||||
PKPApplication::ASSOC_TYPE_NOTE,
|
||||
[$note->getId()]
|
||||
)->filterBySubmissionIds([$submission->getId()])
|
||||
->getMany();
|
||||
|
||||
foreach ($queryDao->getParticipantIds($query->getId()) as $userId) {
|
||||
// Delete any prior notifications of the same type (e.g. prior "new" comments)
|
||||
$notificationDao->deleteByAssoc(
|
||||
PKPApplication::ASSOC_TYPE_QUERY,
|
||||
$query->getId(),
|
||||
$userId,
|
||||
PKPNotification::NOTIFICATION_TYPE_QUERY_ACTIVITY,
|
||||
$context->getId()
|
||||
);
|
||||
|
||||
// No need to additionally notify the posting user.
|
||||
if ($userId == $sender->getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Notify the user of a new query.
|
||||
$notification = $notificationManager->createNotification(
|
||||
$request,
|
||||
$userId,
|
||||
PKPNotification::NOTIFICATION_TYPE_QUERY_ACTIVITY,
|
||||
$request->getContext()->getId(),
|
||||
PKPApplication::ASSOC_TYPE_QUERY,
|
||||
$query->getId(),
|
||||
Notification::NOTIFICATION_LEVEL_TASK
|
||||
);
|
||||
|
||||
// Check if user is subscribed to this type of notification emails
|
||||
if (!$notification || in_array(
|
||||
PKPNotification::NOTIFICATION_TYPE_QUERY_ACTIVITY,
|
||||
$notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings(
|
||||
NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY,
|
||||
$userId,
|
||||
(int) $context->getId()
|
||||
)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$recipient = Repo::user()->get($userId);
|
||||
$mailable = $this->getStageMailable($context, $submission)
|
||||
->sender($sender)
|
||||
->recipients([$recipient])
|
||||
->subject(__('common.re') . ' ' . $title)
|
||||
->body($note->getContents())
|
||||
->allowUnsubscribe($notification);
|
||||
|
||||
$submissionFiles->each(fn(SubmissionFile $item) => $mailable->attachSubmissionFile(
|
||||
$item->getId(),
|
||||
$item->getLocalizedData('name')
|
||||
));
|
||||
|
||||
Mail::send($mailable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueryNotesGridRow.php
|
||||
*
|
||||
* Copyright (c) 2016-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 QueryNotesGridRow
|
||||
*
|
||||
* @ingroup controllers_grid_queries
|
||||
*
|
||||
* @brief Base class for query grid row definition
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
use PKP\controllers\grid\GridRow;
|
||||
use PKP\linkAction\LinkAction;
|
||||
use PKP\linkAction\request\RemoteActionConfirmationModal;
|
||||
use PKP\query\Query;
|
||||
|
||||
class QueryNotesGridRow extends GridRow
|
||||
{
|
||||
/** @var array */
|
||||
public $_actionArgs;
|
||||
|
||||
/** @var Query */
|
||||
public $_query;
|
||||
|
||||
/** @var QueryNotesGridHandler */
|
||||
public $_queryNotesGrid;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $actionArgs Action arguments
|
||||
* @param Query $query
|
||||
* @param QueryNotesGridHandler $queryNotesGrid The notes grid containing this row
|
||||
*/
|
||||
public function __construct($actionArgs, $query, $queryNotesGrid)
|
||||
{
|
||||
$this->_actionArgs = $actionArgs;
|
||||
$this->_query = $query;
|
||||
$this->_queryNotesGrid = $queryNotesGrid;
|
||||
|
||||
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();
|
||||
$headNote = $this->getQuery()->getHeadNote();
|
||||
if (!empty($rowId) && is_numeric($rowId) && (!$headNote || $headNote->getId() != $rowId)) {
|
||||
// Only add row actions if this is an existing row
|
||||
$router = $request->getRouter();
|
||||
$actionArgs = array_merge(
|
||||
$this->_actionArgs,
|
||||
['noteId' => $rowId]
|
||||
);
|
||||
|
||||
// Add row-level actions
|
||||
if ($this->_queryNotesGrid->getCanManage($this->getData())) {
|
||||
$this->addAction(
|
||||
new LinkAction(
|
||||
'deleteNote',
|
||||
new RemoteActionConfirmationModal(
|
||||
$request->getSession(),
|
||||
__('common.confirmDelete'),
|
||||
__('grid.action.delete'),
|
||||
$router->url($request, null, null, 'deleteNote', null, $actionArgs),
|
||||
'modal_delete'
|
||||
),
|
||||
__('grid.action.delete'),
|
||||
'delete'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base arguments that will identify the data in the grid.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequestArgs()
|
||||
{
|
||||
return $this->_actionArgs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/QueryTitleGridColumn.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 QueryTitleGridColumn
|
||||
*
|
||||
* @ingroup controllers_grid_queries
|
||||
*
|
||||
* @brief Implements a query tile column.
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries;
|
||||
|
||||
use PKP\controllers\grid\ColumnBasedGridCellProvider;
|
||||
use PKP\controllers\grid\GridColumn;
|
||||
use PKP\controllers\grid\GridHandler;
|
||||
use PKP\linkAction\LinkAction;
|
||||
use PKP\linkAction\request\AjaxModal;
|
||||
|
||||
class QueryTitleGridColumn extends GridColumn
|
||||
{
|
||||
/** @var array Action args for link actions */
|
||||
public $_actionArgs;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $actionArgs Action args for link actions
|
||||
*/
|
||||
public function __construct($actionArgs)
|
||||
{
|
||||
$this->_actionArgs = $actionArgs;
|
||||
|
||||
$cellProvider = new ColumnBasedGridCellProvider();
|
||||
|
||||
parent::__construct(
|
||||
'name',
|
||||
'common.name',
|
||||
null,
|
||||
null,
|
||||
$cellProvider,
|
||||
['width' => 60, 'alignment' => GridColumn::COLUMN_ALIGNMENT_LEFT]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Method expected by ColumnBasedGridCellProvider
|
||||
* to render a cell in this column.
|
||||
*
|
||||
* @copydoc ColumnBasedGridCellProvider::getTemplateVarsFromRowColumn()
|
||||
*/
|
||||
public function getTemplateVarsFromRow($row)
|
||||
{
|
||||
// We do not need any template variables because
|
||||
// the only content of this column's cell will be
|
||||
// an action. See QueryTitleGridColumn::getCellActions().
|
||||
return ['label' => ''];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Override methods from GridColumn
|
||||
//
|
||||
/**
|
||||
* @copydoc GridColumn::getCellActions()
|
||||
*/
|
||||
public function getCellActions($request, $row, $position = GridHandler::GRID_ACTION_POSITION_DEFAULT)
|
||||
{
|
||||
// Retrieve the submission file.
|
||||
$query = $row->getData();
|
||||
$headNote = $query->getHeadNote();
|
||||
|
||||
// Create the cell action to download a file.
|
||||
$router = $request->getRouter();
|
||||
$actionArgs = array_merge(
|
||||
$this->_actionArgs,
|
||||
['queryId' => $query->getId()]
|
||||
);
|
||||
|
||||
return array_merge(
|
||||
parent::getCellActions($request, $row, $position),
|
||||
[
|
||||
new LinkAction(
|
||||
'readQuery',
|
||||
new AjaxModal(
|
||||
$router->url($request, null, null, 'readQuery', null, $actionArgs),
|
||||
$headNote ? htmlspecialchars($headNote->getTitle()) : '—',
|
||||
'modal_edit'
|
||||
),
|
||||
($headNote && $headNote->getTitle() != '') ? htmlspecialchars($headNote->getTitle()) : '—',
|
||||
null
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/form/QueryForm.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 QueryForm
|
||||
*
|
||||
* @ingroup controllers_grid_users_queries_form
|
||||
*
|
||||
* @brief Form for adding/editing a new query
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries\form;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use APP\facades\Repo;
|
||||
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\note\NoteDAO;
|
||||
use PKP\notification\NotificationDAO;
|
||||
use PKP\query\Query;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\Role;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class QueryForm extends Form
|
||||
{
|
||||
use StageMailable;
|
||||
|
||||
/** @var int Application::ASSOC_TYPE_... */
|
||||
public $_assocType;
|
||||
|
||||
/** @var int Assoc ID (per _assocType) */
|
||||
public $_assocId;
|
||||
|
||||
/** @var int The stage id associated with the query being edited */
|
||||
public $_stageId;
|
||||
|
||||
/** @var Query The query being edited */
|
||||
public $_query;
|
||||
|
||||
/** @var bool True iff this is a newly-created query */
|
||||
public $_isNew;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $assocType Application::ASSOC_TYPE_...
|
||||
* @param int $assocId Assoc ID (per assocType)
|
||||
* @param int $stageId WORKFLOW_STAGE_...
|
||||
* @param int $queryId Optional query ID to edit. If none provided, a
|
||||
* (potentially temporary) query will be created.
|
||||
*/
|
||||
public function __construct($request, $assocType, $assocId, $stageId, $queryId = null)
|
||||
{
|
||||
parent::__construct('controllers/grid/queries/form/queryForm.tpl');
|
||||
$this->setStageId($stageId);
|
||||
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
if (!$queryId) {
|
||||
$this->_isNew = true;
|
||||
|
||||
// Create a query
|
||||
$query = $queryDao->newDataObject();
|
||||
$query->setAssocType($assocType);
|
||||
$query->setAssocId($assocId);
|
||||
$query->setStageId($stageId);
|
||||
$query->setSequence(REALLY_BIG_NUMBER);
|
||||
$queryDao->insertObject($query);
|
||||
$queryDao->resequence($assocType, $assocId);
|
||||
|
||||
// Add the current user as a participant by default.
|
||||
$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(Application::ASSOC_TYPE_QUERY);
|
||||
$headNote->setAssocId($query->getId());
|
||||
$headNote->setDateCreated(Core::getCurrentDate());
|
||||
$noteDao->insertObject($headNote);
|
||||
} else {
|
||||
$query = $queryDao->getById($queryId, $assocType, $assocId);
|
||||
assert(isset($query));
|
||||
// New queries will not have a head note.
|
||||
$this->_isNew = !$query->getHeadNote();
|
||||
}
|
||||
|
||||
$this->setQuery($query);
|
||||
|
||||
// Validation checks for this form
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'users', 'required', 'stageParticipants.notify.warning', function ($users) {
|
||||
return count($users) > 1;
|
||||
}));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'subject', 'required', 'submission.queries.subjectRequired'));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'comment', 'required', 'submission.queries.messageRequired'));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
|
||||
}
|
||||
|
||||
//
|
||||
// Getters and Setters
|
||||
//
|
||||
/**
|
||||
* Set the flag indicating whether the query is new (i.e. creates a placeholder that needs deleting on cancel)
|
||||
*
|
||||
*/
|
||||
public function setIsNew(bool $isNew)
|
||||
{
|
||||
$this->_isNew = $isNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query
|
||||
*
|
||||
* @param Query $query
|
||||
*/
|
||||
public function setQuery($query)
|
||||
{
|
||||
$this->_query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stage id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStageId()
|
||||
{
|
||||
return $this->_stageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stage id
|
||||
*
|
||||
* @param int $stageId
|
||||
*/
|
||||
public function setStageId($stageId)
|
||||
{
|
||||
$this->_stageId = $stageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assoc type
|
||||
*
|
||||
* @return int Application::ASSOC_TYPE_...
|
||||
*/
|
||||
public function getAssocType()
|
||||
{
|
||||
return $this->getData('assocType');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set assoc type
|
||||
*
|
||||
* @param int $assocType Application::ASSOC_TYPE_...
|
||||
*/
|
||||
public function setAssocType($assocType)
|
||||
{
|
||||
$this->setData('assocType', $assocType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assoc id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocId()
|
||||
{
|
||||
return $this->getData('assocId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set assoc id
|
||||
*
|
||||
* @param int $assocId
|
||||
*/
|
||||
public function setAssocId($assocId)
|
||||
{
|
||||
$this->setData('assocId', $assocId);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Overridden template methods
|
||||
//
|
||||
/**
|
||||
* Initialize form data from the associated author.
|
||||
*/
|
||||
public function initData()
|
||||
{
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
if ($query = $this->getQuery()) {
|
||||
$headNote = $query->getHeadNote();
|
||||
$this->_data = [
|
||||
'queryId' => $query->getId(),
|
||||
'subject' => $headNote ? $headNote->getTitle() : null,
|
||||
'comment' => $headNote ? $headNote->getContents() : null,
|
||||
'userIds' => $queryDao->getParticipantIds($query->getId()),
|
||||
'template' => null,
|
||||
];
|
||||
} else {
|
||||
// set initial defaults for queries.
|
||||
}
|
||||
// in order to be able to use the hook
|
||||
return parent::initData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the form.
|
||||
*
|
||||
* @see Form::fetch()
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param array $actionArgs Optional list of additional arguments
|
||||
* @param null|mixed $template
|
||||
*/
|
||||
public function fetch($request, $template = null, $display = false, $actionArgs = [])
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
$headNote = $query->getHeadNote();
|
||||
$user = $request->getUser();
|
||||
$context = $request->getContext();
|
||||
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$templateMgr->assign([
|
||||
'isNew' => $this->_isNew,
|
||||
'noteId' => $headNote->getId(),
|
||||
'actionArgs' => $actionArgs,
|
||||
'csrfToken' => $request->getSession()->getCSRFToken(),
|
||||
'stageId' => $this->getStageId(),
|
||||
'assocId' => $query->getAssocId(),
|
||||
'assocType' => $query->getAssocType(),
|
||||
]);
|
||||
|
||||
// Queries only support Application::ASSOC_TYPE_SUBMISSION so far
|
||||
if ($query->getAssocType() !== PKPApplication::ASSOC_TYPE_SUBMISSION) {
|
||||
return parent::fetch($request, $template, $display);
|
||||
}
|
||||
|
||||
$submission = Repo::submission()->get($query->getAssocId());
|
||||
|
||||
// Add the templates that can be used for this discussion
|
||||
$templateKeySubjectPairs = [];
|
||||
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());
|
||||
$templateKeySubjectPairs = [$mailable::getEmailTemplateKey() => $defaultTemplate->getLocalizedData('name')];
|
||||
$alternateTemplates = Repo::emailTemplate()->getCollector($context->getId())
|
||||
->alternateTo([$mailable::getEmailTemplateKey()])
|
||||
->getMany();
|
||||
foreach ($alternateTemplates as $alternateTemplate) {
|
||||
$templateKeySubjectPairs[$alternateTemplate->getData('key')] = Mail::compileParams(
|
||||
$alternateTemplate->getLocalizedData('name'),
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$templateMgr->assign('templates', $templateKeySubjectPairs);
|
||||
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
|
||||
// Get currently selected participants in the query
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$assignedParticipants = $query->getId() ? $queryDao->getParticipantIds($query->getId()) : [];
|
||||
|
||||
// Always include current user, even if not with a stage assignment
|
||||
$includeUsers[] = $user->getId();
|
||||
$excludeUsers = null;
|
||||
|
||||
// When in review stage, include/exclude users depending on the current users role
|
||||
$reviewAssignments = [];
|
||||
// Get current users roles
|
||||
$assignedRoles = (function () use ($stageAssignmentDao, $query, $user) {
|
||||
$assignedRoles = [];
|
||||
$usersAssignments = $stageAssignmentDao->getBySubmissionAndStageId($query->getAssocId(), $query->getStageId(), null, $user->getId());
|
||||
while ($usersAssignment = $usersAssignments->next()) {
|
||||
$userGroup = Repo::userGroup()->get($usersAssignment->getUserGroupId());
|
||||
$assignedRoles[] = $userGroup->getRoleId();
|
||||
}
|
||||
return $assignedRoles;
|
||||
})();
|
||||
|
||||
if ($query->getStageId() == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW || $query->getStageId() == WORKFLOW_STAGE_ID_INTERNAL_REVIEW) {
|
||||
// Get all review assignments for current submission
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignments = $reviewAssignmentDao->getBySubmissionId($submission->getId());
|
||||
|
||||
// if current user is editor/journal manager/site admin and not have author role , add all reviewers
|
||||
if (array_intersect($assignedRoles, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]) || (empty($assignedRoles) && ($user->hasRole([Role::ROLE_ID_MANAGER], $context->getId()) || $user->hasRole([Role::ROLE_ID_SITE_ADMIN], PKPApplication::CONTEXT_SITE)))) {
|
||||
foreach ($reviewAssignments as $reviewAssignment) {
|
||||
$includeUsers[] = $reviewAssignment->getReviewerId();
|
||||
}
|
||||
}
|
||||
|
||||
// if current user is an anonymous reviewer, filter out authors
|
||||
foreach ($reviewAssignments as $reviewAssignment) {
|
||||
if ($reviewAssignment->getReviewerId() == $user->getId()) {
|
||||
if ($reviewAssignment->getReviewMethod() != ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN) {
|
||||
$authorAssignments = $stageAssignmentDao->getBySubmissionAndRoleId($query->getAssocId(), Role::ROLE_ID_AUTHOR);
|
||||
while ($assignment = $authorAssignments->next()) {
|
||||
$excludeUsers[] = $assignment->getUserId();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if current user is author, add open reviewers who have accepted the request
|
||||
if (array_intersect([Role::ROLE_ID_AUTHOR], $assignedRoles)) {
|
||||
foreach ($reviewAssignments as $reviewAssignment) {
|
||||
if ($reviewAssignment->getReviewMethod() == ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN && $reviewAssignment->getDateConfirmed() && !$reviewAssignment->getDeclined()) {
|
||||
$includeUsers[] = $reviewAssignment->getReviewerId();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$usersIterator = Repo::user()->getCollector()
|
||||
->filterByContextIds([$context->getId()])
|
||||
->limit(100)
|
||||
->offset(0)
|
||||
->assignedTo($query->getAssocId(), $query->getStageId())
|
||||
->excludeUserIds($excludeUsers)
|
||||
->getMany();
|
||||
|
||||
$includedUsersIterator = Repo::user()->getCollector()->filterByUserIds($includeUsers)->getMany();
|
||||
$usersIterator = $usersIterator->merge($includedUsersIterator);
|
||||
|
||||
$allParticipants = [];
|
||||
foreach ($usersIterator as $user) {
|
||||
$allUserGroups = Repo::userGroup()->userUserGroups($user->getId(), $context->getId());
|
||||
|
||||
$userRoles = [];
|
||||
$userAssignments = $stageAssignmentDao->getBySubmissionAndStageId($query->getAssocId(), $query->getStageId(), null, $user->getId())->toArray();
|
||||
foreach ($userAssignments as $userAssignment) {
|
||||
foreach ($allUserGroups as $userGroup) {
|
||||
if ($userGroup->getId() == $userAssignment->getUserGroupId()) {
|
||||
$userRoles[] = $userGroup->getLocalizedName();
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($reviewAssignments as $assignment) {
|
||||
if ($assignment->getReviewerId() == $user->getId()) {
|
||||
$userRoles[] = __('user.role.reviewer') . ' (' . __($assignment->getReviewMethodKey()) . ')';
|
||||
}
|
||||
}
|
||||
if (!count($userRoles)) {
|
||||
$userRoles[] = __('submission.status.unassigned');
|
||||
}
|
||||
$allParticipants[$user->getId()] = __('submission.query.participantTitle', [
|
||||
'fullName' => $user->getFullName(),
|
||||
'userGroup' => join(__('common.commaListSeparator'), $userRoles),
|
||||
]);
|
||||
}
|
||||
|
||||
// Notify assistants, authors and reviewers that they have x minutes to update their own discussion
|
||||
$allowedEditTimeNotice = ['show' => false, 'limit' => 60];
|
||||
if (array_intersect($assignedRoles, [Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR, Role::ROLE_ID_REVIEWER])) {
|
||||
$allowedEditTimeNotice['show'] = true;
|
||||
$allowedEditTimeNotice['limit'] = (int) ($allowedEditTimeNotice['limit'] - (time() - strtotime($headNote->getDateCreated())) / 60);
|
||||
}
|
||||
|
||||
$templateMgr->assign([
|
||||
'allParticipants' => $allParticipants,
|
||||
'assignedParticipants' => $assignedParticipants,
|
||||
'allowedEditTimeNotice' => $allowedEditTimeNotice,
|
||||
]);
|
||||
|
||||
return parent::fetch($request, $template, $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign form data to user-submitted data.
|
||||
*
|
||||
* @see Form::readInputData()
|
||||
*/
|
||||
public function readInputData()
|
||||
{
|
||||
$this->readUserVars([
|
||||
'subject',
|
||||
'comment',
|
||||
'users',
|
||||
'template',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc Form::validate()
|
||||
*/
|
||||
public function validate($callHooks = true)
|
||||
{
|
||||
// Display error if anonymity is impacted in a review stage:
|
||||
// 1) several blind reviewers are selected, or
|
||||
// 2) a blind reviewer and another participant (other than editor or assistant) are selected.
|
||||
// Editors and assistants are ignored, they can see everything.
|
||||
// Also admin and manager, if they are creating the discussion, are ignored -- they can see everything.
|
||||
// In other stages validate that participants are assigned to that stage.
|
||||
$query = $this->getQuery();
|
||||
// Queries only support Application::ASSOC_TYPE_SUBMISSION so far (see above)
|
||||
if ($query->getAssocType() == Application::ASSOC_TYPE_SUBMISSION) {
|
||||
$request = Application::get()->getRequest();
|
||||
$user = $request->getUser();
|
||||
$context = $request->getContext();
|
||||
$submissionId = $query->getAssocId();
|
||||
$stageId = $query->getStageId();
|
||||
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
|
||||
// get the selected participants
|
||||
$newParticipantIds = (array) $this->getData('users');
|
||||
$participantsToConsider = $blindReviewerCount = 0;
|
||||
foreach ($newParticipantIds as $participantId) {
|
||||
// get participant roles in this workflow stage
|
||||
$assignedRoles = [];
|
||||
$usersAssignments = $stageAssignmentDao->getBySubmissionAndStageId($submissionId, $stageId, null, $participantId);
|
||||
while ($usersAssignment = $usersAssignments->next()) {
|
||||
$userGroup = Repo::userGroup()->get($usersAssignment->getUserGroupId());
|
||||
$assignedRoles[] = $userGroup->getRoleId();
|
||||
}
|
||||
|
||||
if ($stageId == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW || $stageId == WORKFLOW_STAGE_ID_INTERNAL_REVIEW) {
|
||||
// validate the anonymity
|
||||
// get participant review assignments
|
||||
$reviewAssignments = $reviewAssignmentDao->getBySubmissionReviewer($submissionId, $participantId, $stageId);
|
||||
// if participant has no role in this stage and is not a reviewer
|
||||
if (empty($assignedRoles) && empty($reviewAssignments)) {
|
||||
// if participant is current user and the user has admin or manager role, ignore participant
|
||||
if (($participantId == $user->getId()) && ($user->hasRole([Role::ROLE_ID_SITE_ADMIN], PKPApplication::CONTEXT_SITE) || $user->hasRole([Role::ROLE_ID_MANAGER], $context->getId()))) {
|
||||
continue;
|
||||
} else {
|
||||
$this->addError('users', __('editor.discussion.errorNotStageParticipant'));
|
||||
$this->addErrorField('users');
|
||||
break;
|
||||
}
|
||||
}
|
||||
// is participant a blind reviewer
|
||||
$blindReviewer = false;
|
||||
foreach ($reviewAssignments as $reviewAssignment) {
|
||||
if ($reviewAssignment->getReviewMethod() != ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN) {
|
||||
$blindReviewerCount++;
|
||||
$blindReviewer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if participant is not a blind reviewer and has a role different than editor or assistant
|
||||
if (!$blindReviewer && !array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT], $assignedRoles)) {
|
||||
$participantsToConsider++;
|
||||
}
|
||||
// if anonymity is impacted, display error
|
||||
if (($blindReviewerCount > 1) || ($blindReviewerCount > 0 && $participantsToConsider > 0)) {
|
||||
$this->addError('users', __('editor.discussion.errorAnonymousParticipants'));
|
||||
$this->addErrorField('users');
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// if participant has no role/assignment in the current stage
|
||||
if (empty($assignedRoles)) {
|
||||
// if participant is current user and the user has admin or manager role, ignore participant
|
||||
if (($participantId == $user->getId()) && ($user->hasRole([Role::ROLE_ID_SITE_ADMIN], PKPApplication::CONTEXT_SITE) || $user->hasRole([Role::ROLE_ID_MANAGER], $context->getId()))) {
|
||||
continue;
|
||||
} else {
|
||||
$this->addError('users', __('editor.discussion.errorNotStageParticipant'));
|
||||
$this->addErrorField('users');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parent::validate($callHooks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc Form::execute()
|
||||
*/
|
||||
public function execute(...$functionArgs)
|
||||
{
|
||||
$request = Application::get()->getRequest();
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$query = $this->getQuery();
|
||||
|
||||
$headNote = $query->getHeadNote();
|
||||
$headNote->setTitle($this->getData('subject'));
|
||||
$headNote->setContents($this->getData('comment'));
|
||||
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$noteDao->updateObject($headNote);
|
||||
|
||||
$queryDao->updateObject($query);
|
||||
|
||||
// Update participants
|
||||
$oldParticipantIds = $queryDao->getParticipantIds($query->getId());
|
||||
$newParticipantIds = $this->getData('users');
|
||||
$queryDao->removeAllParticipants($query->getId());
|
||||
foreach ($newParticipantIds as $userId) {
|
||||
$queryDao->insertParticipant($query->getId(), $userId);
|
||||
}
|
||||
|
||||
$removed = array_diff($oldParticipantIds, $newParticipantIds);
|
||||
foreach ($removed as $userId) {
|
||||
// Delete this users' notifications relating to this query
|
||||
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
|
||||
$notificationDao->deleteByAssoc(Application::ASSOC_TYPE_QUERY, $query->getId(), $userId);
|
||||
}
|
||||
|
||||
// Stamp the submission status modification date.
|
||||
if ($query->getAssocType() == Application::ASSOC_TYPE_SUBMISSION) {
|
||||
$submission = Repo::submission()->get($query->getAssocId());
|
||||
$submission->stampLastActivity();
|
||||
Repo::submission()->dao->update($submission);
|
||||
}
|
||||
|
||||
parent::execute(...$functionArgs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file controllers/grid/queries/form/QueryNoteForm.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 QueryNoteForm
|
||||
*
|
||||
* @ingroup controllers_grid_queries_form
|
||||
*
|
||||
* @brief Form for adding/editing a new query note.
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries\form;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\template\TemplateManager;
|
||||
use PKP\core\Core;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\form\Form;
|
||||
use PKP\note\Note;
|
||||
use PKP\note\NoteDAO;
|
||||
use PKP\query\Query;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\user\User;
|
||||
|
||||
class QueryNoteForm extends Form
|
||||
{
|
||||
/** @var array Action arguments */
|
||||
public $_actionArgs;
|
||||
|
||||
/** @var Query */
|
||||
public $_query;
|
||||
|
||||
/** @var int Note ID */
|
||||
public $_noteId;
|
||||
|
||||
/** @var bool Whether or not this is a new note */
|
||||
public $_isNew;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $actionArgs Action arguments
|
||||
* @param Query $query
|
||||
* @param User $user The current user ID
|
||||
* @param int $noteId The note ID to edit, or null for new.
|
||||
*/
|
||||
public function __construct($actionArgs, $query, $user, $noteId = null)
|
||||
{
|
||||
parent::__construct('controllers/grid/queries/form/queryNoteForm.tpl');
|
||||
$this->_actionArgs = $actionArgs;
|
||||
$this->setQuery($query);
|
||||
|
||||
if ($noteId === null) {
|
||||
// Create a new (placeholder) note.
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$note = $noteDao->newDataObject();
|
||||
$note->setAssocType(Application::ASSOC_TYPE_QUERY);
|
||||
$note->setAssocId($query->getId());
|
||||
$note->setUserId($user->getId());
|
||||
$note->setDateCreated(Core::getCurrentDate());
|
||||
$this->_noteId = $noteDao->insertObject($note);
|
||||
$this->_isNew = true;
|
||||
} else {
|
||||
$this->_noteId = $noteId;
|
||||
$this->_isNew = false;
|
||||
}
|
||||
|
||||
// Validation checks for this form
|
||||
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'comment', 'required', 'submission.queries.messageRequired'));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
|
||||
}
|
||||
|
||||
//
|
||||
// Getters and Setters
|
||||
//
|
||||
/**
|
||||
* Get the query
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query
|
||||
*
|
||||
* @param Query $query
|
||||
*/
|
||||
public function setQuery($query)
|
||||
{
|
||||
$this->_query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign form data to user-submitted data.
|
||||
*
|
||||
* @see Form::readInputData()
|
||||
*/
|
||||
public function readInputData()
|
||||
{
|
||||
$this->readUserVars([
|
||||
'comment',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc Form::fetch
|
||||
*
|
||||
* @param null|mixed $template
|
||||
*/
|
||||
public function fetch($request, $template = null, $display = false)
|
||||
{
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$templateMgr->assign([
|
||||
'actionArgs' => $this->_actionArgs,
|
||||
'noteId' => $this->_noteId,
|
||||
'csrfToken' => $request->getSession()->getCSRFToken(),
|
||||
]);
|
||||
return parent::fetch($request, $template, $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc Form::execute()
|
||||
*
|
||||
* @return Note The created note object.
|
||||
*/
|
||||
public function execute(...$functionArgs)
|
||||
{
|
||||
$request = Application::get()->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
// Create a new note.
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$note = $noteDao->getById($this->_noteId);
|
||||
$note->setUserId($request->getUser()->getId());
|
||||
$note->setContents($this->getData('comment'));
|
||||
$noteDao->updateObject($note);
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
|
||||
// Check whether the query needs re-opening
|
||||
$query = $this->getQuery();
|
||||
if ($query->getIsClosed()) {
|
||||
$headNote = $query->getHeadNote();
|
||||
if ($user->getId() != $headNote->getUserId()) {
|
||||
// Re-open the query.
|
||||
$query->setIsClosed(false);
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$queryDao->updateObject($query);
|
||||
}
|
||||
}
|
||||
|
||||
// Always include current user to query participants
|
||||
if (!in_array($user->getId(), $queryDao->getParticipantIds($query->getId()))) {
|
||||
$queryDao->insertParticipant($query->getId(), $user->getId());
|
||||
}
|
||||
|
||||
parent::execute(...$functionArgs);
|
||||
|
||||
return $note;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file mail/traits/StageMailable.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StageMailable
|
||||
*
|
||||
* @ingroup mail_traits
|
||||
*
|
||||
* @brief Mailable trait to associate Workflow Stage with specific Discussion email
|
||||
*/
|
||||
|
||||
namespace PKP\controllers\grid\queries\traits;
|
||||
|
||||
use APP\submission\Submission;
|
||||
use PKP\context\Context;
|
||||
use PKP\mail\Mailable;
|
||||
use PKP\mail\mailables\DiscussionCopyediting;
|
||||
use PKP\mail\mailables\DiscussionProduction;
|
||||
use PKP\mail\mailables\DiscussionReview;
|
||||
use PKP\mail\mailables\DiscussionSubmission;
|
||||
|
||||
trait StageMailable
|
||||
{
|
||||
abstract public function getStageId();
|
||||
|
||||
/**
|
||||
* @return Mailable which corresponds to the given workflow stage
|
||||
*/
|
||||
protected function getStageMailable(Context $context, Submission $submission): Mailable
|
||||
{
|
||||
$map = [
|
||||
WORKFLOW_STAGE_ID_SUBMISSION => DiscussionSubmission::class,
|
||||
WORKFLOW_STAGE_ID_INTERNAL_REVIEW => DiscussionReview::class,
|
||||
WORKFLOW_STAGE_ID_EXTERNAL_REVIEW => DiscussionReview::class,
|
||||
WORKFLOW_STAGE_ID_EDITING => DiscussionCopyediting::class,
|
||||
WORKFLOW_STAGE_ID_PRODUCTION => DiscussionProduction::class,
|
||||
];
|
||||
$mailableClassName = $map[$this->getStageId()];
|
||||
return new $mailableClassName($context, $submission);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user