[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
lib
/
pkp
/
pages
/
workflow
/
[
Home
]
File: PKPWorkflowHandler.php
<?php /** * @file pages/workflow/PKPWorkflowHandler.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 PKPWorkflowHandler * * @ingroup pages_reviewer * * @brief Handle requests for the submssion workflow. */ namespace PKP\pages\workflow; use APP\core\Application; use APP\core\PageRouter; use APP\core\Request; use APP\core\Services; use APP\facades\Repo; use APP\handler\Handler; use APP\publication\Publication; use APP\submission\Submission; use APP\template\TemplateManager; use Exception; use Illuminate\Support\Enumerable; use PKP\components\forms\publication\PKPCitationsForm; use PKP\components\forms\publication\PKPMetadataForm; use PKP\components\forms\publication\PKPPublicationLicenseForm; use PKP\components\forms\publication\TitleAbstractForm; use PKP\components\listPanels\ContributorsListPanel; use PKP\context\Context; use PKP\core\JSONMessage; use PKP\core\PKPApplication; use PKP\core\PKPRequest; use PKP\db\DAORegistry; use PKP\notification\NotificationDAO; use PKP\notification\PKPNotification; use PKP\plugins\PluginRegistry; use PKP\security\authorization\internal\SubmissionRequiredPolicy; use PKP\security\authorization\internal\UserAccessibleWorkflowStageRequiredPolicy; use PKP\security\authorization\WorkflowStageAccessPolicy; use PKP\security\Role; use PKP\stageAssignment\StageAssignmentDAO; use PKP\submission\GenreDAO; use PKP\submission\PKPSubmission; use PKP\submission\reviewRound\ReviewRoundDAO; use PKP\user\User; use PKP\workflow\WorkflowStageDAO; abstract class PKPWorkflowHandler extends Handler { /** @copydoc PKPHandler::_isBackendPage */ public $_isBackendPage = true; // // Implement template methods from PKPHandler // /** * @copydoc PKPHandler::authorize() */ public function authorize($request, &$args, $roleAssignments) { /** @var PageRouter */ $router = $request->getRouter(); $operation = $router->getRequestedOp($request); if ($operation == 'access') { // Authorize requested submission. $this->addPolicy(new SubmissionRequiredPolicy($request, $args, 'submissionId')); // This policy will deny access if user has no accessible workflow stage. // Otherwise it will build an authorized object with all accessible // workflow stages and authorize user operation access. $this->addPolicy(new UserAccessibleWorkflowStageRequiredPolicy($request, PKPApplication::WORKFLOW_TYPE_EDITORIAL)); $this->markRoleAssignmentsChecked(); } else { $this->addPolicy(new WorkflowStageAccessPolicy($request, $args, $roleAssignments, 'submissionId', $this->identifyStageId($request, $args), PKPApplication::WORKFLOW_TYPE_EDITORIAL)); } return parent::authorize($request, $args, $roleAssignments); } // // Public handler methods // /** * Redirect users to their most appropriate * submission workflow stage. * * @param array $args * @param PKPRequest $request */ public function access($args, $request) { $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $currentStageId = $submission->getStageId(); $accessibleWorkflowStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES); $workflowRoles = Application::getWorkflowTypeRoles(); $editorialWorkflowRoles = $workflowRoles[PKPApplication::WORKFLOW_TYPE_EDITORIAL]; // Get the closest workflow stage that user has an assignment. $workingStageId = null; for ($workingStageId = $currentStageId; $workingStageId >= WORKFLOW_STAGE_ID_SUBMISSION; $workingStageId--) { if (isset($accessibleWorkflowStages[$workingStageId]) && array_intersect($editorialWorkflowRoles, $accessibleWorkflowStages[$workingStageId] ?? [])) { break; } } // If no stage was found, user still have access to future stages of the // submission. Try to get the closest future workflow stage. if ($workingStageId == null) { for ($workingStageId = $currentStageId; $workingStageId <= WORKFLOW_STAGE_ID_PRODUCTION; $workingStageId++) { if (isset($accessibleWorkflowStages[$workingStageId]) && array_intersect($editorialWorkflowRoles, $accessibleWorkflowStages[$workingStageId] ?? [])) { break; } } } assert(isset($workingStageId)); $router = $request->getRouter(); $request->redirectUrl($router->url($request, null, 'workflow', 'index', [$submission->getId(), $workingStageId])); } /** * Show the workflow stage, with the stage path as an #anchor. * * @param array $args * @param PKPRequest $request */ public function index($args, $request) { $this->setupTemplate($request); $templateMgr = TemplateManager::getManager($request); $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $requestedStageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE); $submissionContext = $request->getContext(); if ($submission->getContextId() !== $submissionContext->getId()) { $submissionContext = Services::get('context')->get($submission->getContextId()); } $workflowStages = WorkflowStageDAO::getWorkflowStageKeysAndPaths(); $accessibleWorkflowStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES); $workflowRoles = Application::getWorkflowTypeRoles(); $editorialWorkflowRoles = $workflowRoles[PKPApplication::WORKFLOW_TYPE_EDITORIAL]; $result = Repo::userGroup()->getCollector() ->filterByContextIds([$submission->getData('contextId')]) ->getMany(); $authorUserGroups = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_AUTHOR], $submission->getData('contextId')); $workflowUserGroups = Repo::userGroup()->getByRoleIds($editorialWorkflowRoles, $submission->getData('contextId')); // Publication tab // Users have access to the publication tab if they are assigned to // the active stage id or if they are assigned as an editor or if // they are not assigned in any role and have a manager role in the // context. $currentStageId = $submission->getStageId(); $accessibleWorkflowStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES); $canAccessPublication = false; // View title, metadata, etc. $canEditPublication = Repo::submission()->canEditPublication($submission->getId(), $request->getUser()->getId()); $canAccessProduction = false; // Access to galleys and issue entry $canPublish = false; // Ability to publish, unpublish and create versions $canAccessEditorialHistory = false; // Access to activity log // unassigned managers if (!$accessibleWorkflowStages && array_intersect($this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN] ?? [])) { $canAccessProduction = true; $canPublish = true; $canAccessPublication = true; $canAccessEditorialHistory = true; } elseif (!empty($accessibleWorkflowStages[$currentStageId]) && array_intersect($editorialWorkflowRoles, $accessibleWorkflowStages[$currentStageId] ?? [])) { $canAccessProduction = (bool) array_intersect($editorialWorkflowRoles, $accessibleWorkflowStages[WORKFLOW_STAGE_ID_PRODUCTION] ?? []); $canAccessPublication = true; $stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */ $result = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId( $submission->getId(), $request->getUser()->getId(), WORKFLOW_STAGE_ID_PRODUCTION )->toArray(); // If they have no stage assignments, check the role they have been granted // for the production workflow stage. An unassigned admin or manager may // have been granted access and should be allowed to publish. if (empty($result) && is_array($accessibleWorkflowStages[WORKFLOW_STAGE_ID_PRODUCTION])) { $canPublish = (bool) array_intersect([Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER], $accessibleWorkflowStages[WORKFLOW_STAGE_ID_PRODUCTION] ?? []); // Otherwise, check stage assignments // "Recommend only" stage assignments can not publish } else { foreach ($result as $stageAssignment) { foreach ($workflowUserGroups as $workflowUserGroup) { if ($stageAssignment->getUserGroupId() == $workflowUserGroup->getId() && !$stageAssignment->getRecommendOnly()) { $canPublish = true; break; } } } } } if (!empty($accessibleWorkflowStages[$currentStageId]) && array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR], $accessibleWorkflowStages[$currentStageId] ?? [])) { $canAccessEditorialHistory = true; } /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); $locales = $submissionContext->getSupportedSubmissionLocaleNames(); $locales = array_map(fn (string $locale, string $name) => ['key' => $locale, 'label' => $name], array_keys($locales), $locales); $latestPublication = $submission->getLatestPublication(); $submissionApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getData('urlPath'), 'submissions/' . $submission->getId()); $submissionFileApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getData('urlPath'), 'submissions/' . $submission->getId() . '/files'); $latestPublicationApiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $submissionContext->getData('urlPath'), 'submissions/' . $submission->getId() . '/publications/' . $latestPublication->getId()); $decisionUrl = $request->url( $submissionContext->getData('urlPath'), 'decision', 'record', $submission->getId(), [ 'decision' => '__decision__', 'reviewRoundId' => '__reviewRoundId__', ] ); $editorialHistoryUrl = $request->getDispatcher()->url( $request, Application::ROUTE_COMPONENT, null, 'informationCenter.SubmissionInformationCenterHandler', 'viewInformationCenter', null, ['submissionId' => $submission->getId()] ); $submissionLibraryUrl = $request->getDispatcher()->url( $request, Application::ROUTE_COMPONENT, null, 'modals.documentLibrary.DocumentLibraryHandler', 'documentLibrary', null, ['submissionId' => $submission->getId()] ); $publishUrl = $request->getDispatcher()->url( $request, Application::ROUTE_COMPONENT, null, 'modals.publish.PublishHandler', 'publish', null, [ 'submissionId' => $submission->getId(), 'publicationId' => '__publicationId__', ] ); $citationsForm = new PKPCitationsForm($latestPublicationApiUrl, $latestPublication); $publicationLicenseForm = new PKPPublicationLicenseForm($latestPublicationApiUrl, $locales, $latestPublication, $submissionContext, $authorUserGroups); $titleAbstractForm = $this->getTitleAbstractForm($latestPublicationApiUrl, $locales, $latestPublication, $submissionContext); $authorItems = []; foreach ($latestPublication->getData('authors') as $contributor) { $authorItems[] = Repo::author()->getSchemaMap()->map($contributor); } $contributorsListPanel = $this->getContributorsListPanel( $submission, $submissionContext, $locales, $authorItems, $canEditPublication ); // Import constants import('classes.components.forms.publication.PublishForm'); $templateMgr->setConstants([ 'STATUS_QUEUED' => PKPSubmission::STATUS_QUEUED, 'STATUS_PUBLISHED' => PKPSubmission::STATUS_PUBLISHED, 'STATUS_DECLINED' => PKPSubmission::STATUS_DECLINED, 'STATUS_SCHEDULED' => PKPSubmission::STATUS_SCHEDULED, 'FORM_CITATIONS' => FORM_CITATIONS, 'FORM_PUBLICATION_LICENSE' => FORM_PUBLICATION_LICENSE, 'FORM_PUBLISH' => FORM_PUBLISH, 'FORM_TITLE_ABSTRACT' => FORM_TITLE_ABSTRACT, ]); // Get the submission props without the full publication details. We'll // retrieve just the publication information that we need separately to // reduce the amount of data passed to the browser $submissionProps = Repo::submission()->getSchemaMap()->summarizeWithoutPublication($submission); // Get an array of publications $publications = $submission->getData('publications'); /** @var Enumerable $publications */ $publicationList = $publications->map(function ($publication) { return [ 'id' => $publication->getId(), 'datePublished' => $publication->getData('datePublished'), 'status' => $publication->getData('status'), 'version' => $publication->getData('version') ]; })->values(); // Get full details of the working publication and the current publication $mapper = Repo::publication()->getSchemaMap($submission, $authorUserGroups, $genres); $workingPublicationProps = $mapper->map($submission->getLatestPublication()); $currentPublicationProps = $submission->getLatestPublication()->getId() === $submission->getCurrentPublication()->getId() ? $workingPublicationProps : $mapper->map($submission->getCurrentPublication()); $state = [ 'activityLogLabel' => __('submission.list.infoCenter'), 'canAccessPublication' => $canAccessPublication, 'canEditPublication' => $canEditPublication, 'components' => [ $contributorsListPanel->id => $contributorsListPanel->getConfig(), $citationsForm->id => $citationsForm->getConfig(), $publicationLicenseForm->id => $publicationLicenseForm->getConfig(), $titleAbstractForm->id => $titleAbstractForm->getConfig(), ], 'currentPublication' => $currentPublicationProps, 'decisionUrl' => $decisionUrl, 'editorialHistoryUrl' => $editorialHistoryUrl, 'publicationFormIds' => [ FORM_CITATIONS, FORM_PUBLICATION_LICENSE, FORM_PUBLISH, FORM_TITLE_ABSTRACT, ], 'publicationList' => $publicationList, 'publicationTabsLabel' => __('publication.version.details'), 'publishLabel' => __('publication.publish'), 'publishUrl' => $publishUrl, 'representationsGridUrl' => $this->_getRepresentationsGridUrl($request, $submission), 'schedulePublicationLabel' => __('editor.submission.schedulePublication'), 'statusLabel' => __('semicolon', ['label' => __('common.status')]), 'submission' => $submissionProps, 'submissionFileApiUrl' => $submissionFileApiUrl, 'submissionApiUrl' => $submissionApiUrl, 'submissionLibraryLabel' => __('grid.libraryFiles.submission.title'), 'submissionLibraryUrl' => $submissionLibraryUrl, 'supportsReferences' => !!$submissionContext->getData('citations'), 'unpublishConfirmLabel' => __('publication.unpublish.confirm'), 'unpublishLabel' => __('publication.unpublish'), 'unscheduleConfirmLabel' => __('publication.unschedule.confirm'), 'unscheduleLabel' => __('publication.unschedule'), 'versionLabel' => __('semicolon', ['label' => __('admin.version')]), 'versionConfirmTitle' => __('publication.createVersion'), 'versionConfirmMessage' => __('publication.version.confirm'), 'workingPublication' => $workingPublicationProps, ]; // Add the metadata form if one or more metadata fields are enabled $vocabSuggestionUrlBase = $request->getDispatcher()->url($request, PKPApplication::ROUTE_API, $submissionContext->getData('urlPath'), 'vocabs', null, null, ['vocab' => '__vocab__']); $metadataForm = new PKPMetadataForm($latestPublicationApiUrl, $locales, $latestPublication, $submissionContext, $vocabSuggestionUrlBase, true); $metadataFormConfig = $metadataForm->getConfig(); $metadataEnabled = count($metadataForm->fields); if ($metadataEnabled) { $templateMgr->setConstants([ 'FORM_METADATA' => FORM_METADATA, ]); $state['components'][FORM_METADATA] = $metadataFormConfig; $state['publicationFormIds'][] = FORM_METADATA; } // Add the identifiers form if one or more identifier is enabled $identifiersEnabled = false; $pubIdPlugins = PluginRegistry::getPlugins('pubIds'); foreach ($pubIdPlugins as $pubIdPlugin) { if ($pubIdPlugin->isObjectTypeEnabled('Publication', $request->getContext()->getId())) { $identifiersEnabled = true; break; } } if ($identifiersEnabled) { $identifiersForm = new \PKP\components\forms\publication\PKPPublicationIdentifiersForm($latestPublicationApiUrl, $locales, $latestPublication, $submissionContext); $templateMgr->setConstants([ 'FORM_PUBLICATION_IDENTIFIERS' => FORM_PUBLICATION_IDENTIFIERS, ]); $state['components'][FORM_PUBLICATION_IDENTIFIERS] = $identifiersForm->getConfig(); $state['publicationFormIds'][] = FORM_PUBLICATION_IDENTIFIERS; } // Add the revision decision/recommendation forms if this app supports a review stage if (count(array_intersect([WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW], Application::getApplicationStages() ?? []))) { $selectRevisionDecisionForm = new \PKP\components\forms\decision\SelectRevisionDecisionForm(); $selectRevisionRecommendationForm = new \PKP\components\forms\decision\SelectRevisionRecommendationForm(); $state['components'][$selectRevisionDecisionForm->id] = $selectRevisionDecisionForm->getConfig(); $state['components'][$selectRevisionRecommendationForm->id] = $selectRevisionRecommendationForm->getConfig(); $templateMgr->setConstants([ 'FORM_SELECT_REVISION_DECISION' => FORM_SELECT_REVISION_DECISION, 'FORM_SELECT_REVISION_RECOMMENDATION' => FORM_SELECT_REVISION_RECOMMENDATION, ]); } $templateMgr->setState($state); $templateMgr->assign([ 'canAccessEditorialHistory' => $canAccessEditorialHistory, 'canAccessPublication' => $canAccessPublication, 'canEditPublication' => $canEditPublication, 'canAccessProduction' => $canAccessProduction, 'canPublish' => $canPublish, 'identifiersEnabled' => $identifiersEnabled, 'metadataEnabled' => $metadataEnabled, 'pageComponent' => 'WorkflowPage', 'pageTitle' => implode(__('common.titleSeparator'), array_filter([ $submission->getLatestPublication()->getShortAuthorString(), $submission->getLocalizedTitle() ])), 'pageWidth' => TemplateManager::PAGE_WIDTH_WIDE, 'requestedStageId' => $requestedStageId, 'submission' => $submission, 'workflowStages' => $workflowStages, ]); $this->setupIndex($request); $templateMgr->display('workflow/workflow.tpl'); } /** * Show the submission stage. * * @param array $args * @param PKPRequest $request */ public function submission($args, $request) { $this->_redirectToIndex($args, $request); } /** * Show the external review stage. * * @param array $args * @param PKPRequest $request */ public function externalReview($args, $request) { $this->_redirectToIndex($args, $request); } /** * Show the editorial stage * * @param PKPRequest $request * @param array $args */ public function editorial($args, $request) { $this->_redirectToIndex($args, $request); } /** * Show the production stage * * @param PKPRequest $request * @param array $args */ public function production($args, $request) { $this->_redirectToIndex($args, $request); } /** * Redirect all old stage paths to index * * @param array $args * @param PKPRequest $request */ protected function _redirectToIndex($args, $request) { // Translate the operation to a workflow stage identifier. $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $router = $request->getRouter(); $workflowPath = $router->getRequestedOp($request); $stageId = WorkflowStageDAO::getIdFromPath($workflowPath); $request->redirectUrl($router->url($request, null, 'workflow', 'index', [$submission->getId(), $stageId])); } /** * Fetch JSON-encoded editor decision options. * * @param array $args * @param Request $request * * @return JSONMessage JSON object */ public function editorDecisionActions($args, $request) { $this->setupTemplate($request); $reviewRoundId = (int) $request->getUserVar('reviewRoundId'); // Prepare the action arguments. $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $stageId = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_WORKFLOW_STAGE); $actionArgs = [ 'submissionId' => $submission->getId(), 'stageId' => (int) $stageId, ]; // If a review round was specified, include it in the args; // must also check that this is the last round or decisions // cannot be recorded. $reviewRound = null; if ($reviewRoundId) { $actionArgs['reviewRoundId'] = $reviewRoundId; $reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */ $lastReviewRound = $reviewRoundDao->getLastReviewRoundBySubmissionId($submission->getId(), $stageId); $reviewRound = $reviewRoundDao->getById($reviewRoundId); } else { $lastReviewRound = null; } // If there is an editor assigned, retrieve stage decisions. $stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */ $editorsStageAssignments = $stageAssignmentDao->getEditorsAssignedToStage($submission->getId(), $stageId); $user = $request->getUser(); $makeRecommendation = $makeDecision = false; // if the user is assigned several times in an editorial role, check his/her assignments permissions i.e. // if the user is assigned with both possibilities: to only recommend as well as make decision foreach ($editorsStageAssignments as $editorsStageAssignment) { if ($editorsStageAssignment->getUserId() == $user->getId()) { if (!$editorsStageAssignment->getRecommendOnly()) { $makeDecision = true; } else { $makeRecommendation = true; } } } // If user is not assigned to the submission, // see if the user is manager, and // if the group is recommendOnly if (!$makeRecommendation && !$makeDecision) { $userGroups = Repo::userGroup()->userUserGroups($user->getId(), $request->getContext()->getId()); foreach ($userGroups as $userGroup) { if (in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN])) { if (!$userGroup->getRecommendOnly()) { $makeDecision = true; } else { $makeRecommendation = true; } } } } // if the user can make recommendations, check whether there are any decisions that can be made given // the stage that we are operating into. $isOnlyRecommending = $makeRecommendation && !$makeDecision; if ($isOnlyRecommending) { $recommendatorsAvailableDecisions = Repo::decision() ->getDecisionTypesMadeByRecommendingUsers($stageId); if (!empty($recommendatorsAvailableDecisions)) { // If there are any, then the user can be considered a decision user. $makeDecision = true; } } $lastRecommendation = null; $allRecommendations = null; $hasDecidingEditors = false; if (!empty($editorsStageAssignments) && (!$reviewRoundId || ($lastReviewRound && $reviewRoundId == $lastReviewRound->getId()))) { // If this is a review stage and the user has "recommend only role" if (($stageId == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW || $stageId == WORKFLOW_STAGE_ID_INTERNAL_REVIEW)) { if ($makeRecommendation) { // Get the made editorial decisions from the current user $editorDecisions = Repo::decision()->getCollector() ->filterBySubmissionIds([$submission->getId()]) ->filterByStageIds([$stageId]) ->filterByReviewRoundIds([$reviewRound->getId()]) ->filterByEditorIds([$user->getId()]) ->getMany(); // Get the last recommendation foreach ($editorDecisions as $editorDecision) { if (Repo::decision()->isRecommendation($editorDecision->getData('decision'))) { if ($lastRecommendation) { if ($editorDecision->getData('dateDecided') >= $lastRecommendation->getData('dateDecided')) { $lastRecommendation = $editorDecision; } } else { $lastRecommendation = $editorDecision; } } } if ($lastRecommendation) { $lastRecommendation = $this->getRecommendationLabel($lastRecommendation->getData('decision')); } // At least one deciding editor must be assigned before a recommendation can be made /** @var StageAssignmentDAO $stageAssignmentDao */ $stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); $decidingEditorIds = $stageAssignmentDao->getDecidingEditorIds($submission->getId(), $stageId); $hasDecidingEditors = count($decidingEditorIds) > 0; } elseif ($makeDecision) { // Get the made editorial decisions from all users $editorDecisions = Repo::decision() ->getCollector() ->filterBySubmissionIds([$submission->getId()]) ->filterByStageIds([$stageId]) ->filterByReviewRoundIds([$reviewRound->getId()]) ->getMany(); // Get all recommendations $recommendations = []; foreach ($editorDecisions as $editorDecision) { if (Repo::decision()->isRecommendation($editorDecision->getData('decision'))) { if (array_key_exists($editorDecision->getData('editorId'), $recommendations)) { if ($editorDecision->getData('dateDecided') >= $recommendations[$editorDecision->getData('editorId')]['dateDecided']) { $recommendations[$editorDecision->getData('editorId')] = ['dateDecided' => $editorDecision->getData('dateDecided'), 'decision' => $editorDecision->getData('decision')]; } } else { $recommendations[$editorDecision->getData('editorId')] = ['dateDecided' => $editorDecision->getData('dateDecided'), 'decision' => $editorDecision->getData('decision')]; } } } $allRecommendations = []; foreach ($recommendations as $recommendation) { $allRecommendations[] = $this->getRecommendationLabel($recommendation['decision']); } $allRecommendations = join(__('common.commaListSeparator'), $allRecommendations); } } } $hasSubmissionPassedThisStage = $submission->getStageId() > $stageId; $lastDecision = null; switch ($submission->getStatus()) { case PKPSubmission::STATUS_QUEUED: switch ($stageId) { case WORKFLOW_STAGE_ID_SUBMISSION: if ($hasSubmissionPassedThisStage) { $lastDecision = 'editor.submission.workflowDecision.submission.underReview'; } break; case WORKFLOW_STAGE_ID_INTERNAL_REVIEW: case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW: if ($reviewRoundId < $lastReviewRound->getId()) { $lastDecision = 'editor.submission.workflowDecision.submission.reviewRound'; } elseif ($hasSubmissionPassedThisStage) { $lastDecision = 'editor.submission.workflowDecision.submission.accepted'; } break; case WORKFLOW_STAGE_ID_EDITING: if ($hasSubmissionPassedThisStage) { $lastDecision = 'editor.submission.workflowDecision.submission.production'; } break; } break; case PKPSubmission::STATUS_PUBLISHED: $lastDecision = 'editor.submission.workflowDecision.submission.published'; break; case PKPSubmission::STATUS_DECLINED: $lastDecision = 'editor.submission.workflowDecision.submission.declined'; break; } $canRecordDecision = // Only allow decisions to be recorded on the submission's current stage $submission->getData('stageId') == $stageId // Only allow decisions on the latest review round && (!$lastReviewRound || $lastReviewRound->getId() == $reviewRoundId) // At least one deciding editor must be assigned to make a recommendation && ($makeDecision || $hasDecidingEditors); $decisions = $this->getStageDecisionTypes($stageId); if ($isOnlyRecommending) { $decisions = Repo::decision() ->getDecisionTypesMadeByRecommendingUsers($stageId); } // Assign the actions to the template. $templateMgr = TemplateManager::getManager($request); $templateMgr->assign([ 'canRecordDecision' => $canRecordDecision, 'decisions' => $decisions, 'recommendations' => $this->getStageRecommendationTypes($stageId), 'primaryDecisions' => $this->getPrimaryDecisionTypes(), 'warnableDecisions' => $this->getWarnableDecisionTypes(), 'editorsAssigned' => count($editorsStageAssignments) > 0, 'stageId' => $stageId, 'reviewRoundId' => $reviewRound ? $reviewRound->getId() : null, 'lastDecision' => $lastDecision, 'lastReviewRound' => $lastReviewRound, 'submission' => $submission, 'makeRecommendation' => $makeRecommendation, 'makeDecision' => $makeDecision, 'lastRecommendation' => $lastRecommendation, 'allRecommendations' => $allRecommendations, ]); return $templateMgr->fetchJson('workflow/editorialLinkActions.tpl'); } /** * Fetch the JSON-encoded submission progress bar. * * @param array $args * @param Request $request * * @return JSONMessage JSON object */ public function submissionProgressBar($args, $request) { $this->setupTemplate($request); $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $workflowStages = WorkflowStageDAO::getWorkflowStageKeysAndPaths(); $templateMgr = TemplateManager::getManager($request); $templateMgr->assign([ 'submission' => $submission, 'currentStageId' => $this->identifyStageId($request, $args), 'workflowStages' => $workflowStages, ]); return $templateMgr->fetchJson('workflow/submissionProgressBar.tpl'); } /** * Placeholder method to be overridden by apps in order to add * app-specific data to the template * * @param Request $request */ public function setupIndex($request) { } // // Protected helper methods // /** * Translate the requested operation to a stage id. * * @param Request $request * @param array $args * * @return int One of the WORKFLOW_STAGE_* constants. */ protected function identifyStageId($request, $args) { if ($stageId = $request->getUserVar('stageId')) { return (int) $stageId; } // Maintain the old check for previous path urls $router = $request->getRouter(); $workflowPath = $router->getRequestedOp($request); $stageId = WorkflowStageDAO::getIdFromPath($workflowPath); if ($stageId) { return $stageId; } // Finally, retrieve the requested operation, if the stage id is // passed in via an argument in the URL, like index/submissionId/stageId $stageId = $args[1]; // Translate the operation to a workflow stage identifier. assert(WorkflowStageDAO::getPathFromId($stageId) !== null); return $stageId; } /** * Determine if a particular stage has a notification pending. If so, return true. * This is used to set the CSS class of the submission progress bar. * * @param User $user * @param int $stageId * @param int $contextId * * @return bool */ protected function notificationOptionsByStage($user, $stageId, $contextId) { $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */ $editorAssignmentNotificationType = $this->getEditorAssignmentNotificationTypeByStageId($stageId); $editorAssignments = $notificationDao->getByAssoc(Application::ASSOC_TYPE_SUBMISSION, $submission->getId(), null, $editorAssignmentNotificationType, $contextId); // if the User has assigned TASKs in this stage check, return true if ($editorAssignments->next()) { return true; } // check for more specific notifications on those stages that have them. if ($stageId == WORKFLOW_STAGE_ID_PRODUCTION) { $submissionApprovalNotification = $notificationDao->getByAssoc(Application::ASSOC_TYPE_SUBMISSION, $submission->getId(), null, PKPNotification::NOTIFICATION_TYPE_APPROVE_SUBMISSION, $contextId); if ($submissionApprovalNotification->next()) { return true; } } return false; } /** * Get a label for a recommendation decision type */ protected function getRecommendationLabel(int $decision): string { $decisionType = Repo::decision()->getDecisionType($decision); if (!$decisionType || !method_exists($decisionType, 'getRecommendationLabel')) { throw new Exception('Could not find label for unknown recommendation type.'); } return $decisionType->getRecommendationLabel(); } /** * Get the contributor list panel */ protected function getContributorsListPanel(Submission $submission, Context $context, array $locales, array $authorItems, bool $canEditPublication): ContributorsListPanel { return new ContributorsListPanel( 'contributors', __('publication.contributors'), $submission, $context, $locales, $authorItems, $canEditPublication ); } // // Abstract protected methods. // /** * Return the editor assignment notification type based on stage id. * * @param int $stageId * * @return int */ abstract protected function getEditorAssignmentNotificationTypeByStageId($stageId); /** * Get the URL for the galley/publication formats grid with a placeholder for * the publicationId value * * @param Request $request * @param Submission $submission * * @return string */ abstract protected function _getRepresentationsGridUrl($request, $submission); /** * A helper method to get a list of editor decisions to * show on the right panel of each stage * * @return string[] */ abstract protected function getStageDecisionTypes(int $stageId): array; /** * A helper method to get a list of editor recommendations to * show on the right panel of the review stage * */ abstract protected function getStageRecommendationTypes(int $stageId): array; /** * Get the editor decision types that should be shown * as primary buttons (eg - Accept) * * @return string[] */ abstract protected function getPrimaryDecisionTypes(): array; /** * Get the editor decision types that should be shown * as warnable buttons (eg - Decline) * * @return string[] */ abstract protected function getWarnableDecisionTypes(): array; /** * Get the form for entering the title/abstract details */ abstract protected function getTitleAbstractForm(string $latestPublicationApiUrl, array $locales, Publication $latestPublication, Context $context): TitleAbstractForm; }