318 lines
12 KiB
PHP
318 lines
12 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file pages/workflow/WorkflowHandler.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 WorkflowHandler
|
|
*
|
|
* @ingroup pages_reviewer
|
|
*
|
|
* @brief Handle requests for the submission workflow.
|
|
*/
|
|
|
|
namespace APP\pages\workflow;
|
|
|
|
use APP\core\Application;
|
|
use APP\core\Services;
|
|
use APP\decision\types\Accept;
|
|
use APP\decision\types\SkipExternalReview;
|
|
use APP\facades\Repo;
|
|
use APP\file\PublicFileManager;
|
|
use APP\publication\Publication;
|
|
use APP\submission\Submission;
|
|
use APP\template\TemplateManager;
|
|
use PKP\components\forms\publication\TitleAbstractForm;
|
|
use PKP\context\Context;
|
|
use PKP\decision\types\BackFromCopyediting;
|
|
use PKP\decision\types\BackFromProduction;
|
|
use PKP\decision\types\CancelReviewRound;
|
|
use PKP\decision\types\Decline;
|
|
use PKP\decision\types\InitialDecline;
|
|
use PKP\decision\types\RecommendAccept;
|
|
use PKP\decision\types\RecommendDecline;
|
|
use PKP\decision\types\RecommendRevisions;
|
|
use PKP\decision\types\RequestRevisions;
|
|
use PKP\decision\types\RevertDecline;
|
|
use PKP\decision\types\RevertInitialDecline;
|
|
use PKP\decision\types\SendExternalReview;
|
|
use PKP\decision\types\SendToProduction;
|
|
use PKP\notification\PKPNotification;
|
|
use PKP\pages\workflow\PKPWorkflowHandler;
|
|
use PKP\plugins\Hook;
|
|
use PKP\security\Role;
|
|
|
|
class WorkflowHandler extends PKPWorkflowHandler
|
|
{
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
|
|
$this->addRoleAssignment(
|
|
[Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_ASSISTANT],
|
|
[
|
|
'access', 'index', 'submission',
|
|
'editorDecisionActions', // Submission & review
|
|
'externalReview', // review
|
|
'editorial',
|
|
'production',
|
|
'submissionHeader',
|
|
'submissionProgressBar',
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Setup variables for the template
|
|
*
|
|
* @param \APP\core\Request $request
|
|
*/
|
|
public function setupIndex($request)
|
|
{
|
|
parent::setupIndex($request);
|
|
|
|
$templateMgr = TemplateManager::getManager($request);
|
|
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
|
|
|
$submissionContext = $request->getContext();
|
|
if ($submission->getContextId() !== $submissionContext->getId()) {
|
|
$submissionContext = Services::get('context')->get($submission->getContextId());
|
|
}
|
|
|
|
$locales = $submissionContext->getSupportedSubmissionLocaleNames();
|
|
$locales = array_map(fn (string $locale, string $name) => ['key' => $locale, 'label' => $name], array_keys($locales), $locales);
|
|
|
|
$latestPublication = $submission->getLatestPublication();
|
|
|
|
$latestPublicationApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getPath(), 'submissions/' . $submission->getId() . '/publications/' . $latestPublication->getId());
|
|
$temporaryFileApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getPath(), 'temporaryFiles');
|
|
$issueApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getData('urlPath'), 'issues/__issueId__');
|
|
|
|
$publicFileManager = new PublicFileManager();
|
|
$baseUrl = $request->getBaseUrl() . '/' . $publicFileManager->getContextFilesPath($submissionContext->getId());
|
|
|
|
$issueEntryForm = new \APP\components\forms\publication\IssueEntryForm($latestPublicationApiUrl, $locales, $latestPublication, $submissionContext, $baseUrl, $temporaryFileApiUrl);
|
|
|
|
$sectionWordLimits = [];
|
|
$sections = Repo::section()->getCollector()->filterByContextIds([$submissionContext->getId()])->getMany();
|
|
foreach ($sections as $section) {
|
|
$sectionWordLimits[$section->getId()] = (int) $section->getAbstractWordCount() ?? 0;
|
|
}
|
|
|
|
class_exists(\APP\components\forms\publication\AssignToIssueForm::class); // Force define of FORM_ASSIGN_TO_ISSUE
|
|
$templateMgr->setConstants([
|
|
'FORM_ASSIGN_TO_ISSUE' => FORM_ASSIGN_TO_ISSUE,
|
|
'FORM_ISSUE_ENTRY' => FORM_ISSUE_ENTRY,
|
|
'FORM_PUBLISH' => FORM_PUBLISH,
|
|
]);
|
|
|
|
$components = $templateMgr->getState('components');
|
|
$components[FORM_ISSUE_ENTRY] = $issueEntryForm->getConfig();
|
|
|
|
// Add payments form if enabled
|
|
$paymentManager = Application::getPaymentManager($submissionContext);
|
|
$templateMgr->assign([
|
|
'submissionPaymentsEnabled' => $paymentManager->publicationEnabled(),
|
|
]);
|
|
if ($paymentManager->publicationEnabled()) {
|
|
$submissionPaymentsForm = new \APP\components\forms\publication\SubmissionPaymentsForm(
|
|
$request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getPath(), '_submissions/' . $submission->getId() . '/payment'),
|
|
$submission,
|
|
$request->getContext()
|
|
);
|
|
$components[FORM_SUBMISSION_PAYMENTS] = $submissionPaymentsForm->getConfig();
|
|
$templateMgr->setConstants([
|
|
'FORM_SUBMISSION_PAYMENTS' => FORM_SUBMISSION_PAYMENTS,
|
|
]);
|
|
}
|
|
|
|
// Add the word limit to the existing title/abstract form
|
|
if (!empty($components[FORM_TITLE_ABSTRACT]) &&
|
|
array_key_exists($submission->getLatestPublication()->getData('sectionId'), $sectionWordLimits)) {
|
|
$limit = (int) $sectionWordLimits[$submission->getLatestPublication()->getData('sectionId')];
|
|
foreach ($components[FORM_TITLE_ABSTRACT]['fields'] as $key => $field) {
|
|
if ($field['name'] === 'abstract') {
|
|
$components[FORM_TITLE_ABSTRACT]['fields'][$key]['wordLimit'] = $limit;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$assignToIssueUrl = $request->getDispatcher()->url(
|
|
$request,
|
|
Application::ROUTE_COMPONENT,
|
|
null,
|
|
'modals.publish.AssignToIssueHandler',
|
|
'assign',
|
|
null,
|
|
[
|
|
'submissionId' => $submission->getId(),
|
|
'publicationId' => '__publicationId__',
|
|
]
|
|
);
|
|
|
|
$publicationFormIds = $templateMgr->getState('publicationFormIds');
|
|
$publicationFormIds[] = FORM_ISSUE_ENTRY;
|
|
|
|
$templateMgr->setState([
|
|
'assignToIssueUrl' => $assignToIssueUrl,
|
|
'components' => $components,
|
|
'publicationFormIds' => $publicationFormIds,
|
|
'issueApiUrl' => $issueApiUrl,
|
|
'sectionWordLimits' => $sectionWordLimits,
|
|
'selectIssueLabel' => __('publication.selectIssue'),
|
|
]);
|
|
}
|
|
|
|
|
|
//
|
|
// Protected helper methods
|
|
//
|
|
/**
|
|
* Return the editor assignment notification type based on stage id.
|
|
*
|
|
* @param int $stageId
|
|
*
|
|
* @return ?int
|
|
*/
|
|
protected function getEditorAssignmentNotificationTypeByStageId($stageId)
|
|
{
|
|
switch ($stageId) {
|
|
case WORKFLOW_STAGE_ID_SUBMISSION:
|
|
return PKPNotification::NOTIFICATION_TYPE_EDITOR_ASSIGNMENT_SUBMISSION;
|
|
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW:
|
|
return PKPNotification::NOTIFICATION_TYPE_EDITOR_ASSIGNMENT_EXTERNAL_REVIEW;
|
|
case WORKFLOW_STAGE_ID_EDITING:
|
|
return PKPNotification::NOTIFICATION_TYPE_EDITOR_ASSIGNMENT_EDITING;
|
|
case WORKFLOW_STAGE_ID_PRODUCTION:
|
|
return PKPNotification::NOTIFICATION_TYPE_EDITOR_ASSIGNMENT_PRODUCTION;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected function _getRepresentationsGridUrl($request, $submission)
|
|
{
|
|
return $request->getDispatcher()->url(
|
|
$request,
|
|
Application::ROUTE_COMPONENT,
|
|
null,
|
|
'grid.articleGalleys.ArticleGalleyGridHandler',
|
|
'fetchGrid',
|
|
null,
|
|
[
|
|
'submissionId' => $submission->getId(),
|
|
'publicationId' => '__publicationId__',
|
|
]
|
|
);
|
|
}
|
|
|
|
protected function getStageDecisionTypes(int $stageId): array
|
|
{
|
|
$submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION);
|
|
$request = Application::get()->getRequest();
|
|
$reviewRoundId = (int) $request->getUserVar('reviewRoundId');
|
|
|
|
switch ($stageId) {
|
|
case WORKFLOW_STAGE_ID_SUBMISSION:
|
|
$decisionTypes = [
|
|
new SendExternalReview(),
|
|
new SkipExternalReview(),
|
|
];
|
|
if ($submission->getData('status') === Submission::STATUS_DECLINED) {
|
|
$decisionTypes[] = new RevertInitialDecline();
|
|
} elseif ($submission->getData('status') === Submission::STATUS_QUEUED) {
|
|
$decisionTypes[] = new InitialDecline();
|
|
}
|
|
break;
|
|
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW:
|
|
$decisionTypes = [
|
|
new RequestRevisions(),
|
|
new Accept(),
|
|
];
|
|
$cancelReviewRound = new CancelReviewRound();
|
|
if ($cancelReviewRound->canRetract($submission, $reviewRoundId)) {
|
|
$decisionTypes[] = $cancelReviewRound;
|
|
}
|
|
if ($submission->getData('status') === Submission::STATUS_DECLINED) {
|
|
$decisionTypes[] = new RevertDecline();
|
|
} elseif ($submission->getData('status') === Submission::STATUS_QUEUED) {
|
|
$decisionTypes[] = new Decline();
|
|
}
|
|
break;
|
|
case WORKFLOW_STAGE_ID_EDITING:
|
|
$decisionTypes = [
|
|
new SendToProduction(),
|
|
new BackFromCopyediting(),
|
|
];
|
|
break;
|
|
case WORKFLOW_STAGE_ID_PRODUCTION:
|
|
$decisionTypes = [
|
|
new BackFromProduction(),
|
|
];
|
|
break;
|
|
}
|
|
|
|
Hook::call('Workflow::Decisions', [&$decisionTypes, $stageId]);
|
|
|
|
return $decisionTypes;
|
|
}
|
|
|
|
protected function getStageRecommendationTypes(int $stageId): array
|
|
{
|
|
switch ($stageId) {
|
|
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW:
|
|
$decisionTypes = [
|
|
new RecommendRevisions(),
|
|
new RecommendAccept(),
|
|
new RecommendDecline(),
|
|
];
|
|
break;
|
|
default:
|
|
$decisionTypes = [];
|
|
}
|
|
|
|
|
|
Hook::call('Workflow::Recommendations', [$decisionTypes, $stageId]);
|
|
|
|
return $decisionTypes;
|
|
}
|
|
|
|
protected function getPrimaryDecisionTypes(): array
|
|
{
|
|
return [
|
|
SendExternalReview::class,
|
|
Accept::class,
|
|
SendToProduction::class,
|
|
];
|
|
}
|
|
|
|
protected function getWarnableDecisionTypes(): array
|
|
{
|
|
return [
|
|
InitialDecline::class,
|
|
Decline::class,
|
|
CancelReviewRound::class,
|
|
BackFromCopyediting::class,
|
|
BackFromProduction::class,
|
|
];
|
|
}
|
|
|
|
protected function getTitleAbstractForm(string $latestPublicationApiUrl, array $locales, Publication $latestPublication, Context $context): TitleAbstractForm
|
|
{
|
|
$section = Repo::section()->get($latestPublication->getData('sectionId'), $context->getId());
|
|
return new TitleAbstractForm(
|
|
$latestPublicationApiUrl,
|
|
$locales,
|
|
$latestPublication,
|
|
(int) $section->getData('wordCount'),
|
|
!$section->getData('abstractsNotRequired')
|
|
);
|
|
}
|
|
}
|