<?php
/**
* @file api/v1/submissions/PKPSubmissionHandler.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 PKPSubmissionHandler
*
* @ingroup api_v1_submission
*
* @brief Handle API requests for submission operations.
*
*/
namespace PKP\API\v1\submissions;
use APP\core\Application;
use APP\core\Request;
use APP\core\Services;
use APP\facades\Repo;
use APP\mail\variables\ContextEmailVariable;
use APP\notification\Notification;
use APP\notification\NotificationManager;
use APP\section\Section;
use APP\submission\Collector;
use APP\submission\Submission;
use Illuminate\Support\Enumerable;
use Illuminate\Support\Facades\Mail;
use PKP\core\APIResponse;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\decision\DecisionType;
use PKP\handler\APIHandler;
use PKP\log\event\PKPSubmissionEventLogEntry;
use PKP\mail\mailables\PublicationVersionNotify;
use PKP\mail\mailables\SubmissionSavedForLater;
use PKP\notification\NotificationSubscriptionSettingsDAO;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\security\authorization\ContextAccessPolicy;
use PKP\security\authorization\DecisionWritePolicy;
use PKP\security\authorization\PublicationWritePolicy;
use PKP\security\authorization\StageRolePolicy;
use PKP\security\authorization\SubmissionAccessPolicy;
use PKP\security\authorization\UserRolesRequiredPolicy;
use PKP\security\authorization\internal\SubmissionCompletePolicy;
use PKP\security\Role;
use PKP\security\Validation;
use PKP\services\PKPSchemaService;
use PKP\stageAssignment\StageAssignmentDAO;
use PKP\submission\GenreDAO;
use PKP\submission\PKPSubmission;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\userGroup\UserGroup;
use Slim\Http\Request as SlimRequest;
class PKPSubmissionHandler extends APIHandler
{
/** @var int The default number of items to return in one request */
public const DEFAULT_COUNT = 30;
/** @var int Max items that can be requested */
public const MAX_COUNT = 100;
/** @var array Handlers that must be authorized to access a submission */
public $requiresSubmissionAccess = [
'get',
'edit',
'saveForLater',
'submit',
'delete',
'getGalleys',
'getDecisions',
'getParticipants',
'getPublications',
'getPublication',
'addPublication',
'versionPublication',
'editPublication',
'publishPublication',
'unpublishPublication',
'deletePublication',
'getContributors',
'getContributor',
'addContributor',
'deleteContributor',
'editContributor',
'saveContributorsOrder',
'addDecision',
];
/** @var array Handlers that must be authorized to write to a publication */
public $requiresPublicationWriteAccess = [
'editPublication',
'addContributor',
'deleteContributor',
'editContributor',
'saveContributorsOrder',
];
/** @var array Handlers that must be authorized to access a submission's production stage */
public $requiresProductionStageAccess = [
'addPublication',
'versionPublication',
'publishPublication',
'unpublishPublication',
'deletePublication',
];
/** @var array Roles that can access a submission's production stage */
public $productionStageAccessRoles = [
Role::ROLE_ID_MANAGER,
Role::ROLE_ID_SUB_EDITOR,
Role::ROLE_ID_ASSISTANT
];
/**
* Constructor
*/
public function __construct()
{
$this->_handlerPath = 'submissions';
$this->_endpoints = [
'GET' => [
[
'pattern' => $this->getEndpointPattern(),
'handler' => [$this, 'getMany'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}',
'handler' => [$this, 'get'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/decisions',
'handler' => [$this, 'getDecisions'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/participants',
'handler' => [$this, 'getParticipants'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/participants/{stageId:\d+}',
'handler' => [$this, 'getParticipants'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications',
'handler' => [$this, 'getPublications'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}',
'handler' => [$this, 'getPublication'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/contributors',
'handler' => [$this, 'getContributors'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/contributors/{contributorId:\d+}',
'handler' => [$this, 'getContributor'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR],
],
],
'POST' => [
[
'pattern' => $this->getEndpointPattern(),
'handler' => [$this, 'add'],
'roles' => Role::getAllRoles(),
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications',
'handler' => [$this, 'addPublication'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/version',
'handler' => [$this, 'versionPublication'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/contributors',
'handler' => [$this, 'addContributor'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/decisions',
'handler' => [$this, 'addDecision'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR],
],
],
'PUT' => [
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}',
'handler' => [$this, 'edit'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/saveForLater',
'handler' => [$this, 'saveForLater'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/submit',
'handler' => [$this, 'submit'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}',
'handler' => [$this, 'editPublication'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/publish',
'handler' => [$this, 'publishPublication'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/unpublish',
'handler' => [$this, 'unpublishPublication'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/contributors/{contributorId:\d+}',
'handler' => [$this, 'editContributor'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/contributors/saveOrder',
'handler' => [$this, 'saveContributorsOrder'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR],
],
],
'DELETE' => [
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}',
'handler' => [$this, 'delete'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}',
'handler' => [$this, 'deletePublication'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
],
[
'pattern' => $this->getEndpointPattern() . '/{submissionId:\d+}/publications/{publicationId:\d+}/contributors/{contributorId:\d+}',
'handler' => [$this, 'deleteContributor'],
'roles' => [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_AUTHOR],
],
],
];
parent::__construct();
}
//
// Implement methods from PKPHandler
//
public function authorize($request, &$args, $roleAssignments)
{
$routeName = $this->getSlimRequest()->getAttribute('route')->getName();
$this->addPolicy(new UserRolesRequiredPolicy($request), true);
$this->addPolicy(new ContextAccessPolicy($request, $roleAssignments));
if (in_array($routeName, $this->requiresSubmissionAccess)) {
$this->addPolicy(new SubmissionAccessPolicy($request, $args, $roleAssignments));
}
if (in_array($routeName, $this->requiresPublicationWriteAccess)) {
$this->addPolicy(new PublicationWritePolicy($request, $args, $roleAssignments));
}
if (in_array($routeName, $this->requiresProductionStageAccess)) {
$this->addPolicy(new StageRolePolicy($this->productionStageAccessRoles, WORKFLOW_STAGE_ID_PRODUCTION, false));
}
if ($routeName === 'addDecision') {
$this->addPolicy(new SubmissionCompletePolicy($request, $args));
$this->addPolicy(new DecisionWritePolicy($request, $args, (int) $request->getUserVar('decision'), $request->getUser()));
}
return parent::authorize($request, $args, $roleAssignments);
}
/**
* Get a collection of submissions
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function getMany($slimRequest, $response, $args)
{
$request = Application::get()->getRequest();
$currentUser = $request->getUser();
$context = $request->getContext();
$collector = $this->getSubmissionCollector($slimRequest->getQueryParams());
Hook::call('API::submissions::params', [$collector, $slimRequest]);
// Prevent users from viewing submissions they're not assigned to,
// except for journal managers and admins.
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
$canAccessUnassignedSubmission = !empty(array_intersect([Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER], $userRoles));
if (!$canAccessUnassignedSubmission) {
if (!is_array($collector->assignedTo)) {
$collector->assignedTo([$currentUser->getId()]);
} elseif ($collector->assignedTo != [$currentUser->getId()]) {
return $response->withStatus(403)->withJsonError('api.submissions.403.requestedOthersUnpublishedSubmissions');
}
}
$submissions = $collector->getMany();
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
/** @var \PKP\submission\GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($context->getId())->toArray();
return $response->withJson([
'itemsMax' => $collector->getCount(),
'items' => Repo::submission()->getSchemaMap()->summarizeMany($submissions, $userGroups, $genres)->values(),
], 200);
}
/**
* Configure a submission Collector based on the query params
*/
protected function getSubmissionCollector(array $queryParams): Collector
{
$request = Application::get()->getRequest();
/** @var \PKP\context\Context $context */
$context = $request->getContext();
$collector = Repo::submission()->getCollector()
->filterByContextIds([$context->getId()])
->limit(self::DEFAULT_COUNT)
->offset(0);
foreach ($queryParams as $param => $val) {
switch ($param) {
case 'orderBy':
if (in_array($val, [
$collector::ORDERBY_DATE_PUBLISHED,
$collector::ORDERBY_DATE_SUBMITTED,
$collector::ORDERBY_LAST_ACTIVITY,
$collector::ORDERBY_LAST_MODIFIED,
$collector::ORDERBY_SEQUENCE,
$collector::ORDERBY_TITLE,
])) {
$direction = isset($queryParams['orderDirection']) && $queryParams['orderDirection'] === $collector::ORDER_DIR_ASC
? $collector::ORDER_DIR_ASC
: $collector::ORDER_DIR_DESC;
$collector->orderBy($val, $direction);
}
break;
case 'categoryIds':
$collector->filterByCategoryIds(array_map('intval', $this->paramToArray($val)));
break;
case 'status':
$collector->filterByStatus(array_map('intval', $this->paramToArray($val)));
break;
case 'stageIds':
$collector->filterByStageIds(array_map('intval', $this->paramToArray($val)));
break;
case 'assignedTo':
$val = array_map('intval', $this->paramToArray($val));
if ($val == [\PKP\submission\Collector::UNASSIGNED]) {
$val = array_shift($val);
}
$collector->assignedTo($val);
break;
case 'daysInactive':
$collector->filterByDaysInactive((int) $val);
break;
case 'offset':
$collector->offset((int) $val);
break;
case 'searchPhrase':
$collector->searchPhrase($val);
break;
case 'count':
$collector->limit(min(self::MAX_COUNT, (int) $val));
break;
case 'isIncomplete':
$collector->filterByIncomplete(true);
break;
case 'isOverdue':
$collector->filterByOverdue(true);
break;
case 'doiStatus':
$collector->filterByDoiStatuses(array_map('intval', $this->paramToArray($val)));
break;
case 'hasDois':
$collector->filterByHasDois((bool) $val, $context->getEnabledDoiTypes());
break;
}
}
return $collector;
}
/**
* Get a single submission
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function get($slimRequest, $response, $args)
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), 200);
}
/**
* Add a new submission
*
*/
public function add(SlimRequest $slimRequest, APIResponse $response, array $args): APIResponse
{
$request = $this->getRequest();
$context = $request->getContext();
$user = $request->getUser();
if ($context->getData('disableSubmissions')) {
return $response->withStatus(403)->withJsonError('author.submit.notAccepting');
}
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_SUBMISSION, $slimRequest->getParsedBody());
$readOnlyErrors = $this->getWriteDisabledErrors(PKPSchemaService::SCHEMA_SUBMISSION, $params);
if (!empty($readOnlyErrors)) {
return $response->withStatus(400)->withJson($readOnlyErrors);
}
$params['contextId'] = $context->getId();
$errors = Repo::submission()->validate(null, $params, $context);
$sectionIdPropName = Application::getSectionIdPropName();
if (isset($params[$sectionIdPropName])) {
$sectionId = $params[$sectionIdPropName];
$section = Repo::section()->get($sectionId, $context->getId());
if ($section->getIsInactive()) {
$errors[$sectionIdPropName] = [__('api.submission.400.inactiveSection')];
} else {
if ($section->getEditorRestricted() && !$this->isEditor()) {
$errors[$sectionIdPropName] = [__('submission.sectionRestrictedToEditors')];
}
}
}
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
$submitterUserGroups = Repo::userGroup()
->getCollector()
->filterByContextIds([$context->getId()])
->filterByUserIds([$user->getId()])
->filterByRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_AUTHOR])
->getMany();
if (isset($params['userGroupId'])) {
$submitAsUserGroup = $submitterUserGroups
->first(function (UserGroup $userGroup) use ($params) {
return $userGroup->getId() === $params['userGroupId'];
});
if (!$submitAsUserGroup) {
return $response->withStatus(400)->withJson([
'userGroupId' => [__('api.submissions.400.invalidSubmitAs')]
]);
}
} elseif ($submitterUserGroups->count()) {
$submitAsUserGroup = $submitterUserGroups
->sort(function (UserGroup $a, UserGroup $b) {
return $a->getRoleId() === Role::ROLE_ID_AUTHOR ? 1 : -1;
})
->first();
} else {
$submitAsUserGroup = Repo::userGroup()->getFirstSubmitAsAuthorUserGroup($context->getId());
if (!$submitAsUserGroup) {
return $response->withStatus(400)->withJson([
'userGroupId' => [__('submission.wizard.notAllowed.description')]
]);
}
Repo::userGroup()->assignUserToGroup(
$user->getId(),
$submitAsUserGroup->getId()
);
}
$publicationProps = [];
if (isset($params[$sectionIdPropName])) {
$publicationProps[$sectionIdPropName] = $params[$sectionIdPropName];
unset($params[$sectionIdPropName]);
}
$submission = Repo::submission()->newDataObject($params);
$publication = Repo::publication()->newDataObject($publicationProps);
$submissionId = Repo::submission()->add($submission, $publication, $request->getContext());
$submission = Repo::submission()->get($submissionId);
// Assign submitter to submission
/** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO');
$stageAssignmentDao->build(
$submission->getId(),
$submitAsUserGroup->getId(),
$request->getUser()->getId(),
$submitAsUserGroup->getRecommendOnly(),
// Authors can always edit metadata before submitting
$submission->getData('submissionProgress')
? true
: $submitAsUserGroup->getPermitMetadataEdit()
);
// Create an author record from the submitter's user account
if ($submitAsUserGroup->getRoleId() === Role::ROLE_ID_AUTHOR) {
$author = Repo::author()->newAuthorFromUser($request->getUser());
$author->setData('publicationId', $publication->getId());
$author->setUserGroupId($submitAsUserGroup->getId());
$authorId = Repo::author()->add($author);
Repo::publication()->edit($publication, ['primaryContactId' => $authorId]);
}
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), 200);
}
/**
* Edit a submission
*/
public function edit(SlimRequest $slimRequest, APIResponse $response, array $args): APIResponse
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$parsedBody = $slimRequest->getParsedBody();
if (isset($parsedBody)){
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_SUBMISSION, $parsedBody);
$readOnlyErrors = $this->getWriteDisabledErrors(PKPSchemaService::SCHEMA_SUBMISSION, $params);
if (!empty($readOnlyErrors)) {
return $response->withStatus(400)->withJson($readOnlyErrors);
}
$params['id'] = $submission->getId();
$params['contextId'] = $request->getContext()->getId();
$errors = Repo::submission()->validate($submission, $params, $request->getContext());
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
Repo::submission()->edit($submission, $params);
}
$submission = Repo::submission()->get($submission->getId());
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), 200);
}
/**
* Save a submission for later
*
* Saves the current step and sends the submitter an
* email with a link to resume their submission.
*/
public function saveForLater(SlimRequest $slimRequest, APIResponse $response, array $args): APIResponse
{
$request = $this->getRequest();
$context = $request->getContext();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$params = $slimRequest->getParsedBody();
if (!empty($params['step'])) {
if (!ctype_alnum(str_replace(['-', '_'], '', $params['step']))) {
return $response->withStatus(400)->withJson([
'step' => [__('validator.alpha_dash')]
]);
}
Repo::submission()->edit($submission, ['submissionProgress' => $params['step']]);
}
$emailTemplate = Repo::emailTemplate()->getByKey($context->getId(), SubmissionSavedForLater::getEmailTemplateKey());
$mailable = new SubmissionSavedForLater($context, $submission);
$mailable
->from($context->getData('contactEmail'), $context->getData('contactName'))
->recipients([$request->getUser()])
// The template may not exist, see pkp/pkp-lib#9217
->subject($emailTemplate?->getLocalizedData('subject') ?? __('emails.submissionSavedForLater.subject'))
->body($emailTemplate?->getLocalizedData('body') ?? __('emails.submissionSavedForLater.body'));
if (!$emailTemplate) {
$templateVariables = $mailable->getData();
$mailable->addData([
'contextName' => $templateVariables[ContextEmailVariable::CONTEXT_NAME],
'contextUrl' => $templateVariables[ContextEmailVariable::CONTEXT_URL],
]);
}
Mail::send($mailable);
$submission = Repo::submission()->get($submission->getId());
$userGroups = Repo::userGroup()
->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), 200);
}
/**
* Submit a submission
*
* Submits a submission by changing its `submissionProgress` property.
*
* Pass the `_validateOnly` property to validate the submission without submitting it.
*/
public function submit(SlimRequest $slimRequest, APIResponse $response, array $args): APIResponse
{
$request = $this->getRequest();
$context = $request->getContext();
/** @var Submission $submission*/
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = $submission->getCurrentPublication();
$errors = Repo::submission()->validateSubmit($submission, $context);
/** @var int $sectionId */
$sectionId = $publication->getData(Application::getSectionIdPropName());
if ($sectionId) {
$section = Repo::section()->get($sectionId, $context->getId());
}
if (isset($section) &&
(
$section->getIsInactive() ||
($section->getEditorRestricted() && !$this->isEditor())
)
) {
$errors[Application::getSectionIdPropName()] = __('submission.wizard.sectionClosed.message', [
'contextName' => $context->getLocalizedData('name'),
'section' => $section->getLocalizedTitle(),
'email' => $context->getData('contactEmail'),
'name' => $context->getData('contactName'),
]);
}
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
if ($slimRequest->getParsedBodyParam('_validateOnly')) {
return $response->withStatus(200);
}
Repo::submission()->submit($submission, $context);
$submission = Repo::submission()->get($submission->getId());
if ($slimRequest->getParsedBodyParam('confirmCopyright')) {
$user = $request->getUser();
$eventLog = Repo::eventLog()->newDataObject([
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_COPYRIGHT_AGREED,
'userId' => Validation::loggedInAs() ?? $user->getId(),
'message' => 'submission.event.copyrightAgreed',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
'username' => $user->getUsername(),
'userFullName' => $user->getFullName(),
'copyrightNotice' => $context->getData('copyrightNotice'),
]);
Repo::eventLog()->add($eventLog);
}
$userGroups = Repo::userGroup()
->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), 200);
}
/**
* Delete a submission
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function delete($slimRequest, $response, $args)
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
if (!$submission) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
$submissionProps = Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres);
Repo::submission()->delete($submission);
return $response->withJson($submissionProps, 200);
}
/**
* Get the decisions recorded on a submission
*/
public function getDecisions(SlimRequest $slimRequest, APIResponse $response, array $args): APIResponse
{
$request = Application::get()->getRequest();
$context = $request->getContext();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
if (!$submission || $submission->getData('contextId') !== $context->getId()) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
$decisionIterator = Repo::decision()->getCollector()
->filterBySubmissionIds([$submission->getId()])
->getMany();
$data = Repo::decision()
->getSchemaMap()
->mapMany($decisionIterator->values());
return $response->withJson($data, 200);
}
/**
* Get the participants assigned to a submission
*
* This does not return reviewers.
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function getParticipants($slimRequest, $response, $args)
{
$request = Application::get()->getRequest();
$context = $request->getContext();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$stageId = $args['stageId'] ?? null;
if (!$submission || $submission->getData('contextId') !== $context->getId()) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
$data = [];
$usersIterator = Repo::user()->getCollector()
->filterByContextIds([$context->getId()])
->assignedTo($submission->getId(), $stageId)
->getMany();
$map = Repo::user()->getSchemaMap();
foreach ($usersIterator as $user) {
$data[] = $map->summarizeReviewer($user);
}
return $response->withJson($data, 200);
}
/**
* Get all of this submissions's publications
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function getPublications($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
if (!$submission) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
$collector = Repo::publication()->getCollector()
->filterBySubmissionIds([$submission->getId()]);
$publications = $collector->getMany();
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var \PKP\submission\reviewAssignment\ReviewAssignmentDAO $reviewAssignmentDao */
$currentUserReviewAssignment = $reviewAssignmentDao->getLastReviewRoundReviewAssignmentByReviewer(
$submission->getId(),
$request->getUser()->getId()
);
$anonymize = $currentUserReviewAssignment && $currentUserReviewAssignment->getReviewMethod() === ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS;
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson([
'itemsMax' => $collector->getCount(),
'items' => Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->summarizeMany($publications, $anonymize)->values(),
], 200);
}
/**
* Get one of this submission's publications
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function getPublication($slimRequest, $response, $args)
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(
Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication),
200
);
}
/**
* Add a new publication to this submission
*
* This will create a new publication from scratch. If you want to create a new
* version of a publication, see self::versionPublication().
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function addPublication($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_PUBLICATION, $slimRequest->getParsedBody());
$params['submissionId'] = $submission->getId();
$submissionContext = $request->getContext();
if (!$submissionContext || $submissionContext->getId() !== $submission->getData('contextId')) {
$submissionContext = Services::get('context')->get($submission->getData('contextId'));
}
$errors = Repo::publication()->validate(null, $params, $submission, $submissionContext);
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
$publication = Repo::publication()->newDataObject($params);
$newId = Repo::publication()->add($publication);
$publication = Repo::publication()->get($newId);
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(
Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication),
200
);
}
/**
* Create a new version of a publication
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function versionPublication($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = Repo::publication()->get((int) $args['publicationId']);
$context = $request->getContext();
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
$newId = Repo::publication()->version($publication);
$publication = Repo::publication()->get($newId);
$notificationManager = new NotificationManager();
$usersIterator = Repo::user()->getCollector()
->filterByContextIds([$submission->getContextId()])
->assignedTo($submission->getId())
->getMany();
/** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO');
foreach ($usersIterator as $user) {
$notification = $notificationManager->createNotification(
$request,
$user->getId(),
PKPNotification::NOTIFICATION_TYPE_SUBMISSION_NEW_VERSION,
$submission->getContextId(),
Application::ASSOC_TYPE_SUBMISSION,
$submission->getId(),
Notification::NOTIFICATION_LEVEL_TASK,
);
// Check if user is subscribed to this type of notification emails
if (!$notification || in_array(
PKPNotification::NOTIFICATION_TYPE_SUBMISSION_NEW_VERSION,
$notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings(
NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY,
$user->getId(),
(int) $context->getId()
)
)
) {
continue;
}
$mailable = new PublicationVersionNotify($context, $submission);
$template = Repo::emailTemplate()->getByKey($context->getId(), PublicationVersionNotify::getEmailTemplateKey());
$mailable
->from($context->getData('contactEmail'), $context->getData('contactName'))
->recipients([$user])
->body($template->getLocalizedData('body'))
->subject($template->getLocalizedData('subject'))
->allowUnsubscribe($notification);
Mail::send($mailable);
}
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(
Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication),
200
);
}
/**
* Edit one of this submission's publications
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function editPublication($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$currentUser = $request->getUser();
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
// Publications can not be edited when they are published
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.publication.403.cantEditPublished');
}
// Prevent users from editing publications if they do not have permission. Except for admins.
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
if (!in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles) && !Repo::submission()->canEditPublication($submission->getId(), $currentUser->getId())) {
return $response->withStatus(403)->withJsonError('api.submissions.403.userCantEdit');
}
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_PUBLICATION, $slimRequest->getParsedBody());
$params['id'] = $publication->getId();
// Don't allow the status to be modified through the API. The `/publish` and /unpublish endpoints
// should be used instead.
if (array_key_exists('status', $params)) {
return $response->withStatus(403)->withJsonError('api.publication.403.cantEditStatus');
}
$submissionContext = $request->getContext();
if (!$submissionContext || $submissionContext->getId() !== $submission->getData('contextId')) {
$submissionContext = Services::get('context')->get($submission->getData('contextId'));
}
$errors = Repo::publication()->validate($publication, $params, $submission, $submissionContext);
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
Repo::publication()->edit($publication, $params);
$publication = Repo::publication()->get($publication->getId());
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(
Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication),
200
);
}
/**
* Publish one of this submission's publications
*
* If this is a GET request, it will run the pre-publish validation
* checks and return errors but it will not perform the final
* publication step.
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function publishPublication($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.publication.403.alreadyPublished');
}
$submissionContext = $request->getContext();
if (!$submissionContext || $submissionContext->getId() !== $submission->getData('contextId')) {
$submissionContext = Services::get('context')->get($submission->getData('contextId'));
}
$primaryLocale = $submission->getData('locale');
$allowedLocales = $submissionContext->getData('supportedSubmissionLocales');
$errors = Repo::publication()->validatePublish($publication, $submission, $allowedLocales, $primaryLocale);
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
Repo::publication()->publish($publication);
$publication = Repo::publication()->get($publication->getId());
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(
Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication),
200
);
}
/**
* Unpublish one of this submission's publications
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function unpublishPublication($slimRequest, $response, $args)
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
if (!in_array($publication->getData('status'), [PKPSubmission::STATUS_PUBLISHED, PKPSubmission::STATUS_SCHEDULED])) {
return $response->withStatus(403)->withJsonError('api.publication.403.alreadyUnpublished');
}
Repo::publication()->unpublish($publication);
$publication = Repo::publication()->get($publication->getId());
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
return $response->withJson(
Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication),
200
);
}
/**
* Delete one of this submission's publications
*
* Published publications can not be deleted. First you must unpublish them.
* See self::unpublishPublication().
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function deletePublication($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.publication.403.cantDeletePublished');
}
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$submission->getData('contextId')])
->getMany();
/** @var GenreDAO $genreDao */
$genreDao = DAORegistry::getDAO('GenreDAO');
$genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray();
$output = Repo::publication()->getSchemaMap($submission, $userGroups, $genres)->map($publication);
Repo::publication()->delete($publication);
return $response->withJson($output, 200);
}
/**
* Get one of a publication's contributors
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function getContributor($slimRequest, $response, $args)
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = Repo::publication()->get((int) $args['publicationId']);
$author = Repo::author()->get((int) $args['contributorId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if (!$author) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
if ($publication->getId() !== $author->getData('publicationId')) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
return $response->withJson(
Repo::author()->getSchemaMap()->map($author),
200
);
}
/**
* Get all publication's contributors
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function getContributors($slimRequest, $response, $args)
{
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
$collector = Repo::author()->getCollector()
->filterByPublicationIds([$publication->getId()]);
$authors = $collector->getMany();
return $response->withJson([
'itemsMax' => $collector->getCount(),
'items' => Repo::author()->getSchemaMap()->summarizeMany($authors)->values(),
], 200);
}
/**
* Add a new contributor to publication
*
* This will create a new contributor from scratch.
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function addContributor($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$currentUser = $request->getUser();
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
// Publications can not be edited when they are published
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.publication.403.cantEditPublished');
}
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_AUTHOR, $slimRequest->getParsedBody());
$params['publicationId'] = $publication->getId();
$submissionContext = $request->getContext();
if (!$submissionContext || $submissionContext->getId() !== $submission->getData('contextId')) {
$submissionContext = Services::get('context')->get($submission->getData('contextId'));
}
$errors = Repo::author()->validate(null, $params, $submission, $submissionContext);
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
$author = Repo::author()->newDataObject($params);
$newId = Repo::author()->add($author);
$author = Repo::author()->get($newId);
return $response->withJson(
Repo::author()->getSchemaMap()->map($author),
200
);
}
/**
* Delete one of this publication's contributors
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function deleteContributor($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$currentUser = $request->getUser();
$publication = Repo::publication()->get((int) $args['publicationId']);
$author = Repo::author()->get((int) $args['contributorId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
// Publications can not be edited when they are published
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.publication.403.cantEditPublished');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
if (!$author) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($publication->getId() !== $author->getData('publicationId')) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
$output = Repo::author()->getSchemaMap()->map($author);
Repo::author()->delete($author);
return $response->withJson($output, 200);
}
/**
* Edit one of this publication's contributors
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function editContributor($slimRequest, $response, $args)
{
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$currentUser = $request->getUser();
$publication = Repo::publication()->get((int) $args['publicationId']);
$author = Repo::author()->get((int) $args['contributorId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if (!$author) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
// Publications can not be edited when they are published
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.publication.403.cantEditPublished');
}
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_AUTHOR, $slimRequest->getParsedBody());
$params['id'] = $author->getId();
$submissionContext = $request->getContext();
if (!$submissionContext || $submissionContext->getId() !== $submission->getData('contextId')) {
$submissionContext = Services::get('context')->get($submission->getData('contextId'));
}
if ($publication->getId() !== $author->getData('publicationId')) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
// Prevent users from editing publications if they do not have permission. Except for admins.
$userRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES);
if (!in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles) && !Repo::submission()->canEditPublication($submission->getId(), $currentUser->getId())) {
return $response->withStatus(403)->withJsonError('api.submissions.403.userCantEdit');
}
$errors = Repo::author()->validate($author, $params, $submission, $submissionContext);
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
Repo::author()->edit($author, $params);
$author = Repo::author()->get($author->getId());
return $response->withJson(
Repo::author()->getSchemaMap()->map($author),
200
);
}
/**
* Save new order of contributors array
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function saveContributorsOrder($slimRequest, $response, $args)
{
$params = $slimRequest->getParsedBody();
$request = $this->getRequest();
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
$currentUser = $request->getUser();
$publication = Repo::publication()->get((int) $args['publicationId']);
if (!$publication) {
return $response->withStatus(404)->withJsonError('api.404.resourceNotFound');
}
if ($submission->getId() !== $publication->getData('submissionId')) {
return $response->withStatus(403)->withJsonError('api.publications.403.submissionsDidNotMatch');
}
// Publications can not be edited when they are published
if ($publication->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.publication.403.cantEditPublished');
}
if (!empty($params['sortedAuthors'])) {
$authors = [];
foreach ($params['sortedAuthors'] as $author) {
$newAuthor = Repo::author()->get((int) $author['id']);
array_push($authors, $newAuthor);
}
Repo::author()->setAuthorsOrder($publication->getId(), $authors);
}
$authors = Repo::author()
->getCollector()
->filterByPublicationIds([$publication->getId()])
->getMany();
return $response->withJson(Repo::author()->getSchemaMap()->summarizeMany($authors));
}
/**
* Record an editorial decision for a submission, such as
* a decision to accept or reject the submission, request
* revisions, or send it to another stage.
*
* @param SlimRequest $slimRequest Slim request object
* @param APIResponse $response object
* @param array $args arguments
*
* @return APIResponse
*/
public function addDecision($slimRequest, $response, $args)
{
$request = $this->getRequest(); /** @var Request $request */
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); /** @var Submission $submission */
$decisionType = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_DECISION_TYPE); /** @var DecisionType $decisionType */
if ($submission->getData('status') === Submission::STATUS_PUBLISHED) {
return $response->withStatus(403)->withJsonError('api.decisions.403.alreadyPublished');
}
$params = $this->convertStringsToSchema(PKPSchemaService::SCHEMA_DECISION, $slimRequest->getParsedBody());
$params['submissionId'] = $submission->getId();
$params['dateDecided'] = Core::getCurrentDate();
$params['editorId'] = $request->getUser()->getId();
$params['stageId'] = $decisionType->getStageId();
$errors = Repo::decision()->validate($params, $decisionType, $submission, $request->getContext());
if (!empty($errors)) {
return $response->withStatus(400)->withJson($errors);
}
$decision = Repo::decision()->newDataObject($params);
$decisionId = Repo::decision()->add($decision);
// In some cases, recording a decision may delete the decision. This
// happens for example with the Cancel Review Round decision. When
// the decision is added, the review round is deleted and all decisions
// related to that round are deleted. In such cases, we return the
// original Decision object rather than fetching it from the data store.
$decision = Repo::decision()->get($decisionId) ?? $decision;
return $response->withJson(Repo::decision()->getSchemaMap()->map($decision), 200);
}
protected function getFirstUserGroupInRole(Enumerable $userGroups, int $role): ?UserGroup
{
return $userGroups->first(fn (UserGroup $userGroup) => $userGroup->getRoleId() === $role);
}
/**
* Is the current user an editor
*/
protected function isEditor(): bool
{
return !empty(
array_intersect(
Section::getEditorRestrictedRoles(),
$this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES)
)
);
}
/**
* This method returns errors for any params that match
* properties in the schema with writeDisabledInApi set to true.
*
* This is used for properties that can not be edited through
* the API, but which otherwise can be edited by the entity's
* repository.
*/
protected function getWriteDisabledErrors(string $schemaName, array $params): array
{
$schema = Services::get('schema')->get($schemaName);
$writeDisabledProps = [];
foreach ($schema->properties as $propName => $propSchema) {
if (!empty($propSchema->writeDisabledInApi)) {
$writeDisabledProps[] = $propName;
}
}
$errors = [];
$notAllowedProps = array_intersect(
$writeDisabledProps,
array_keys($params)
);
if (!empty($notAllowedProps)) {
foreach ($notAllowedProps as $propName) {
$errors[$propName] = [__('api.400.propReadOnly', ['prop' => $propName])];
}
}
return $errors;
}
}
|