<?php
/**
* @file classes/submission/maps/Schema.php
*
* Copyright (c) 2014-2020 Simon Fraser University
* Copyright (c) 2000-2020 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class Schema
*
* @brief Map submissions to the properties defined in the submission schema
*/
namespace PKP\submission\maps;
use APP\core\Application;
use APP\facades\Repo;
use APP\submission\Submission;
use Illuminate\Support\Enumerable;
use Illuminate\Support\LazyCollection;
use PKP\db\DAORegistry;
use PKP\plugins\Hook;
use PKP\plugins\PluginRegistry;
use PKP\query\QueryDAO;
use PKP\services\PKPSchemaService;
use PKP\stageAssignment\StageAssignment;
use PKP\stageAssignment\StageAssignmentDAO;
use PKP\submission\Genre;
use PKP\submission\reviewAssignment\ReviewAssignment;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\reviewRound\ReviewRoundDAO;
use PKP\submissionFile\SubmissionFile;
use PKP\userGroup\UserGroup;
use PKP\workflow\WorkflowStageDAO;
class Schema extends \PKP\core\maps\Schema
{
/** @copydoc \PKP\core\maps\Schema::$collection */
public Enumerable $collection;
/** @copydoc \PKP\core\maps\Schema::$schema */
public string $schema = PKPSchemaService::SCHEMA_SUBMISSION;
/** @var LazyCollection<int,UserGroup> The user groups for this context. */
public LazyCollection $userGroups;
/** @var Genre[] The file genres in this context. */
public array $genres;
/**
* Get extra property names used in the submissions list
*/
protected function getSubmissionsListProps(): array
{
PluginRegistry::loadCategory('pubIds', true);
$props = [
'_href',
'contextId',
'currentPublicationId',
'dateLastActivity',
'dateSubmitted',
'id',
'lastModified',
'publications',
'reviewAssignments',
'reviewRounds',
'stageId',
'stages',
'status',
'statusLabel',
'submissionProgress',
'urlAuthorWorkflow',
'urlEditorialWorkflow',
'urlWorkflow',
'urlPublished',
];
Hook::call('Submission::getSubmissionsListProps', [&$props]);
return $props;
}
/**
* Map a submission
*
* Includes all properties in the submission schema.
*
* @param LazyCollection<int,UserGroup> $userGroups The user groups in this context
* @param Genre[] $genres The file genres in this context
*/
public function map(Submission $item, LazyCollection $userGroups, array $genres): array
{
$this->userGroups = $userGroups;
$this->genres = $genres;
return $this->mapByProperties($this->getProps(), $item);
}
/**
* Summarize a submission
*
* Includes properties with the apiSummary flag in the submission schema.
*
* @param LazyCollection<int,UserGroup> $userGroups The user groups in this context
* @param Genre[] $genres The file genres in this context
*/
public function summarize(Submission $item, LazyCollection $userGroups, array $genres): array
{
$this->userGroups = $userGroups;
$this->genres = $genres;
return $this->mapByProperties($this->getSummaryProps(), $item);
}
/**
* Map a collection of Submissions
*
* @see self::map
*
* @param LazyCollection<int,UserGroup> $userGroups The user groups in this context
* @param Genre[] $genres The file genres in this context
*/
public function mapMany(Enumerable $collection, LazyCollection $userGroups, array $genres): Enumerable
{
$this->collection = $collection;
$this->userGroups = $userGroups;
$this->genres = $genres;
return $collection->map(function ($item) {
return $this->map($item, $this->userGroups, $this->genres);
});
}
/**
* Summarize a collection of Submissions
*
* @see self::summarize
*
* @param LazyCollection<int,UserGroup> $userGroups The user groups in this context
* @param Genre[] $genres The file genres in this context
*/
public function summarizeMany(Enumerable $collection, LazyCollection $userGroups, array $genres): Enumerable
{
$this->collection = $collection;
$this->userGroups = $userGroups;
$this->genres = $genres;
return $collection->map(function ($item) {
return $this->summarize($item, $this->userGroups, $this->genres);
});
}
/**
* Map a submission with extra properties for the submissions list
*
* @param LazyCollection<int,UserGroup> $userGroups The user groups in this context
* @param Genre[] $genres The file genres in this context
*/
public function mapToSubmissionsList(Submission $item, LazyCollection $userGroups, array $genres): array
{
$this->userGroups = $userGroups;
$this->genres = $genres;
return $this->mapByProperties($this->getSubmissionsListProps(), $item);
}
/**
* Map a collection of submissions with extra properties for the submissions list
*
* @see self::map
*
* @param LazyCollection<int,UserGroup> $userGroups The user groups in this context
* @param Genre[] $genres The file genres in this context
*/
public function mapManyToSubmissionsList(Enumerable $collection, LazyCollection $userGroups, array $genres): Enumerable
{
$this->collection = $collection;
$this->userGroups = $userGroups;
$this->genres = $genres;
return $collection->map(function ($item) {
return $this->mapToSubmissionsList($item, $this->userGroups, $this->genres);
});
}
/**
* Map a submission with only the title, authors, and URLs for the stats list
*/
public function mapToStats(Submission $submission): array
{
$props = $this->mapByProperties([
'_href',
'id',
'urlWorkflow',
'urlPublished',
], $submission);
$currentPublication = $submission->getCurrentPublication();
if ($currentPublication) {
$props['authorsStringShort'] = $currentPublication->getShortAuthorString();
$props['fullTitle'] = $currentPublication->getFullTitles('html');
}
return $props;
}
/**
* Summarize a submission without publication details
*/
public function summarizeWithoutPublication(Submission $item): array
{
$props = array_filter($this->getSummaryProps(), function ($prop) {
return $prop !== 'publications';
});
return $this->mapByProperties($props, $item);
}
/**
* Map schema properties of a Submission to an assoc array
*/
protected function mapByProperties(array $props, Submission $submission): array
{
$output = [];
if (in_array('publications', $props)) {
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$currentUserReviewAssignment = $reviewAssignmentDao->getLastReviewRoundReviewAssignmentByReviewer(
$submission->getId(),
$this->request->getUser()->getId()
);
$anonymize = $currentUserReviewAssignment && $currentUserReviewAssignment->getReviewMethod() === ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS;
}
foreach ($props as $prop) {
switch ($prop) {
case '_href':
$output[$prop] = Repo::submission()->getUrlApi($this->context, $submission->getId());
break;
case 'publications':
$output[$prop] = Repo::publication()->getSchemaMap($submission, $this->userGroups, $this->genres)
->summarizeMany($submission->getData('publications'), $anonymize)->values();
break;
case 'reviewAssignments':
$output[$prop] = $this->getPropertyReviewAssignments($submission);
break;
case 'reviewRounds':
$output[$prop] = $this->getPropertyReviewRounds($submission);
break;
case 'stages':
$output[$prop] = $this->getPropertyStages($submission);
break;
case 'statusLabel':
$output[$prop] = __($submission->getStatusKey());
break;
case 'urlAuthorWorkflow':
$output[$prop] = Repo::submission()->getUrlAuthorWorkflow($this->context, $submission->getId());
break;
case 'urlEditorialWorkflow':
$output[$prop] = Repo::submission()->getUrlEditorialWorkflow($this->context, $submission->getId());
break;
case 'urlSubmissionWizard':
$output[$prop] = Repo::submission()->getUrlSubmissionWizard($this->context, $submission->getId());
break;
case 'urlWorkflow':
$output[$prop] = Repo::submission()->getWorkflowUrlByUserRoles($submission);
break;
default:
$output[$prop] = $submission->getData($prop);
break;
}
}
return $output;
}
/**
* Get details about the review assignments for a submission
*/
protected function getPropertyReviewAssignments(Submission $submission): array
{
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignments = $reviewAssignmentDao->getBySubmissionId($submission->getId());
$reviews = [];
foreach ($reviewAssignments as $reviewAssignment) {
// @todo for now, only show reviews that haven't been
// declined or cancelled
if ($reviewAssignment->getDeclined() || $reviewAssignment->getCancelled()) {
continue;
}
$request = Application::get()->getRequest();
$currentUser = $request->getUser();
$context = $request->getContext();
$due = is_null($reviewAssignment->getDateDue()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateDue()));
$responseDue = is_null($reviewAssignment->getDateResponseDue()) ? null : date('Y-m-d', strtotime($reviewAssignment->getDateResponseDue()));
$reviews[] = [
'id' => (int) $reviewAssignment->getId(),
'isCurrentUserAssigned' => $currentUser->getId() == (int) $reviewAssignment->getReviewerId(),
'statusId' => (int) $reviewAssignment->getStatus(),
'status' => __($reviewAssignment->getStatusKey()),
'due' => $due,
'responseDue' => $responseDue,
'round' => (int) $reviewAssignment->getRound(),
'roundId' => (int) $reviewAssignment->getReviewRoundId(),
];
}
return $reviews;
}
/**
* Get details about the review rounds for a submission
*/
protected function getPropertyReviewRounds(Submission $submission): array
{
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$reviewRounds = $reviewRoundDao->getBySubmissionId($submission->getId())->toIterator();
$rounds = [];
foreach ($reviewRounds as $reviewRound) {
$rounds[] = [
'id' => $reviewRound->getId(),
'round' => $reviewRound->getRound(),
'stageId' => $reviewRound->getStageId(),
'statusId' => $reviewRound->determineStatus(),
'status' => __($reviewRound->getStatusKey()),
];
}
return $rounds;
}
/**
* Get details about a submission's stage(s)
*
* @return array
* [
* {
* `id` int stage id
* `label` string translated stage name
* `queries` array [{
* `id` int query id
* `assocType` int
* `assocId` int
* `stageId` int
* `seq` int
* `closed` bool
* }]
* `statusId` int stage status. note: on review stage, this refers to the
* status of the latest round.
* `status` string translated stage status name
* `files` array {
* `count` int number of files attached to stage. note: this only counts
* revision files.
* }
* ]
*/
public function getPropertyStages(Submission $submission): array
{
$stageIds = Application::get()->getApplicationStages();
$request = Application::get()->getRequest();
$currentUser = $request->getUser();
$context = $request->getContext();
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignments = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId($submission->getId(), $currentUser->getId() ?? 0)->toArray();
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
$openPerStage = $queryDao->countOpenPerStage($submission->getId(), [$request->getUser()->getId()]);
$stages = [];
foreach ($stageIds as $stageId) {
$workflowStageDao = DAORegistry::getDAO('WorkflowStageDAO'); /** @var WorkflowStageDAO $workflowStageDao */
$stage = [
'id' => (int) $stageId,
'label' => __($workflowStageDao->getTranslationKeyFromId($stageId)),
'isActiveStage' => $submission->getData('stageId') == $stageId,
'openQueryCount' => $openPerStage[$stageId],
];
$currentUserAssignedRoles = [];
if ($currentUser) {
/** @var StageAssignment $stageAssignment */
foreach ($stageAssignments as $stageAssignment) {
$userGroup = $this->getUserGroup($stageAssignment->getUserGroupId());
if ($userGroup) {
$currentUserAssignedRoles[] = $userGroup->getRoleId();
}
}
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$stageAssignmentsResult = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId($submission->getId(), $currentUser->getId(), $stageId);
while ($stageAssignment = $stageAssignmentsResult->next()) {
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
$currentUserAssignedRoles[] = (int) $userGroup->getRoleId();
}
}
$stage['currentUserAssignedRoles'] = array_values(array_unique($currentUserAssignedRoles));
// Stage-specific statuses
switch ($stageId) {
case WORKFLOW_STAGE_ID_SUBMISSION:
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$assignedEditors = $stageAssignmentDao->editorAssignedToStage($submission->getId(), $stageId);
if (!$assignedEditors) {
$stage['statusId'] = Repo::submission()::STAGE_STATUS_SUBMISSION_UNASSIGNED;
$stage['status'] = __('submissions.queuedUnassigned');
}
// Submission stage never has revisions
$stage['files'] = [
'count' => 0,
];
break;
case WORKFLOW_STAGE_ID_INTERNAL_REVIEW:
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW:
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$reviewRound = $reviewRoundDao->getLastReviewRoundBySubmissionId($submission->getId(), $stageId);
if ($reviewRound) {
$stage['statusId'] = $reviewRound->determineStatus();
$stage['status'] = __($reviewRound->getStatusKey());
// Revision files in this round.
$stage['files'] = [
'count' => Repo::submissionFile()->getCollector()
->filterBySubmissionIds([$submission->getId()])
->filterByFileStages([SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION])
->filterByReviewRoundIds([$reviewRound->getId()])
->getCount()
];
// See if the current user can only recommend:
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$user = $request->getUser();
$editorsStageAssignments = $stageAssignmentDao->getEditorsAssignedToStage($submission->getId(), $stageId);
// if the user is assigned several times in the editorial role, and
// one of the assignments have recommendOnly option set, consider it here
$stage['currentUserCanRecommendOnly'] = false;
foreach ($editorsStageAssignments as $editorsStageAssignment) {
if ($editorsStageAssignment->getUserId() == $user->getId() && $editorsStageAssignment->getRecommendOnly()) {
$stage['currentUserCanRecommendOnly'] = true;
break;
}
}
} else {
// workaround for pkp/pkp-lib#4231, pending formal data model
$stage['files'] = [
'count' => 0
];
}
break;
// Get revision files for editing and production stages.
// Review rounds are handled separately in the review stage below.
case WORKFLOW_STAGE_ID_EDITING:
case WORKFLOW_STAGE_ID_PRODUCTION:
$fileStages = [WORKFLOW_STAGE_ID_EDITING ? SubmissionFile::SUBMISSION_FILE_COPYEDIT : SubmissionFile::SUBMISSION_FILE_PROOF];
// Revision files in this round.
$stage['files'] = [
'count' => Repo::submissionFile()->getCollector()
->filterBySubmissionIds([$submission->getId()])
->filterByFileStages($fileStages)
->getCount()
];
break;
}
$stages[] = $stage;
}
return $stages;
}
protected function getUserGroup(int $userGroupId): ?UserGroup
{
/** @var UserGroup $userGroup */
foreach ($this->userGroups as $userGroup) {
if ($userGroup->getId() === $userGroupId) {
return $userGroup;
}
}
return null;
}
}
|