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,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() : '&mdash;') . '<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() : '&mdash;') . '<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() : '&mdash;') . '<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()) : '&mdash;',
'modal_edit'
),
($headNote && $headNote->getTitle() != '') ? htmlspecialchars($headNote->getTitle()) : '&mdash;',
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);
}
}