first commit
This commit is contained in:
@@ -0,0 +1,906 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/submission/Repository.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 Repository
|
||||
*
|
||||
* @brief A repository to find and manage submissions.
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use APP\author\Author;
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use APP\publication\Publication;
|
||||
use APP\section\Section;
|
||||
use APP\submission\Collector;
|
||||
use APP\submission\DAO;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\context\Context;
|
||||
use PKP\core\Core;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\doi\exceptions\DoiException;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\observers\events\SubmissionSubmitted;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\security\Role;
|
||||
use PKP\security\RoleDAO;
|
||||
use PKP\services\PKPSchemaService;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\submissionFile\SubmissionFile;
|
||||
use PKP\user\User;
|
||||
use PKP\validation\ValidatorFactory;
|
||||
|
||||
abstract class Repository
|
||||
{
|
||||
public const STAGE_STATUS_SUBMISSION_UNASSIGNED = 1;
|
||||
|
||||
/** @var DAO $dao */
|
||||
public $dao;
|
||||
|
||||
/** @var string $schemaMap The name of the class to map this entity to its schema */
|
||||
public $schemaMap = maps\Schema::class;
|
||||
|
||||
/** @var Request $request */
|
||||
protected $request;
|
||||
|
||||
/** @var PKPSchemaService<Submission> $schemaService */
|
||||
protected $schemaService;
|
||||
|
||||
public function __construct(DAO $dao, Request $request, PKPSchemaService $schemaService)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
$this->request = $request;
|
||||
$this->schemaService = $schemaService;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::newDataObject() */
|
||||
public function newDataObject(array $params = []): Submission
|
||||
{
|
||||
$object = $this->dao->newDataObject();
|
||||
if (!empty($params)) {
|
||||
$object->setAllData($params);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::exists() */
|
||||
public function exists(int $id, int $contextId = null): bool
|
||||
{
|
||||
return $this->dao->exists($id, $contextId);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::get() */
|
||||
public function get(int $id, int $contextId = null): ?Submission
|
||||
{
|
||||
return $this->dao->get($id, $contextId);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::getCollector() */
|
||||
public function getCollector(): Collector
|
||||
{
|
||||
return app(Collector::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the map class for mapping
|
||||
* submissions to their schema
|
||||
*/
|
||||
public function getSchemaMap(): maps\Schema
|
||||
{
|
||||
return app('maps')->withExtensions($this->schemaMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a submission by "best" submission id -- url path if it exists,
|
||||
* falling back on the internal submission ID otherwise.
|
||||
*/
|
||||
public function getByBestId(string $idOrUrlPath, int $contextId = null): ?Submission
|
||||
{
|
||||
return ctype_digit((string) $idOrUrlPath)
|
||||
? $this->get((int) $idOrUrlPath, $contextId)
|
||||
: $this->getByUrlPath($idOrUrlPath, $contextId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a submission by its urlPath
|
||||
*
|
||||
* This returns a submission if any of its publications have a
|
||||
* matching urlPath.
|
||||
*/
|
||||
public function getByUrlPath(string $urlPath, int $contextId): ?Submission
|
||||
{
|
||||
$submissionId = $this->dao->getIdByUrlPath($urlPath, $contextId);
|
||||
|
||||
return $submissionId
|
||||
? $this->get($submissionId)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a submission by its current publication's DOI
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function getByDoi(string $doi, int $contextId): ?Submission
|
||||
{
|
||||
return $this->dao->getByDoi($doi, $contextId);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::getIdsBySetting() */
|
||||
public function getIdsBySetting(string $settingName, $settingValue, int $contextId): Enumerable
|
||||
{
|
||||
return $this->dao->getIdsBySetting($settingName, $settingValue, $contextId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct access URL for a submission's workflow based on a user's
|
||||
* role.
|
||||
*
|
||||
* The returned URL will point to the correct workflow page based on whether
|
||||
* the user should be treated as an author, reviewer or editor/assistant for
|
||||
* this submission.
|
||||
*/
|
||||
public function getWorkflowUrlByUserRoles(Submission $submission, ?int $userId = null): string
|
||||
{
|
||||
$request = Application::get()->getRequest();
|
||||
|
||||
if (is_null($userId)) {
|
||||
$user = $request->getUser();
|
||||
} else {
|
||||
$user = Repo::user()->get($userId);
|
||||
}
|
||||
|
||||
if (is_null($user)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$submissionContext = $request->getContext();
|
||||
|
||||
if (!$submissionContext || $submissionContext->getId() != $submission->getData('contextId')) {
|
||||
$submissionContext = Services::get('context')->get($submission->getData('contextId'));
|
||||
}
|
||||
|
||||
$dispatcher = $request->getDispatcher();
|
||||
|
||||
// Check if the user is an author of this submission
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$authorUserGroupIds = Repo::userGroup()->getArrayIdByRoleId(Role::ROLE_ID_AUTHOR);
|
||||
$stageAssignmentsFactory = $stageAssignmentDao->getBySubmissionAndStageId($submission->getId(), null, null, $user->getId());
|
||||
|
||||
$authorDashboard = false;
|
||||
while ($stageAssignment = $stageAssignmentsFactory->next()) {
|
||||
if (in_array($stageAssignment->getUserGroupId(), $authorUserGroupIds)) {
|
||||
$authorDashboard = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Send authors, journal managers and site admins to the submission
|
||||
// wizard for incomplete submissions
|
||||
if ($submission->getSubmissionProgress() &&
|
||||
($authorDashboard ||
|
||||
$user->hasRole([Role::ROLE_ID_MANAGER], $submissionContext->getId()) ||
|
||||
$user->hasRole([Role::ROLE_ID_SITE_ADMIN], Application::CONTEXT_SITE))) {
|
||||
return $dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
$submissionContext->getPath(),
|
||||
'submission',
|
||||
null,
|
||||
null,
|
||||
['id' => $submission->getId()]
|
||||
);
|
||||
}
|
||||
|
||||
// Send authors to author dashboard
|
||||
if ($authorDashboard) {
|
||||
return $dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
$submissionContext->getPath(),
|
||||
'authorDashboard',
|
||||
'submission',
|
||||
$submission->getId()
|
||||
);
|
||||
}
|
||||
|
||||
// Send reviewers to review wizard
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignment = $reviewAssignmentDao->getLastReviewRoundReviewAssignmentByReviewer($submission->getId(), $user->getId());
|
||||
if ($reviewAssignment && !$reviewAssignment->getCancelled() && !$reviewAssignment->getDeclined()) {
|
||||
return $dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
$submissionContext->getPath(),
|
||||
'reviewer',
|
||||
'submission',
|
||||
$submission->getId()
|
||||
);
|
||||
}
|
||||
|
||||
// Give any other users the editorial workflow URL. If they can't access
|
||||
// it, they'll be blocked there.
|
||||
return $dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
$submissionContext->getPath(),
|
||||
'workflow',
|
||||
'access',
|
||||
$submission->getId()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate properties for a submission
|
||||
*
|
||||
* Perform validation checks on data used to add or edit a submission.
|
||||
*
|
||||
* @param Submission|null $submission The submission being edited. Pass `null` if creating a new submission
|
||||
* @param array $props A key/value array with the new data to validate
|
||||
*
|
||||
* @return array A key/value array with validation errors. Empty if no errors
|
||||
*/
|
||||
public function validate(?Submission $submission, array $props, Context $context): array
|
||||
{
|
||||
$primaryLocale = $props['locale'] ?? $submission?->getLocale() ?? $context->getPrimaryLocale();
|
||||
$allowedLocales = $context->getSupportedSubmissionLocales();
|
||||
|
||||
$errors = [];
|
||||
|
||||
$validator = ValidatorFactory::make(
|
||||
$props,
|
||||
$this->schemaService->getValidationRules(PKPSchemaService::SCHEMA_SUBMISSION, $allowedLocales)
|
||||
);
|
||||
|
||||
// Check required fields
|
||||
ValidatorFactory::required(
|
||||
$validator,
|
||||
$submission,
|
||||
$this->schemaService->getRequiredProps(PKPSchemaService::SCHEMA_SUBMISSION),
|
||||
$this->schemaService->getMultilingualProps(PKPSchemaService::SCHEMA_SUBMISSION),
|
||||
$primaryLocale,
|
||||
$allowedLocales
|
||||
);
|
||||
|
||||
// Check for input from disallowed locales
|
||||
ValidatorFactory::allowedLocales($validator, $this->schemaService->getMultilingualProps(PKPSchemaService::SCHEMA_SUBMISSION), $allowedLocales);
|
||||
|
||||
// The submission's locale must be one of the context's supported submission locales
|
||||
$validator->after(function ($validator) use ($props, $allowedLocales) {
|
||||
if (isset($props['locale']) && !$validator->errors()->get('locale')) {
|
||||
if (!in_array($props['locale'], $allowedLocales)) {
|
||||
$validator->errors()->add('locale', __('validator.locale'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// The contextId must match an existing context
|
||||
$validator->after(function ($validator) use ($props) {
|
||||
if (isset($props['contextId']) && !$validator->errors()->get('contextId')) {
|
||||
$submissionContext = Services::get('context')->exists($props['contextId']);
|
||||
if (!$submissionContext) {
|
||||
$validator->errors()->add('contextId', __('submission.submit.noContext'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// The sectionId must match an existing section in this context
|
||||
$validator->after(function ($validator) use ($props, $submission) {
|
||||
$propName = Application::getSectionIdPropName();
|
||||
if ($validator->errors()->get($propName)) {
|
||||
return;
|
||||
}
|
||||
$sectionId = $props[$propName] ?? ($submission ? $submission->getCurrentPublication()->getData($propName) : null);
|
||||
if (!$sectionId) {
|
||||
return;
|
||||
}
|
||||
$contextId = $props['contextId'] ?? ($submission ? $submission->getData('contextId') : null);
|
||||
if (!Repo::section()->exists($sectionId, $contextId)) {
|
||||
$validator->errors()->add($propName, __('submission.sectionNotFound'));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Comments for the editors are invalid after a submission has been submitted
|
||||
if ($submission && !$submission->getData('submissionProgress')) {
|
||||
$validator->after(function ($validator) use ($props, $submission) {
|
||||
if (isset($props['commentsForTheEditors']) && !$validator->errors()->get('commentsForTheEditors')) {
|
||||
$validator->errors()->add('commentsForTheEditors', __('form.disallowedProp'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $this->schemaService->formatValidationErrors($validator->errors());
|
||||
}
|
||||
|
||||
Hook::call('Submission::validate', [&$errors, $submission, $props, $allowedLocales, $primaryLocale]);
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a submission meets all requirements to be submitted
|
||||
*
|
||||
* @return array A key/value array with validation errors. Empty if no errors
|
||||
*/
|
||||
public function validateSubmit(Submission $submission, Context $context): array
|
||||
{
|
||||
$locale = $submission->getData('locale');
|
||||
$publication = $submission->getCurrentPublication();
|
||||
|
||||
$errors = [];
|
||||
|
||||
// Can't submit a submission twice or a submission with the wrong status
|
||||
if (!$submission->getData('submissionProgress') || ($submission->getData('status') !== Submission::STATUS_QUEUED)) {
|
||||
$errors['submissionProgress'] = __(
|
||||
'submission.wizard.alreadySubmitted',
|
||||
[
|
||||
'url' => Application::get()
|
||||
->getDispatcher()
|
||||
->url(
|
||||
Application::get()->getRequest(),
|
||||
Application::ROUTE_PAGE,
|
||||
$context->getData('path'),
|
||||
'submissions'
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Title required in submission locale
|
||||
if (!$publication->getData('title', $locale)) {
|
||||
$errors['title'] = [$locale => [__('validator.required')]];
|
||||
}
|
||||
|
||||
// Author names required in submission locale
|
||||
foreach ($publication->getData('authors') as $author) {
|
||||
/** @var Author $author */
|
||||
if (!$author->getGivenName($submission->getLocale())) {
|
||||
if (!isset($errors['contributors'])) {
|
||||
$errors['contributors'] = [];
|
||||
}
|
||||
$errors['contributors'][] = __('submission.wizard.missingContributorLanguage', ['language' => Locale::getMetadata($locale)->getDisplayName()]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Required metadata
|
||||
$publicationSchema = $this->schemaService->get(PKPSchemaService::SCHEMA_PUBLICATION);
|
||||
foreach ($context->getRequiredMetadata() as $metadata) {
|
||||
// The `citations` metadata is received and validated at `citationsRaw`
|
||||
if ($metadata === 'citations') {
|
||||
$metadata = 'citationsRaw';
|
||||
}
|
||||
// The `supportingAgencies` metadata is called `agencies` on the context
|
||||
if ($metadata === 'agencies') {
|
||||
$metadata = 'supportingAgencies';
|
||||
}
|
||||
$schema = $publicationSchema->properties?->{$metadata};
|
||||
if (!$schema) {
|
||||
continue;
|
||||
}
|
||||
if (empty($schema->multilingual) && empty($publication->getData($metadata))) {
|
||||
$errors[$metadata] = [__('validator.required')];
|
||||
} elseif (!empty($schema->multilingual) && empty($publication->getData($metadata, $locale))) {
|
||||
$errors[$metadata] = [$locale => [__('validator.required')]];
|
||||
}
|
||||
}
|
||||
|
||||
// Required submission files
|
||||
$genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */
|
||||
$requiredGenres = $genreDao->getRequiredToSubmit($context->getId());
|
||||
if (!$requiredGenres->isEmpty()) {
|
||||
$submissionFiles = Repo::submissionFile()
|
||||
->getCollector()
|
||||
->filterBySubmissionIds([$submission->getId()])
|
||||
->filterByGenreIds(
|
||||
$requiredGenres->map(
|
||||
function (Genre $genre) {
|
||||
return $genre->getId();
|
||||
}
|
||||
)->toArray()
|
||||
)
|
||||
->getMany();
|
||||
$missingGenres = $submissionFiles->isEmpty()
|
||||
? clone $requiredGenres
|
||||
: $requiredGenres->filter(
|
||||
function (Genre $genre) use ($submissionFiles) {
|
||||
$exists = $submissionFiles->first(
|
||||
function (SubmissionFile $submissionFile) use ($genre) {
|
||||
return $submissionFile->getData('genreId') === $genre->getId();
|
||||
}
|
||||
);
|
||||
return !$exists;
|
||||
}
|
||||
);
|
||||
if ($missingGenres->count()) {
|
||||
$missingGenreNames = $missingGenres->map(
|
||||
function (Genre $genre) {
|
||||
return $genre->getLocalizedName();
|
||||
}
|
||||
);
|
||||
$errors['files'] = [
|
||||
$missingGenres->count() > 1
|
||||
? __('submission.files.required.genres', [
|
||||
'genres' => $missingGenreNames->join(__('common.commaListSeparator'))
|
||||
])
|
||||
: __('submission.files.required.genre', ['genre' => $missingGenreNames->first()])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Hook::call('Submission::validateSubmit', [&$errors, $submission, $context]);
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user can delete a submission
|
||||
*/
|
||||
public function canCurrentUserDelete(Submission $submission): bool
|
||||
{
|
||||
$this->request = Application::get()->getRequest();
|
||||
$contextId = $submission->getData('contextId');
|
||||
|
||||
$currentUser = $this->request->getUser();
|
||||
if (!$currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$canDelete = false;
|
||||
|
||||
// Only allow admins and journal managers to delete submissions, except
|
||||
// for authors who can delete their own incomplete submissions
|
||||
if ($currentUser->hasRole([Role::ROLE_ID_MANAGER], $contextId) || $currentUser->hasRole([Role::ROLE_ID_SITE_ADMIN], Application::CONTEXT_SITE)) {
|
||||
$canDelete = true;
|
||||
} else {
|
||||
if ($submission->getData('submissionProgress')) {
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$assignments = $stageAssignmentDao->getBySubmissionAndRoleIds($submission->getId(), [Role::ROLE_ID_AUTHOR], WORKFLOW_STAGE_ID_SUBMISSION, $currentUser->getId());
|
||||
$assignment = $assignments->next();
|
||||
if ($assignment) {
|
||||
$canDelete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $canDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user can edit the publication metadata of a submission
|
||||
*/
|
||||
public function canEditPublication(int $submissionId, int $userId): bool
|
||||
{
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$stageAssignments = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId($submissionId, $userId, null)->toArray();
|
||||
// Check for permission from stage assignments
|
||||
foreach ($stageAssignments as $stageAssignment) {
|
||||
if ($stageAssignment->getCanChangeMetadata()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If user has no stage assigments, check if user can edit anyway ie. is manager
|
||||
$context = Application::get()->getRequest()->getContext();
|
||||
if (count($stageAssignments) == 0 && $this->_canUserAccessUnassignedSubmissions($context->getId(), $userId)) {
|
||||
return true;
|
||||
}
|
||||
// Else deny access
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this user is granted reader access to pre-publication submissions
|
||||
* based on their roles in the context (i.e. Manager, Editor, etc).
|
||||
*/
|
||||
public function canPreview(?User $user, Submission $submission): bool
|
||||
{
|
||||
// Only grant access when in copyediting or production stage
|
||||
if (!in_array($submission->getData('stageId'), [WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->_roleCanPreview($user, $submission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
/** @var StageAssignmentDAO */
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO');
|
||||
$stageAssignments = $stageAssignmentDao->getBySubmissionAndRoleId($submission->getId(), Role::ROLE_ID_AUTHOR, null, $user->getId());
|
||||
$stageAssignment = $stageAssignments->next();
|
||||
if ($stageAssignment) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new submission
|
||||
*/
|
||||
public function add(Submission $submission, Publication $publication, Context $context): int
|
||||
{
|
||||
$submission->stampLastActivity();
|
||||
$submission->stampModified();
|
||||
if (!$submission->getData('dateSubmitted') && !$submission->getData('submissionProgress')) {
|
||||
$submission->setData('dateSubmitted', Core::getCurrentDate());
|
||||
}
|
||||
if (!$submission->getData('status')) {
|
||||
$submission->setData('status', Submission::STATUS_QUEUED);
|
||||
}
|
||||
if (!$submission->getData('locale')) {
|
||||
$submission->setData('locale', $context->getPrimaryLocale());
|
||||
}
|
||||
$submissionId = $this->dao->insert($submission);
|
||||
$submission = Repo::submission()->get($submissionId);
|
||||
|
||||
$publication->setData('submissionId', $submission->getId());
|
||||
$publication->setData('version', 1);
|
||||
if (!$publication->getData('status')) {
|
||||
$publication->setData('status', $submission->getData('status'));
|
||||
}
|
||||
|
||||
$publicationId = Repo::publication()->add($publication);
|
||||
|
||||
$this->edit($submission, ['currentPublicationId' => $publicationId]);
|
||||
|
||||
Hook::call('Submission::add', [$submission]);
|
||||
|
||||
return $submission->getId();
|
||||
}
|
||||
|
||||
/** @copydoc DAO::update */
|
||||
public function edit(Submission $submission, array $params)
|
||||
{
|
||||
$newSubmission = Repo::submission()->newDataObject(array_merge($submission->_data, $params));
|
||||
$newSubmission->stampLastActivity();
|
||||
$newSubmission->stampModified();
|
||||
|
||||
Hook::call('Submission::edit', [$newSubmission, $submission, $params]);
|
||||
|
||||
$this->dao->update($newSubmission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a submission
|
||||
*
|
||||
* Changes the submissionProgress property, creates the comments
|
||||
* for the editors discussion, and fires the SubmissionSubmitted
|
||||
* event.
|
||||
*/
|
||||
public function submit(Submission $submission, Context $context): void
|
||||
{
|
||||
$this->edit($submission, [
|
||||
'submissionProgress' => '',
|
||||
'dateSubmitted' => Core::getCurrentDate(),
|
||||
]);
|
||||
|
||||
$submission = $this->get($submission->getId());
|
||||
|
||||
event(
|
||||
new SubmissionSubmitted(
|
||||
$submission,
|
||||
$context
|
||||
)
|
||||
);
|
||||
|
||||
if ($submission->getData('commentsForTheEditors')) {
|
||||
/** @var QueryDAO $queryDao */
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO');
|
||||
$queryDao->addCommentsForEditorsQuery($submission);
|
||||
}
|
||||
}
|
||||
|
||||
/** @copydoc DAO::delete */
|
||||
public function delete(Submission $submission)
|
||||
{
|
||||
Hook::call('Submission::delete::before', [&$submission]);
|
||||
|
||||
$this->dao->delete($submission);
|
||||
|
||||
Hook::call('Submission::delete', [$submission]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all submissions in a context
|
||||
*/
|
||||
public function deleteByContextId(int $contextId)
|
||||
{
|
||||
$submissionIds = Repo::submission()->getCollector()->filterByContextIds([$contextId])->getIds();
|
||||
foreach ($submissionIds as $submissionId) {
|
||||
$this->dao->deleteById($submissionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a submission's status
|
||||
*
|
||||
* Changes a submission's status. Or, if no new status is provided,
|
||||
* sets the appropriate status based on all of the submission's
|
||||
* publications.
|
||||
*
|
||||
* This method performs any actions necessary when a submission's
|
||||
* status changes, such as changing the current publication ID
|
||||
* and creating or deleting tombstones.
|
||||
*
|
||||
* @param ?Section $section If this submission is being deleted, its previous section ID should be specified
|
||||
* in order to ensure a correctly created tombstone.
|
||||
*/
|
||||
public function updateStatus(Submission $submission, ?int $newStatus = null, ?Section $section = null)
|
||||
{
|
||||
$status = $submission->getData('status');
|
||||
|
||||
if ($newStatus === null) {
|
||||
$newStatus = $this->getStatusByPublications($submission);
|
||||
}
|
||||
|
||||
Hook::call('Submission::updateStatus', [&$newStatus, $status, $submission]);
|
||||
|
||||
if ($status !== $newStatus) {
|
||||
$submission->setData('status', $newStatus);
|
||||
}
|
||||
|
||||
$currentPublicationId = $newCurrentPublicationId = $submission->getData('currentPublicationId');
|
||||
$newCurrentPublicationId = $this->getCurrentPublicationIdByPublications($submission);
|
||||
if ($currentPublicationId !== $newCurrentPublicationId) {
|
||||
$submission->setData('currentPublicationId', $newCurrentPublicationId);
|
||||
}
|
||||
|
||||
// Use the DAO instead of the Repository to prevent
|
||||
// calling this method over and over again.
|
||||
$this->dao->update($submission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set license information for all submissions in a context
|
||||
* to the context's default license.
|
||||
*/
|
||||
public function resetPermissions(int $contextId)
|
||||
{
|
||||
$submissions = Repo::submission()->getCollector()->filterByContextIds([$contextId])->getMany();
|
||||
foreach ($submissions as $submission) {
|
||||
$publications = $submission->getData('publications');
|
||||
if (empty($publications)) {
|
||||
continue;
|
||||
}
|
||||
$params = [
|
||||
'copyrightYear' => $submission->_getContextLicenseFieldValue(null, Submission::PERMISSIONS_FIELD_COPYRIGHT_YEAR),
|
||||
'copyrightHolder' => $submission->_getContextLicenseFieldValue(null, Submission::PERMISSIONS_FIELD_COPYRIGHT_HOLDER),
|
||||
'licenseUrl' => $submission->_getContextLicenseFieldValue(null, Submission::PERMISSIONS_FIELD_LICENSE_URL),
|
||||
];
|
||||
foreach ($publications as $publication) {
|
||||
Repo::publication()->edit($publication, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of sort options used in forms when configuring
|
||||
* how published submissions are displayed
|
||||
*/
|
||||
public function getSortSelectOptions(): array
|
||||
{
|
||||
return [
|
||||
$this->getSortOption(Collector::ORDERBY_TITLE, Collector::ORDER_DIR_ASC) => __('catalog.sortBy.titleAsc'),
|
||||
$this->getSortOption(Collector::ORDERBY_TITLE, Collector::ORDER_DIR_DESC) => __('catalog.sortBy.titleDesc'),
|
||||
$this->getSortOption(Collector::ORDERBY_DATE_PUBLISHED, Collector::ORDER_DIR_ASC) => __('catalog.sortBy.datePublishedAsc'),
|
||||
$this->getSortOption(Collector::ORDERBY_DATE_PUBLISHED, Collector::ORDER_DIR_DESC) => __('catalog.sortBy.datePublishedDesc'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default sort option used in forms when configuring
|
||||
* how published submissions are displayed
|
||||
*
|
||||
* @see self::getSortSelectOptions()
|
||||
*/
|
||||
public function getDefaultSortOption(): string
|
||||
{
|
||||
return $this->getSortOption(Collector::ORDERBY_DATE_PUBLISHED, Collector::ORDER_DIR_DESC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the API endpoint for a submission
|
||||
*/
|
||||
public function getUrlApi(Context $context, ?int $submissionId = null): string
|
||||
{
|
||||
return Application::get()->getDispatcher()->url(
|
||||
Application::get()->getRequest(),
|
||||
Application::ROUTE_API,
|
||||
$context->getData('urlPath'),
|
||||
'submissions' . ($submissionId ? '/' . $submissionId : ''),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the author workflow for a submission
|
||||
*/
|
||||
public function getUrlAuthorWorkflow(Context $context, int $submissionId): string
|
||||
{
|
||||
return Application::get()->getDispatcher()->url(
|
||||
Application::get()->getRequest(),
|
||||
Application::ROUTE_PAGE,
|
||||
$context->getData('urlPath'),
|
||||
'authorDashboard',
|
||||
'submission',
|
||||
$submissionId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the editorial workflow for a submission
|
||||
*/
|
||||
public function getUrlEditorialWorkflow(Context $context, int $submissionId): string
|
||||
{
|
||||
return Application::get()->getDispatcher()->url(
|
||||
Application::get()->getRequest(),
|
||||
Application::ROUTE_PAGE,
|
||||
$context->getData('urlPath'),
|
||||
'workflow',
|
||||
'access',
|
||||
$submissionId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the submission wizard for a submission
|
||||
*/
|
||||
public function getUrlSubmissionWizard(Context $context, ?int $submissionId = null): string
|
||||
{
|
||||
return Application::get()->getDispatcher()->url(
|
||||
Application::get()->getRequest(),
|
||||
Application::ROUTE_PAGE,
|
||||
$context->getData('urlPath'),
|
||||
'submission',
|
||||
null,
|
||||
null,
|
||||
$submissionId
|
||||
? ['id' => $submissionId]
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and assigns DOIs to all sub-objects if:
|
||||
* 1) the suffix pattern can currently be created, and
|
||||
* 2) it does not already exist.
|
||||
*
|
||||
* @return DoiException[]
|
||||
*/
|
||||
abstract public function createDois(Submission $submission): array;
|
||||
|
||||
/**
|
||||
* Compile the sort orderBy and orderDirection into an option
|
||||
* used in forms
|
||||
*/
|
||||
protected function getSortOption(string $sortBy, string $sortDir): string
|
||||
{
|
||||
return $sortBy . '-' . $sortDir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a user is allowed to edit publication metadata for submissions
|
||||
* they are not assigned to
|
||||
*/
|
||||
protected function _canUserAccessUnassignedSubmissions(int $contextId, int $userId): bool
|
||||
{
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
|
||||
$roles = $roleDao->getByUserId($userId, $contextId);
|
||||
|
||||
$allowedRoles = Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES;
|
||||
foreach ($roles as $role) {
|
||||
if (in_array($role->getRoleId(), $allowedRoles)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate status of a submission based on the
|
||||
* statuses of its publications
|
||||
*/
|
||||
protected function getStatusByPublications(Submission $submission): int
|
||||
{
|
||||
$publications = $submission->getData('publications'); /** @var LazyCollection $publications */
|
||||
|
||||
// Declined submissions should remain declined regardless of their publications' statuses
|
||||
if ($submission->getData('status') === Submission::STATUS_DECLINED) {
|
||||
return Submission::STATUS_DECLINED;
|
||||
}
|
||||
|
||||
// If there are no publications, we are probably in the process of deleting a submission.
|
||||
// To be safe, reset the status anyway.
|
||||
if (!$publications->count()) {
|
||||
return Submission::STATUS_DECLINED
|
||||
? Submission::STATUS_DECLINED
|
||||
: Submission::STATUS_QUEUED;
|
||||
}
|
||||
|
||||
$newStatus = Submission::STATUS_QUEUED;
|
||||
foreach ($publications as $publication) {
|
||||
if ($publication->getData('status') === Submission::STATUS_PUBLISHED) {
|
||||
$newStatus = Submission::STATUS_PUBLISHED;
|
||||
break;
|
||||
}
|
||||
if ($publication->getData('status') === Submission::STATUS_SCHEDULED) {
|
||||
$newStatus = Submission::STATUS_SCHEDULED;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $newStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate currentPublicationId for a submission based on the
|
||||
* statues of its publications
|
||||
*/
|
||||
protected function getCurrentPublicationIdByPublications(Submission $submission): ?int
|
||||
{
|
||||
$publications = $submission->getData('publications'); /** @var LazyCollection $publications */
|
||||
|
||||
if (!$publications->count()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use the latest published publication
|
||||
$newCurrentPublicationId = $publications->reduce(function ($a, $b) {
|
||||
return $b->getData('status') === Submission::STATUS_PUBLISHED && $b->getId() > $a ? $b->getId() : $a;
|
||||
}, 0);
|
||||
|
||||
// If there is no published publication, use the latest publication
|
||||
if (!$newCurrentPublicationId) {
|
||||
$newCurrentPublicationId = $publications->reduce(function ($a, $b) {
|
||||
return $a > $b->getId() ? $a : $b->getId();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return $newCurrentPublicationId ?? $submission->getData('currentPublicationId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this user is granted access to preview
|
||||
* based on their roles in the context (i.e. Manager, Editor, etc).
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
*/
|
||||
protected function _roleCanPreview(?User $user, Submission $submission): bool
|
||||
{
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscriptionAssumedRoles = [
|
||||
Role::ROLE_ID_MANAGER,
|
||||
Role::ROLE_ID_SUB_EDITOR,
|
||||
Role::ROLE_ID_ASSISTANT,
|
||||
Role::ROLE_ID_SUBSCRIPTION_MANAGER
|
||||
];
|
||||
|
||||
/** @var RoleDAO */
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO');
|
||||
$roles = $roleDao->getByUserId($user->getId(), $submission->getData('contextId'));
|
||||
foreach ($roles as $role) {
|
||||
if (in_array($role->getRoleId(), $subscriptionAssumedRoles)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user