first commit
This commit is contained in:
@@ -0,0 +1,539 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/submission/Collector.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Collector
|
||||
*
|
||||
* @brief A helper class to configure a Query Builder to get a collection of submissions
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Collector as AppCollector;
|
||||
use APP\submission\Submission;
|
||||
use Exception;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\core\Core;
|
||||
use PKP\core\interfaces\CollectorInterface;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\identity\Identity;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\search\SubmissionSearch;
|
||||
use PKP\security\Role;
|
||||
use PKP\submission\reviewRound\ReviewRound;
|
||||
|
||||
/**
|
||||
* @template T of Submission
|
||||
*/
|
||||
abstract class Collector implements CollectorInterface
|
||||
{
|
||||
public const ORDERBY_DATE_PUBLISHED = 'datePublished';
|
||||
public const ORDERBY_DATE_SUBMITTED = 'dateSubmitted';
|
||||
public const ORDERBY_LAST_ACTIVITY = 'lastActivity';
|
||||
public const ORDERBY_LAST_MODIFIED = 'lastModified';
|
||||
public const ORDERBY_SEQUENCE = 'sequence';
|
||||
public const ORDERBY_TITLE = 'title';
|
||||
public const ORDERBY_SEARCH_RANKING = 'ranking';
|
||||
public const ORDER_DIR_ASC = 'ASC';
|
||||
public const ORDER_DIR_DESC = 'DESC';
|
||||
|
||||
public const UNASSIGNED = -1;
|
||||
|
||||
public DAO $dao;
|
||||
public ?array $categoryIds = null;
|
||||
public ?array $contextIds = null;
|
||||
public ?int $count = null;
|
||||
public ?int $daysInactive = null;
|
||||
public bool $isIncomplete = false;
|
||||
public bool $isOverdue = false;
|
||||
public ?int $offset = null;
|
||||
public string $orderBy = self::ORDERBY_DATE_SUBMITTED;
|
||||
public string $orderDirection = 'DESC';
|
||||
public ?string $searchPhrase = null;
|
||||
public ?int $maxSearchKeywords = null;
|
||||
public ?array $statuses = null;
|
||||
public ?array $stageIds = null;
|
||||
public ?array $doiStatuses = null;
|
||||
public ?bool $hasDois = null;
|
||||
public ?array $excludeIds = null;
|
||||
|
||||
/** @var array Which DOI types should be considered when checking if a submission has DOIs set */
|
||||
public array $enabledDoiTypes = [];
|
||||
|
||||
/** @var array|int */
|
||||
public $assignedTo = null;
|
||||
|
||||
public function __construct(DAO $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
public function getCount(): int
|
||||
{
|
||||
return $this->dao->getCount($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int,int>
|
||||
*/
|
||||
public function getIds(): Collection
|
||||
{
|
||||
return $this->dao->getIds($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc DAO::getMany()
|
||||
* @return LazyCollection<int,T>
|
||||
*/
|
||||
public function getMany(): LazyCollection
|
||||
{
|
||||
return $this->dao->getMany($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to submissions in these contexts
|
||||
*/
|
||||
public function filterByContextIds(?array $contextIds): AppCollector
|
||||
{
|
||||
$this->contextIds = $contextIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results by submissions assigned to these categories
|
||||
*/
|
||||
public function filterByCategoryIds(?array $categoryIds): AppCollector
|
||||
{
|
||||
$this->categoryIds = $categoryIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to submissions that contain any pub objects (e.g. publication and galley) with these statuses
|
||||
*
|
||||
* @param array|null $statuses One or more of DOI::STATUS_* constants
|
||||
*
|
||||
*/
|
||||
public function filterByDoiStatuses(?array $statuses): AppCollector
|
||||
{
|
||||
$this->doiStatuses = $statuses;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to submissions that do/don't have any DOIs assign to their sub objects
|
||||
*
|
||||
* @param array|null $enabledDoiTypes TYPE_* constants to consider when checking submission has DOIs
|
||||
*/
|
||||
public function filterByHasDois(?bool $hasDois, ?array $enabledDoiTypes = null): AppCollector
|
||||
{
|
||||
$this->hasDois = $hasDois;
|
||||
$this->enabledDoiTypes = $enabledDoiTypes === null ? [Repo::doi()::TYPE_PUBLICATION] : $enabledDoiTypes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results by submissions with these statuses
|
||||
*
|
||||
* @see \PKP\submissions\PKPSubmission::STATUS_
|
||||
*/
|
||||
public function filterByStatus(?array $statuses): AppCollector
|
||||
{
|
||||
$this->statuses = $statuses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results by submissions in these workflow stage ids
|
||||
*/
|
||||
public function filterByStageIds(?array $stageIds): AppCollector
|
||||
{
|
||||
$this->stageIds = $stageIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to incomplete submissions
|
||||
*
|
||||
* Submissions are incomplete when the author has begun to enter
|
||||
* details about their submission but not yet submitted it.
|
||||
*/
|
||||
public function filterByIncomplete(bool $isIncomplete): AppCollector
|
||||
{
|
||||
$this->isIncomplete = $isIncomplete;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to submissions with overdue tasks
|
||||
*/
|
||||
public function filterByOverdue(bool $isOverdue): AppCollector
|
||||
{
|
||||
$this->isOverdue = $isOverdue;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to submission with no activity for X days
|
||||
*/
|
||||
public function filterByDaysInactive(?int $daysInactive): AppCollector
|
||||
{
|
||||
$this->daysInactive = $daysInactive;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to submissions assigned to these users
|
||||
*
|
||||
* @param int|array $assignedTo An array of user IDs
|
||||
* or self::UNASSIGNED to get unassigned submissions
|
||||
*/
|
||||
public function assignedTo($assignedTo): AppCollector
|
||||
{
|
||||
$this->assignedTo = $assignedTo;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to submissions matching this search query
|
||||
*/
|
||||
public function searchPhrase(?string $phrase, ?int $maxSearchKeywords = null): AppCollector
|
||||
{
|
||||
$this->searchPhrase = $phrase;
|
||||
$this->maxSearchKeywords = $maxSearchKeywords;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the given submission IDs are not included
|
||||
*/
|
||||
public function excludeIds(?array $ids): AppCollector
|
||||
{
|
||||
$this->excludeIds = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the number of objects retrieved
|
||||
*/
|
||||
public function limit(?int $count): AppCollector
|
||||
{
|
||||
$this->count = $count;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset the number of objects retrieved, for example to
|
||||
* retrieve the second page of contents
|
||||
*/
|
||||
public function offset(?int $offset): AppCollector
|
||||
{
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order the results
|
||||
*
|
||||
* The following column values are supported:
|
||||
*
|
||||
* - lastModified
|
||||
* - dateLastActivity
|
||||
* - title
|
||||
* - seq (sequence)
|
||||
* - DAO::ORDERBY_DATE_PUBLISHED
|
||||
*
|
||||
* Results are ordered by the date submitted by default.
|
||||
*
|
||||
* @param string $sorter One of the self::ORDERBY_ constants
|
||||
* @param string $direction One of the self::ORDER_DIR_ constants
|
||||
*/
|
||||
public function orderBy(string $sorter, string $direction = self::ORDER_DIR_DESC): AppCollector
|
||||
{
|
||||
$this->orderBy = $sorter;
|
||||
$this->orderDirection = $direction;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add APP-specific filtering methods for submission sub objects DOI statuses
|
||||
*
|
||||
*/
|
||||
abstract protected function addDoiStatusFilterToQuery(Builder $q);
|
||||
|
||||
/**
|
||||
* Add APP-specific filtering methods for checking if submission sub objects have DOIs assigned
|
||||
*/
|
||||
abstract protected function addHasDoisFilterToQuery(Builder $q);
|
||||
|
||||
/**
|
||||
* @copydoc CollectorInterface::getQueryBuilder()
|
||||
*/
|
||||
public function getQueryBuilder(): Builder
|
||||
{
|
||||
$q = DB::table('submissions AS s')
|
||||
->leftJoin('publications AS po', 's.current_publication_id', '=', 'po.publication_id')
|
||||
->select(['s.*']);
|
||||
|
||||
// Never permit a query without a context_id unless the CONTEXT_ID_ALL wildcard
|
||||
// has been set explicitly.
|
||||
if (!isset($this->contextIds)) {
|
||||
throw new Exception('Submissions can not be retrieved without a context id. Pass the CONTEXT_ID_ALL wildcard to get submissions from any context.');
|
||||
} elseif (!in_array(Application::CONTEXT_ID_ALL, $this->contextIds)) {
|
||||
$q->whereIn('s.context_id', $this->contextIds);
|
||||
}
|
||||
|
||||
// Prepare keywords (allows short and numeric words)
|
||||
$keywords = collect(Application::getSubmissionSearchIndex()->filterKeywords($this->searchPhrase, false, true, true))
|
||||
->unique()
|
||||
->take($this->maxSearchKeywords ?? PHP_INT_MAX);
|
||||
|
||||
// Setup the order by
|
||||
switch ($this->orderBy) {
|
||||
case self::ORDERBY_DATE_PUBLISHED:
|
||||
$q->addSelect(['po.date_published']);
|
||||
$q->orderBy('po.date_published', $this->orderDirection);
|
||||
break;
|
||||
case self::ORDERBY_LAST_ACTIVITY:
|
||||
$q->orderBy('s.date_last_activity', $this->orderDirection);
|
||||
break;
|
||||
case self::ORDERBY_LAST_MODIFIED:
|
||||
$q->orderBy('s.last_modified', $this->orderDirection);
|
||||
break;
|
||||
case self::ORDERBY_SEQUENCE:
|
||||
$q->addSelect(['po.seq']);
|
||||
$q->orderBy('po.seq', $this->orderDirection);
|
||||
break;
|
||||
case self::ORDERBY_TITLE:
|
||||
$locale = Locale::getLocale();
|
||||
$q->leftJoin('publications as publication_tlp', 's.current_publication_id', '=', 'publication_tlp.publication_id')
|
||||
->leftJoin('publication_settings as publication_tlps', fn (JoinClause $join) =>
|
||||
$join->on('publication_tlp.publication_id', '=', 'publication_tlps.publication_id')
|
||||
->where('publication_tlps.setting_name', '=', 'title')
|
||||
->where('publication_tlps.setting_value', '!=', '')
|
||||
->where('publication_tlps.locale', '=', $locale)
|
||||
);
|
||||
$q->leftJoin('publications as publication_tlpl', 's.current_publication_id', '=', 'publication_tlpl.publication_id')
|
||||
->leftJoin('publication_settings as publication_tlpsl', fn (JoinClause $join) =>
|
||||
$join->on('publication_tlp.publication_id', '=', 'publication_tlpsl.publication_id')
|
||||
->on('publication_tlpsl.locale', '=', 's.locale')
|
||||
->where('publication_tlpsl.setting_name', '=', 'title')
|
||||
);
|
||||
$coalesceTitles = 'COALESCE(publication_tlps.setting_value, publication_tlpsl.setting_value)';
|
||||
$q->addSelect([DB::raw($coalesceTitles)]);
|
||||
$q->orderBy(DB::raw($coalesceTitles), $this->orderDirection);
|
||||
break;
|
||||
case self::ORDERBY_SEARCH_RANKING:
|
||||
if (!$keywords->count()) {
|
||||
$q->orderBy('s.date_submitted', $this->orderDirection);
|
||||
break;
|
||||
}
|
||||
// Retrieves the number of matches for all keywords
|
||||
$orderByMatchCount = DB::table('submission_search_objects', 'sso')
|
||||
->join('submission_search_object_keywords AS ssok', 'ssok.object_id', '=', 'sso.object_id')
|
||||
->join('submission_search_keyword_list AS sskl', 'sskl.keyword_id', '=', 'ssok.keyword_id')
|
||||
->where(fn (Builder $q) =>
|
||||
$keywords->map(fn (string $keyword) => $q
|
||||
->orWhere('sskl.keyword_text', '=', DB::raw('LOWER(?)'))
|
||||
->addBinding($keyword)
|
||||
)
|
||||
)
|
||||
->whereColumn('s.submission_id', '=', 'sso.submission_id')
|
||||
->selectRaw('COUNT(0)');
|
||||
// Retrieves the number of distinct matched keywords
|
||||
$orderByDistinctKeyword = (clone $orderByMatchCount)->select(DB::raw('COUNT(DISTINCT sskl.keyword_id)'));
|
||||
$q->orderBy($orderByDistinctKeyword, $this->orderDirection)
|
||||
->orderBy($orderByMatchCount, $this->orderDirection);
|
||||
break;
|
||||
case self::ORDERBY_DATE_SUBMITTED:
|
||||
default:
|
||||
$q->orderBy('s.date_submitted', $this->orderDirection);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($this->statuses)) {
|
||||
$q->whereIn('s.status', $this->statuses);
|
||||
}
|
||||
|
||||
if (isset($this->stageIds)) {
|
||||
$q->whereIn('s.stage_id', $this->stageIds);
|
||||
}
|
||||
|
||||
if ($this->isIncomplete) {
|
||||
$q->where('s.submission_progress', '<>', '');
|
||||
}
|
||||
|
||||
if (isset($this->daysInactive)) {
|
||||
$q->where('s.date_last_activity', '<', Core::getCurrentDate(strtotime('-' . $this->daysInactive . ' days')));
|
||||
}
|
||||
|
||||
if ($this->isOverdue) {
|
||||
$q->leftJoin('review_assignments as raod', 'raod.submission_id', '=', 's.submission_id')
|
||||
->leftJoin('review_rounds as rr', fn (Builder $table) =>
|
||||
$table->on('rr.submission_id', '=', 's.submission_id')
|
||||
->on('raod.review_round_id', '=', 'rr.review_round_id')
|
||||
);
|
||||
// Only get overdue assignments on active review rounds
|
||||
$q->whereNotIn('rr.status', [
|
||||
ReviewRound::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW,
|
||||
ReviewRound::REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL,
|
||||
ReviewRound::REVIEW_ROUND_STATUS_ACCEPTED,
|
||||
ReviewRound::REVIEW_ROUND_STATUS_DECLINED,
|
||||
]);
|
||||
$q->where(fn (Builder $q) =>
|
||||
$q->where('raod.declined', '<>', 1)
|
||||
->where('raod.cancelled', '<>', 1)
|
||||
->where(fn (Builder $q) =>
|
||||
$q->where('raod.date_due', '<', Core::getCurrentDate(strtotime('tomorrow')))
|
||||
->whereNull('raod.date_completed')
|
||||
)
|
||||
->orWhere(fn (Builder $q) =>
|
||||
$q->where('raod.date_response_due', '<', Core::getCurrentDate(strtotime('tomorrow')))
|
||||
->whereNull('raod.date_confirmed')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (is_array($this->assignedTo)) {
|
||||
$q->whereIn('s.submission_id', fn (Builder $q) =>
|
||||
$q->select('s.submission_id')
|
||||
->from('submissions AS s')
|
||||
->leftJoin('stage_assignments as sa', fn (Builder $q) =>
|
||||
$q->on('s.submission_id', '=', 'sa.submission_id')
|
||||
->whereIn('sa.user_id', $this->assignedTo)
|
||||
)
|
||||
->leftJoin('review_assignments as ra', fn (Builder $table) =>
|
||||
$table->on('s.submission_id', '=', 'ra.submission_id')
|
||||
->where('ra.declined', '=', (int) 0)
|
||||
->where('ra.cancelled', '=', (int) 0)
|
||||
->whereIn('ra.reviewer_id', $this->assignedTo)
|
||||
)
|
||||
->whereNotNull('sa.stage_assignment_id')
|
||||
->orWhereNotNull('ra.review_id')
|
||||
);
|
||||
} elseif ($this->assignedTo === self::UNASSIGNED) {
|
||||
$sub = DB::table('stage_assignments')
|
||||
->select(DB::raw('count(stage_assignments.stage_assignment_id)'))
|
||||
->leftJoin('user_groups', 'stage_assignments.user_group_id', '=', 'user_groups.user_group_id')
|
||||
->where('stage_assignments.submission_id', '=', DB::raw('s.submission_id'))
|
||||
->whereIn('user_groups.role_id', [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]);
|
||||
|
||||
$q->whereNotNull('s.date_submitted')
|
||||
->mergeBindings($sub)
|
||||
->where(DB::raw('(' . $sub->toSql() . ')'), '=', '0');
|
||||
}
|
||||
|
||||
// Search phrase
|
||||
if ($keywords->count()) {
|
||||
$likePattern = DB::raw("CONCAT('%', LOWER(?), '%')");
|
||||
if(!empty($this->assignedTo)) {
|
||||
// Holds a single random row to check whether we have any assignment
|
||||
$q->leftJoinSub(fn (Builder $q) => $q
|
||||
->from('review_assignments', 'ra')
|
||||
->whereIn('ra.reviewer_id', $this->assignedTo == self::UNASSIGNED ? [] : (array) $this->assignedTo)
|
||||
->select(DB::raw('1 AS value'))
|
||||
->limit(1),
|
||||
'any_assignment', 'any_assignment.value', '=', DB::raw('1')
|
||||
);
|
||||
}
|
||||
// Builds the filters
|
||||
$q->where(fn (Builder $q) => $keywords
|
||||
->map(fn (string $keyword) => $q
|
||||
// Look for matches on the indexed data
|
||||
->orWhereExists(fn (Builder $query) => $query
|
||||
->from('submission_search_objects', 'sso')
|
||||
->join('submission_search_object_keywords AS ssok', 'sso.object_id', '=', 'ssok.object_id')
|
||||
->join('submission_search_keyword_list AS sskl', 'sskl.keyword_id', '=', 'ssok.keyword_id')
|
||||
->where('sskl.keyword_text', '=', DB::raw('LOWER(?)'))->addBinding($keyword)
|
||||
->whereColumn('s.submission_id', '=', 'sso.submission_id')
|
||||
// Don't permit reviewers to search on author names
|
||||
->when(!empty($this->assignedTo), fn (Builder $q) => $q
|
||||
->where(fn (Builder $q) => $q
|
||||
->whereNull('any_assignment.value')
|
||||
->orWhere('sso.type', '!=', SubmissionSearch::SUBMISSION_SEARCH_AUTHOR)
|
||||
)
|
||||
)
|
||||
)
|
||||
// Search on the publication title
|
||||
->orWhereIn('s.submission_id', fn (Builder $query) => $query
|
||||
->select('p.submission_id')->from('publications AS p')
|
||||
->join('publication_settings AS ps', 'p.publication_id', '=', 'ps.publication_id')
|
||||
->where('ps.setting_name', '=', 'title')
|
||||
->where(DB::raw('LOWER(ps.setting_value)'), 'LIKE', $likePattern)
|
||||
->addBinding($keyword)
|
||||
)
|
||||
// Search on the author name and ORCID
|
||||
->orWhereIn('s.submission_id', fn (Builder $query) => $query
|
||||
->select('p.submission_id')
|
||||
->from('publications AS p')
|
||||
->join('authors AS au', 'au.publication_id', '=', 'p.publication_id')
|
||||
->join('author_settings AS aus', 'aus.author_id', '=', 'au.author_id')
|
||||
->whereIn('aus.setting_name', [
|
||||
Identity::IDENTITY_SETTING_GIVENNAME,
|
||||
Identity::IDENTITY_SETTING_FAMILYNAME,
|
||||
'orcid'
|
||||
])
|
||||
// Don't permit reviewers to search on author names
|
||||
->when(!empty($this->assignedTo), fn (Builder $q) => $q
|
||||
->where(fn (Builder $q) => $q
|
||||
->whereNull('any_assignment.value')
|
||||
->orWhereNotIn('aus.setting_name', [
|
||||
Identity::IDENTITY_SETTING_GIVENNAME,
|
||||
Identity::IDENTITY_SETTING_FAMILYNAME
|
||||
])
|
||||
)
|
||||
)
|
||||
->where(DB::raw('LOWER(aus.setting_value)'), 'LIKE', $likePattern)
|
||||
->addBinding($keyword)
|
||||
)
|
||||
// Search for the exact submission ID
|
||||
->when(
|
||||
($numericWords = $keywords->filter(fn (string $keyword) => ctype_digit($keyword)))->count(),
|
||||
fn (Builder $query) => $query->orWhereIn('s.submission_id', $numericWords)
|
||||
)
|
||||
)
|
||||
);
|
||||
} elseif (strlen($this->searchPhrase ?? '')) {
|
||||
// If there's search text, but no keywords could be extracted from it, force the query to return nothing
|
||||
$q->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
if (isset($this->categoryIds)) {
|
||||
$q->join('publication_categories as pc', 's.current_publication_id', '=', 'pc.publication_id')
|
||||
->whereIn('pc.category_id', $this->categoryIds);
|
||||
}
|
||||
|
||||
// Filter by any child pub object's DOI status
|
||||
$q->when($this->doiStatuses !== null, fn (Builder $q) => $this->addDoiStatusFilterToQuery($q));
|
||||
|
||||
// Filter by whether any child pub objects have DOIs assigned
|
||||
$q->when($this->hasDois !== null, fn (Builder $q) => $this->addHasDoisFilterToQuery($q));
|
||||
|
||||
// Filter out excluded submission IDs
|
||||
$q->when($this->excludeIds !== null, fn (Builder $q) => $q->whereNotIn('s.submission_id', $this->excludeIds));
|
||||
|
||||
// Limit and offset results for pagination
|
||||
if (isset($this->count)) {
|
||||
$q->limit($this->count);
|
||||
}
|
||||
if (isset($this->offset)) {
|
||||
$q->offset($this->offset);
|
||||
}
|
||||
|
||||
// Add app-specific query statements
|
||||
Hook::call('Submission::Collector', [&$q, $this]);
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/submission/DAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DAO
|
||||
*
|
||||
* @brief Read and write submissions to the database.
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Collector;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\core\EntityDAO;
|
||||
use PKP\core\traits\EntityWithParent;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\log\event\EventLogEntry;
|
||||
use PKP\log\SubmissionEmailLogDAO;
|
||||
use PKP\note\NoteDAO;
|
||||
use PKP\notification\NotificationDAO;
|
||||
use PKP\query\QueryDAO;
|
||||
use PKP\services\PKPSchemaService;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\submission\reviewRound\ReviewRoundDAO;
|
||||
|
||||
/**
|
||||
* @template T of Submission
|
||||
* @extends EntityDAO<T>
|
||||
*/
|
||||
class DAO extends EntityDAO
|
||||
{
|
||||
use EntityWithParent;
|
||||
|
||||
/** @copydoc EntityDAO::$schema */
|
||||
public $schema = PKPSchemaService::SCHEMA_SUBMISSION;
|
||||
|
||||
/** @copydoc EntityDAO::$table */
|
||||
public $table = 'submissions';
|
||||
|
||||
/** @copydoc EntityDAO::$settingsTable */
|
||||
public $settingsTable = 'submission_settings';
|
||||
|
||||
/** @copydoc EntityDAO::$primaryKeyColumn */
|
||||
public $primaryKeyColumn = 'submission_id';
|
||||
|
||||
/** @copydoc SchemaDAO::$primaryTableColumns */
|
||||
public $primaryTableColumns = [
|
||||
'id' => 'submission_id',
|
||||
'contextId' => 'context_id',
|
||||
'currentPublicationId' => 'current_publication_id',
|
||||
'dateLastActivity' => 'date_last_activity',
|
||||
'dateSubmitted' => 'date_submitted',
|
||||
'lastModified' => 'last_modified',
|
||||
'locale' => 'locale',
|
||||
'stageId' => 'stage_id',
|
||||
'status' => 'status',
|
||||
'submissionProgress' => 'submission_progress',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the parent object ID column name
|
||||
*/
|
||||
public function getParentColumn(): string
|
||||
{
|
||||
return 'context_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new DataObject
|
||||
*/
|
||||
public function newDataObject(): Submission
|
||||
{
|
||||
return app(Submission::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total count of submissions matching the configured query
|
||||
*/
|
||||
public function getCount(Collector $query): int
|
||||
{
|
||||
return $query
|
||||
->getQueryBuilder()
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of ids matching the configured query
|
||||
*
|
||||
* @return Collection<int,int>
|
||||
*/
|
||||
public function getIds(Collector $query): Collection
|
||||
{
|
||||
return $query
|
||||
->getQueryBuilder()
|
||||
->select('s.' . $this->primaryKeyColumn)
|
||||
->pluck('s.' . $this->primaryKeyColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of announcements matching the configured query
|
||||
* @return LazyCollection<int,T>
|
||||
*/
|
||||
public function getMany(Collector $query): LazyCollection
|
||||
{
|
||||
$rows = $query
|
||||
->getQueryBuilder()
|
||||
->get();
|
||||
|
||||
return LazyCollection::make(function () use ($rows) {
|
||||
foreach ($rows as $row) {
|
||||
yield $row->submission_id => $this->fromRow($row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the submission id by its url path
|
||||
*/
|
||||
public function getIdByUrlPath(string $urlPath, int $contextId): ?int
|
||||
{
|
||||
$publication = DB::table('publications as p')
|
||||
->leftJoin('submissions as s', 's.submission_id', '=', 'p.submission_id')
|
||||
->where('s.context_id', '=', $contextId)
|
||||
->where('p.url_path', '=', $urlPath)
|
||||
->first();
|
||||
|
||||
return $publication
|
||||
? $publication->submission_id
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get submission ids that have a matching setting
|
||||
*/
|
||||
public function getIdsBySetting(string $settingName, $settingValue, int $contextId): Enumerable
|
||||
{
|
||||
return DB::table($this->table . ' as s')
|
||||
->join($this->settingsTable . ' as ss', 's.submission_id', '=', 'ss.submission_id')
|
||||
->where('ss.setting_name', '=', $settingName)
|
||||
->where('ss.setting_value', '=', $settingValue)
|
||||
->where('s.context_id', '=', (int) $contextId)
|
||||
->select('s.submission_id')
|
||||
->pluck('s.submission_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a submission by public id
|
||||
*
|
||||
* @param string $pubIdType One of the NLM pub-id-type values or
|
||||
* 'other::something' if not part of the official NLM list
|
||||
* (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
|
||||
* @param null|mixed $contextId
|
||||
*/
|
||||
public function getByPubId(string $pubIdType, string $pubId, $contextId = null): ?Submission
|
||||
{
|
||||
// Add check for incoming DOI request for legacy calls that bypass the Submission Repository
|
||||
if ($pubIdType == 'doi') {
|
||||
return $this->getByDoi($pubId, $contextId);
|
||||
} else {
|
||||
$qb = DB::table('publication_settings ps')
|
||||
->join('publications p', 'p.publication_id', '=', 'ps.publication_id')
|
||||
->join('submissions s', 'p.publication_id', '=', 's.current_publication_id')
|
||||
->where('ps.setting_name', '=', 'pub-id::' . $pubIdType)
|
||||
->where('ps.setting_value', '=', $pubId);
|
||||
|
||||
if ($contextId) {
|
||||
$qb->where('s.context_id', '=', (int) $contextId);
|
||||
}
|
||||
|
||||
$row = $qb->get(['s.submission_id'])->first();
|
||||
|
||||
return $row
|
||||
? $this->get($row->submission_id)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a submission by its current publication's DOI
|
||||
*/
|
||||
public function getByDoi(string $doi, int $contextId): ?Submission
|
||||
{
|
||||
$q = DB::table($this->table, 's')
|
||||
->leftJoin('publications AS p', 'p.publication_id', '=', 's.current_publication_id')
|
||||
->leftJoin('dois AS d', 'd.doi_id', '=', 'p.doi_id')
|
||||
->where('d.doi', '=', $doi)
|
||||
->where('s.context_id', '=', $contextId);
|
||||
$row = $q->select(['s.submission_id AS submission_id'])->get()->first();
|
||||
return $row ? $this->get($row->submission_id) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::fromRow()
|
||||
*/
|
||||
public function fromRow(object $row): Submission
|
||||
{
|
||||
$submission = parent::fromRow($row);
|
||||
|
||||
$submission->setData(
|
||||
'publications',
|
||||
Repo::publication()->getCollector()
|
||||
->filterBySubmissionIds([$submission->getId()])
|
||||
->getMany()
|
||||
->remember()
|
||||
);
|
||||
|
||||
return $submission;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::_insert()
|
||||
*/
|
||||
public function insert(Submission $submission): int
|
||||
{
|
||||
return parent::_insert($submission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::_update()
|
||||
*/
|
||||
public function update(Submission $submission)
|
||||
{
|
||||
parent::_update($submission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::_delete()
|
||||
*/
|
||||
public function delete(Submission $submission)
|
||||
{
|
||||
parent::_delete($submission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \PKP\core\EntityDAO::deleteById()
|
||||
*/
|
||||
public function deleteById(int $id)
|
||||
{
|
||||
$submission = Repo::submission()->get($id);
|
||||
|
||||
// Delete publications
|
||||
$publications = Repo::publication()->getCollector()
|
||||
->filterBySubmissionIds([$id])
|
||||
->getMany();
|
||||
|
||||
foreach ($publications as $publication) {
|
||||
Repo::publication()->delete($publication);
|
||||
}
|
||||
|
||||
// Delete submission files.
|
||||
$submissionFiles = Repo::submissionFile()
|
||||
->getCollector()
|
||||
->filterBySubmissionIds([$submission->getId()])
|
||||
->getMany();
|
||||
|
||||
foreach ($submissionFiles as $submissionFile) {
|
||||
Repo::submissionFile()->delete($submissionFile);
|
||||
}
|
||||
|
||||
Repo::decision()->deleteBySubmissionId($id);
|
||||
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignmentDao->deleteBySubmissionId($id);
|
||||
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
|
||||
$reviewRoundDao->deleteBySubmissionId($id);
|
||||
|
||||
// Delete the queries associated with a submission
|
||||
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
|
||||
$queryDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);
|
||||
|
||||
// Delete the stage assignments.
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$stageAssignments = $stageAssignmentDao->getBySubmissionAndStageId($id);
|
||||
while ($stageAssignment = $stageAssignments->next()) {
|
||||
$stageAssignmentDao->deleteObject($stageAssignment);
|
||||
}
|
||||
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$noteDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);
|
||||
|
||||
$submissionCommentDao = DAORegistry::getDAO('SubmissionCommentDAO'); /** @var SubmissionCommentDAO $submissionCommentDao */
|
||||
$submissionCommentDao->deleteBySubmissionId($id);
|
||||
|
||||
// Delete any outstanding notifications for this submission
|
||||
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
|
||||
$notificationDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);
|
||||
|
||||
Repo::eventLog()->getCollector()
|
||||
->filterByAssoc(Application::ASSOC_TYPE_SUBMISSION, [$id])
|
||||
->getMany()
|
||||
->each(function (EventLogEntry $logEntry) {
|
||||
Repo::eventLog()->delete($logEntry);
|
||||
});
|
||||
|
||||
$submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $submissionEmailLogDao */
|
||||
$submissionEmailLogDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);
|
||||
|
||||
parent::deleteById($id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/Genre.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 Genre
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see GenreDAO
|
||||
*
|
||||
* @brief Basic class describing a genre.
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class Genre extends \PKP\core\DataObject
|
||||
{
|
||||
public const GENRE_CATEGORY_DOCUMENT = 1;
|
||||
public const GENRE_CATEGORY_ARTWORK = 2;
|
||||
public const GENRE_CATEGORY_SUPPLEMENTARY = 3;
|
||||
|
||||
/**
|
||||
* Get ID of context.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContextId()
|
||||
{
|
||||
return $this->getData('contextId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID of context.
|
||||
*
|
||||
* @param int $contextId
|
||||
*/
|
||||
public function setContextId($contextId)
|
||||
{
|
||||
$this->setData('contextId', $contextId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sequence of genre.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSequence()
|
||||
{
|
||||
return $this->getData('sequence');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sequence of genre.
|
||||
*
|
||||
* @param float $sequence
|
||||
*/
|
||||
public function setSequence($sequence)
|
||||
{
|
||||
$this->setData('sequence', $sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key of genre.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->getData('key');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key of genre.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function setKey($key)
|
||||
{
|
||||
$this->setData('key', $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enabled status of genre.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getEnabled()
|
||||
{
|
||||
return $this->getData('enabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set enabled status of genre.
|
||||
*
|
||||
* @param bool $enabled
|
||||
*/
|
||||
public function setEnabled($enabled)
|
||||
{
|
||||
$this->setData('enabled', $enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the genre
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setName($name, $locale)
|
||||
{
|
||||
$this->setData('name', $name, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the genre
|
||||
*
|
||||
* @param string $locale
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName($locale)
|
||||
{
|
||||
return $this->getData('name', $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized name of the genre
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalizedName()
|
||||
{
|
||||
return $this->getLocalizedData('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get context file category (e.g. artwork or document)
|
||||
*
|
||||
* @return int GENRE_CATEGORY_...
|
||||
*/
|
||||
public function getCategory()
|
||||
{
|
||||
return $this->getData('category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set context file category (e.g. artwork or document)
|
||||
*
|
||||
* @param int $category GENRE_CATEGORY_...
|
||||
*/
|
||||
public function setCategory($category)
|
||||
{
|
||||
$this->setData('category', $category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dependent flag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getDependent()
|
||||
{
|
||||
return $this->getData('dependent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set dependent flag
|
||||
*
|
||||
* @param bool $dependent
|
||||
*/
|
||||
public function setDependent($dependent)
|
||||
{
|
||||
$this->setData('dependent', $dependent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supplementary flag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getSupplementary()
|
||||
{
|
||||
return $this->getData('supplementary');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set supplementary flag
|
||||
*
|
||||
* @param bool $supplementary
|
||||
*/
|
||||
public function setSupplementary($supplementary)
|
||||
{
|
||||
$this->setData('supplementary', $supplementary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this file is required for new submissions
|
||||
*/
|
||||
public function getRequired(): bool
|
||||
{
|
||||
return (bool) $this->getData('required');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this file is required for new submissions
|
||||
*/
|
||||
public function setRequired(bool $required): void
|
||||
{
|
||||
$this->setData('required', $required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a default genre.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefault()
|
||||
{
|
||||
$genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */
|
||||
$defaultKeys = $genreDao->getDefaultKeys();
|
||||
return in_array($this->getKey(), $defaultKeys);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\Genre', '\Genre');
|
||||
foreach (['GENRE_CATEGORY_DOCUMENT', 'GENRE_CATEGORY_ARTWORK', 'GENRE_CATEGORY_SUPPLEMENTARY'] as $constantName) {
|
||||
define($constantName, constant('\Genre::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/GenreDAO.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 GenreDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Genre
|
||||
*
|
||||
* @brief Operations for retrieving and modifying Genre objects.
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\db\DAO;
|
||||
use PKP\db\DAOResultFactory;
|
||||
use PKP\db\XMLDAO;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
class GenreDAO extends DAO
|
||||
{
|
||||
/**
|
||||
* Retrieve a genre by type id.
|
||||
*
|
||||
* @param int $genreId
|
||||
* @param null|mixed $contextId
|
||||
*
|
||||
* @return Genre
|
||||
*/
|
||||
public function getById($genreId, $contextId = null)
|
||||
{
|
||||
$params = [(int) $genreId];
|
||||
if ($contextId) {
|
||||
$params[] = (int) $contextId;
|
||||
}
|
||||
|
||||
$result = $this->retrieve(
|
||||
'SELECT * FROM genres WHERE genre_id = ?' .
|
||||
($contextId ? ' AND context_id = ?' : '') .
|
||||
' ORDER BY seq',
|
||||
$params
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all genres
|
||||
*
|
||||
* @param int $contextId
|
||||
* @param ?\PKP\db\DBResultRange $rangeInfo optional
|
||||
*
|
||||
* @return DAOResultFactory<Genre> containing matching genres
|
||||
*/
|
||||
public function getEnabledByContextId($contextId, $rangeInfo = null)
|
||||
{
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT * FROM genres
|
||||
WHERE enabled = ? AND context_id = ?
|
||||
ORDER BY seq',
|
||||
[1, (int) $contextId],
|
||||
$rangeInfo
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow', ['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve genres based on whether they are dependent or not.
|
||||
*
|
||||
* @param bool $dependentFilesOnly
|
||||
* @param int $contextId
|
||||
* @param ?\PKP\db\DBResultRange $rangeInfo optional
|
||||
*
|
||||
* @return DAOResultFactory<Genre> containing matching genres
|
||||
*/
|
||||
public function getByDependenceAndContextId($dependentFilesOnly, $contextId, $rangeInfo = null)
|
||||
{
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT * FROM genres
|
||||
WHERE enabled = ? AND context_id = ? AND dependent = ?
|
||||
ORDER BY seq',
|
||||
[1, (int) $contextId, (int) $dependentFilesOnly],
|
||||
$rangeInfo
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow', ['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve genres based on whether they are supplementary or not.
|
||||
*
|
||||
* @param bool $supplementaryFilesOnly
|
||||
* @param int $contextId
|
||||
* @param ?\PKP\db\DBResultRange $rangeInfo optional
|
||||
*
|
||||
* @return DAOResultFactory<Genre>
|
||||
*/
|
||||
public function getBySupplementaryAndContextId($supplementaryFilesOnly, $contextId, $rangeInfo = null)
|
||||
{
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT * FROM genres
|
||||
WHERE enabled = ? AND context_id = ? AND supplementary = ?
|
||||
ORDER BY seq',
|
||||
[1, (int) $contextId, (int) $supplementaryFilesOnly],
|
||||
$rangeInfo
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow', ['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve genres that are not supplementary or dependent.
|
||||
*
|
||||
* @param int $contextId
|
||||
* @param ?\PKP\db\DBResultRange $rangeInfo optional
|
||||
*
|
||||
* @return DAOResultFactory<Genre>
|
||||
*/
|
||||
public function getPrimaryByContextId($contextId, $rangeInfo = null)
|
||||
{
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT * FROM genres
|
||||
WHERE enabled = ? AND context_id = ? AND dependent = ? AND supplementary = ?
|
||||
ORDER BY seq',
|
||||
[1, (int) $contextId, 0, 0],
|
||||
$rangeInfo
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow', ['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all genres
|
||||
*
|
||||
* @param int $contextId
|
||||
* @param ?\PKP\db\DBResultRange $rangeInfo optional
|
||||
*
|
||||
* @return DAOResultFactory<Genre> containing matching genres
|
||||
*/
|
||||
public function getByContextId($contextId, $rangeInfo = null)
|
||||
{
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT * FROM genres WHERE context_id = ? ORDER BY seq',
|
||||
[(int) $contextId],
|
||||
$rangeInfo
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow', ['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get genres that are required for a new
|
||||
* submission in a context
|
||||
*/
|
||||
public function getRequiredToSubmit(int $contextId): Collection
|
||||
{
|
||||
return DB::table('genres')
|
||||
->where('context_id', $contextId)
|
||||
->where('required', 1)
|
||||
->get()
|
||||
->map(function (object $row) {
|
||||
return $this->_fromRow((array) $row);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the genre associated with a key.
|
||||
*
|
||||
* @param string $key the entry key
|
||||
* @param int $contextId Optional context ID
|
||||
*
|
||||
* @return Genre
|
||||
*/
|
||||
public function getByKey($key, $contextId = null)
|
||||
{
|
||||
$params = [$key];
|
||||
if ($contextId) {
|
||||
$params[] = (int) $contextId;
|
||||
}
|
||||
|
||||
$result = $this->retrieve(
|
||||
'SELECT * FROM genres WHERE entry_key = ? ' .
|
||||
($contextId ? ' AND context_id = ?' : ''),
|
||||
$params
|
||||
);
|
||||
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of field names for which data is localized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLocaleFieldNames()
|
||||
{
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the settings for this object
|
||||
*
|
||||
* @param object $genre
|
||||
*/
|
||||
public function updateLocaleFields($genre)
|
||||
{
|
||||
$this->updateDataObjectSettings(
|
||||
'genre_settings',
|
||||
$genre,
|
||||
['genre_id' => $genre->getId()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new data object corresponding to this DAO.
|
||||
*
|
||||
* @return Genre
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new Genre();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to return a Genre object from a row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return Genre
|
||||
*/
|
||||
public function _fromRow($row)
|
||||
{
|
||||
$genre = $this->newDataObject();
|
||||
$genre->setId((int) $row['genre_id']);
|
||||
$genre->setKey($row['entry_key']);
|
||||
$genre->setContextId($row['context_id']);
|
||||
$genre->setCategory((int) $row['category']);
|
||||
$genre->setDependent($row['dependent']);
|
||||
$genre->setSupplementary($row['supplementary']);
|
||||
$genre->setRequired($row['required']);
|
||||
$genre->setSequence($row['seq']);
|
||||
$genre->setEnabled($row['enabled']);
|
||||
|
||||
$this->getDataObjectSettings('genre_settings', 'genre_id', $row['genre_id'], $genre);
|
||||
|
||||
Hook::call('GenreDAO::_fromRow', [&$genre, &$row]);
|
||||
|
||||
return $genre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new genre.
|
||||
*
|
||||
* @param Genre $genre
|
||||
*
|
||||
* @return int Inserted genre ID
|
||||
*/
|
||||
public function insertObject($genre)
|
||||
{
|
||||
$this->update(
|
||||
'INSERT INTO genres
|
||||
(entry_key, seq, context_id, category, dependent, supplementary, required)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
$genre->getKey(),
|
||||
(float) $genre->getSequence(),
|
||||
(int) $genre->getContextId(),
|
||||
(int) $genre->getCategory(),
|
||||
$genre->getDependent() ? 1 : 0,
|
||||
$genre->getSupplementary() ? 1 : 0,
|
||||
$genre->getRequired() ? 1 : 0,
|
||||
]
|
||||
);
|
||||
|
||||
$genre->setId($this->getInsertId());
|
||||
$this->updateLocaleFields($genre);
|
||||
return $genre->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing genre.
|
||||
*
|
||||
* @param Genre $genre
|
||||
*/
|
||||
public function updateObject($genre)
|
||||
{
|
||||
$this->update(
|
||||
'UPDATE genres
|
||||
SET entry_key = ?,
|
||||
seq = ?,
|
||||
dependent = ?,
|
||||
supplementary = ?,
|
||||
enabled = ?,
|
||||
category = ?,
|
||||
required = ?
|
||||
WHERE genre_id = ?',
|
||||
[
|
||||
$genre->getKey(),
|
||||
(float) $genre->getSequence(),
|
||||
$genre->getDependent() ? 1 : 0,
|
||||
$genre->getSupplementary() ? 1 : 0,
|
||||
$genre->getEnabled() ? 1 : 0,
|
||||
$genre->getCategory(),
|
||||
$genre->getRequired() ? 1 : 0,
|
||||
(int) $genre->getId(),
|
||||
]
|
||||
);
|
||||
$this->updateLocaleFields($genre);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a genre by id.
|
||||
*
|
||||
* @param Genre $genre
|
||||
*/
|
||||
public function deleteObject($genre)
|
||||
{
|
||||
return $this->deleteById($genre->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete a genre by id.
|
||||
*
|
||||
* @param int $genreId Genre ID
|
||||
*/
|
||||
public function deleteById($genreId)
|
||||
{
|
||||
return $this->update(
|
||||
'UPDATE genres SET enabled = ? WHERE genre_id = ?',
|
||||
[0, (int) $genreId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the genre entries associated with a context.
|
||||
* Called when deleting a Context in ContextDAO.
|
||||
*
|
||||
* @param int $contextId Context ID
|
||||
*/
|
||||
public function deleteByContextId($contextId)
|
||||
{
|
||||
$genres = $this->getByContextId($contextId);
|
||||
while ($genre = $genres->next()) {
|
||||
$this->update('DELETE FROM genre_settings WHERE genre_id = ?', [(int) $genre->getId()]);
|
||||
}
|
||||
$this->update(
|
||||
'DELETE FROM genres WHERE context_id = ?',
|
||||
[(int) $contextId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install default data for settings.
|
||||
*
|
||||
* @param int $contextId Context ID
|
||||
* @param array $locales List of locale codes
|
||||
*/
|
||||
public function installDefaults($contextId, $locales)
|
||||
{
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parseStruct('registry/genres.xml', ['genre']);
|
||||
if (!isset($data['genre'])) {
|
||||
return false;
|
||||
}
|
||||
$seq = 0;
|
||||
|
||||
foreach ($data['genre'] as $entry) {
|
||||
$attrs = $entry['attributes'];
|
||||
// attempt to retrieve an installed Genre with this key.
|
||||
// Do this to preserve the genreId.
|
||||
$genre = $this->getByKey($attrs['key'], $contextId);
|
||||
if (!$genre) {
|
||||
$genre = $this->newDataObject();
|
||||
}
|
||||
$genre->setContextId($contextId);
|
||||
$genre->setKey($attrs['key']);
|
||||
$genre->setCategory($attrs['category']);
|
||||
$genre->setDependent($attrs['dependent']);
|
||||
$genre->setSupplementary($attrs['supplementary']);
|
||||
$genre->setRequired((bool) ($attrs['required'] ?? false));
|
||||
$genre->setSequence($seq++);
|
||||
foreach ($locales as $locale) {
|
||||
$genre->setName(__($attrs['localeKey'], [], $locale), $locale);
|
||||
}
|
||||
|
||||
if ($genre->getId() > 0) { // existing genre.
|
||||
$genre->setEnabled(1);
|
||||
$this->updateObject($genre);
|
||||
} else {
|
||||
$this->insertObject($genre);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default keys.
|
||||
*
|
||||
* @return array List of default keys
|
||||
*/
|
||||
public function getDefaultKeys()
|
||||
{
|
||||
$defaultKeys = [];
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parseStruct('registry/genres.xml', ['genre']);
|
||||
if (isset($data['genre'])) {
|
||||
foreach ($data['genre'] as $entry) {
|
||||
$attrs = $entry['attributes'];
|
||||
$defaultKeys[] = $attrs['key'];
|
||||
}
|
||||
}
|
||||
return $defaultKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a key exists for a context.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $contextId
|
||||
* @param int $genreId (optional) Current genre to be ignored
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function keyExists($key, $contextId, $genreId = null)
|
||||
{
|
||||
$params = [$key, (int) $contextId];
|
||||
if ($genreId) {
|
||||
$params[] = (int) $genreId;
|
||||
}
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT COUNT(*) AS row_count FROM genres WHERE entry_key = ? AND context_id = ?' . (isset($genreId) ? ' AND genre_id <> ?' : ''),
|
||||
$params
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? (bool) $row->row_count : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all settings associated with a locale
|
||||
*
|
||||
* @param string $locale Locale code
|
||||
*/
|
||||
public function deleteSettingsByLocale($locale)
|
||||
{
|
||||
$this->update('DELETE FROM genre_settings WHERE locale = ?', [$locale]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\GenreDAO', '\GenreDAO');
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/Representation.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 Representation
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @brief A submission's representation (Publication Format, Galley, ...)
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
|
||||
/**
|
||||
* @extends \PKP\core\DataObject<DAO|RepresentationDAOInterface>
|
||||
*/
|
||||
class Representation extends \PKP\core\DataObject
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Switch on meta-data adapter support.
|
||||
$this->setHasLoadableAdapters(true);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sequence of format in format listings for the submission.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSequence()
|
||||
{
|
||||
return $this->getData('seq');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sequence of format in format listings for the submission.
|
||||
*
|
||||
* @param float $seq
|
||||
*/
|
||||
public function setSequence($seq)
|
||||
{
|
||||
$this->setData('seq', $seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "localized" format name (if applicable).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalizedName()
|
||||
{
|
||||
return $this->getLocalizedData('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format name (if applicable).
|
||||
*
|
||||
* @param ?string $locale
|
||||
*
|
||||
* @return string|array<string,string>
|
||||
*/
|
||||
public function getName($locale = null)
|
||||
{
|
||||
return $this->getData('name', $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setName($name, $locale = null)
|
||||
{
|
||||
$this->setData('name', $name, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a representation is approved or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getIsApproved()
|
||||
{
|
||||
return (bool) $this->getData('isApproved');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a representation is approved or not.
|
||||
*
|
||||
* @param bool $isApproved
|
||||
*/
|
||||
public function setIsApproved($isApproved)
|
||||
{
|
||||
return $this->setData('isApproved', $isApproved);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current DOI
|
||||
*
|
||||
*/
|
||||
public function getDoi(): ?string
|
||||
{
|
||||
$doiObject = $this->getData('doiObject');
|
||||
|
||||
if (empty($doiObject)) {
|
||||
return null;
|
||||
} else {
|
||||
return $doiObject->getData('doi');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored public ID of the submission.
|
||||
*
|
||||
* This helper function is required by PKPPubIdPlugins.
|
||||
* NB: To maintain backwards compatibility, getDoi() is called from here
|
||||
*
|
||||
* @param string $pubIdType One of the NLM pub-id-type values or
|
||||
* 'other::something' if not part of the official NLM list
|
||||
* (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStoredPubId($pubIdType)
|
||||
{
|
||||
if ($pubIdType == 'doi') {
|
||||
return $this->getDoi();
|
||||
} else {
|
||||
return $this->getData('pub-id::' . $pubIdType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stored public ID of the submission.
|
||||
*
|
||||
* @param string $pubIdType One of the NLM pub-id-type values or
|
||||
* 'other::something' if not part of the official NLM list
|
||||
* (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
|
||||
* @param string $pubId
|
||||
*/
|
||||
public function setStoredPubId($pubIdType, $pubId)
|
||||
{
|
||||
$this->setData('pub-id::' . $pubIdType, $pubId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote URL at which this representation is retrievable.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated 3.2.0.0
|
||||
*/
|
||||
public function getRemoteURL()
|
||||
{
|
||||
return $this->getData('urlRemote');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the remote URL for retrieving this representation.
|
||||
*
|
||||
* @param string $remoteURL
|
||||
*
|
||||
* @deprecated 3.2.0.0
|
||||
*/
|
||||
public function setRemoteURL($remoteURL)
|
||||
{
|
||||
return $this->setData('urlRemote', $remoteURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context id from the submission assigned to this representation.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getContextId()
|
||||
{
|
||||
$publication = Repo::publication()->get($this->getData('publicationId'));
|
||||
$submission = Repo::submission()->get($publication->getData('submissionId'));
|
||||
return $submission->getContextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \PKP\core\DataObject::getDAO()
|
||||
*/
|
||||
public function getDAO()
|
||||
{
|
||||
return Application::getRepresentationDAO();
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\Representation', '\Representation');
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/galley/DAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class galley
|
||||
*
|
||||
* @brief An interface for representation DAOs (galleys and publication formats)
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\plugins\PKPPubIdPluginDAO;
|
||||
|
||||
interface RepresentationDAOInterface extends PKPPubIdPluginDAO
|
||||
{
|
||||
/**
|
||||
* Instantiate a new Representation object
|
||||
*/
|
||||
public function newDataObject(): Representation;
|
||||
|
||||
/**
|
||||
* Get a representation by id
|
||||
*/
|
||||
public function getById(int $id, ?int $publicationId = null, ?int $contextId = null): ?Representation;
|
||||
|
||||
/**
|
||||
* Get the representations of a publication
|
||||
*/
|
||||
public function getByPublicationId(int $publicationId): array;
|
||||
|
||||
/**
|
||||
* Update the representation object
|
||||
*/
|
||||
public function updateObject(Representation $representation): void;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/ReviewFilesDAO.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 ReviewFilesDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @brief Operations for managing review round / submission file associations.
|
||||
* These control which files are available for download by reviewers during review.
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ReviewFilesDAO extends \PKP\db\DAO
|
||||
{
|
||||
/**
|
||||
* Grant a review file to a review.
|
||||
*
|
||||
* @param int $reviewId Review assignment ID
|
||||
* @param int $submissionFileId Submission file ID
|
||||
*/
|
||||
public function grant($reviewId, $submissionFileId)
|
||||
{
|
||||
$this->update(
|
||||
'INSERT INTO review_files
|
||||
(review_id, submission_file_id)
|
||||
VALUES
|
||||
(?, ?)',
|
||||
[(int) $reviewId, (int) $submissionFileId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a review's association with a review file.
|
||||
*
|
||||
* @param int $reviewId Review assignment ID.
|
||||
* @param int $fileId Review file ID.
|
||||
*/
|
||||
public function revoke($reviewId, $fileId)
|
||||
{
|
||||
$this->update(
|
||||
'DELETE FROM review_files WHERE review_id = ? AND file_id = ?',
|
||||
[(int) $reviewId, (int) $fileId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a review's association with all submission files.
|
||||
*
|
||||
* @param int $reviewId Review assignment ID.
|
||||
*/
|
||||
public function revokeByReviewId($reviewId)
|
||||
{
|
||||
$this->update(
|
||||
'DELETE FROM review_files WHERE review_id = ?',
|
||||
[(int) $reviewId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a review's association based on submission file id.
|
||||
*/
|
||||
public function revokeBySubmissionFileId(int $submissionFileId)
|
||||
{
|
||||
$this->update(
|
||||
'DELETE FROM review_files WHERE submission_file_id = ?',
|
||||
[(int) $submissionFileId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check review file availability
|
||||
*
|
||||
* @param int $reviewId
|
||||
* @param int $submissionFileId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check($reviewId, $submissionFileId)
|
||||
{
|
||||
return DB::table('review_files')
|
||||
->where('review_id', (int) $reviewId)
|
||||
->where('submission_file_id', (int) $submissionFileId)
|
||||
->exists();
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\ReviewFilesDAO', '\ReviewFilesDAO');
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/Sanitizer.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 Sanitizer
|
||||
*
|
||||
* @brief A sanitization class to sanitize submission data before saving
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use PKP\core\PKPString;
|
||||
|
||||
class Sanitizer
|
||||
{
|
||||
/**
|
||||
* Defined sanitization rule/method mapping to attributes
|
||||
* It's possible to have multiple sanitization rule for a single attributes
|
||||
*
|
||||
* $sanitizeMap = [
|
||||
* 'attribute_1' => 'class_method_1',
|
||||
* 'attribute_2' => ['class_method_21', 'class_method_22'],
|
||||
* ...
|
||||
* ]
|
||||
*/
|
||||
protected array $sanitizeMap = [];
|
||||
|
||||
/**
|
||||
* Passed params to sanitize
|
||||
*/
|
||||
protected array $sanitizeParams;
|
||||
|
||||
/**
|
||||
* Define if allow empty sanitization for attributes
|
||||
*/
|
||||
protected bool $allowEmptySanization = false;
|
||||
|
||||
/**
|
||||
* The entity code to number conversion to update
|
||||
* As TinyMCE do a force entity conversion even when defined the 'entity_encoding' as 'raw'
|
||||
*
|
||||
* @see 5.0+ : https://www.tiny.cloud/docs/configure/content-filtering/#entity_encoding
|
||||
* @see 6.0+ : https://www.tiny.cloud/docs/tinymce/6/content-filtering/#entity_encoding
|
||||
*/
|
||||
protected static array $entityCodeToCharMapping = [
|
||||
'&' => '&',
|
||||
'>' => '>',
|
||||
'<' => '<',
|
||||
'"' => '"',
|
||||
"'" => ''',
|
||||
];
|
||||
|
||||
/**
|
||||
* Convert the TinyMCE/HTMLPurify based converted entity codes to actual character
|
||||
*/
|
||||
public static function replaceSpecialCharEntityValueWithCharacter(string $string): string
|
||||
{
|
||||
return str_replace(
|
||||
array_values(static::$entityCodeToCharMapping),
|
||||
array_keys(static::$entityCodeToCharMapping),
|
||||
$string
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the sanitization process for given attribute
|
||||
*/
|
||||
protected function runSanitizationProcess(string $method, mixed $paramKey, mixed $beforeSanitizevalue): void
|
||||
{
|
||||
$this->sanitizeParams = array_merge($this->sanitizeParams, [
|
||||
$paramKey => $this->{$method}($beforeSanitizevalue),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the submission localized/unlocalized title[title] attribute
|
||||
*/
|
||||
public function title(string|array $param): string|array
|
||||
{
|
||||
if (is_array($param)) {
|
||||
foreach ($param as $localeKey => $localizedSubmissionTitle) {
|
||||
// TinyMCE sometimes converts special chars to entity code and some times not
|
||||
// A very weird quirk by tinyMCE
|
||||
// e.g '&' turned into '&'
|
||||
$param[$localeKey] = self::replaceSpecialCharEntityValueWithCharacter(
|
||||
PKPString::stripUnsafeHtml($localizedSubmissionTitle, 'allowed_title_html')
|
||||
);
|
||||
}
|
||||
|
||||
return $param;
|
||||
}
|
||||
|
||||
return self::replaceSpecialCharEntityValueWithCharacter(
|
||||
PKPString::stripUnsafeHtml($param, 'allowed_title_html')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the submission localized/unlocalized sub title[subtitle] attribute
|
||||
*/
|
||||
public function subtitle(string|array $param): string|array
|
||||
{
|
||||
return $this->title($param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define should allow attributes with empty sanitization rules
|
||||
*/
|
||||
public function allowEmptySanitization(): self
|
||||
{
|
||||
$this->allowEmptySanization = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run sanitization
|
||||
*/
|
||||
public function sanitize(array $params, array $sanitizingKeys = []): array
|
||||
{
|
||||
$this->sanitizeParams = $params;
|
||||
|
||||
$sanitizableParams = empty($sanitizingKeys)
|
||||
? $params
|
||||
: array_intersect_key($params, array_flip($sanitizingKeys));
|
||||
|
||||
foreach ($sanitizableParams as $paramKey => $paramValue) {
|
||||
if (in_array($paramKey, $this->sanitizeMap)) {
|
||||
collect(Arr::wrap($this->sanitizeMap[$paramKey]))
|
||||
->each(
|
||||
fn ($method) => $this->runSanitizationProcess(
|
||||
$method,
|
||||
$paramKey,
|
||||
$paramValue
|
||||
)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method_exists($this, $paramKey)) {
|
||||
$this->runSanitizationProcess($paramKey, $paramKey, $paramValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->allowEmptySanization) {
|
||||
throw new Exception(
|
||||
sprintf("Running empty sanitization for attribute '%s' is now allowed", $paramKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sanitizeParams;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionAgency.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionAgency
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see SubmissionAgencyEntryDAO
|
||||
*
|
||||
* @brief Basic class describing a submission agency
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
class SubmissionAgency extends \PKP\controlledVocab\ControlledVocabEntry
|
||||
{
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get the agency
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAgency()
|
||||
{
|
||||
return $this->getData('submissionAgency');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the agency text
|
||||
*
|
||||
* @param string $agency
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setAgency($agency, $locale)
|
||||
{
|
||||
$this->setData('submissionAgency', $agency, $locale);
|
||||
}
|
||||
|
||||
public function getLocaleMetadataFieldNames()
|
||||
{
|
||||
return ['submissionAgency'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionAgency', '\SubmissionAgency');
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionAgencyDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionAgencyDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's assigned agencies
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocab;
|
||||
use PKP\controlledVocab\ControlledVocabDAO;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class SubmissionAgencyDAO extends ControlledVocabDAO
|
||||
{
|
||||
public const CONTROLLED_VOCAB_SUBMISSION_AGENCY = 'submissionAgency';
|
||||
|
||||
/**
|
||||
* Build/fetch and return a controlled vocabulary for agencies.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*
|
||||
* @return ControlledVocab
|
||||
*/
|
||||
public function build($publicationId, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
return parent::_build(self::CONTROLLED_VOCAB_SUBMISSION_AGENCY, $assocType, $publicationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of localized additional fields to store.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLocaleFieldNames()
|
||||
{
|
||||
return ['submissionAgency'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agencies for a specified submission ID.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param array $locales
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#6213
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAgencies($publicationId, $locales = [], $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$agencies = $this->build($publicationId, $assocType);
|
||||
$submissionAgencyEntryDao = DAORegistry::getDAO('SubmissionAgencyEntryDAO'); /** @var SubmissionAgencyEntryDAO $submissionAgencyEntryDao */
|
||||
$submissionAgencies = $submissionAgencyEntryDao->getByControlledVocabId($agencies->getId());
|
||||
while ($agencyEntry = $submissionAgencies->next()) {
|
||||
$agency = $agencyEntry->getAgency();
|
||||
foreach ($agency as $locale => $value) {
|
||||
if (empty($locales) || in_array($locale, $locales)) {
|
||||
$result[$locale][] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all of the submission's agencies
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllUniqueAgencies()
|
||||
{
|
||||
$result = $this->retrieve('SELECT DISTINCT setting_value FROM controlled_vocab_entry_settings WHERE setting_name = ?', [self::CONTROLLED_VOCAB_SUBMISSION_AGENCY]);
|
||||
|
||||
$agencies = [];
|
||||
foreach ($result as $row) {
|
||||
$agencies[] = $row->setting_value;
|
||||
}
|
||||
return $agencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array of agencies
|
||||
*
|
||||
* @param array $agencies List of agencies.
|
||||
* @param int $publicationId Submission ID.
|
||||
* @param bool $deleteFirst True iff existing agencies should be removed first.
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*/
|
||||
public function insertAgencies($agencies, $publicationId, $deleteFirst = true, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$agencyDao = DAORegistry::getDAO('SubmissionAgencyDAO'); /** @var SubmissionAgencyDAO $agencyDao */
|
||||
$submissionAgencyEntryDao = DAORegistry::getDAO('SubmissionAgencyEntryDAO'); /** @var SubmissionAgencyEntryDAO $submissionAgencyEntryDao */
|
||||
$currentAgencies = $this->build($publicationId, $assocType);
|
||||
|
||||
if ($deleteFirst) {
|
||||
$existingEntries = $agencyDao->enumerate($currentAgencies->getId(), self::CONTROLLED_VOCAB_SUBMISSION_AGENCY);
|
||||
|
||||
foreach ($existingEntries as $id => $entry) {
|
||||
$entry = trim($entry);
|
||||
$submissionAgencyEntryDao->deleteObjectById($id);
|
||||
}
|
||||
}
|
||||
if (is_array($agencies)) { // localized, array of arrays
|
||||
foreach ($agencies as $locale => $list) {
|
||||
if (is_array($list)) {
|
||||
$list = array_unique($list); // Remove any duplicate keywords
|
||||
$i = 1;
|
||||
foreach ($list as $agency) {
|
||||
$agencyEntry = $submissionAgencyEntryDao->newDataObject();
|
||||
$agencyEntry->setControlledVocabId($currentAgencies->getId());
|
||||
$agencyEntry->setAgency($agency, $locale);
|
||||
$agencyEntry->setSequence($i);
|
||||
$i++;
|
||||
$submissionAgencyEntryDao->insertObject($agencyEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionAgencyDAO', '\SubmissionAgencyDAO');
|
||||
define('CONTROLLED_VOCAB_SUBMISSION_AGENCY', SubmissionAgencyDAO::CONTROLLED_VOCAB_SUBMISSION_AGENCY);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionAgencyEntryDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionAgencyEntryDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's agencies
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocabEntryDAO;
|
||||
use PKP\db\DAOResultFactory;
|
||||
use PKP\db\DBResultRange;
|
||||
|
||||
class SubmissionAgencyEntryDAO extends ControlledVocabEntryDAO
|
||||
{
|
||||
/**
|
||||
* Construct a new data object corresponding to this DAO.
|
||||
*
|
||||
* @return SubmissionAgency
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new SubmissionAgency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of controlled vocabulary entries matching a
|
||||
* particular controlled vocabulary ID.
|
||||
*
|
||||
* @param int $controlledVocabId
|
||||
* @param mixed $filter (Not yet supported)
|
||||
* @param ?DBResultRange $rangeInfo
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionAgency> Object containing matching CVE objects
|
||||
*/
|
||||
public function getByControlledVocabId($controlledVocabId, $rangeInfo = null, $filter = null)
|
||||
{
|
||||
assert($filter == null); // Parent class supports this, but this class does not
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT cve.* FROM controlled_vocab_entries cve WHERE cve.controlled_vocab_id = ? ORDER BY seq',
|
||||
[(int) $controlledVocabId],
|
||||
$rangeInfo
|
||||
);
|
||||
return new DAOResultFactory($result, $this, '_fromRow');
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionAgencyEntryDAO', '\SubmissionAgencyEntryDAO');
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionComment.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 SubmissionComment
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see SubmissionCommentDAO
|
||||
*
|
||||
* @brief Class for SubmissionComment.
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use APP\facades\Repo;
|
||||
|
||||
class SubmissionComment extends \PKP\core\DataObject
|
||||
{
|
||||
public const COMMENT_TYPE_PEER_REVIEW = 1;
|
||||
public const COMMENT_TYPE_EDITOR_DECISION = 2;
|
||||
public const COMMENT_TYPE_COPYEDIT = 3;
|
||||
public const COMMENT_TYPE_LAYOUT = 4;
|
||||
public const COMMENT_TYPE_PROOFREAD = 5;
|
||||
|
||||
/**
|
||||
* get comment type
|
||||
*
|
||||
* @return int COMMENT_TYPE_...
|
||||
*/
|
||||
public function getCommentType()
|
||||
{
|
||||
return $this->getData('commentType');
|
||||
}
|
||||
|
||||
/**
|
||||
* set comment type
|
||||
*
|
||||
* @param int $commentType COMMENT_TYPE_...
|
||||
*/
|
||||
public function setCommentType($commentType)
|
||||
{
|
||||
$this->setData('commentType', $commentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* get role id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRoleId()
|
||||
{
|
||||
return $this->getData('roleId');
|
||||
}
|
||||
|
||||
/**
|
||||
* set role id
|
||||
*
|
||||
* @param int $roleId
|
||||
*/
|
||||
public function setRoleId($roleId)
|
||||
{
|
||||
$this->setData('roleId', $roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* get submission id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSubmissionId()
|
||||
{
|
||||
return $this->getData('submissionId');
|
||||
}
|
||||
|
||||
/**
|
||||
* set submission id
|
||||
*
|
||||
* @param int $submissionId
|
||||
*/
|
||||
public function setSubmissionId($submissionId)
|
||||
{
|
||||
$this->setData('submissionId', $submissionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* get assoc id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocId()
|
||||
{
|
||||
return $this->getData('assocId');
|
||||
}
|
||||
|
||||
/**
|
||||
* set assoc id
|
||||
*
|
||||
* @param int $assocId
|
||||
*/
|
||||
public function setAssocId($assocId)
|
||||
{
|
||||
$this->setData('assocId', $assocId);
|
||||
}
|
||||
|
||||
/**
|
||||
* get author id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAuthorId()
|
||||
{
|
||||
return $this->getData('authorId');
|
||||
}
|
||||
|
||||
/**
|
||||
* set author id
|
||||
*
|
||||
* @param int $authorId
|
||||
*/
|
||||
public function setAuthorId($authorId)
|
||||
{
|
||||
$this->setData('authorId', $authorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* get author name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthorName()
|
||||
{
|
||||
// Reference used to set if not already fetched
|
||||
$authorFullName = & $this->getData('authorFullName');
|
||||
|
||||
if (!isset($authorFullName)) {
|
||||
$user = Repo::user()->get($this->getAuthorId(), true);
|
||||
$authorFullName = $user->getFullName();
|
||||
}
|
||||
|
||||
return $authorFullName ? $authorFullName : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* get author email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthorEmail()
|
||||
{
|
||||
// Reference used to set if not already fetched
|
||||
$authorEmail = & $this->getData('authorEmail');
|
||||
|
||||
if (!isset($authorEmail)) {
|
||||
$user = Repo::user()->get($this->getAuthorId(), true);
|
||||
return $user->getEmail();
|
||||
}
|
||||
|
||||
return $authorEmail ? $authorEmail : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* get comment title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCommentTitle()
|
||||
{
|
||||
return $this->getData('commentTitle');
|
||||
}
|
||||
|
||||
/**
|
||||
* set comment title
|
||||
*
|
||||
* @param string $commentTitle
|
||||
*/
|
||||
public function setCommentTitle($commentTitle)
|
||||
{
|
||||
$this->setData('commentTitle', $commentTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* get comments
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComments()
|
||||
{
|
||||
return $this->getData('comments');
|
||||
}
|
||||
|
||||
/**
|
||||
* set comments
|
||||
*
|
||||
* @param string $comments
|
||||
*/
|
||||
public function setComments($comments)
|
||||
{
|
||||
$this->setData('comments', $comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* get date posted
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDatePosted()
|
||||
{
|
||||
return $this->getData('datePosted');
|
||||
}
|
||||
|
||||
/**
|
||||
* set date posted
|
||||
*
|
||||
* @param string $datePosted
|
||||
*/
|
||||
public function setDatePosted($datePosted)
|
||||
{
|
||||
$this->setData('datePosted', $datePosted);
|
||||
}
|
||||
|
||||
/**
|
||||
* get date modified
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateModified()
|
||||
{
|
||||
return $this->getData('dateModified');
|
||||
}
|
||||
|
||||
/**
|
||||
* set date modified
|
||||
*
|
||||
* @param string $dateModified
|
||||
*/
|
||||
public function setDateModified($dateModified)
|
||||
{
|
||||
$this->setData('dateModified', $dateModified);
|
||||
}
|
||||
|
||||
/**
|
||||
* get viewable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getViewable()
|
||||
{
|
||||
return $this->getData('viewable');
|
||||
}
|
||||
|
||||
/**
|
||||
* set viewable
|
||||
*
|
||||
* @param bool $viewable
|
||||
*/
|
||||
public function setViewable($viewable)
|
||||
{
|
||||
$this->setData('viewable', $viewable);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionComment', '\SubmissionComment');
|
||||
foreach (['COMMENT_TYPE_PEER_REVIEW', 'COMMENT_TYPE_EDITOR_DECISION', 'COMMENT_TYPE_COPYEDIT', 'COMMENT_TYPE_LAYOUT', 'COMMENT_TYPE_PROOFREAD'] as $constantName) {
|
||||
define($constantName, constant('\SubmissionComment::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionCommentDAO.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 SubmissionCommentDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see SubmissionComment
|
||||
*
|
||||
* @brief Operations for retrieving and modifying SubmissionComment objects.
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\db\DAOResultFactory;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
class SubmissionCommentDAO extends \PKP\db\DAO
|
||||
{
|
||||
/**
|
||||
* Retrieve SubmissionComments by submission id
|
||||
*
|
||||
* @param int $submissionId Submission ID
|
||||
* @param int $commentType Comment type
|
||||
* @param int $assocId Assoc ID
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionComment>
|
||||
*/
|
||||
public function getSubmissionComments($submissionId, $commentType = null, $assocId = null)
|
||||
{
|
||||
$params = [(int) $submissionId];
|
||||
if ($commentType) {
|
||||
$params[] = (int) $commentType;
|
||||
}
|
||||
if ($assocId) {
|
||||
$params[] = (int) $assocId;
|
||||
}
|
||||
return new DAOResultFactory(
|
||||
$this->retrieve(
|
||||
'SELECT a.*
|
||||
FROM submission_comments a
|
||||
WHERE submission_id = ?'
|
||||
. ($commentType ? ' AND comment_type = ?' : '')
|
||||
. ($assocId ? ' AND assoc_id = ?' : '')
|
||||
. ' ORDER BY date_posted',
|
||||
$params
|
||||
),
|
||||
$this,
|
||||
'_fromRow'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve SubmissionComments by user id
|
||||
*
|
||||
* @param int $userId User ID.
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionComment>
|
||||
*/
|
||||
public function getByUserId($userId)
|
||||
{
|
||||
return new DAOResultFactory(
|
||||
$this->retrieve(
|
||||
'SELECT a.* FROM submission_comments a WHERE author_id = ? ORDER BY date_posted',
|
||||
[(int) $userId]
|
||||
),
|
||||
$this,
|
||||
'_fromRow'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve SubmissionComments made my reviewers on a submission
|
||||
*
|
||||
* @param int $submissionId The submission Id that was reviewered/commented on.
|
||||
* @param int $reviewerId The user id of the reviewer.
|
||||
* @param int $reviewId (optional) The review assignment ID the comment pertains to.
|
||||
* @param bool $viewable True for only viewable comments; false for non-viewable; null for both
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionComment>
|
||||
*/
|
||||
public function getReviewerCommentsByReviewerId($submissionId, $reviewerId = null, $reviewId = null, $viewable = null)
|
||||
{
|
||||
$params = [(int) $submissionId];
|
||||
if ($reviewerId) {
|
||||
$params[] = (int) $reviewerId;
|
||||
}
|
||||
if ($reviewId) {
|
||||
$params[] = (int) $reviewId;
|
||||
}
|
||||
return new DAOResultFactory(
|
||||
$this->retrieve(
|
||||
$sql = 'SELECT a.*
|
||||
FROM submission_comments a
|
||||
WHERE submission_id = ?
|
||||
' . ($reviewerId ? ' AND author_id = ?' : '') . '
|
||||
' . ($reviewId ? ' AND assoc_id = ?' : '') . '
|
||||
' . ($viewable === true ? ' AND viewable = 1' : '') . '
|
||||
' . ($viewable === false ? ' AND viewable = 0' : '') . '
|
||||
ORDER BY date_posted DESC',
|
||||
$params
|
||||
),
|
||||
$this,
|
||||
'_fromRow',
|
||||
[],
|
||||
$sql,
|
||||
$params // Counted in readReview.tpl and authorReadReview.tpl
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve submission comment by id
|
||||
*
|
||||
* @param int $commentId Comment ID.
|
||||
*
|
||||
* @return SubmissionComment object
|
||||
*/
|
||||
public function getById($commentId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT * FROM submission_comments WHERE comment_id = ?',
|
||||
[(int) $commentId]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new \PKP\core\DataObject.
|
||||
*
|
||||
* @return SubmissionComment
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new SubmissionComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a submission comment object from a row
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return SubmissionComment object
|
||||
*/
|
||||
public function _fromRow($row)
|
||||
{
|
||||
$submissionComment = $this->newDataObject();
|
||||
$submissionComment->setId($row['comment_id']);
|
||||
$submissionComment->setCommentType($row['comment_type']);
|
||||
$submissionComment->setRoleId($row['role_id']);
|
||||
$submissionComment->setSubmissionId($row['submission_id']);
|
||||
$submissionComment->setAssocId($row['assoc_id']);
|
||||
$submissionComment->setAuthorId($row['author_id']);
|
||||
$submissionComment->setCommentTitle($row['comment_title']);
|
||||
$submissionComment->setComments($row['comments']);
|
||||
$submissionComment->setDatePosted($this->datetimeFromDB($row['date_posted']));
|
||||
$submissionComment->setDateModified($this->datetimeFromDB($row['date_modified']));
|
||||
$submissionComment->setViewable($row['viewable']);
|
||||
|
||||
Hook::call('SubmissionCommentDAO::_fromRow', [&$submissionComment, &$row]);
|
||||
|
||||
return $submissionComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* inserts a new submission comment into the submission_comments table
|
||||
*
|
||||
* @param SubmissionComment $submissionComment object
|
||||
*
|
||||
* @return int note ID int
|
||||
*/
|
||||
public function insertObject($submissionComment)
|
||||
{
|
||||
$this->update(
|
||||
sprintf(
|
||||
'INSERT INTO submission_comments
|
||||
(comment_type, role_id, submission_id, assoc_id, author_id, date_posted, date_modified, comment_title, comments, viewable)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, %s, %s, ?, ?, ?)',
|
||||
$this->datetimeToDB($submissionComment->getDatePosted()),
|
||||
$this->datetimeToDB($submissionComment->getDateModified())
|
||||
),
|
||||
[
|
||||
(int) $submissionComment->getCommentType(),
|
||||
(int) $submissionComment->getRoleId(),
|
||||
(int) $submissionComment->getSubmissionId(),
|
||||
(int) $submissionComment->getAssocId(),
|
||||
(int) $submissionComment->getAuthorId(),
|
||||
$submissionComment->getCommentTitle(),
|
||||
$submissionComment->getComments(),
|
||||
(int) $submissionComment->getViewable()
|
||||
]
|
||||
);
|
||||
|
||||
$submissionComment->setId($this->getInsertId());
|
||||
return $submissionComment->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a submission comment from the submission_comments table
|
||||
*
|
||||
* @param SubmissionComment $submissionComment object
|
||||
*/
|
||||
public function deleteObject($submissionComment)
|
||||
{
|
||||
$this->deleteById($submissionComment->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a submission note by id
|
||||
*
|
||||
* @param int $commentId
|
||||
*/
|
||||
public function deleteById($commentId)
|
||||
{
|
||||
$this->update(
|
||||
'DELETE FROM submission_comments WHERE comment_id = ?',
|
||||
[(int) $commentId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all comments for a submission.
|
||||
*
|
||||
* @param int $submissionId
|
||||
*/
|
||||
public function deleteBySubmissionId($submissionId)
|
||||
{
|
||||
$this->update(
|
||||
'DELETE FROM submission_comments WHERE submission_id = ?',
|
||||
[(int) $submissionId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a submission comment
|
||||
*
|
||||
* @param SubmissionComment $submissionComment object
|
||||
*/
|
||||
public function updateObject($submissionComment)
|
||||
{
|
||||
$this->update(
|
||||
sprintf(
|
||||
'UPDATE submission_comments
|
||||
SET
|
||||
comment_type = ?,
|
||||
role_id = ?,
|
||||
submission_id = ?,
|
||||
assoc_id = ?,
|
||||
author_id = ?,
|
||||
date_posted = %s,
|
||||
date_modified = %s,
|
||||
comment_title = ?,
|
||||
comments = ?,
|
||||
viewable = ?
|
||||
WHERE comment_id = ?',
|
||||
$this->datetimeToDB($submissionComment->getDatePosted()),
|
||||
$this->datetimeToDB($submissionComment->getDateModified())
|
||||
),
|
||||
[
|
||||
(int) $submissionComment->getCommentType(),
|
||||
(int) $submissionComment->getRoleId(),
|
||||
(int) $submissionComment->getSubmissionId(),
|
||||
(int) $submissionComment->getAssocId(),
|
||||
(int) $submissionComment->getAuthorId(),
|
||||
$submissionComment->getCommentTitle(),
|
||||
$submissionComment->getComments(),
|
||||
$submissionComment->getViewable() === null ? 1 : (int) $submissionComment->getViewable(),
|
||||
(int) $submissionComment->getId()
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionCommentDAO', '\SubmissionCommentDAO');
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionDiscipline.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionDiscipline
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see SubmissionDisciplineEntryDAO
|
||||
*
|
||||
* @brief Basic class describing a submission discipline
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
class SubmissionDiscipline extends \PKP\controlledVocab\ControlledVocabEntry
|
||||
{
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get the discipline
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDiscipline()
|
||||
{
|
||||
return $this->getData('submissionDiscipline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the discipline text
|
||||
*
|
||||
* @param string $discipline
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setDiscipline($discipline, $locale)
|
||||
{
|
||||
$this->setData('submissionDiscipline', $discipline, $locale);
|
||||
}
|
||||
|
||||
public function getLocaleMetadataFieldNames()
|
||||
{
|
||||
return ['submissionDiscipline'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionDiscipline', '\SubmissionDiscipline');
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionDisciplineDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionDisciplineDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's assigned
|
||||
* disciplines
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocab;
|
||||
use PKP\controlledVocab\ControlledVocabDAO;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class SubmissionDisciplineDAO extends ControlledVocabDAO
|
||||
{
|
||||
public const CONTROLLED_VOCAB_SUBMISSION_DISCIPLINE = 'submissionDiscipline';
|
||||
|
||||
/**
|
||||
* Build/fetch a publication's discipline controlled vocabulary.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*
|
||||
* @return ControlledVocab
|
||||
*/
|
||||
public function build($publicationId, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
return parent::_build(self::CONTROLLED_VOCAB_SUBMISSION_DISCIPLINE, $assocType, $publicationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of localized additional fields to store.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLocaleFieldNames()
|
||||
{
|
||||
return ['submissionDiscipline'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disciplines for a submission.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param array $locales
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#6213
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDisciplines($publicationId, $locales = [], $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$disciplines = $this->build($publicationId, $assocType);
|
||||
$submissionDisciplineEntryDao = DAORegistry::getDAO('SubmissionDisciplineEntryDAO'); /** @var SubmissionDisciplineEntryDAO $submissionDisciplineEntryDao */
|
||||
$submissionDisciplines = $submissionDisciplineEntryDao->getByControlledVocabId($disciplines->getId());
|
||||
while ($disciplineEntry = $submissionDisciplines->next()) {
|
||||
$discipline = $disciplineEntry->getDiscipline();
|
||||
foreach ($discipline as $locale => $value) {
|
||||
if (empty($locales) || in_array($locale, $locales)) {
|
||||
$result[$locale][] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all of the submission's disciplines
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllUniqueDisciplines()
|
||||
{
|
||||
$result = $this->retrieve('SELECT DISTINCT setting_value FROM controlled_vocab_entry_settings WHERE setting_name = ?', [self::CONTROLLED_VOCAB_SUBMISSION_DISCIPLINE]);
|
||||
|
||||
$disciplines = [];
|
||||
foreach ($result as $row) {
|
||||
$disciplines[] = $row->setting_value;
|
||||
}
|
||||
return $disciplines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array of disciplines
|
||||
*
|
||||
* @param array $disciplines
|
||||
* @param int $publicationId
|
||||
* @param bool $deleteFirst
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*/
|
||||
public function insertDisciplines($disciplines, $publicationId, $deleteFirst = true, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$disciplineDao = DAORegistry::getDAO('SubmissionDisciplineDAO'); /** @var SubmissionDisciplineDAO $disciplineDao */
|
||||
$submissionDisciplineEntryDao = DAORegistry::getDAO('SubmissionDisciplineEntryDAO'); /** @var SubmissionDisciplineEntryDAO $submissionDisciplineEntryDao */
|
||||
$currentDisciplines = $this->build($publicationId, $assocType);
|
||||
|
||||
if ($deleteFirst) {
|
||||
$existingEntries = $disciplineDao->enumerate($currentDisciplines->getId(), self::CONTROLLED_VOCAB_SUBMISSION_DISCIPLINE);
|
||||
|
||||
foreach ($existingEntries as $id => $entry) {
|
||||
$entry = trim($entry);
|
||||
$submissionDisciplineEntryDao->deleteObjectById($id);
|
||||
}
|
||||
}
|
||||
if (is_array($disciplines)) { // localized, array of arrays
|
||||
foreach ($disciplines as $locale => $list) {
|
||||
if (is_array($list)) {
|
||||
$list = array_unique($list); // Remove any duplicate keywords
|
||||
$i = 1;
|
||||
foreach ($list as $discipline) {
|
||||
$disciplineEntry = $submissionDisciplineEntryDao->newDataObject();
|
||||
$disciplineEntry->setControlledVocabId($currentDisciplines->getId());
|
||||
$disciplineEntry->setDiscipline($discipline, $locale);
|
||||
$disciplineEntry->setSequence($i);
|
||||
$i++;
|
||||
$submissionDisciplineEntryDao->insertObject($disciplineEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionDisciplineDAO', '\SubmissionDisciplineDAO');
|
||||
define('CONTROLLED_VOCAB_SUBMISSION_DISCIPLINE', SubmissionDisciplineDAO::CONTROLLED_VOCAB_SUBMISSION_DISCIPLINE);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionDisciplineEntryDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionDisciplineEntryDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's disciplines
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocabEntryDAO;
|
||||
use PKP\db\DAOResultFactory;
|
||||
use PKP\db\DBResultRange;
|
||||
|
||||
class SubmissionDisciplineEntryDAO extends ControlledVocabEntryDAO
|
||||
{
|
||||
/**
|
||||
* Construct a new data object corresponding to this DAO.
|
||||
*
|
||||
* @return SubmissionDiscipline
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new SubmissionDiscipline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of controlled vocabulary entries matching a
|
||||
* particular controlled vocabulary ID.
|
||||
*
|
||||
* @param int $controlledVocabId
|
||||
* @param mixed $filter (Not yet supported)
|
||||
* @param null|DBResultRange $rangeInfo
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionDiscipline> matching CVE objects
|
||||
*/
|
||||
public function getByControlledVocabId($controlledVocabId, $rangeInfo = null, $filter = null)
|
||||
{
|
||||
assert($filter == null); // Parent class supports this, but this class does not
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT cve.* FROM controlled_vocab_entries cve WHERE cve.controlled_vocab_id = ? ORDER BY seq',
|
||||
[(int) $controlledVocabId],
|
||||
$rangeInfo
|
||||
);
|
||||
return new DAOResultFactory($result, $this, '_fromRow');
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionDisciplineEntryDAO', '\SubmissionDisciplineEntryDAO');
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionKeyword.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionKeyword
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see SubmissionKeywordEntryDAO
|
||||
*
|
||||
* @brief Basic class describing a submission keyword
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
class SubmissionKeyword extends \PKP\controlledVocab\ControlledVocabEntry
|
||||
{
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get the keyword
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyword()
|
||||
{
|
||||
return $this->getData('submissionKeyword');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keyword text
|
||||
*
|
||||
* @param string $keyword
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setKeyword($keyword, $locale)
|
||||
{
|
||||
$this->setData('submissionKeyword', $keyword, $locale);
|
||||
}
|
||||
|
||||
public function getLocaleMetadataFieldNames()
|
||||
{
|
||||
return ['submissionKeyword'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionKeyword', '\SubmissionKeyword');
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionKeywordDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionKeywordDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's assigned keywords
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocab;
|
||||
use PKP\controlledVocab\ControlledVocabDAO;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class SubmissionKeywordDAO extends ControlledVocabDAO
|
||||
{
|
||||
public const CONTROLLED_VOCAB_SUBMISSION_KEYWORD = 'submissionKeyword';
|
||||
|
||||
/**
|
||||
* Build/fetch and return a controlled vocabulary for keywords.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*
|
||||
* @return ControlledVocab
|
||||
*/
|
||||
public function build($publicationId, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
// may return an array of ControlledVocabs
|
||||
return parent::_build(self::CONTROLLED_VOCAB_SUBMISSION_KEYWORD, $assocType, $publicationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of localized additional fields to store.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLocaleFieldNames()
|
||||
{
|
||||
return ['submissionKeyword'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get keywords for a submission.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param array $locales
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#6213
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getKeywords($publicationId, $locales = [], $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$keywords = $this->build($publicationId, $assocType);
|
||||
$submissionKeywordEntryDao = DAORegistry::getDAO('SubmissionKeywordEntryDAO'); /** @var SubmissionKeywordEntryDAO $submissionKeywordEntryDao */
|
||||
$submissionKeywords = $submissionKeywordEntryDao->getByControlledVocabId($keywords->getId());
|
||||
while ($keywordEntry = $submissionKeywords->next()) {
|
||||
$keyword = $keywordEntry->getKeyword();
|
||||
foreach ($keyword as $locale => $value) {
|
||||
if (empty($locales) || in_array($locale, $locales)) {
|
||||
if (!array_key_exists($locale, $result)) {
|
||||
$result[$locale] = [];
|
||||
}
|
||||
$result[$locale][] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all of the submission's keywords
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllUniqueKeywords()
|
||||
{
|
||||
$result = $this->retrieve('SELECT DISTINCT setting_value FROM controlled_vocab_entry_settings WHERE setting_name = ?', [self::CONTROLLED_VOCAB_SUBMISSION_KEYWORD]);
|
||||
|
||||
$keywords = [];
|
||||
foreach ($result as $row) {
|
||||
$keywords[] = $row->setting_value;
|
||||
}
|
||||
return $keywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array of keywords
|
||||
*
|
||||
* @param array $keywords
|
||||
* @param int $publicationId
|
||||
* @param bool $deleteFirst
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*/
|
||||
public function insertKeywords($keywords, $publicationId, $deleteFirst = true, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$submissionKeywordEntryDao = DAORegistry::getDAO('SubmissionKeywordEntryDAO'); /** @var SubmissionKeywordEntryDAO $submissionKeywordEntryDao */
|
||||
|
||||
if ($deleteFirst) {
|
||||
$currentKeywords = $this->deleteByPublicationId($publicationId);
|
||||
} else {
|
||||
$currentKeywords = $this->build($publicationId, $assocType);
|
||||
}
|
||||
if (is_array($keywords)) { // localized, array of arrays
|
||||
foreach ($keywords as $locale => $list) {
|
||||
if (is_array($list)) {
|
||||
$list = array_unique($list); // Remove any duplicate keywords
|
||||
$i = 1;
|
||||
foreach ($list as $keyword) {
|
||||
$keywordEntry = $submissionKeywordEntryDao->newDataObject();
|
||||
$keywordEntry->setControlledVocabId($currentKeywords->getId());
|
||||
$keywordEntry->setKeyword($keyword, $locale);
|
||||
$keywordEntry->setSequence($i);
|
||||
$i++;
|
||||
$submissionKeywordEntryDao->insertObject($keywordEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete keywords by publication ID
|
||||
*
|
||||
* @return ControlledVocab Controlled Vocab
|
||||
*/
|
||||
public function deleteByPublicationId($publicationId)
|
||||
{
|
||||
$keywordDao = DAORegistry::getDAO('SubmissionKeywordDAO'); /** @var SubmissionKeywordDAO $keywordDao */
|
||||
$submissionKeywordEntryDao = DAORegistry::getDAO('SubmissionKeywordEntryDAO'); /** @var SubmissionKeywordEntryDAO $submissionKeywordEntryDao */
|
||||
$currentKeywords = $this->build($publicationId);
|
||||
|
||||
$existingEntries = $keywordDao->enumerate($currentKeywords->getId(), self::CONTROLLED_VOCAB_SUBMISSION_KEYWORD);
|
||||
foreach ($existingEntries as $id => $entry) {
|
||||
$entry = trim($entry);
|
||||
$entryObj = $submissionKeywordEntryDao->getById($id);
|
||||
$submissionKeywordEntryDao->deleteObjectById($id);
|
||||
}
|
||||
|
||||
return $currentKeywords;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionKeywordDAO', '\SubmissionKeywordDAO');
|
||||
define('CONTROLLED_VOCAB_SUBMISSION_KEYWORD', SubmissionKeywordDAO::CONTROLLED_VOCAB_SUBMISSION_KEYWORD);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionKeywordEntryDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionKeywordEntryDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's keywords
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocabEntryDAO;
|
||||
use PKP\db\DAOResultFactory;
|
||||
use PKP\db\DBResultRange;
|
||||
|
||||
class SubmissionKeywordEntryDAO extends ControlledVocabEntryDAO
|
||||
{
|
||||
/**
|
||||
* Construct a new data object corresponding to this DAO.
|
||||
*
|
||||
* @return SubmissionKeyword
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new SubmissionKeyword();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of controlled vocabulary entries matching a
|
||||
* particular controlled vocabulary ID.
|
||||
*
|
||||
* @param int $controlledVocabId
|
||||
* @param mixed $filter (Not yet supported)
|
||||
* @param ?DBResultRange $rangeInfo
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionKeyword> Object containing matching CVE objects
|
||||
*/
|
||||
public function getByControlledVocabId($controlledVocabId, $rangeInfo = null, $filter = null)
|
||||
{
|
||||
assert($filter == null); // Parent class supports this, but this class does not
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT cve.* FROM controlled_vocab_entries cve WHERE cve.controlled_vocab_id = ? ORDER BY seq',
|
||||
[(int) $controlledVocabId],
|
||||
$rangeInfo
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow');
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionKeywordEntryDAO', '\SubmissionKeywordEntryDAO');
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionLanguage.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionLanguage
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see SubmissionLanguageEntryDAO
|
||||
*
|
||||
* @brief Basic class describing a submission language
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
class SubmissionLanguage extends \PKP\controlledVocab\ControlledVocabEntry
|
||||
{
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get the language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->getData('submissionLanguage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the language text
|
||||
*
|
||||
* @param string $language
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setLanguage($language, $locale)
|
||||
{
|
||||
$this->setData('submissionLanguage', $language, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \PKP\controlledVocab\ControlledVocabEntry::getLocaleMetadataFieldNames()
|
||||
*/
|
||||
public function getLocaleMetadataFieldNames()
|
||||
{
|
||||
return ['submissionLanguage'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionLanguage', '\SubmissionLanguage');
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionLanguageDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionLanguageDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's assigned languages
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocab;
|
||||
use PKP\controlledVocab\ControlledVocabDAO;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class SubmissionLanguageDAO extends ControlledVocabDAO
|
||||
{
|
||||
public const CONTROLLED_VOCAB_SUBMISSION_LANGUAGE = 'submissionLanguage';
|
||||
|
||||
/**
|
||||
* Build/fetch and return a controlled vocabulary for languages.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*
|
||||
* @return ControlledVocab
|
||||
*/
|
||||
public function build($publicationId, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
// may return an array of ControlledVocabs
|
||||
return parent::_build(self::CONTROLLED_VOCAB_SUBMISSION_LANGUAGE, $assocType, $publicationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of localized additional fields to store.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLocaleFieldNames()
|
||||
{
|
||||
return ['submissionLanguage'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Languages for a submission.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param array $locales
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#6213
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLanguages($publicationId, $locales = [], $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$languages = $this->build($publicationId, $assocType);
|
||||
$submissionLanguageEntryDao = DAORegistry::getDAO('SubmissionLanguageEntryDAO'); /** @var SubmissionLanguageEntryDAO $submissionLanguageEntryDao */
|
||||
$submissionLanguages = $submissionLanguageEntryDao->getByControlledVocabId($languages->getId());
|
||||
while ($languageEntry = $submissionLanguages->next()) {
|
||||
$language = $languageEntry->getLanguage();
|
||||
foreach ($language as $locale => $value) {
|
||||
if (empty($locales) || in_array($locale, $locales)) {
|
||||
$result[$locale][] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all of the submission's Languages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllUniqueLanguages()
|
||||
{
|
||||
$result = $this->retrieve('SELECT DISTINCT setting_value FROM controlled_vocab_entry_settings WHERE setting_name = ?', [self::CONTROLLED_VOCAB_SUBMISSION_LANGUAGE]);
|
||||
|
||||
$languages = [];
|
||||
foreach ($result as $row) {
|
||||
$languages[] = $row->setting_value;
|
||||
}
|
||||
return $languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array of languages
|
||||
*
|
||||
* @param array $languages
|
||||
* @param int $publicationId
|
||||
* @param bool $deleteFirst
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*/
|
||||
public function insertLanguages($languages, $publicationId, $deleteFirst = true, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$languageDao = DAORegistry::getDAO('SubmissionLanguageDAO'); /** @var SubmissionLanguageDAO $languageDao */
|
||||
$submissionLanguageEntryDao = DAORegistry::getDAO('SubmissionLanguageEntryDAO'); /** @var SubmissionLanguageEntryDAO $submissionLanguageEntryDao */
|
||||
$currentLanguages = $this->build($publicationId, $assocType);
|
||||
|
||||
if ($deleteFirst) {
|
||||
$existingEntries = $languageDao->enumerate($currentLanguages->getId(), self::CONTROLLED_VOCAB_SUBMISSION_LANGUAGE);
|
||||
|
||||
foreach ($existingEntries as $id => $entry) {
|
||||
$entry = trim($entry);
|
||||
$submissionLanguageEntryDao->deleteObjectById($id);
|
||||
}
|
||||
}
|
||||
if (is_array($languages)) { // localized, array of arrays
|
||||
foreach ($languages as $locale => $list) {
|
||||
if (is_array($list)) {
|
||||
$list = array_unique($list); // Remove any duplicate Languages
|
||||
$i = 1;
|
||||
foreach ($list as $language) {
|
||||
$languageEntry = $submissionLanguageEntryDao->newDataObject();
|
||||
$languageEntry->setControlledVocabId($currentLanguages->getId());
|
||||
$languageEntry->setLanguage($language, $locale);
|
||||
$languageEntry->setSequence($i);
|
||||
$i++;
|
||||
$submissionLanguageEntryDao->insertObject($languageEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionLanguageDAO', '\SubmissionLanguageDAO');
|
||||
define('CONTROLLED_VOCAB_SUBMISSION_LANGUAGE', SubmissionLanguageDAO::CONTROLLED_VOCAB_SUBMISSION_LANGUAGE);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionLanguageEntryDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionLanguageEntryDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's languages
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocabEntryDAO;
|
||||
use PKP\db\DAOResultFactory;
|
||||
use PKP\db\DBResultRange;
|
||||
|
||||
class SubmissionLanguageEntryDAO extends ControlledVocabEntryDAO
|
||||
{
|
||||
/**
|
||||
* Construct a new data object corresponding to this DAO.
|
||||
*
|
||||
* @return submissionLanguage
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new SubmissionLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of controlled vocabulary entries matching a
|
||||
* particular controlled vocabulary ID.
|
||||
*
|
||||
* @param int $controlledVocabId
|
||||
* @param mixed $filter (Not yet supported)
|
||||
* @param ?DBResultRange $rangeInfo
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionLanguage> Object containing matching CVE objects
|
||||
*/
|
||||
public function getByControlledVocabId($controlledVocabId, $rangeInfo = null, $filter = null)
|
||||
{
|
||||
assert($filter == null); // Parent class supports this, but this class does not
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT cve.* FROM controlled_vocab_entries cve WHERE cve.controlled_vocab_id = ? ORDER BY seq',
|
||||
[(int) $controlledVocabId],
|
||||
$rangeInfo
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow');
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionLanguageEntryDAO', '\SubmissionLanguageEntryDAO');
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionSubject.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionSubject
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see SubmissionSubjectEntryDAO
|
||||
*
|
||||
* @brief Basic class describing a submission subject
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
class SubmissionSubject extends \PKP\controlledVocab\ControlledVocabEntry
|
||||
{
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get the subject
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->getData('submissionSubject');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subject text
|
||||
*
|
||||
* @param string $subject
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setSubject($subject, $locale)
|
||||
{
|
||||
$this->setData('submissionSubject', $subject, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \PKP\controlledVocab\ControlledVocabEntry::getLocaleMetadataFieldNames()
|
||||
*/
|
||||
public function getLocaleMetadataFieldNames()
|
||||
{
|
||||
return ['submissionSubject'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionSubject', '\SubmissionSubject');
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionSubjectDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionSubjectDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's assigned subjects
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocab;
|
||||
use PKP\controlledVocab\ControlledVocabDAO;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
class SubmissionSubjectDAO extends ControlledVocabDAO
|
||||
{
|
||||
public const CONTROLLED_VOCAB_SUBMISSION_SUBJECT = 'submissionSubject';
|
||||
|
||||
/**
|
||||
* Build/fetch and return a controlled vocabulary for subjects.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*
|
||||
* @return ControlledVocab
|
||||
*/
|
||||
public function build($publicationId, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
// may return an array of ControlledVocabs
|
||||
return parent::_build(SubmissionSubjectDAO::CONTROLLED_VOCAB_SUBMISSION_SUBJECT, $assocType, $publicationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of localized additional fields to store.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLocaleFieldNames()
|
||||
{
|
||||
return ['submissionSubject'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Subjects for a submission.
|
||||
*
|
||||
* @param int $publicationId
|
||||
* @param array $locales
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#6213
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSubjects($publicationId, $locales = [], $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$subjects = $this->build($publicationId, $assocType);
|
||||
$submissionSubjectEntryDao = DAORegistry::getDAO('SubmissionSubjectEntryDAO'); /** @var SubmissionSubjectEntryDAO $submissionSubjectEntryDao */
|
||||
$submissionSubjects = $submissionSubjectEntryDao->getByControlledVocabId($subjects->getId());
|
||||
/** @var SubmissionSubject */
|
||||
foreach ($submissionSubjects->toIterator() as $subjectEntry) {
|
||||
$subject = $subjectEntry->getSubject();
|
||||
foreach ($subject as $locale => $value) {
|
||||
if (empty($locales) || in_array($locale, $locales)) {
|
||||
$result[$locale][] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all of the submission's Subjects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllUniqueSubjects()
|
||||
{
|
||||
$result = $this->retrieve('SELECT DISTINCT setting_value FROM controlled_vocab_entry_settings WHERE setting_name = ?', [SubmissionSubjectDAO::CONTROLLED_VOCAB_SUBMISSION_SUBJECT]);
|
||||
|
||||
$subjects = [];
|
||||
foreach ($result as $row) {
|
||||
$subjects[] = $row->setting_value;
|
||||
}
|
||||
return $subjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array of subjects
|
||||
*
|
||||
* @param array $subjects
|
||||
* @param int $publicationId
|
||||
* @param bool $deleteFirst
|
||||
* @param int $assocType DO NOT USE: For <3.1 to 3.x migration pkp/pkp-lib#3572 pkp/pkp-lib#6213
|
||||
*/
|
||||
public function insertSubjects($subjects, $publicationId, $deleteFirst = true, $assocType = PKPApplication::ASSOC_TYPE_PUBLICATION)
|
||||
{
|
||||
$subjectDao = DAORegistry::getDAO('SubmissionSubjectDAO'); /** @var SubmissionSubjectDAO $subjectDao */
|
||||
$submissionSubjectEntryDao = DAORegistry::getDAO('SubmissionSubjectEntryDAO'); /** @var SubmissionSubjectEntryDAO $submissionSubjectEntryDao */
|
||||
$currentSubjects = $this->build($publicationId, $assocType);
|
||||
|
||||
if ($deleteFirst) {
|
||||
$existingEntries = $subjectDao->enumerate($currentSubjects->getId(), SubmissionSubjectDAO::CONTROLLED_VOCAB_SUBMISSION_SUBJECT);
|
||||
|
||||
foreach ($existingEntries as $id => $entry) {
|
||||
$entry = trim($entry);
|
||||
$submissionSubjectEntryDao->deleteObjectById($id);
|
||||
}
|
||||
}
|
||||
if (is_array($subjects)) { // localized, array of arrays
|
||||
foreach ($subjects as $locale => $list) {
|
||||
if (is_array($list)) {
|
||||
$list = array_unique($list); // Remove any duplicate Subjects
|
||||
$i = 1;
|
||||
foreach ($list as $subject) {
|
||||
$subjectEntry = $submissionSubjectEntryDao->newDataObject();
|
||||
$subjectEntry->setControlledVocabId($currentSubjects->getId());
|
||||
$subjectEntry->setSubject($subject, $locale);
|
||||
$subjectEntry->setSequence($i);
|
||||
$i++;
|
||||
$submissionSubjectEntryDao->insertObject($subjectEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionSubjectDAO', '\SubmissionSubjectDAO');
|
||||
define('CONTROLLED_VOCAB_SUBMISSION_SUBJECT', SubmissionSubjectDAO::CONTROLLED_VOCAB_SUBMISSION_SUBJECT);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/SubmissionSubjectEntryDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SubmissionSubjectEntryDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see Submission
|
||||
*
|
||||
* @brief Operations for retrieving and modifying a submission's subjects
|
||||
*/
|
||||
|
||||
namespace PKP\submission;
|
||||
|
||||
use PKP\controlledVocab\ControlledVocabEntryDAO;
|
||||
use PKP\db\DAOResultFactory;
|
||||
use PKP\db\DBResultRange;
|
||||
|
||||
class SubmissionSubjectEntryDAO extends ControlledVocabEntryDAO
|
||||
{
|
||||
/**
|
||||
* Construct a new data object corresponding to this DAO.
|
||||
*
|
||||
* @return submissionSubject
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new SubmissionSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of controlled vocabulary entries matching a
|
||||
* particular controlled vocabulary ID.
|
||||
*
|
||||
* @param int $controlledVocabId
|
||||
* @param mixed $filter (Not yet supported)
|
||||
* @param ?DBResultRange $rangeInfo
|
||||
*
|
||||
* @return DAOResultFactory<SubmissionSubject> Object containing matching CVE objects
|
||||
*/
|
||||
public function getByControlledVocabId($controlledVocabId, $rangeInfo = null, $filter = null)
|
||||
{
|
||||
assert($filter == null); // Parent class supports this, but this class does not
|
||||
$result = $this->retrieveRange(
|
||||
'SELECT cve.* FROM controlled_vocab_entries cve WHERE cve.controlled_vocab_id = ? ORDER BY seq',
|
||||
[(int) $controlledVocabId],
|
||||
$rangeInfo
|
||||
);
|
||||
return new DAOResultFactory($result, $this, '_fromRow');
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\SubmissionSubjectEntryDAO', '\SubmissionSubjectEntryDAO');
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/action/EditorAction.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 EditorAction
|
||||
*
|
||||
* @ingroup submission_action
|
||||
*
|
||||
* @brief Editor actions.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\action;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use APP\notification\Notification;
|
||||
use APP\notification\NotificationManager;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\context\Context;
|
||||
use PKP\core\Core;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\core\PKPServices;
|
||||
use PKP\core\PKPString;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\log\event\PKPSubmissionEventLogEntry;
|
||||
use PKP\mail\mailables\ReviewRequest;
|
||||
use PKP\mail\mailables\ReviewRequestSubsequent;
|
||||
use PKP\mail\variables\ReviewAssignmentEmailVariable;
|
||||
use PKP\notification\PKPNotification;
|
||||
use PKP\notification\PKPNotificationManager;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\security\AccessKeyManager;
|
||||
use PKP\security\Validation;
|
||||
use PKP\submission\PKPSubmission;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\submission\reviewRound\ReviewRound;
|
||||
use PKP\submission\reviewRound\ReviewRoundDAO;
|
||||
use PKP\user\User;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
|
||||
class EditorAction
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// Actions.
|
||||
//
|
||||
|
||||
/**
|
||||
* Assigns a reviewer to a submission.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param object $submission
|
||||
* @param int $reviewerId
|
||||
* @param ReviewRound $reviewRound
|
||||
* @param string $reviewDueDate
|
||||
* @param string $responseDueDate
|
||||
* @param null|mixed $reviewMethod
|
||||
*/
|
||||
public function addReviewer($request, $submission, $reviewerId, &$reviewRound, $reviewDueDate, $responseDueDate, $reviewMethod = null)
|
||||
{
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewer = Repo::user()->get($reviewerId);
|
||||
|
||||
// Check to see if the requested reviewer is not already
|
||||
// assigned to review this submission.
|
||||
|
||||
$assigned = $reviewAssignmentDao->reviewerExists($reviewRound->getId(), $reviewerId);
|
||||
|
||||
// Only add the reviewer if he has not already
|
||||
// been assigned to review this submission.
|
||||
$stageId = $reviewRound->getStageId();
|
||||
$round = $reviewRound->getRound();
|
||||
if (!$assigned && isset($reviewer) && !Hook::call('EditorAction::addReviewer', [&$submission, $reviewerId])) {
|
||||
$reviewAssignment = $reviewAssignmentDao->newDataObject();
|
||||
$reviewAssignment->setSubmissionId($submission->getId());
|
||||
$reviewAssignment->setReviewerId($reviewerId);
|
||||
$reviewAssignment->setDateAssigned(Core::getCurrentDate());
|
||||
$reviewAssignment->setStageId($stageId);
|
||||
$reviewAssignment->setRound($round);
|
||||
$reviewAssignment->setReviewRoundId($reviewRound->getId());
|
||||
if (isset($reviewMethod)) {
|
||||
$reviewAssignment->setReviewMethod($reviewMethod);
|
||||
}
|
||||
$reviewAssignmentDao->insertObject($reviewAssignment);
|
||||
|
||||
$this->setDueDates($request, $submission, $reviewAssignment, $reviewDueDate, $responseDueDate);
|
||||
// Add notification
|
||||
$notificationMgr = new NotificationManager();
|
||||
$notificationMgr->createNotification(
|
||||
$request,
|
||||
$reviewerId,
|
||||
PKPNotification::NOTIFICATION_TYPE_REVIEW_ASSIGNMENT,
|
||||
$submission->getContextId(),
|
||||
PKPApplication::ASSOC_TYPE_REVIEW_ASSIGNMENT,
|
||||
$reviewAssignment->getId(),
|
||||
Notification::NOTIFICATION_LEVEL_TASK
|
||||
);
|
||||
|
||||
// Add log
|
||||
$user = $request->getUser();
|
||||
$eventLog = Repo::eventLog()->newDataObject([
|
||||
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
|
||||
'assocId' => $submission->getId(),
|
||||
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_ASSIGN,
|
||||
'userId' => Validation::loggedInAs() ?? $user->getId(),
|
||||
'message' => 'log.review.reviewerAssigned',
|
||||
'isTranslated' => false,
|
||||
'dateLogged' => Core::getCurrentDate(),
|
||||
'reviewAssignment' => $reviewAssignment->getId(),
|
||||
'reviewerName' => $reviewer->getFullName(),
|
||||
'submissionId' => $submission->getId(),
|
||||
'stageId' => $stageId,
|
||||
'round' => $round
|
||||
]);
|
||||
Repo::eventLog()->add($eventLog);
|
||||
|
||||
// Send mail
|
||||
if (!$request->getUserVar('skipEmail')) {
|
||||
$context = PKPServices::get('context')->get($submission->getData('contextId'));
|
||||
$emailTemplate = Repo::emailTemplate()->getByKey($submission->getData('contextId'), $request->getUserVar('template'));
|
||||
$emailBody = $request->getUserVar('personalMessage');
|
||||
$emailSubject = $emailTemplate->getLocalizedData('subject');
|
||||
$mailable = $this->createMail($submission, $reviewAssignment, $reviewer, $user, $emailBody, $emailSubject, $context);
|
||||
|
||||
try {
|
||||
Mail::send($mailable);
|
||||
} catch (TransportException $e) {
|
||||
$notificationMgr = new PKPNotificationManager();
|
||||
$notificationMgr->createTrivialNotification(
|
||||
$user->getId(),
|
||||
PKPNotification::NOTIFICATION_TYPE_ERROR,
|
||||
['contents' => __('email.compose.error')]
|
||||
);
|
||||
trigger_error('Failed to send email: ' . $e->getMessage(), E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the due date for a review assignment.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param Submission $submission
|
||||
* @param ReviewAssignment $reviewAssignment
|
||||
* @param string $reviewDueDate
|
||||
* @param string $responseDueDate
|
||||
* @param bool $logEntry
|
||||
*/
|
||||
public function setDueDates($request, $submission, $reviewAssignment, $reviewDueDate, $responseDueDate, $logEntry = false)
|
||||
{
|
||||
$context = $request->getContext();
|
||||
|
||||
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId());
|
||||
if (!isset($reviewer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($reviewAssignment->getSubmissionId() == $submission->getId() && !Hook::call('EditorAction::setDueDates', [&$reviewAssignment, &$reviewer, &$reviewDueDate, &$responseDueDate])) {
|
||||
// Set the review due date
|
||||
$defaultNumWeeks = $context->getData('numWeeksPerReview');
|
||||
$reviewAssignment->setDateDue($reviewDueDate);
|
||||
|
||||
// Set the response due date
|
||||
$defaultNumWeeks = $context->getData('numWeeksPerResponse');
|
||||
$reviewAssignment->setDateResponseDue($responseDueDate);
|
||||
|
||||
// update the assignment (with both the new dates)
|
||||
$reviewAssignment->stampModified();
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignmentDao->updateObject($reviewAssignment);
|
||||
|
||||
// N.B. Only logging Date Due
|
||||
if ($logEntry) {
|
||||
// Add log
|
||||
$eventLog = Repo::eventLog()->newDataObject([
|
||||
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
|
||||
'assocId' => $submission->getId(),
|
||||
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_SET_DUE_DATE,
|
||||
'userId' => Validation::loggedInAs() ?? $request->getUser()->getId(),
|
||||
'message' => 'log.review.reviewDueDateSet',
|
||||
'isTranslated' => false,
|
||||
'dateLogged' => Core::getCurrentDate(),
|
||||
'reviewAssignmentId' => $reviewAssignment->getId(),
|
||||
'reviewerName' => $reviewer->getFullName(),
|
||||
'reviewDueDate' => date(
|
||||
PKPString::convertStrftimeFormat($context->getLocalizedDateFormatShort()),
|
||||
strtotime($reviewAssignment->getDateDue())
|
||||
),
|
||||
'submissionId' => $submission->getId(),
|
||||
'stageId' => $reviewAssignment->getStageId(),
|
||||
'round' => $reviewAssignment->getRound()
|
||||
]);
|
||||
Repo::eventLog()->add($eventLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an email representation based on data entered by the editor to the ReviewerForm
|
||||
* Associated templates: REVIEW_REQUEST, REVIEW_REQUEST_SUBSEQUENT
|
||||
*/
|
||||
protected function createMail(
|
||||
PKPSubmission $submission,
|
||||
ReviewAssignment $reviewAssignment,
|
||||
User $reviewer,
|
||||
User $sender,
|
||||
string $emailBody,
|
||||
string $emailSubject,
|
||||
Context $context
|
||||
): ReviewRequest|ReviewRequestSubsequent {
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
|
||||
$reviewRound = $reviewRoundDao->getById($reviewAssignment->getReviewRoundId());
|
||||
$mailable = $reviewRound->getRound() == 1 ?
|
||||
new ReviewRequest($context, $submission, $reviewAssignment) :
|
||||
new ReviewRequestSubsequent($context, $submission, $reviewAssignment);
|
||||
|
||||
if ($context->getData('reviewerAccessKeysEnabled')) {
|
||||
$accessKeyManager = new AccessKeyManager();
|
||||
$expiryDays = ($context->getData('numWeeksPerReview') + 4) * 7;
|
||||
$accessKey = $accessKeyManager->createKey($context->getId(), $reviewer->getId(), $reviewAssignment->getId(), $expiryDays);
|
||||
$mailable->buildViewDataUsing(function () use ($context, $reviewAssignment, $accessKey) {
|
||||
return [
|
||||
ReviewAssignmentEmailVariable::REVIEW_ASSIGNMENT_URL => PKPApplication::get()->getDispatcher()->url(
|
||||
PKPApplication::get()->getRequest(),
|
||||
PKPApplication::ROUTE_PAGE,
|
||||
$context->getData('urlPath'),
|
||||
'reviewer',
|
||||
'submission',
|
||||
null,
|
||||
[
|
||||
'submissionId' => $reviewAssignment->getSubmissionId(),
|
||||
'reviewId' => $reviewAssignment->getId(),
|
||||
'key' => $accessKey,
|
||||
]
|
||||
)
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
$mailable
|
||||
->body($emailBody)
|
||||
->subject($emailSubject)
|
||||
->sender($sender)
|
||||
->recipients([$reviewer]);
|
||||
|
||||
// Additional template variable
|
||||
$mailable->addData([
|
||||
'reviewerName' => $mailable->viewData['userFullName'] ?? null,
|
||||
'reviewerUserName' => $mailable->viewData['username'] ?? null,
|
||||
]);
|
||||
|
||||
return $mailable;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\action\EditorAction', '\EditorAction');
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,884 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/reviewAssignment/ReviewAssignment.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReviewAssignment
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see ReviewAssignmentDAO
|
||||
*
|
||||
* @brief Describes review assignment properties.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewAssignment;
|
||||
|
||||
use PKP\core\Core;
|
||||
|
||||
class ReviewAssignment extends \PKP\core\DataObject
|
||||
{
|
||||
public const SUBMISSION_REVIEWER_RECOMMENDATION_ACCEPT = 1;
|
||||
public const SUBMISSION_REVIEWER_RECOMMENDATION_PENDING_REVISIONS = 2;
|
||||
public const SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_HERE = 3;
|
||||
public const SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_ELSEWHERE = 4;
|
||||
public const SUBMISSION_REVIEWER_RECOMMENDATION_DECLINE = 5;
|
||||
public const SUBMISSION_REVIEWER_RECOMMENDATION_SEE_COMMENTS = 6;
|
||||
|
||||
public const SUBMISSION_REVIEWER_RATING_VERY_GOOD = 5;
|
||||
public const SUBMISSION_REVIEWER_RATING_GOOD = 4;
|
||||
public const SUBMISSION_REVIEWER_RATING_AVERAGE = 3;
|
||||
public const SUBMISSION_REVIEWER_RATING_POOR = 2;
|
||||
public const SUBMISSION_REVIEWER_RATING_VERY_POOR = 1;
|
||||
|
||||
public const SUBMISSION_REVIEW_METHOD_ANONYMOUS = 1;
|
||||
public const SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS = 2;
|
||||
public const SUBMISSION_REVIEW_METHOD_OPEN = 3;
|
||||
|
||||
public const REVIEW_ASSIGNMENT_NEW = 0; // Has never been considered by an editor, review assignment just created
|
||||
public const REVIEW_ASSIGNMENT_CONSIDERED = 3; // Has been marked considered by an editor
|
||||
public const REVIEW_ASSIGNMENT_UNCONSIDERED = 1; // Considered status has been revoked by an editor and is awaiting re-confirmation by an editor
|
||||
public const REVIEW_ASSIGNMENT_RECONSIDERED = 2; // Considered status has been granted again by an editor
|
||||
|
||||
public const REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE = 0; // request has been sent but reviewer has not responded
|
||||
public const REVIEW_ASSIGNMENT_STATUS_DECLINED = 1; // reviewer declined review request
|
||||
public const REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE = 4; // review not responded within due date
|
||||
public const REVIEW_ASSIGNMENT_STATUS_ACCEPTED = 5; // reviewer has agreed to the review
|
||||
public const REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE = 6; // review not submitted within due date
|
||||
public const REVIEW_ASSIGNMENT_STATUS_RECEIVED = 7; // review has been submitted
|
||||
public const REVIEW_ASSIGNMENT_STATUS_COMPLETE = 8; // review has been confirmed by an editor
|
||||
public const REVIEW_ASSIGNMENT_STATUS_THANKED = 9; // reviewer has been thanked
|
||||
public const REVIEW_ASSIGNMENT_STATUS_CANCELLED = 10; // reviewer cancelled review request
|
||||
public const REVIEW_ASSIGNMENT_STATUS_REQUEST_RESEND = 11; // request resent to reviewer after they declined
|
||||
|
||||
/**
|
||||
* All review assignment statuses that indicate a
|
||||
* review was completed
|
||||
*
|
||||
* @var array<int>
|
||||
*/
|
||||
public const REVIEW_COMPLETE_STATUSES = [
|
||||
self::REVIEW_ASSIGNMENT_STATUS_RECEIVED,
|
||||
self::REVIEW_ASSIGNMENT_STATUS_COMPLETE,
|
||||
self::REVIEW_ASSIGNMENT_STATUS_THANKED,
|
||||
];
|
||||
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get ID of review assignment's submission.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSubmissionId()
|
||||
{
|
||||
return $this->getData('submissionId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID of review assignment's submission
|
||||
*
|
||||
* @param int $submissionId
|
||||
*/
|
||||
public function setSubmissionId($submissionId)
|
||||
{
|
||||
$this->setData('submissionId', $submissionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID of reviewer.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getReviewerId()
|
||||
{
|
||||
return $this->getData('reviewerId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID of reviewer.
|
||||
*
|
||||
* @param int $reviewerId
|
||||
*/
|
||||
public function setReviewerId($reviewerId)
|
||||
{
|
||||
$this->setData('reviewerId', $reviewerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full name of reviewer.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReviewerFullName()
|
||||
{
|
||||
return $this->getData('reviewerFullName');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set full name of reviewer.
|
||||
*
|
||||
* @param string $reviewerFullName
|
||||
*/
|
||||
public function setReviewerFullName($reviewerFullName)
|
||||
{
|
||||
$this->setData('reviewerFullName', $reviewerFullName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reviewer comments.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComments()
|
||||
{
|
||||
return $this->getData('comments');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reviewer comments.
|
||||
*
|
||||
* @param string $comments
|
||||
*/
|
||||
public function setComments($comments)
|
||||
{
|
||||
$this->setData('comments', $comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get competing interests.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCompetingInterests()
|
||||
{
|
||||
return $this->getData('competingInterests');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set competing interests.
|
||||
*
|
||||
* @param string $competingInterests
|
||||
*/
|
||||
public function setCompetingInterests($competingInterests)
|
||||
{
|
||||
$this->setData('competingInterests', $competingInterests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the workflow stage id.
|
||||
*
|
||||
* @return int WORKFLOW_STAGE_ID_...
|
||||
*/
|
||||
public function getStageId()
|
||||
{
|
||||
return $this->getData('stageId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the workflow stage id.
|
||||
*
|
||||
* @param int $stageId WORKFLOW_STAGE_ID_...
|
||||
*/
|
||||
public function setStageId($stageId)
|
||||
{
|
||||
$this->setData('stageId', $stageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method of the review (open, anonymous, or double-anonymous).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getReviewMethod()
|
||||
{
|
||||
return $this->getData('reviewMethod');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of review.
|
||||
*
|
||||
* @param int $method
|
||||
*/
|
||||
public function setReviewMethod($method)
|
||||
{
|
||||
$this->setData('reviewMethod', $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get review round id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getReviewRoundId()
|
||||
{
|
||||
return $this->getData('reviewRoundId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set review round id.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
*/
|
||||
public function setReviewRoundId($reviewRoundId)
|
||||
{
|
||||
$this->setData('reviewRoundId', $reviewRoundId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reviewer recommendation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRecommendation()
|
||||
{
|
||||
return $this->getData('recommendation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reviewer recommendation.
|
||||
*
|
||||
* @param string $recommendation
|
||||
*/
|
||||
public function setRecommendation($recommendation)
|
||||
{
|
||||
$this->setData('recommendation', $recommendation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get considered state.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getConsidered()
|
||||
{
|
||||
return $this->getData('considered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set considered state.
|
||||
*
|
||||
* @param int $considered
|
||||
*/
|
||||
public function setConsidered($considered)
|
||||
{
|
||||
$this->setData('considered', $considered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date the reviewer was rated.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateRated()
|
||||
{
|
||||
return $this->getData('dateRated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date the reviewer was rated.
|
||||
*
|
||||
* @param string $dateRated
|
||||
*/
|
||||
public function setDateRated($dateRated)
|
||||
{
|
||||
$this->setData('dateRated', $dateRated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date of the last modification.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->getData('lastModified');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date of the last modification.
|
||||
*
|
||||
* @param string $dateModified
|
||||
*/
|
||||
public function setLastModified($dateModified)
|
||||
{
|
||||
$this->setData('lastModified', $dateModified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stamp the date of the last modification to the current time.
|
||||
*/
|
||||
public function stampModified()
|
||||
{
|
||||
return $this->setLastModified(Core::getCurrentDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's assigned date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateAssigned()
|
||||
{
|
||||
return $this->getData('dateAssigned');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's assigned date.
|
||||
*
|
||||
* @param string $dateAssigned
|
||||
*/
|
||||
public function setDateAssigned($dateAssigned)
|
||||
{
|
||||
$this->setData('dateAssigned', $dateAssigned);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's notified date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateNotified()
|
||||
{
|
||||
return $this->getData('dateNotified');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's notified date.
|
||||
*
|
||||
* @param string $dateNotified
|
||||
*/
|
||||
public function setDateNotified($dateNotified)
|
||||
{
|
||||
$this->setData('dateNotified', $dateNotified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's confirmed date.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDateConfirmed()
|
||||
{
|
||||
return $this->getData('dateConfirmed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's confirmed date.
|
||||
*
|
||||
* @param string|null $dateConfirmed
|
||||
*/
|
||||
public function setDateConfirmed($dateConfirmed)
|
||||
{
|
||||
$this->setData('dateConfirmed', $dateConfirmed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's completed date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateCompleted()
|
||||
{
|
||||
return $this->getData('dateCompleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's completed date.
|
||||
*
|
||||
* @param string $dateCompleted
|
||||
*/
|
||||
public function setDateCompleted($dateCompleted)
|
||||
{
|
||||
$this->setData('dateCompleted', $dateCompleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's acknowledged date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateAcknowledged()
|
||||
{
|
||||
return $this->getData('dateAcknowledged');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's acknowledged date.
|
||||
*
|
||||
* @param string $dateAcknowledged
|
||||
*/
|
||||
public function setDateAcknowledged($dateAcknowledged)
|
||||
{
|
||||
$this->setData('dateAcknowledged', $dateAcknowledged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's last reminder date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateReminded()
|
||||
{
|
||||
return $this->getData('dateReminded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's last reminder date.
|
||||
*
|
||||
* @param string $dateReminded
|
||||
*/
|
||||
public function setDateReminded($dateReminded)
|
||||
{
|
||||
$this->setData('dateReminded', $dateReminded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's due date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateDue()
|
||||
{
|
||||
return $this->getData('dateDue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's due date.
|
||||
*
|
||||
* @param string $dateDue
|
||||
*/
|
||||
public function setDateDue($dateDue)
|
||||
{
|
||||
$this->setData('dateDue', $dateDue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's response due date.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateResponseDue()
|
||||
{
|
||||
return $this->getData('dateResponseDue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's response due date.
|
||||
*
|
||||
* @param string $dateResponseDue
|
||||
*/
|
||||
public function setDateResponseDue($dateResponseDue)
|
||||
{
|
||||
$this->setData('dateResponseDue', $dateResponseDue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the declined value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getDeclined()
|
||||
{
|
||||
return $this->getData('declined');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's declined value.
|
||||
*
|
||||
* @param bool $declined
|
||||
*/
|
||||
public function setDeclined($declined)
|
||||
{
|
||||
$this->setData('declined', $declined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cancelled value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCancelled()
|
||||
{
|
||||
return $this->getData('cancelled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's cancelled value.
|
||||
*
|
||||
* @param bool $cancelled
|
||||
*/
|
||||
public function setCancelled($cancelled)
|
||||
{
|
||||
$this->setData('cancelled', $cancelled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer's request resent value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getRequestResent()
|
||||
{
|
||||
return $this->getData('request_resent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the reviewer's request resent value.
|
||||
*
|
||||
* @param bool $resent
|
||||
*/
|
||||
public function setRequestResent($resent)
|
||||
{
|
||||
$this->setData('request_resent', $resent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a boolean indicating whether or not the last reminder was automatic.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getReminderWasAutomatic()
|
||||
{
|
||||
return $this->getData('reminderWasAutomatic') == 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the boolean indicating whether or not the last reminder was automatic.
|
||||
*
|
||||
* @param bool $wasAutomatic
|
||||
*/
|
||||
public function setReminderWasAutomatic($wasAutomatic)
|
||||
{
|
||||
$this->setData('reminderWasAutomatic', $wasAutomatic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quality.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getQuality()
|
||||
{
|
||||
return $this->getData('quality');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set quality.
|
||||
*
|
||||
* @param int|null $quality
|
||||
*/
|
||||
public function setQuality($quality)
|
||||
{
|
||||
$this->setData('quality', $quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get round.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRound()
|
||||
{
|
||||
return $this->getData('round');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set round.
|
||||
*
|
||||
* @param int $round
|
||||
*/
|
||||
public function setRound($round)
|
||||
{
|
||||
$this->setData('round', $round);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get step.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStep()
|
||||
{
|
||||
return $this->getData('step');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set step.
|
||||
*
|
||||
* @param int $step
|
||||
*/
|
||||
public function setStep($step)
|
||||
{
|
||||
$this->setData('step', $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get review form id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getReviewFormId()
|
||||
{
|
||||
return $this->getData('reviewFormId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set review form id.
|
||||
*
|
||||
* @param int $reviewFormId
|
||||
*/
|
||||
public function setReviewFormId($reviewFormId)
|
||||
{
|
||||
$this->setData('reviewFormId', $reviewFormId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current status of this review assignment
|
||||
*
|
||||
* @return int ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_...
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
if ($this->getDeclined()) {
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_DECLINED;
|
||||
}
|
||||
if ($this->getCancelled()) {
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_CANCELLED;
|
||||
}
|
||||
|
||||
if (!$this->getDeclined() && !$this->getDateConfirmed() && $this->getRequestResent()) {
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_REQUEST_RESEND;
|
||||
}
|
||||
|
||||
if (!$this->getDateCompleted()) {
|
||||
$dueTimes = array_map(function ($dateTime) {
|
||||
// If no due time, set it to the end of the day
|
||||
if (substr($dateTime, 11) === '00:00:00') {
|
||||
$dateTime = substr($dateTime, 0, 11) . '23:59:59';
|
||||
}
|
||||
return strtotime($dateTime);
|
||||
}, [$this->getDateResponseDue(), $this->getDateDue()]);
|
||||
$responseDueTime = $dueTimes[0];
|
||||
$reviewDueTime = $dueTimes[1];
|
||||
if (!$this->getDateConfirmed()) { // no response
|
||||
if ($responseDueTime < time()) { // response overdue
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE;
|
||||
} elseif ($reviewDueTime < strtotime('tomorrow')) { // review overdue but not response
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE;
|
||||
} else { // response not due yet
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE;
|
||||
}
|
||||
} else { // response given
|
||||
if ($reviewDueTime < strtotime('tomorrow')) { // review due
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE;
|
||||
} else {
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_ACCEPTED;
|
||||
}
|
||||
}
|
||||
} elseif ($this->getDateAcknowledged()) { // reviewer thanked...
|
||||
if ($this->getConsidered() == self::REVIEW_ASSIGNMENT_UNCONSIDERED) { // ...but review later unconsidered
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_RECEIVED;
|
||||
}
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_THANKED;
|
||||
} elseif ($this->getDateCompleted()) { // review submitted...
|
||||
if ($this->getConsidered() != self::REVIEW_ASSIGNMENT_UNCONSIDERED && $this->isRead()) { // ...and confirmed by an editor
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_COMPLETE;
|
||||
}
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_RECEIVED;
|
||||
}
|
||||
|
||||
return self::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an editorial user has read this review
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRead()
|
||||
{
|
||||
if ($this->getConsidered() === self::REVIEW_ASSIGNMENT_CONSIDERED || $this->getConsidered() === self::REVIEW_ASSIGNMENT_RECONSIDERED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translation key for the current status
|
||||
*
|
||||
* @param int $status Optionally pass a status to retrieve a specific key.
|
||||
* Default will return the key for the current status.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatusKey($status = null)
|
||||
{
|
||||
if (is_null($status)) {
|
||||
$status = $this->getStatus();
|
||||
}
|
||||
|
||||
switch ($status) {
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE:
|
||||
return 'submission.review.status.awaitingResponse';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_CANCELLED:
|
||||
return 'common.cancelled';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_DECLINED:
|
||||
return 'submission.review.status.declined';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE:
|
||||
return 'submission.review.status.responseOverdue';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE:
|
||||
return 'submission.review.status.reviewOverdue';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_ACCEPTED:
|
||||
return 'submission.review.status.accepted';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_RECEIVED:
|
||||
return 'submission.review.status.received';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_COMPLETE:
|
||||
return 'submission.review.status.complete';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_THANKED:
|
||||
return 'submission.review.status.thanked';
|
||||
case self::REVIEW_ASSIGNMENT_STATUS_REQUEST_RESEND:
|
||||
return 'submission.review.status.awaitingResponse';
|
||||
}
|
||||
|
||||
assert(false, 'No status key could be found for ' . get_class($this) . ' on ' . __LINE__);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translation key for the review method
|
||||
*
|
||||
* @param int|null $method Optionally pass a method to retrieve a specific key.
|
||||
* Default will return the key for the current review method
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReviewMethodKey($method = null)
|
||||
{
|
||||
if (is_null($method)) {
|
||||
$method = $this->getReviewMethod();
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case self::SUBMISSION_REVIEW_METHOD_OPEN:
|
||||
return 'editor.submissionReview.open';
|
||||
case self::SUBMISSION_REVIEW_METHOD_ANONYMOUS:
|
||||
return 'editor.submissionReview.anonymous';
|
||||
case self::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS:
|
||||
return 'editor.submissionReview.doubleAnonymous';
|
||||
}
|
||||
|
||||
assert(false, 'No review method key could be found for ' . get_class($this) . ' on ' . __LINE__);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
//
|
||||
// Files
|
||||
//
|
||||
|
||||
/**
|
||||
* Get number of weeks until review is due (or number of weeks overdue).
|
||||
*
|
||||
* @return ?int
|
||||
*/
|
||||
public function getWeeksDue()
|
||||
{
|
||||
$dateDue = $this->getDateDue();
|
||||
if ($dateDue === null) {
|
||||
return null;
|
||||
}
|
||||
return round((strtotime($dateDue) - time()) / (86400 * 7.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an associative array matching reviewer recommendation codes with locale strings.
|
||||
* (Includes default '' => "Choose One" string.)
|
||||
*
|
||||
* @return array recommendation => localeString
|
||||
*/
|
||||
public static function getReviewerRecommendationOptions()
|
||||
{
|
||||
static $reviewerRecommendationOptions = [
|
||||
'' => 'common.chooseOne',
|
||||
self::SUBMISSION_REVIEWER_RECOMMENDATION_ACCEPT => 'reviewer.article.decision.accept',
|
||||
self::SUBMISSION_REVIEWER_RECOMMENDATION_PENDING_REVISIONS => 'reviewer.article.decision.pendingRevisions',
|
||||
self::SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_HERE => 'reviewer.article.decision.resubmitHere',
|
||||
self::SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_ELSEWHERE => 'reviewer.article.decision.resubmitElsewhere',
|
||||
self::SUBMISSION_REVIEWER_RECOMMENDATION_DECLINE => 'reviewer.article.decision.decline',
|
||||
self::SUBMISSION_REVIEWER_RECOMMENDATION_SEE_COMMENTS => 'reviewer.article.decision.seeComments'
|
||||
];
|
||||
return $reviewerRecommendationOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a localized string representing the reviewer recommendation.
|
||||
*/
|
||||
public function getLocalizedRecommendation()
|
||||
{
|
||||
$options = self::getReviewerRecommendationOptions();
|
||||
if (array_key_exists($this->getRecommendation(), $options)) {
|
||||
return __($options[$this->getRecommendation()]);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if can resend request to reconsider review for this review assignment
|
||||
*/
|
||||
public function canResendReviewRequest(): bool
|
||||
{
|
||||
if ($this->getCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->getDeclined()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewAssignment\ReviewAssignment', '\ReviewAssignment');
|
||||
foreach ([
|
||||
'SUBMISSION_REVIEWER_RECOMMENDATION_ACCEPT',
|
||||
'SUBMISSION_REVIEWER_RECOMMENDATION_PENDING_REVISIONS',
|
||||
'SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_HERE',
|
||||
'SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_ELSEWHERE',
|
||||
'SUBMISSION_REVIEWER_RECOMMENDATION_DECLINE',
|
||||
'SUBMISSION_REVIEWER_RECOMMENDATION_SEE_COMMENTS',
|
||||
'SUBMISSION_REVIEWER_RATING_VERY_GOOD',
|
||||
'SUBMISSION_REVIEWER_RATING_GOOD',
|
||||
'SUBMISSION_REVIEWER_RATING_AVERAGE',
|
||||
'SUBMISSION_REVIEWER_RATING_POOR',
|
||||
'SUBMISSION_REVIEWER_RATING_VERY_POOR',
|
||||
'SUBMISSION_REVIEW_METHOD_ANONYMOUS',
|
||||
'SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS',
|
||||
'SUBMISSION_REVIEW_METHOD_OPEN',
|
||||
'REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE',
|
||||
'REVIEW_ASSIGNMENT_STATUS_DECLINED',
|
||||
'REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE',
|
||||
'REVIEW_ASSIGNMENT_STATUS_ACCEPTED',
|
||||
'REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE',
|
||||
'REVIEW_ASSIGNMENT_STATUS_RECEIVED',
|
||||
'REVIEW_ASSIGNMENT_STATUS_COMPLETE',
|
||||
'REVIEW_ASSIGNMENT_STATUS_THANKED',
|
||||
'REVIEW_ASSIGNMENT_STATUS_CANCELLED',
|
||||
'REVIEW_ASSIGNMENT_STATUS_REQUEST_RESEND',
|
||||
] as $constantName) {
|
||||
if (!defined($constantName)) {
|
||||
define($constantName, constant('\PKP\submission\reviewAssignment\ReviewAssignment::' . $constantName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/reviewAssignment/ReviewAssignmentDAO.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 ReviewAssignmentDAO
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @see ReviewAssignment
|
||||
*
|
||||
* @brief Class for DAO relating reviewers to submissions.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewAssignment;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\notification\NotificationDAO;
|
||||
use PKP\reviewForm\ReviewFormResponseDAO;
|
||||
use PKP\submission\ReviewFilesDAO;
|
||||
use PKP\submission\reviewRound\ReviewRoundDAO;
|
||||
|
||||
class ReviewAssignmentDAO extends \PKP\db\DAO
|
||||
{
|
||||
/**
|
||||
* Retrieve review assignments for the passed review round id.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getByReviewRoundId($reviewRoundId)
|
||||
{
|
||||
$params = [(int)$reviewRoundId];
|
||||
$query = $this->_getSelectQuery() .
|
||||
' WHERE r.review_round_id = ? ORDER BY review_id';
|
||||
return $this->_getReviewAssignmentsArray($query, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve open review assignments for the passed review round id.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOpenReviewsByReviewRoundId($reviewRoundId)
|
||||
{
|
||||
$params = [(int)$reviewRoundId, ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN];
|
||||
$query = $this->_getSelectQuery() .
|
||||
' WHERE r.review_round_id = ? AND r.review_method = ? AND r.date_confirmed IS NOT NULL AND r.declined <> 1 ORDER BY review_id';
|
||||
return $this->_getReviewAssignmentsArray($query, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve review assignments from table using the passed
|
||||
* sql query and parameters.
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $queryParams
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function _getReviewAssignmentsArray($query, $queryParams)
|
||||
{
|
||||
$result = $this->retrieve($query, $queryParams);
|
||||
|
||||
$reviewAssignments = [];
|
||||
foreach ($result as $row) {
|
||||
$reviewAssignments[$row->review_id] = $this->_fromRow((array) $row);
|
||||
}
|
||||
return $reviewAssignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the review_rounds join string. Must be implemented
|
||||
* by subclasses.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReviewRoundJoin()
|
||||
{
|
||||
return 'r.review_round_id = r2.review_round_id';
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Public methods.
|
||||
//
|
||||
/**
|
||||
* Retrieve a review assignment by review round and reviewer.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
* @param int $reviewerId
|
||||
*
|
||||
* @return ReviewAssignment
|
||||
*/
|
||||
public function getReviewAssignment($reviewRoundId, $reviewerId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
$this->_getSelectQuery() .
|
||||
' WHERE r.review_round_id = ? AND
|
||||
r.reviewer_id = ?',
|
||||
[
|
||||
(int) $reviewRoundId,
|
||||
(int) $reviewerId
|
||||
]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a review assignment by review assignment id.
|
||||
*
|
||||
* @param int $reviewId
|
||||
*
|
||||
* @return ReviewAssignment
|
||||
*/
|
||||
public function getById($reviewId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT r.*, r2.review_revision
|
||||
FROM review_assignments r
|
||||
LEFT JOIN review_rounds r2 ON (' . $this->getReviewRoundJoin() . ')
|
||||
WHERE r.review_id = ?',
|
||||
[(int) $reviewId]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all incomplete review assignments for all journals/conferences/presses
|
||||
*
|
||||
* @return array ReviewAssignments
|
||||
*/
|
||||
public function getIncompleteReviewAssignments()
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT r.*, r2.review_revision
|
||||
FROM review_assignments r
|
||||
LEFT JOIN review_rounds r2 ON (' . $this->getReviewRoundJoin() . ')
|
||||
WHERE' . $this->getIncompleteReviewAssignmentsWhereString() .
|
||||
' ORDER BY r.submission_id'
|
||||
);
|
||||
|
||||
$reviewAssignments = [];
|
||||
foreach ($result as $row) {
|
||||
$reviewAssignments[] = $this->_fromRow((array) $row);
|
||||
}
|
||||
return $reviewAssignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WHERE sql string to filter incomplete review
|
||||
* assignments.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIncompleteReviewAssignmentsWhereString()
|
||||
{
|
||||
return ' r.date_notified IS NOT NULL AND
|
||||
r.date_completed IS NULL AND
|
||||
r.declined <> 1 AND
|
||||
r.cancelled <> 1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all review assignments for a submission.
|
||||
*
|
||||
* @param int $submissionId Submission ID
|
||||
* @param int $reviewRoundId Review round ID
|
||||
* @param int $stageId Optional stage ID
|
||||
*
|
||||
* @return array ReviewAssignments
|
||||
*/
|
||||
public function getBySubmissionId($submissionId, $reviewRoundId = null, $stageId = null)
|
||||
{
|
||||
$query = $this->_getSelectQuery() .
|
||||
' WHERE r.submission_id = ?';
|
||||
|
||||
$orderBy = ' ORDER BY review_id';
|
||||
|
||||
$queryParams[] = (int) $submissionId;
|
||||
|
||||
if ($reviewRoundId != null) {
|
||||
$query .= ' AND r2.review_round_id = ?';
|
||||
$queryParams[] = (int) $reviewRoundId;
|
||||
} else {
|
||||
$orderBy .= ', r2.review_round_id';
|
||||
}
|
||||
|
||||
if ($stageId != null) {
|
||||
$query .= ' AND r2.stage_id = ?';
|
||||
$queryParams[] = (int) $stageId;
|
||||
} else {
|
||||
$orderBy .= ', r2.stage_id';
|
||||
}
|
||||
|
||||
$query .= $orderBy;
|
||||
|
||||
return $this->_getReviewAssignmentsArray($query, $queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all review assignments by submission and reviewer.
|
||||
*
|
||||
* @param int $submissionId
|
||||
* @param int $reviewerId
|
||||
* @param int $stageId optional
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBySubmissionReviewer($submissionId, $reviewerId, $stageId = null)
|
||||
{
|
||||
$query = $this->_getSelectQuery() .
|
||||
' WHERE r.submission_id = ? AND r.reviewer_id = ?';
|
||||
$queryParams = [(int) $submissionId, (int) $reviewerId];
|
||||
if ($stageId != null) {
|
||||
$query .= ' AND r.stage_id = ?';
|
||||
$queryParams[] = (int) $stageId;
|
||||
}
|
||||
return $this->_getReviewAssignmentsArray($query, $queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all review assignments for a reviewer.
|
||||
*
|
||||
* @param int $userId
|
||||
*
|
||||
* @return array ReviewAssignments
|
||||
*/
|
||||
public function getByUserId($userId)
|
||||
{
|
||||
$reviewRoundJoinString = $this->getReviewRoundJoin();
|
||||
|
||||
if (!$reviewRoundJoinString) {
|
||||
throw new Exception('Review round join string not specified');
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT r.*, r2.review_revision
|
||||
FROM review_assignments r
|
||||
LEFT JOIN review_rounds r2 ON (' . $reviewRoundJoinString . ')
|
||||
WHERE r.reviewer_id = ?
|
||||
ORDER BY round, review_id',
|
||||
[(int) $userId]
|
||||
);
|
||||
|
||||
$reviewAssignments = [];
|
||||
foreach ($result as $row) {
|
||||
$reviewAssignments[] = $this->_fromRow((array) $row);
|
||||
}
|
||||
|
||||
return $reviewAssignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a reviewer is assigned to a specified submission.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
* @param int $reviewerId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function reviewerExists($reviewRoundId, $reviewerId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT COUNT(*) AS row_count
|
||||
FROM review_assignments
|
||||
WHERE review_round_id = ? AND
|
||||
reviewer_id = ?',
|
||||
[(int) $reviewRoundId, (int) $reviewerId]
|
||||
);
|
||||
$row = (array) $result->current();
|
||||
return $row && $row['row_count'] == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all review assignments for a review form.
|
||||
*
|
||||
* @param int $reviewFormId
|
||||
*
|
||||
* @return array ReviewAssignments
|
||||
*/
|
||||
public function getByReviewFormId($reviewFormId)
|
||||
{
|
||||
$reviewRoundJoinString = $this->getReviewRoundJoin();
|
||||
|
||||
if (!$reviewRoundJoinString) {
|
||||
throw new Exception('Review round join string not specified');
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT r.*, r2.review_revision
|
||||
FROM review_assignments r
|
||||
LEFT JOIN review_rounds r2 ON (' . $reviewRoundJoinString . ')
|
||||
WHERE r.review_form_id = ?
|
||||
ORDER BY round, review_id',
|
||||
[(int) $reviewFormId]
|
||||
);
|
||||
|
||||
$reviewAssignments = [];
|
||||
foreach ($result as $row) {
|
||||
$reviewAssignments[] = $this->_fromRow((array) $row);
|
||||
}
|
||||
return $reviewAssignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the order of active reviews for the given round of the given submission
|
||||
*
|
||||
* @param int $submissionId Submission ID
|
||||
* @param int $reviewRoundId Review round ID
|
||||
*
|
||||
* @return array Associating review ID with number, i.e. if review ID 26 is first returned['26']=0.
|
||||
*/
|
||||
public function getReviewIndexesForRound($submissionId, $reviewRoundId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT review_id
|
||||
FROM review_assignments
|
||||
WHERE submission_id = ? AND
|
||||
review_round_id = ?
|
||||
ORDER BY review_id',
|
||||
[(int) $submissionId, (int) $reviewRoundId]
|
||||
);
|
||||
|
||||
$index = 0;
|
||||
$returner = [];
|
||||
foreach ($result as $row) {
|
||||
$returner[$row->review_id] = $index++;
|
||||
}
|
||||
return $returner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new Review Assignment.
|
||||
*
|
||||
* @param ReviewAssignment $reviewAssignment
|
||||
*/
|
||||
public function insertObject($reviewAssignment)
|
||||
{
|
||||
$result = $this->update(
|
||||
sprintf(
|
||||
'INSERT INTO review_assignments (
|
||||
submission_id,
|
||||
reviewer_id,
|
||||
stage_id,
|
||||
review_method,
|
||||
round,
|
||||
step,
|
||||
competing_interests,
|
||||
recommendation,
|
||||
declined,
|
||||
cancelled,
|
||||
date_assigned, date_notified, date_confirmed,
|
||||
date_completed, date_acknowledged, date_due, date_response_due,
|
||||
quality, date_rated,
|
||||
last_modified,
|
||||
date_reminded, reminder_was_automatic,
|
||||
review_form_id,
|
||||
review_round_id,
|
||||
considered,
|
||||
request_resent
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, %s, %s, %s, %s, %s, %s, %s, ?, %s, %s, %s, ?, ?, ?, ?, ?
|
||||
)',
|
||||
$this->datetimeToDB($reviewAssignment->getDateAssigned()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateNotified()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateConfirmed()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateCompleted()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateAcknowledged()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateDue()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateResponseDue()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateRated()),
|
||||
$this->datetimeToDB($reviewAssignment->getLastModified()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateReminded())
|
||||
),
|
||||
[
|
||||
(int) $reviewAssignment->getSubmissionId(),
|
||||
(int) $reviewAssignment->getReviewerId(),
|
||||
(int) $reviewAssignment->getStageId(),
|
||||
(int) $reviewAssignment->getReviewMethod(),
|
||||
max((int) $reviewAssignment->getRound(), 1),
|
||||
max((int) $reviewAssignment->getStep(), 1),
|
||||
$reviewAssignment->getCompetingInterests(),
|
||||
$reviewAssignment->getRecommendation(),
|
||||
(int) $reviewAssignment->getDeclined(),
|
||||
(int) $reviewAssignment->getCancelled(),
|
||||
$reviewAssignment->getQuality(),
|
||||
(int) $reviewAssignment->getReminderWasAutomatic(),
|
||||
$reviewAssignment->getReviewFormId(),
|
||||
(int) $reviewAssignment->getReviewRoundId(),
|
||||
(int) $reviewAssignment->getConsidered(),
|
||||
(int) $reviewAssignment->getRequestResent(),
|
||||
]
|
||||
);
|
||||
|
||||
$reviewAssignment->setId($this->getInsertId());
|
||||
|
||||
// Update review stage status whenever a review assignment is changed
|
||||
$this->updateReviewRoundStatus($reviewAssignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing review assignment.
|
||||
*
|
||||
* @param ReviewAssignment $reviewAssignment
|
||||
*/
|
||||
public function updateObject($reviewAssignment)
|
||||
{
|
||||
$result = $this->update(
|
||||
sprintf(
|
||||
'UPDATE review_assignments
|
||||
SET submission_id = ?,
|
||||
reviewer_id = ?,
|
||||
stage_id = ?,
|
||||
review_method = ?,
|
||||
round = ?,
|
||||
step = ?,
|
||||
competing_interests = ?,
|
||||
recommendation = ?,
|
||||
declined = ?,
|
||||
cancelled = ?,
|
||||
date_assigned = %s,
|
||||
date_notified = %s,
|
||||
date_confirmed = %s,
|
||||
date_completed = %s,
|
||||
date_acknowledged = %s,
|
||||
date_due = %s,
|
||||
date_response_due = %s,
|
||||
quality = ?,
|
||||
date_rated = %s,
|
||||
last_modified = %s,
|
||||
date_reminded = %s,
|
||||
reminder_was_automatic = ?,
|
||||
review_form_id = ?,
|
||||
review_round_id = ?,
|
||||
considered = ?,
|
||||
request_resent = ?
|
||||
WHERE review_id = ?',
|
||||
$this->datetimeToDB($reviewAssignment->getDateAssigned()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateNotified()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateConfirmed()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateCompleted()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateAcknowledged()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateDue()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateResponseDue()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateRated()),
|
||||
$this->datetimeToDB($reviewAssignment->getLastModified()),
|
||||
$this->datetimeToDB($reviewAssignment->getDateReminded())
|
||||
),
|
||||
[
|
||||
(int) $reviewAssignment->getSubmissionId(),
|
||||
(int) $reviewAssignment->getReviewerId(),
|
||||
(int) $reviewAssignment->getStageId(),
|
||||
(int) $reviewAssignment->getReviewMethod(),
|
||||
(int) $reviewAssignment->getRound(),
|
||||
(int) $reviewAssignment->getStep(),
|
||||
$reviewAssignment->getCompetingInterests(),
|
||||
$reviewAssignment->getRecommendation(),
|
||||
(int) $reviewAssignment->getDeclined(),
|
||||
(int) $reviewAssignment->getCancelled(),
|
||||
$reviewAssignment->getQuality(),
|
||||
$reviewAssignment->getReminderWasAutomatic() ? 1 : 0,
|
||||
$reviewAssignment->getReviewFormId(),
|
||||
(int) $reviewAssignment->getReviewRoundId(),
|
||||
(int) $reviewAssignment->getConsidered(),
|
||||
(int) $reviewAssignment->getRequestResent(),
|
||||
(int) $reviewAssignment->getId(),
|
||||
]
|
||||
);
|
||||
|
||||
// Update review stage status whenever a review assignment is changed
|
||||
$this->updateReviewRoundStatus($reviewAssignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status of the review round an assignment is attached to. This
|
||||
* should be fired whenever a reviewer assignment is modified.
|
||||
*
|
||||
* @param ReviewAssignment $reviewAssignment
|
||||
*/
|
||||
public function updateReviewRoundStatus($reviewAssignment)
|
||||
{
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
|
||||
$reviewRound = $reviewRoundDao->getReviewRound(
|
||||
$reviewAssignment->getSubmissionId(),
|
||||
$reviewAssignment->getStageId(),
|
||||
$reviewAssignment->getRound()
|
||||
);
|
||||
|
||||
// Review round may not exist if submission is being deleted
|
||||
if ($reviewRound) {
|
||||
return $reviewRoundDao->updateStatus($reviewRound);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to return a review assignment object from a row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return ReviewAssignment
|
||||
*/
|
||||
public function _fromRow($row)
|
||||
{
|
||||
$reviewAssignment = $this->newDataObject(); /** @var ReviewAssignment $reviewAssignment */
|
||||
$user = Repo::user()->get($row['reviewer_id'], true);
|
||||
|
||||
$reviewAssignment->setId((int) $row['review_id']);
|
||||
$reviewAssignment->setSubmissionId((int) $row['submission_id']);
|
||||
$reviewAssignment->setReviewerId((int) $row['reviewer_id']);
|
||||
$reviewAssignment->setReviewerFullName($user->getFullName());
|
||||
$reviewAssignment->setCompetingInterests($row['competing_interests']);
|
||||
$reviewAssignment->setRecommendation($row['recommendation']);
|
||||
$reviewAssignment->setDateAssigned($this->datetimeFromDB($row['date_assigned']));
|
||||
$reviewAssignment->setDateNotified($this->datetimeFromDB($row['date_notified']));
|
||||
$reviewAssignment->setDateConfirmed($this->datetimeFromDB($row['date_confirmed']));
|
||||
$reviewAssignment->setDateCompleted($this->datetimeFromDB($row['date_completed']));
|
||||
$reviewAssignment->setDateAcknowledged($this->datetimeFromDB($row['date_acknowledged']));
|
||||
$reviewAssignment->setDateDue($this->datetimeFromDB($row['date_due']));
|
||||
$reviewAssignment->setDateResponseDue($this->datetimeFromDB($row['date_response_due']));
|
||||
$reviewAssignment->setLastModified($this->datetimeFromDB($row['last_modified']));
|
||||
$reviewAssignment->setDeclined((int) $row['declined']);
|
||||
$reviewAssignment->setCancelled((int) $row['cancelled']);
|
||||
$reviewAssignment->setQuality($row['quality']);
|
||||
$reviewAssignment->setDateRated($this->datetimeFromDB($row['date_rated']));
|
||||
$reviewAssignment->setDateReminded($this->datetimeFromDB($row['date_reminded']));
|
||||
$reviewAssignment->setReminderWasAutomatic((int) $row['reminder_was_automatic']);
|
||||
$reviewAssignment->setRound((int) $row['round']);
|
||||
$reviewAssignment->setStep((int) $row['step']);
|
||||
$reviewAssignment->setReviewFormId($row['review_form_id']);
|
||||
$reviewAssignment->setReviewRoundId((int) $row['review_round_id']);
|
||||
$reviewAssignment->setReviewMethod((int) $row['review_method']);
|
||||
$reviewAssignment->setStageId((int) $row['stage_id']);
|
||||
$reviewAssignment->setConsidered((int) $row['considered']);
|
||||
$reviewAssignment->setRequestResent((int) $row['request_resent'] ?? null);
|
||||
|
||||
return $reviewAssignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new review assignment data object.
|
||||
*
|
||||
* @return ReviewAssignment
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new ReviewAssignment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete review assignment.
|
||||
*
|
||||
* @param int $reviewId
|
||||
*/
|
||||
public function deleteById($reviewId)
|
||||
{
|
||||
$reviewFormResponseDao = DAORegistry::getDAO('ReviewFormResponseDAO'); /** @var ReviewFormResponseDAO $reviewFormResponseDao */
|
||||
$reviewFormResponseDao->deleteByReviewId($reviewId);
|
||||
|
||||
$reviewFilesDao = DAORegistry::getDAO('ReviewFilesDAO'); /** @var ReviewFilesDAO $reviewFilesDao */
|
||||
$reviewFilesDao->revokeByReviewId($reviewId);
|
||||
|
||||
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
|
||||
$notificationDao->deleteByAssoc(Application::ASSOC_TYPE_REVIEW_ASSIGNMENT, $reviewId);
|
||||
|
||||
// Retrieve the review assignment before it's deleted, so it can be
|
||||
// be used to fire an update on the review round status.
|
||||
$reviewAssignment = $this->getById($reviewId);
|
||||
|
||||
$result = $this->update('DELETE FROM review_assignments WHERE review_id = ?', [(int) $reviewId]);
|
||||
|
||||
$this->updateReviewRoundStatus($reviewAssignment);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete review assignments by submission ID.
|
||||
*
|
||||
* @param int $submissionId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteBySubmissionId($submissionId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT review_id FROM review_assignments WHERE submission_id = ?',
|
||||
[(int) $submissionId]
|
||||
);
|
||||
|
||||
$returner = false;
|
||||
foreach ($result as $row) {
|
||||
$this->deleteById($row->review_id);
|
||||
$returner = true;
|
||||
}
|
||||
return $returner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last review round review assignment for a given user.
|
||||
*
|
||||
* @param int $submissionId
|
||||
* @param int $reviewerId
|
||||
*
|
||||
* @return ?ReviewAssignment
|
||||
*/
|
||||
public function getLastReviewRoundReviewAssignmentByReviewer($submissionId, $reviewerId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
$this->_getSelectQuery() . ' WHERE r.submission_id = ? AND r.reviewer_id = ? ORDER BY r2.stage_id DESC, r2.round DESC',
|
||||
[(int) $submissionId, (int) $reviewerId]
|
||||
);
|
||||
$row = (array) $result->current();
|
||||
return $row ? $this->_fromRow($row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the review methods translation keys.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getReviewMethodsTranslationKeys()
|
||||
{
|
||||
return [
|
||||
ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS => 'editor.submissionReview.doubleAnonymous',
|
||||
ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS => 'editor.submissionReview.anonymous',
|
||||
ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN => 'editor.submissionReview.open',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sql query to select review assignments.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function _getSelectQuery()
|
||||
{
|
||||
return 'SELECT r.*, r2.review_revision FROM review_assignments r
|
||||
LEFT JOIN review_rounds r2 ON (r.review_round_id = r2.review_round_id)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete review assignments by review round ID.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
*
|
||||
* @return int Number of deleted review assignments.
|
||||
*/
|
||||
public function deleteByReviewRoundId($reviewRoundId): int
|
||||
{
|
||||
return DB::table('review_assignments')
|
||||
->select(['review_id', 'review_round_id'])
|
||||
->where('review_round_id', $reviewRoundId)
|
||||
->get()
|
||||
->pluck('review_id')
|
||||
->map(fn ($reviewId) => $this->deleteById($reviewId))
|
||||
->count();
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewAssignment\ReviewAssignmentDAO', '\ReviewAssignmentDAO');
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @defgroup submission_reviewRound Review Round
|
||||
*/
|
||||
/**
|
||||
* @file classes/submission/reviewRound/ReviewRound.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 ReviewRound
|
||||
*
|
||||
* @ingroup submission_reviewRound
|
||||
*
|
||||
* @see ReviewRoundDAO
|
||||
*
|
||||
* @brief Basic class describing a review round.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewRound;
|
||||
|
||||
use APP\decision\Decision;
|
||||
use APP\facades\Repo;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class ReviewRound extends \PKP\core\DataObject
|
||||
{
|
||||
// The first four statuses are set explicitly by Decisions, which override
|
||||
// the current status.
|
||||
public const REVIEW_ROUND_STATUS_REVISIONS_REQUESTED = 1;
|
||||
public const REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW = 2;
|
||||
public const REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL = 3;
|
||||
public const REVIEW_ROUND_STATUS_ACCEPTED = 4;
|
||||
public const REVIEW_ROUND_STATUS_DECLINED = 5;
|
||||
|
||||
// The following statuses are calculated based on the statuses of ReviewAssignments
|
||||
// in this round.
|
||||
public const REVIEW_ROUND_STATUS_PENDING_REVIEWERS = 6; // No reviewers have been assigned
|
||||
public const REVIEW_ROUND_STATUS_PENDING_REVIEWS = 7; // Waiting for reviews to be submitted by reviewers
|
||||
public const REVIEW_ROUND_STATUS_REVIEWS_READY = 8; // One or more reviews is ready for an editor to view
|
||||
public const REVIEW_ROUND_STATUS_REVIEWS_COMPLETED = 9; // All assigned reviews have been confirmed by an editor
|
||||
public const REVIEW_ROUND_STATUS_REVIEWS_OVERDUE = 10; // One or more reviews is overdue
|
||||
// The following status is calculated when the round is in ReviewRound::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED and
|
||||
// at least one revision file has been uploaded.
|
||||
public const REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED = 11;
|
||||
|
||||
// The following statuses are calculated based on the statuses of recommendOnly EditorAssignments
|
||||
// and their decisions in this round.
|
||||
public const REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS = 12; // Waiting for recommendations to be submitted by recommendOnly editors
|
||||
public const REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY = 13; // One or more recommendations are ready for an editor to view
|
||||
public const REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED = 14; // All assigned recommendOnly editors have made a recommendation
|
||||
|
||||
// The following status is calculated when the round is in ReviewRound::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW and
|
||||
// at least one revision file has been uploaded.
|
||||
public const REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED = 15;
|
||||
|
||||
// The following status is set when a submission return back from copyediting stage to last review round again
|
||||
public const REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW = 16;
|
||||
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* get submission id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSubmissionId()
|
||||
{
|
||||
return $this->getData('submissionId');
|
||||
}
|
||||
|
||||
/**
|
||||
* set submission id
|
||||
*
|
||||
* @param int $submissionId
|
||||
*/
|
||||
public function setSubmissionId($submissionId)
|
||||
{
|
||||
$this->setData('submissionId', $submissionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get review stage id (internal or external review).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStageId()
|
||||
{
|
||||
return $this->getData('stageId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set review stage id
|
||||
*
|
||||
* @param int $stageId
|
||||
*/
|
||||
public function setStageId($stageId)
|
||||
{
|
||||
$this->setData('stageId', $stageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get review round
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRound()
|
||||
{
|
||||
return $this->getData('round');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set review round
|
||||
*/
|
||||
public function setRound($round)
|
||||
{
|
||||
$this->setData('round', $round);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current round status
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->getData('status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current round status
|
||||
*
|
||||
* @param int $status
|
||||
*/
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->setData('status', $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the status of this review round.
|
||||
*
|
||||
* If the round is in revisions, it will search for revision files and set
|
||||
* the status accordingly. If the round has not reached a revision status
|
||||
* yet, it will determine the status based on the statuses of the round's
|
||||
* ReviewAssignments.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function determineStatus()
|
||||
{
|
||||
// If revisions have been requested, check to see if any have been
|
||||
// submitted
|
||||
if ($this->getStatus() == self::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED || $this->getStatus() == self::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED) {
|
||||
// get editor decisions
|
||||
$pendingRevisionDecision = Repo::decision()->getActivePendingRevisionsDecision($this->getSubmissionId(), $this->getStageId(), Decision::PENDING_REVISIONS);
|
||||
|
||||
if ($pendingRevisionDecision) {
|
||||
if (Repo::decision()->revisionsUploadedSinceDecision($pendingRevisionDecision, $this->getSubmissionId())) {
|
||||
return self::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED;
|
||||
}
|
||||
}
|
||||
return self::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED;
|
||||
}
|
||||
|
||||
// If revisions have been requested for re-submission, check to see if any have been
|
||||
// submitted
|
||||
if ($this->getStatus() == self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW || $this->getStatus() == self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED) {
|
||||
// get editor decisions
|
||||
$pendingRevisionDecision = Repo::decision()->getActivePendingRevisionsDecision($this->getSubmissionId(), $this->getStageId(), Decision::RESUBMIT);
|
||||
|
||||
if ($pendingRevisionDecision) {
|
||||
if (Repo::decision()->revisionsUploadedSinceDecision($pendingRevisionDecision, $this->getSubmissionId())) {
|
||||
return self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED;
|
||||
}
|
||||
}
|
||||
return self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW;
|
||||
}
|
||||
|
||||
$statusFinished = in_array(
|
||||
$this->getStatus(),
|
||||
[
|
||||
self::REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL,
|
||||
self::REVIEW_ROUND_STATUS_ACCEPTED,
|
||||
self::REVIEW_ROUND_STATUS_DECLINED
|
||||
]
|
||||
);
|
||||
if ($statusFinished) {
|
||||
return $this->getStatus();
|
||||
}
|
||||
|
||||
// Determine the round status by looking at the recommendOnly editor assignment statuses
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$pendingRecommendations = false;
|
||||
$recommendationsFinished = true;
|
||||
$recommendationsReady = false;
|
||||
$editorsStageAssignments = $stageAssignmentDao->getEditorsAssignedToStage($this->getSubmissionId(), $this->getStageId());
|
||||
foreach ($editorsStageAssignments as $editorsStageAssignment) {
|
||||
if ($editorsStageAssignment->getRecommendOnly()) {
|
||||
$pendingRecommendations = true;
|
||||
// Get recommendation from the assigned recommendOnly editor
|
||||
$decisions = Repo::decision()->getCollector()
|
||||
->filterBySubmissionIds([$this->getSubmissionId()])
|
||||
->filterByStageIds([$this->getStageId()])
|
||||
->filterByReviewRoundIds([$this->getId()])
|
||||
->filterByEditorIds([$editorsStageAssignment->getUserId()])
|
||||
->getCount();
|
||||
|
||||
if (!$decisions) {
|
||||
$recommendationsFinished = false;
|
||||
} else {
|
||||
$recommendationsReady = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($pendingRecommendations) {
|
||||
if ($recommendationsFinished) {
|
||||
return self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED;
|
||||
} elseif ($recommendationsReady) {
|
||||
return self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the round status by looking at the assignment statuses
|
||||
$anyOverdueReview = false;
|
||||
$anyIncompletedReview = false;
|
||||
$anyUnreadReview = false;
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignments = $reviewAssignmentDao->getByReviewRoundId($this->getId());
|
||||
foreach ($reviewAssignments as $reviewAssignment) {
|
||||
assert($reviewAssignment instanceof ReviewAssignment);
|
||||
|
||||
$assignmentStatus = $reviewAssignment->getStatus();
|
||||
|
||||
switch ($assignmentStatus) {
|
||||
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_DECLINED:
|
||||
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_CANCELLED:
|
||||
break;
|
||||
|
||||
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RESPONSE_OVERDUE:
|
||||
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE:
|
||||
$anyOverdueReview = true;
|
||||
break;
|
||||
|
||||
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_AWAITING_RESPONSE:
|
||||
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_ACCEPTED:
|
||||
$anyIncompletedReview = true;
|
||||
break;
|
||||
|
||||
case ReviewAssignment::REVIEW_ASSIGNMENT_STATUS_RECEIVED:
|
||||
$anyUnreadReview = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the correct review round status based on the state of
|
||||
// the current review assignments. The check order matters: the
|
||||
// first conditions override the others.
|
||||
if (empty($reviewAssignments)) {
|
||||
return self::REVIEW_ROUND_STATUS_PENDING_REVIEWERS;
|
||||
} elseif ($anyOverdueReview) {
|
||||
return self::REVIEW_ROUND_STATUS_REVIEWS_OVERDUE;
|
||||
} elseif ($anyUnreadReview) {
|
||||
return self::REVIEW_ROUND_STATUS_REVIEWS_READY;
|
||||
} elseif ($anyIncompletedReview) {
|
||||
return self::REVIEW_ROUND_STATUS_PENDING_REVIEWS;
|
||||
} elseif ($pendingRecommendations) {
|
||||
return self::REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS;
|
||||
}
|
||||
|
||||
// The submission back form copy editing stage to last review round
|
||||
if ($this->getStatus() == self::REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW) {
|
||||
return self::REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW;
|
||||
}
|
||||
|
||||
return self::REVIEW_ROUND_STATUS_REVIEWS_COMPLETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale key associated with current status
|
||||
*
|
||||
* @param bool $isAuthor True iff the status is to be shown to the author (slightly tweaked phrasing)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatusKey($isAuthor = false)
|
||||
{
|
||||
switch ($this->determineStatus()) {
|
||||
case self::REVIEW_ROUND_STATUS_REVISIONS_REQUESTED:
|
||||
return 'editor.submission.roundStatus.revisionsRequested';
|
||||
case self::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED:
|
||||
return 'editor.submission.roundStatus.revisionsSubmitted';
|
||||
case self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW:
|
||||
return 'editor.submission.roundStatus.resubmitForReview';
|
||||
case self::REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED:
|
||||
return 'editor.submission.roundStatus.submissionResubmitted';
|
||||
case self::REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL:
|
||||
return 'editor.submission.roundStatus.sentToExternal';
|
||||
case self::REVIEW_ROUND_STATUS_ACCEPTED:
|
||||
return 'editor.submission.roundStatus.accepted';
|
||||
case self::REVIEW_ROUND_STATUS_DECLINED:
|
||||
return 'editor.submission.roundStatus.declined';
|
||||
case self::REVIEW_ROUND_STATUS_PENDING_REVIEWERS:
|
||||
return 'editor.submission.roundStatus.pendingReviewers';
|
||||
case self::REVIEW_ROUND_STATUS_PENDING_REVIEWS:
|
||||
return 'editor.submission.roundStatus.pendingReviews';
|
||||
case self::REVIEW_ROUND_STATUS_REVIEWS_READY:
|
||||
return $isAuthor ? 'author.submission.roundStatus.reviewsReady' : 'editor.submission.roundStatus.reviewsReady';
|
||||
case self::REVIEW_ROUND_STATUS_REVIEWS_COMPLETED:
|
||||
return 'editor.submission.roundStatus.reviewsCompleted';
|
||||
case self::REVIEW_ROUND_STATUS_REVIEWS_OVERDUE:
|
||||
return $isAuthor ? 'author.submission.roundStatus.reviewOverdue' : 'editor.submission.roundStatus.reviewOverdue';
|
||||
case self::REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS:
|
||||
return 'editor.submission.roundStatus.pendingRecommendations';
|
||||
case self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY:
|
||||
return 'editor.submission.roundStatus.recommendationsReady';
|
||||
case self::REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED:
|
||||
return 'editor.submission.roundStatus.recommendationsCompleted';
|
||||
case self::REVIEW_ROUND_STATUS_RETURNED_TO_REVIEW:
|
||||
return 'editor.submission.roundStatus.returnedToReview';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewRound\ReviewRound', '\ReviewRound');
|
||||
foreach ([
|
||||
'REVIEW_ROUND_STATUS_REVISIONS_REQUESTED',
|
||||
'REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW',
|
||||
'REVIEW_ROUND_STATUS_SENT_TO_EXTERNAL',
|
||||
'REVIEW_ROUND_STATUS_ACCEPTED',
|
||||
'REVIEW_ROUND_STATUS_DECLINED',
|
||||
'REVIEW_ROUND_STATUS_PENDING_REVIEWERS',
|
||||
'REVIEW_ROUND_STATUS_PENDING_REVIEWS',
|
||||
'REVIEW_ROUND_STATUS_REVIEWS_READY',
|
||||
'REVIEW_ROUND_STATUS_REVIEWS_COMPLETED',
|
||||
'REVIEW_ROUND_STATUS_REVIEWS_OVERDUE',
|
||||
'REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED',
|
||||
'REVIEW_ROUND_STATUS_PENDING_RECOMMENDATIONS',
|
||||
'REVIEW_ROUND_STATUS_RECOMMENDATIONS_READY',
|
||||
'REVIEW_ROUND_STATUS_RECOMMENDATIONS_COMPLETED',
|
||||
'REVIEW_ROUND_STATUS_RESUBMIT_FOR_REVIEW_SUBMITTED',
|
||||
] as $constantName) {
|
||||
define($constantName, constant('\ReviewRound::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/reviewRound/ReviewRoundDAO.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 ReviewRoundDAO
|
||||
*
|
||||
* @ingroup submission_reviewRound
|
||||
*
|
||||
* @see ReviewRound
|
||||
*
|
||||
* @brief Operations for retrieving and modifying ReviewRound objects.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewRound;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\db\DAOResultFactory;
|
||||
|
||||
class ReviewRoundDAO extends \PKP\db\DAO
|
||||
{
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Fetch a review round, creating it if needed.
|
||||
*
|
||||
* @param int $submissionId
|
||||
* @param int $stageId One of the WORKFLOW_*_REVIEW_STAGE_ID constants.
|
||||
* @param int $round
|
||||
* @param int $status One of the ReviewRound::REVIEW_ROUND_STATUS_* constants.
|
||||
*
|
||||
* @return ?ReviewRound
|
||||
*/
|
||||
public function build($submissionId, $stageId, $round, $status = null)
|
||||
{
|
||||
// If one exists, fetch and return.
|
||||
$reviewRound = $this->getReviewRound($submissionId, $stageId, $round);
|
||||
if ($reviewRound) {
|
||||
return $reviewRound;
|
||||
}
|
||||
|
||||
// Otherwise, check the args to build one.
|
||||
if ($stageId == WORKFLOW_STAGE_ID_INTERNAL_REVIEW ||
|
||||
$stageId == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW &&
|
||||
$round > 0
|
||||
) {
|
||||
unset($reviewRound);
|
||||
$reviewRound = $this->newDataObject();
|
||||
$reviewRound->setSubmissionId($submissionId);
|
||||
$reviewRound->setRound($round);
|
||||
$reviewRound->setStageId($stageId);
|
||||
$reviewRound->setStatus($status);
|
||||
$this->insertObject($reviewRound);
|
||||
$reviewRound->setId($this->getInsertId());
|
||||
|
||||
return $reviewRound;
|
||||
} else {
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new data object corresponding to this DAO.
|
||||
*
|
||||
* @return ReviewRound
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new ReviewRound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new review round.
|
||||
*
|
||||
* @param ReviewRound $reviewRound
|
||||
*
|
||||
* @return ReviewRound
|
||||
*/
|
||||
public function insertObject($reviewRound)
|
||||
{
|
||||
$this->update(
|
||||
'INSERT INTO review_rounds
|
||||
(submission_id, stage_id, round, status)
|
||||
VALUES
|
||||
(?, ?, ?, ?)',
|
||||
[
|
||||
(int)$reviewRound->getSubmissionId(),
|
||||
(int)$reviewRound->getStageId(),
|
||||
(int)$reviewRound->getRound(),
|
||||
(int)$reviewRound->getStatus()
|
||||
]
|
||||
);
|
||||
return $reviewRound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing review round.
|
||||
*
|
||||
* @param ReviewRound $reviewRound
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateObject($reviewRound)
|
||||
{
|
||||
$returner = $this->update(
|
||||
'UPDATE review_rounds
|
||||
SET status = ?
|
||||
WHERE submission_id = ? AND
|
||||
stage_id = ? AND
|
||||
round = ?',
|
||||
[
|
||||
(int)$reviewRound->getStatus(),
|
||||
(int)$reviewRound->getSubmissionId(),
|
||||
(int)$reviewRound->getStageId(),
|
||||
(int)$reviewRound->getRound()
|
||||
]
|
||||
);
|
||||
return $returner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a review round
|
||||
*
|
||||
* @param int $submissionId
|
||||
* @param int $stageId One of the Stage_id_* constants.
|
||||
* @param int $round The review round to be retrieved.
|
||||
*
|
||||
* @return ?ReviewRound
|
||||
*/
|
||||
public function getReviewRound($submissionId, $stageId, $round)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT * FROM review_rounds WHERE submission_id = ? AND stage_id = ? AND round = ?',
|
||||
[(int) $submissionId, (int) $stageId, (int) $round]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a review round by its id.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
*
|
||||
* @return ReviewRound
|
||||
*/
|
||||
public function getById($reviewRoundId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT * FROM review_rounds WHERE review_round_id = ?',
|
||||
[(int) $reviewRoundId]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a review round by a submission file id.
|
||||
*
|
||||
* @param int $submissionFileId
|
||||
*
|
||||
* @return ReviewRound
|
||||
*/
|
||||
public function getBySubmissionFileId($submissionFileId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT * FROM review_rounds rr
|
||||
INNER JOIN review_round_files rrf
|
||||
ON rr.review_round_id = rrf.review_round_id
|
||||
WHERE rrf.submission_file_id = ?',
|
||||
[(int) $submissionFileId]
|
||||
);
|
||||
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator of review round objects associated with this submission
|
||||
*
|
||||
* @param int $submissionId
|
||||
* @param int $stageId (optional)
|
||||
* @param int $round (optional)
|
||||
*
|
||||
* @return DAOResultFactory<ReviewRound>
|
||||
*/
|
||||
public function getBySubmissionId($submissionId, $stageId = null, $round = null)
|
||||
{
|
||||
$params = [(int) $submissionId];
|
||||
if ($stageId) {
|
||||
$params[] = $stageId;
|
||||
}
|
||||
if ($round) {
|
||||
$params[] = $round;
|
||||
}
|
||||
|
||||
$result = $this->retrieve(
|
||||
$sql = 'SELECT * FROM review_rounds WHERE submission_id = ?' .
|
||||
($stageId ? ' AND stage_id = ?' : '') .
|
||||
($round ? ' AND round = ?' : '') .
|
||||
' ORDER BY stage_id ASC, round ASC',
|
||||
$params
|
||||
);
|
||||
|
||||
return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current review round for a given stage (or for the latest stage)
|
||||
*
|
||||
* @param int $submissionId
|
||||
* @param int $stageId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCurrentRoundBySubmissionId($submissionId, $stageId = null)
|
||||
{
|
||||
$params = [(int)$submissionId];
|
||||
if ($stageId) {
|
||||
$params[] = (int) $stageId;
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT MAX(stage_id) as stage_id, MAX(round) as round
|
||||
FROM review_rounds
|
||||
WHERE submission_id = ?' .
|
||||
($stageId ? ' AND stage_id = ?' : ''),
|
||||
$params
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? (int) $row->round : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last review round for a give stage (or for the latest stage)
|
||||
*
|
||||
* @param int $submissionId
|
||||
* @param int $stageId
|
||||
*
|
||||
* @return ?ReviewRound
|
||||
*/
|
||||
public function getLastReviewRoundBySubmissionId($submissionId, $stageId = null)
|
||||
{
|
||||
$params = [(int)$submissionId];
|
||||
if ($stageId) {
|
||||
$params[] = (int) $stageId;
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT *
|
||||
FROM review_rounds
|
||||
WHERE submission_id = ?
|
||||
' . ($stageId ? ' AND stage_id = ?' : '') . '
|
||||
ORDER BY stage_id DESC, round DESC',
|
||||
$params
|
||||
);
|
||||
|
||||
$row = (array) $result->current();
|
||||
return $row ? $this->_fromRow($row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if submission has a review round (in the given stage id)
|
||||
*/
|
||||
public function submissionHasReviewRound(int $submissionId, ?int $stageId = null): bool
|
||||
{
|
||||
$params = [(int)$submissionId];
|
||||
if ($stageId) {
|
||||
$params[] = (int) $stageId;
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT review_round_id
|
||||
FROM review_rounds
|
||||
WHERE submission_id = ?
|
||||
' . ($stageId ? ' AND stage_id = ?' : ''),
|
||||
$params
|
||||
);
|
||||
return (bool) $result->current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the review round status.
|
||||
*
|
||||
* @param ReviewRound $reviewRound
|
||||
* @param ?int $status Optionally pass a ReviewRound::REVIEW_ROUND_STATUS_... to set a
|
||||
* specific status. If not included, will determine the appropriate status
|
||||
* based on ReviewRound::determineStatus().
|
||||
*/
|
||||
public function updateStatus($reviewRound, $status = null)
|
||||
{
|
||||
assert($reviewRound instanceof ReviewRound);
|
||||
$currentStatus = $reviewRound->getStatus();
|
||||
|
||||
if (is_null($status)) {
|
||||
$status = $reviewRound->determineStatus();
|
||||
}
|
||||
|
||||
// Avoid unnecessary database access.
|
||||
if ($status != $currentStatus) {
|
||||
$this->update(
|
||||
'UPDATE review_rounds SET status = ? WHERE review_round_id = ?',
|
||||
[(int)$status, (int)$reviewRound->getId()]
|
||||
);
|
||||
// Update the data in object too.
|
||||
$reviewRound->setStatus($status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete review rounds by submission ID.
|
||||
*
|
||||
* @param int $submissionId
|
||||
*/
|
||||
public function deleteBySubmissionId($submissionId)
|
||||
{
|
||||
$reviewRounds = $this->getBySubmissionId($submissionId);
|
||||
while ($reviewRound = $reviewRounds->next()) {
|
||||
$this->deleteObject($reviewRound);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a review round.
|
||||
*
|
||||
* @param ReviewRound $reviewRound
|
||||
*/
|
||||
public function deleteObject($reviewRound)
|
||||
{
|
||||
$this->deleteById($reviewRound->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a review round by ID.
|
||||
*
|
||||
* @param int $reviewRoundId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteById($reviewRoundId)
|
||||
{
|
||||
$this->update('DELETE FROM notifications WHERE assoc_type = ? AND assoc_id = ?', [(int) Application::ASSOC_TYPE_REVIEW_ROUND, (int) $reviewRoundId]);
|
||||
return $this->update('DELETE FROM review_rounds WHERE review_round_id = ?', [(int) $reviewRoundId]);
|
||||
}
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Internal function to return a review round object from a row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return ReviewRound
|
||||
*/
|
||||
public function _fromRow($row)
|
||||
{
|
||||
$reviewRound = $this->newDataObject();
|
||||
|
||||
$reviewRound->setId((int)$row['review_round_id']);
|
||||
$reviewRound->setSubmissionId((int)$row['submission_id']);
|
||||
$reviewRound->setStageId((int)$row['stage_id']);
|
||||
$reviewRound->setRound((int)$row['round']);
|
||||
$reviewRound->setStatus((int)$row['status']);
|
||||
|
||||
return $reviewRound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of review rounds in a submission
|
||||
*
|
||||
* @param int $submissionId Submission id for which review round count need to be determined
|
||||
* @param int $stageId Review stage id for which review round count need to be determined
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return int Number of internal review round associated with this submission
|
||||
*
|
||||
*/
|
||||
public function getReviewRoundCountBySubmissionId(int $submissionId, ?int $stageId = null)
|
||||
{
|
||||
if (!is_null($stageId) && !in_array($stageId, [WORKFLOW_STAGE_ID_EXTERNAL_REVIEW, WORKFLOW_STAGE_ID_INTERNAL_REVIEW])) {
|
||||
throw new \Exception('Not a valid review stage');
|
||||
}
|
||||
|
||||
return DB::table('review_rounds')
|
||||
->where('submission_id', $submissionId)
|
||||
->when(!is_null($stageId), fn ($query) => $query->where('stage_id', $stageId))
|
||||
->count();
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewRound\ReviewRoundDAO', '\ReviewRoundDAO');
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/reviewer/ReviewerAction.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 ReviewerAction
|
||||
*
|
||||
* @ingroup submission
|
||||
*
|
||||
* @brief ReviewerAction class.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewer;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\log\event\SubmissionEventLogEntry;
|
||||
use APP\notification\NotificationManager;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\context\Context;
|
||||
use PKP\core\Core;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\log\SubmissionEmailLogDAO;
|
||||
use PKP\log\SubmissionEmailLogEntry;
|
||||
use PKP\mail\mailables\ReviewConfirm;
|
||||
use PKP\mail\mailables\ReviewDecline;
|
||||
use PKP\notification\PKPNotification;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\security\Role;
|
||||
use PKP\security\Validation;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\submission\PKPSubmission;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use Symfony\Component\Mailer\Exception\TransportException;
|
||||
|
||||
class ReviewerAction
|
||||
{
|
||||
//
|
||||
// Actions.
|
||||
//
|
||||
/**
|
||||
* Records whether the reviewer accepts the review assignment.
|
||||
*/
|
||||
public function confirmReview(
|
||||
PKPRequest $request,
|
||||
ReviewAssignment $reviewAssignment,
|
||||
Submission $submission,
|
||||
bool $decline,
|
||||
?string $emailText = null
|
||||
): void {
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId());
|
||||
if (!isset($reviewer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only confirm the review for the reviewer if
|
||||
// he has not previously done so.
|
||||
if ($reviewAssignment->getDateConfirmed() == null) {
|
||||
$mailable = $this->getResponseEmail($submission, $reviewAssignment, $decline, $emailText);
|
||||
Hook::call('ReviewerAction::confirmReview', [$request, $submission, $mailable, $decline]);
|
||||
|
||||
if (!empty($mailable->to)) {
|
||||
try {
|
||||
Mail::send($mailable);
|
||||
$submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $submissionEmailLogDao */
|
||||
$submissionEmailLogDao->logMailable(
|
||||
$decline ? SubmissionEmailLogEntry::SUBMISSION_EMAIL_REVIEW_DECLINE : SubmissionEmailLogEntry::SUBMISSION_EMAIL_REVIEW_CONFIRM,
|
||||
$mailable,
|
||||
$submission,
|
||||
$mailable->getSenderUser()
|
||||
);
|
||||
} catch (TransportException $e) {
|
||||
$notificationMgr = new NotificationManager();
|
||||
$notificationMgr->createTrivialNotification(
|
||||
$request->getUser()->getId(),
|
||||
PKPNotification::NOTIFICATION_TYPE_ERROR,
|
||||
['contents' => __('email.compose.error')]
|
||||
);
|
||||
trigger_error($e->getMessage(), E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
$reviewAssignment->setDateReminded(null);
|
||||
$reviewAssignment->setReminderWasAutomatic(0);
|
||||
$reviewAssignment->setDeclined($decline);
|
||||
$reviewAssignment->setDateConfirmed(Core::getCurrentDate());
|
||||
$reviewAssignment->stampModified();
|
||||
$reviewAssignmentDao->updateObject($reviewAssignment);
|
||||
|
||||
// Add log
|
||||
$eventLog = Repo::eventLog()->newDataObject([
|
||||
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
|
||||
'assocId' => $submission->getId(),
|
||||
'eventType' => $decline ? SubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_DECLINE : SubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_ACCEPT,
|
||||
'userId' => Validation::loggedInAs() ?? $request->getUser()->getId(),
|
||||
'message' => $decline ? 'log.review.reviewDeclined' : 'log.review.reviewAccepted',
|
||||
'isTranslate' => 0,
|
||||
'dateLogged' => Core::getCurrentDate(),
|
||||
'reviewAssignmentId' => $reviewAssignment->getId(),
|
||||
'reviewerName' => $reviewer->getFullName(),
|
||||
'submissionId' => $reviewAssignment->getSubmissionId(),
|
||||
'round' => $reviewAssignment->getRound()
|
||||
]);
|
||||
Repo::eventLog()->add($eventLog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reviewer response email template.
|
||||
*/
|
||||
public function getResponseEmail(
|
||||
PKPSubmission $submission,
|
||||
ReviewAssignment $reviewAssignment,
|
||||
bool $decline,
|
||||
?string $emailText
|
||||
): ReviewConfirm|ReviewDecline {
|
||||
$context = Application::getContextDAO()->getById($submission->getData('contextId')); /** @var Context $context */
|
||||
|
||||
$mailable = $decline ?
|
||||
new ReviewDecline($submission, $reviewAssignment, $context) :
|
||||
new ReviewConfirm($submission, $reviewAssignment, $context);
|
||||
|
||||
// Get reviewer
|
||||
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId());
|
||||
$mailable->sender($reviewer);
|
||||
$mailable->replyTo($reviewer->getEmail(), $reviewer->getFullName());
|
||||
|
||||
// Get editorial contact name
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$stageAssignments = $stageAssignmentDao->getBySubmissionAndStageId($submission->getId(), $reviewAssignment->getStageId());
|
||||
$recipients = [];
|
||||
while ($stageAssignment = $stageAssignments->next()) {
|
||||
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
|
||||
if (!in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$recipients[] = Repo::user()->get($stageAssignment->getUserId());
|
||||
}
|
||||
|
||||
// Create dummy user if no one assigned
|
||||
if (empty($recipients)) {
|
||||
$contextUser = Repo::user()->getUserFromContextContact($context);
|
||||
if ($contextUser->getData('email')) {
|
||||
$recipients[] = $contextUser;
|
||||
}
|
||||
}
|
||||
|
||||
$mailable->recipients($recipients);
|
||||
|
||||
// Set email body and subject
|
||||
$template = Repo::emailTemplate()->getByKey($context->getId(), $mailable->getEmailTemplateKey());
|
||||
$emailText ? $mailable->body($emailText) : $mailable->body($template->getLocalizedData('body'));
|
||||
$mailable->subject($template->getLocalizedData('subject'));
|
||||
|
||||
return $mailable;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewer\ReviewerAction', '\ReviewerAction');
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/reviewer/form/PKPReviewerReviewStep1Form.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 PKPReviewerReviewStep1Form
|
||||
*
|
||||
* @ingroup submission_reviewer_form
|
||||
*
|
||||
* @brief Form for Step 1 of a review.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewer\form;
|
||||
|
||||
use APP\submission\Submission;
|
||||
use APP\template\TemplateManager;
|
||||
use PKP\controllers\confirmationModal\linkAction\ViewCompetingInterestGuidelinesLinkAction;
|
||||
use PKP\controllers\modals\review\ReviewerViewMetadataLinkAction;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\linkAction\LinkAction;
|
||||
use PKP\linkAction\request\AjaxModal;
|
||||
use PKP\linkAction\request\ConfirmationModal;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewer\ReviewerAction;
|
||||
|
||||
class PKPReviewerReviewStep1Form extends ReviewerReviewForm
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(PKPRequest $request, Submission $reviewSubmission, ReviewAssignment $reviewAssignment)
|
||||
{
|
||||
parent::__construct($request, $reviewSubmission, $reviewAssignment, 1);
|
||||
$context = $request->getContext();
|
||||
if (!$reviewAssignment->getDateConfirmed() && $context->getData('privacyStatement')) {
|
||||
$this->addCheck(new \PKP\form\validation\FormValidator($this, 'privacyConsent', 'required', 'user.profile.form.privacyConsentRequired'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement protected template methods from Form
|
||||
//
|
||||
/**
|
||||
* @copydoc ReviewerReviewForm::fetch()
|
||||
*
|
||||
* @param null|mixed $template
|
||||
*/
|
||||
public function fetch($request, $template = null, $display = false)
|
||||
{
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$context = $request->getContext();
|
||||
|
||||
// Add submission parameters.
|
||||
$reviewAssignment = $this->getReviewAssignment();
|
||||
|
||||
$templateMgr->assign('reviewerCompetingInterests', $reviewAssignment->getCompetingInterests());
|
||||
|
||||
// Add review assignment.
|
||||
$templateMgr->assign([
|
||||
'reviewAssignment' => $reviewAssignment,
|
||||
'reviewRoundId' => $reviewAssignment->getReviewRoundId(),
|
||||
'restrictReviewerFileAccess' => $context->getData('restrictReviewerFileAccess'),
|
||||
'reviewMethod' => __($reviewAssignment->getReviewMethodKey()),
|
||||
]);
|
||||
|
||||
// Add reviewer request text.
|
||||
$templateMgr->assign('reviewerRequest', __('reviewer.step1.requestBoilerplate'));
|
||||
|
||||
//
|
||||
// Assign the link actions
|
||||
//
|
||||
|
||||
// "View metadata" action.
|
||||
$viewMetadataLinkAction = new ReviewerViewMetadataLinkAction($request, $reviewAssignment->getSubmissionId(), $reviewAssignment->getId());
|
||||
$templateMgr->assign('viewMetadataAction', $viewMetadataLinkAction);
|
||||
|
||||
// include the confirmation modal for competing interests if the context has them.
|
||||
if ($context->getLocalizedData('competingInterests') != '') {
|
||||
$competingInterestsAction = new ViewCompetingInterestGuidelinesLinkAction($request);
|
||||
$templateMgr->assign('competingInterestsAction', $competingInterestsAction);
|
||||
}
|
||||
// Instantiate the view review guidelines confirmation modal.
|
||||
$aboutDueDateAction = new LinkAction(
|
||||
'viewReviewGuidelines',
|
||||
new ConfirmationModal(
|
||||
__('reviewer.aboutDueDates.text'),
|
||||
__('reviewer.aboutDueDates'),
|
||||
'modal_information',
|
||||
null,
|
||||
'',
|
||||
false
|
||||
),
|
||||
__('reviewer.aboutDueDates')
|
||||
);
|
||||
|
||||
$templateMgr->assign('aboutDueDatesAction', $aboutDueDateAction);
|
||||
|
||||
$declineReviewLinkAction = new LinkAction(
|
||||
'declineReview',
|
||||
new AjaxModal(
|
||||
$request->url(null, null, 'showDeclineReview', $reviewAssignment->getSubmissionId()),
|
||||
__('reviewer.submission.declineReview')
|
||||
)
|
||||
);
|
||||
$templateMgr->assign('declineReviewAction', $declineReviewLinkAction);
|
||||
|
||||
return parent::fetch($request, $template, $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Form::readInputData()
|
||||
*/
|
||||
public function readInputData()
|
||||
{
|
||||
$this->readUserVars(['competingInterestOption', 'reviewerCompetingInterests', 'privacyConsent']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Form::execute()
|
||||
*/
|
||||
public function execute(...$functionParams)
|
||||
{
|
||||
$reviewAssignment = $this->getReviewAssignment();
|
||||
$reviewSubmission = $this->getReviewSubmission();
|
||||
|
||||
// Set competing interests.
|
||||
if ($this->getData('competingInterestOption') == 'hasCompetingInterests') {
|
||||
$reviewAssignment->setCompetingInterests($this->request->getUserVar('reviewerCompetingInterests'));
|
||||
} else {
|
||||
$reviewAssignment->setCompetingInterests(null);
|
||||
}
|
||||
|
||||
// Set review to next step.
|
||||
$this->updateReviewStepAndSaveSubmission($reviewAssignment);
|
||||
|
||||
// if the reviewer has not previously confirmed the review, then
|
||||
// Set that the reviewer has accepted the review.
|
||||
if (!$reviewAssignment->getDateConfirmed()) {
|
||||
$reviewerAction = new ReviewerAction();
|
||||
$reviewerAction->confirmReview($this->request, $reviewAssignment, $reviewSubmission, false);
|
||||
}
|
||||
|
||||
parent::execute(...$functionParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewer\form\PKPReviewerReviewStep1Form', '\PKPReviewerReviewStep1Form');
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/reviewer/form/PKPReviewerReviewStep2Form.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 PKPReviewerReviewStep2Form
|
||||
*
|
||||
* @ingroup submission_reviewer_form
|
||||
*
|
||||
* @brief Form for Step 2 of a review.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewer\form;
|
||||
|
||||
use APP\submission\Submission;
|
||||
use APP\template\TemplateManager;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
|
||||
class PKPReviewerReviewStep2Form extends ReviewerReviewForm
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(PKPRequest $request, Submission $reviewSubmission, ReviewAssignment $reviewAssignment)
|
||||
{
|
||||
parent::__construct($request, $reviewSubmission, $reviewAssignment, 2);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement protected template methods from Form
|
||||
//
|
||||
/**
|
||||
* @copydoc ReviewerReviewForm::fetch()
|
||||
*
|
||||
* @param null|mixed $template
|
||||
*/
|
||||
public function fetch($request, $template = null, $display = false)
|
||||
{
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$context = $this->request->getContext();
|
||||
|
||||
$reviewAssignment = $this->getReviewAssignment();
|
||||
$reviewerGuidelines = $context->getLocalizedData($reviewAssignment->getStageId() == WORKFLOW_STAGE_ID_INTERNAL_REVIEW ? 'internalReviewGuidelines' : 'reviewGuidelines');
|
||||
if (empty($reviewerGuidelines)) {
|
||||
$reviewerGuidelines = __('reviewer.submission.noGuidelines');
|
||||
}
|
||||
$templateMgr->assign('reviewerGuidelines', $reviewerGuidelines);
|
||||
|
||||
return parent::fetch($request, $template, $display);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see Form::execute()
|
||||
*/
|
||||
public function execute(...$functionParams)
|
||||
{
|
||||
// Set review to next step.
|
||||
$this->updateReviewStepAndSaveSubmission($this->getReviewAssignment());
|
||||
|
||||
parent::execute(...$functionParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewer\form\PKPReviewerReviewStep2Form', '\PKPReviewerReviewStep2Form');
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/submission/reviewer/form/PKPReviewerReviewStep3Form.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 PKPReviewerReviewStep3Form
|
||||
*
|
||||
* @ingroup submission_reviewer_form
|
||||
*
|
||||
* @brief Form for Step 3 of a review.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewer\form;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\notification\NotificationManager;
|
||||
use APP\submission\Submission;
|
||||
use APP\template\TemplateManager;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\controllers\confirmationModal\linkAction\ViewReviewGuidelinesLinkAction;
|
||||
use PKP\core\Core;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\log\event\PKPSubmissionEventLogEntry;
|
||||
use PKP\mail\mailables\ReviewCompleteNotifyEditors;
|
||||
use PKP\notification\NotificationDAO;
|
||||
use PKP\notification\NotificationSubscriptionSettingsDAO;
|
||||
use PKP\notification\PKPNotification;
|
||||
use PKP\reviewForm\ReviewFormDAO;
|
||||
use PKP\reviewForm\ReviewFormElement;
|
||||
use PKP\reviewForm\ReviewFormElementDAO;
|
||||
use PKP\reviewForm\ReviewFormResponse;
|
||||
use PKP\reviewForm\ReviewFormResponseDAO;
|
||||
use PKP\security\Role;
|
||||
use PKP\security\Validation;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\submission\SubmissionComment;
|
||||
use PKP\submission\SubmissionCommentDAO;
|
||||
|
||||
class PKPReviewerReviewStep3Form extends ReviewerReviewForm
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(PKPRequest $request, Submission $reviewSubmission, ReviewAssignment $reviewAssignment)
|
||||
{
|
||||
parent::__construct($request, $reviewSubmission, $reviewAssignment, 3);
|
||||
|
||||
// Validation checks for this form
|
||||
$reviewFormElementDao = DAORegistry::getDAO('ReviewFormElementDAO'); /** @var ReviewFormElementDAO $reviewFormElementDao */
|
||||
$requiredReviewFormElementIds = $reviewFormElementDao->getRequiredReviewFormElementIds($reviewAssignment->getReviewFormId());
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'reviewFormResponses', 'required', 'reviewer.submission.reviewFormResponse.form.responseRequired', function ($reviewFormResponses) use ($requiredReviewFormElementIds) {
|
||||
foreach ($requiredReviewFormElementIds as $requiredReviewFormElementId) {
|
||||
if (!isset($reviewFormResponses[$requiredReviewFormElementId]) || $reviewFormResponses[$requiredReviewFormElementId] == '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc ReviewerReviewForm::initData
|
||||
*/
|
||||
public function initData()
|
||||
{
|
||||
$reviewAssignment = $this->getReviewAssignment();
|
||||
|
||||
// Retrieve most recent reviewer comments, one private, one public.
|
||||
$submissionCommentDao = DAORegistry::getDAO('SubmissionCommentDAO'); /** @var SubmissionCommentDAO $submissionCommentDao */
|
||||
|
||||
$submissionComments = $submissionCommentDao->getReviewerCommentsByReviewerId($reviewAssignment->getSubmissionId(), $reviewAssignment->getReviewerId(), $reviewAssignment->getId(), true);
|
||||
$submissionComment = $submissionComments->next();
|
||||
$this->setData('comments', $submissionComment ? $submissionComment->getComments() : '');
|
||||
|
||||
$submissionCommentsPrivate = $submissionCommentDao->getReviewerCommentsByReviewerId($reviewAssignment->getSubmissionId(), $reviewAssignment->getReviewerId(), $reviewAssignment->getId(), false);
|
||||
$submissionCommentPrivate = $submissionCommentsPrivate->next();
|
||||
$this->setData('commentsPrivate', $submissionCommentPrivate ? $submissionCommentPrivate->getComments() : '');
|
||||
|
||||
parent::initData();
|
||||
}
|
||||
|
||||
//
|
||||
// Implement protected template methods from Form
|
||||
//
|
||||
/**
|
||||
* @see Form::readInputData()
|
||||
*/
|
||||
public function readInputData()
|
||||
{
|
||||
$this->readUserVars(
|
||||
['reviewFormResponses', 'comments', 'recommendation', 'commentsPrivate']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc ReviewerReviewForm::fetch()
|
||||
*
|
||||
* @param null|mixed $template
|
||||
*/
|
||||
public function fetch($request, $template = null, $display = false)
|
||||
{
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$reviewAssignment = $this->getReviewAssignment();
|
||||
|
||||
// Assign the objects and data to the template.
|
||||
$context = $this->request->getContext();
|
||||
$templateMgr->assign([
|
||||
'reviewAssignment' => $reviewAssignment,
|
||||
'reviewRoundId' => $reviewAssignment->getReviewRoundId(),
|
||||
'reviewerRecommendationOptions' => ReviewAssignment::getReviewerRecommendationOptions(),
|
||||
]);
|
||||
|
||||
if ($reviewAssignment->getReviewFormId()) {
|
||||
// Get the review form components
|
||||
$reviewFormElementDao = DAORegistry::getDAO('ReviewFormElementDAO'); /** @var ReviewFormElementDAO $reviewFormElementDao */
|
||||
$reviewFormResponseDao = DAORegistry::getDAO('ReviewFormResponseDAO'); /** @var ReviewFormResponseDAO $reviewFormResponseDao */
|
||||
$reviewFormDao = DAORegistry::getDAO('ReviewFormDAO'); /** @var ReviewFormDAO $reviewFormDao */
|
||||
$templateMgr->assign([
|
||||
'reviewForm' => $reviewFormDao->getById($reviewAssignment->getReviewFormId(), Application::getContextAssocType(), $context->getId()),
|
||||
'reviewFormElements' => $reviewFormElementDao->getByReviewFormId($reviewAssignment->getReviewFormId()),
|
||||
'reviewFormResponses' => $reviewFormResponseDao->getReviewReviewFormResponseValues($reviewAssignment->getId()),
|
||||
'disabled' => isset($reviewAssignment) && $reviewAssignment->getDateCompleted() != null,
|
||||
]);
|
||||
}
|
||||
|
||||
//
|
||||
// Assign the link actions
|
||||
//
|
||||
$viewReviewGuidelinesAction = new ViewReviewGuidelinesLinkAction($request, $reviewAssignment->getStageId());
|
||||
if ($viewReviewGuidelinesAction->getGuidelines()) {
|
||||
$templateMgr->assign('viewGuidelinesAction', $viewReviewGuidelinesAction);
|
||||
}
|
||||
|
||||
return parent::fetch($request, $template, $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Form::execute()
|
||||
*/
|
||||
public function execute(...$functionParams)
|
||||
{
|
||||
$reviewAssignment = $this->getReviewAssignment();
|
||||
$notificationMgr = new NotificationManager();
|
||||
|
||||
// Save the answers to the review form
|
||||
$this->saveReviewForm($reviewAssignment);
|
||||
|
||||
// Send notification
|
||||
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
|
||||
$context = Application::getContextDAO()->getById($submission->getData('contextId'));
|
||||
|
||||
// Set review to next step.
|
||||
$this->updateReviewStepAndSaveSubmission($this->getReviewAssignment());
|
||||
|
||||
// Mark the review assignment as completed.
|
||||
$reviewAssignment->setDateCompleted(Core::getCurrentDate());
|
||||
$reviewAssignment->stampModified();
|
||||
|
||||
// assign the recommendation to the review assignment, if there was one.
|
||||
$reviewAssignment->setRecommendation((int) $this->getData('recommendation'));
|
||||
|
||||
// Persist the updated review assignment.
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignmentDao->updateObject($reviewAssignment);
|
||||
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$stageAssignments = $stageAssignmentDao->getBySubmissionAndStageId($submission->getId(), $submission->getStageId());
|
||||
|
||||
$receivedList = []; // Avoid sending twice to the same user.
|
||||
|
||||
/** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */
|
||||
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO');
|
||||
while ($stageAssignment = $stageAssignments->next()) {
|
||||
$userId = $stageAssignment->getUserId();
|
||||
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
|
||||
|
||||
// Never send reviewer comment notification to users other than managers and editors.
|
||||
if (!in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]) || in_array($userId, $receivedList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Notify editors
|
||||
$notification = $notificationMgr->createNotification(
|
||||
Application::get()->getRequest(),
|
||||
$userId,
|
||||
PKPNotification::NOTIFICATION_TYPE_REVIEWER_COMMENT,
|
||||
$submission->getContextId(),
|
||||
PKPApplication::ASSOC_TYPE_REVIEW_ASSIGNMENT,
|
||||
$reviewAssignment->getId()
|
||||
);
|
||||
|
||||
// Check if user is subscribed to this type of notification emails
|
||||
if (!$notification || in_array(
|
||||
PKPNotification::NOTIFICATION_TYPE_REVIEWER_COMMENT,
|
||||
$notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings(
|
||||
NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY,
|
||||
$userId,
|
||||
(int) $context->getId()
|
||||
)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mailable = new ReviewCompleteNotifyEditors($context, $submission, $reviewAssignment);
|
||||
$template = Repo::emailTemplate()->getByKey($context->getId(), ReviewCompleteNotifyEditors::getEmailTemplateKey());
|
||||
|
||||
// The template may not exist, see pkp/pkp-lib#9109
|
||||
if (!$template) {
|
||||
$template = Repo::emailTemplate()->getByKey($context->getId(), 'NOTIFICATION');
|
||||
$request = Application::get()->getRequest();
|
||||
$mailable->addData([
|
||||
'notificationContents' => $notificationMgr->getNotificationContents($request, $notification),
|
||||
'notificationUrl' => $notificationMgr->getNotificationUrl($request, $notification),
|
||||
]);
|
||||
}
|
||||
|
||||
$user = Repo::user()->get($userId);
|
||||
$mailable
|
||||
->from($context->getData('contactEmail'), $context->getData('contactName'))
|
||||
->recipients([$user])
|
||||
->subject($template->getLocalizedData('subject'))
|
||||
->body($template->getLocalizedData('body'))
|
||||
->allowUnsubscribe($notification);
|
||||
|
||||
Mail::send($mailable);
|
||||
|
||||
$receivedList[] = $userId;
|
||||
}
|
||||
|
||||
// Remove the task
|
||||
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
|
||||
$notificationDao->deleteByAssoc(
|
||||
PKPApplication::ASSOC_TYPE_REVIEW_ASSIGNMENT,
|
||||
$reviewAssignment->getId(),
|
||||
$reviewAssignment->getReviewerId(),
|
||||
PKPNotification::NOTIFICATION_TYPE_REVIEW_ASSIGNMENT
|
||||
);
|
||||
|
||||
// Add log
|
||||
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId(), true);
|
||||
$eventLog = Repo::eventLog()->newDataObject([
|
||||
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
|
||||
'assocId' => $submission->getId(),
|
||||
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_READY,
|
||||
'userId' => Validation::loggedInAs() ?? Application::get()->getRequest()->getUser()->getId(),
|
||||
'message' => 'log.review.reviewReady',
|
||||
'isTranslated' => false,
|
||||
'dateLogged' => Core::getCurrentDate(),
|
||||
'reviewAssignmentId' => $reviewAssignment->getId(),
|
||||
'reviewerName' => $reviewer->getFullName(),
|
||||
'submissionId' => $reviewAssignment->getSubmissionId(),
|
||||
'round' => $reviewAssignment->getRound()
|
||||
]);
|
||||
Repo::eventLog()->add($eventLog);
|
||||
|
||||
parent::execute(...$functionParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the given answers for later
|
||||
*/
|
||||
public function saveForLater()
|
||||
{
|
||||
$reviewAssignment = $this->getReviewAssignment();
|
||||
$notificationMgr = new NotificationManager();
|
||||
|
||||
// Save the answers to the review form
|
||||
$this->saveReviewForm($reviewAssignment);
|
||||
|
||||
// Mark the review assignment as modified.
|
||||
$reviewAssignment->stampModified();
|
||||
|
||||
// save the recommendation to the review assignment
|
||||
$reviewAssignment->setRecommendation((int) $this->getData('recommendation'));
|
||||
|
||||
// Persist the updated review assignment.
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewAssignmentDao->updateObject($reviewAssignment);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the given answers to the review form
|
||||
*
|
||||
* @param ReviewAssignment $reviewAssignment
|
||||
*/
|
||||
public function saveReviewForm($reviewAssignment)
|
||||
{
|
||||
if ($reviewAssignment->getReviewFormId()) {
|
||||
$reviewFormResponseDao = DAORegistry::getDAO('ReviewFormResponseDAO'); /** @var ReviewFormResponseDAO $reviewFormResponseDao */
|
||||
$reviewFormResponses = $this->getData('reviewFormResponses');
|
||||
if (is_array($reviewFormResponses)) {
|
||||
foreach ($reviewFormResponses as $reviewFormElementId => $reviewFormResponseValue) {
|
||||
$reviewFormResponse = $reviewFormResponseDao->getReviewFormResponse($reviewAssignment->getId(), $reviewFormElementId);
|
||||
if (!isset($reviewFormResponse)) {
|
||||
$reviewFormResponse = new ReviewFormResponse();
|
||||
}
|
||||
$reviewFormElementDao = DAORegistry::getDAO('ReviewFormElementDAO'); /** @var ReviewFormElementDAO $reviewFormElementDao */
|
||||
$reviewFormElement = $reviewFormElementDao->getById($reviewFormElementId);
|
||||
$elementType = $reviewFormElement->getElementType();
|
||||
switch ($elementType) {
|
||||
case ReviewFormElement::REVIEW_FORM_ELEMENT_TYPE_SMALL_TEXT_FIELD:
|
||||
case ReviewFormElement::REVIEW_FORM_ELEMENT_TYPE_TEXT_FIELD:
|
||||
case ReviewFormElement::REVIEW_FORM_ELEMENT_TYPE_TEXTAREA:
|
||||
$reviewFormResponse->setResponseType('string');
|
||||
$reviewFormResponse->setValue($reviewFormResponseValue);
|
||||
break;
|
||||
case ReviewFormElement::REVIEW_FORM_ELEMENT_TYPE_RADIO_BUTTONS:
|
||||
case ReviewFormElement::REVIEW_FORM_ELEMENT_TYPE_DROP_DOWN_BOX:
|
||||
$reviewFormResponse->setResponseType('int');
|
||||
$reviewFormResponse->setValue($reviewFormResponseValue);
|
||||
break;
|
||||
case ReviewFormElement::REVIEW_FORM_ELEMENT_TYPE_CHECKBOXES:
|
||||
$reviewFormResponse->setResponseType('object');
|
||||
$reviewFormResponse->setValue($reviewFormResponseValue);
|
||||
break;
|
||||
}
|
||||
if ($reviewFormResponse->getReviewFormElementId() != null && $reviewFormResponse->getReviewId() != null) {
|
||||
$reviewFormResponseDao->updateObject($reviewFormResponse);
|
||||
} else {
|
||||
$reviewFormResponse->setReviewFormElementId($reviewFormElementId);
|
||||
$reviewFormResponse->setReviewId($reviewAssignment->getId());
|
||||
$reviewFormResponseDao->insertObject($reviewFormResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No review form configured. Use the default form.
|
||||
if (strlen($comments = $this->getData('comments')) > 0) {
|
||||
// Create a comment with the review.
|
||||
$submissionCommentDao = DAORegistry::getDAO('SubmissionCommentDAO'); /** @var SubmissionCommentDAO $submissionCommentDao */
|
||||
$submissionComments = $submissionCommentDao->getReviewerCommentsByReviewerId($reviewAssignment->getSubmissionId(), $reviewAssignment->getReviewerId(), $reviewAssignment->getId(), true);
|
||||
$comment = $submissionComments->next();
|
||||
|
||||
if (!isset($comment)) {
|
||||
$comment = $submissionCommentDao->newDataObject();
|
||||
}
|
||||
|
||||
$comment->setCommentType(SubmissionComment::COMMENT_TYPE_PEER_REVIEW);
|
||||
$comment->setRoleId(Role::ROLE_ID_REVIEWER);
|
||||
$comment->setAssocId($reviewAssignment->getId());
|
||||
$comment->setSubmissionId($reviewAssignment->getSubmissionId());
|
||||
$comment->setAuthorId($reviewAssignment->getReviewerId());
|
||||
$comment->setComments($comments);
|
||||
$comment->setCommentTitle('');
|
||||
$comment->setViewable(true);
|
||||
$comment->setDatePosted(Core::getCurrentDate());
|
||||
|
||||
// Save or update
|
||||
if ($comment->getId() != null) {
|
||||
$submissionCommentDao->updateObject($comment);
|
||||
} else {
|
||||
$submissionCommentDao->insertObject($comment);
|
||||
}
|
||||
}
|
||||
unset($comment);
|
||||
|
||||
if (strlen($commentsPrivate = $this->getData('commentsPrivate')) > 0) {
|
||||
// Create a comment with the review.
|
||||
$submissionCommentDao = DAORegistry::getDAO('SubmissionCommentDAO'); /** @var SubmissionCommentDAO $submissionCommentDao */
|
||||
$submissionCommentsPrivate = $submissionCommentDao->getReviewerCommentsByReviewerId($reviewAssignment->getSubmissionId(), $reviewAssignment->getReviewerId(), $reviewAssignment->getId(), false);
|
||||
$comment = $submissionCommentsPrivate->next();
|
||||
|
||||
if (!isset($comment)) {
|
||||
$comment = $submissionCommentDao->newDataObject();
|
||||
}
|
||||
|
||||
$comment->setCommentType(SubmissionComment::COMMENT_TYPE_PEER_REVIEW);
|
||||
$comment->setRoleId(Role::ROLE_ID_REVIEWER);
|
||||
$comment->setAssocId($reviewAssignment->getId());
|
||||
$comment->setSubmissionId($reviewAssignment->getSubmissionId());
|
||||
$comment->setAuthorId($reviewAssignment->getReviewerId());
|
||||
$comment->setComments($commentsPrivate);
|
||||
$comment->setCommentTitle('');
|
||||
$comment->setViewable(false);
|
||||
$comment->setDatePosted(Core::getCurrentDate());
|
||||
|
||||
// Save or update
|
||||
if ($comment->getId() != null) {
|
||||
$submissionCommentDao->updateObject($comment);
|
||||
} else {
|
||||
$submissionCommentDao->insertObject($comment);
|
||||
}
|
||||
}
|
||||
unset($comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewer\form\PKPReviewerReviewStep3Form', '\PKPReviewerReviewStep3Form');
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/submission/reviewer/form/ReviewerReviewForm.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 ReviewerReviewForm
|
||||
*
|
||||
* @ingroup submission_reviewer_form
|
||||
*
|
||||
* @brief Base class for reviewer forms.
|
||||
*/
|
||||
|
||||
namespace PKP\submission\reviewer\form;
|
||||
|
||||
use APP\submission\Submission;
|
||||
use APP\template\TemplateManager;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\form\Form;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class ReviewerReviewForm extends Form
|
||||
{
|
||||
/** @var Submission The current submission */
|
||||
public Submission $_reviewSubmission;
|
||||
|
||||
/** @var \PKP\submission\reviewAssignment\ReviewAssignment */
|
||||
public $_reviewAssignment;
|
||||
|
||||
/** @var int the current step */
|
||||
public $_step;
|
||||
|
||||
/** @var PKPRequest the request object */
|
||||
public $request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(PKPRequest $request, Submission $reviewSubmission, ReviewAssignment $reviewAssignment, int $step)
|
||||
{
|
||||
parent::__construct(sprintf('reviewer/review/step%d.tpl', $step));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
|
||||
$this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this));
|
||||
$this->request = $request;
|
||||
$this->_step = (int) $step;
|
||||
$this->_reviewSubmission = $reviewSubmission;
|
||||
$this->_reviewAssignment = $reviewAssignment;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Setters and Getters
|
||||
//
|
||||
/**
|
||||
* Get the reviewer submission.
|
||||
*/
|
||||
public function getReviewSubmission(): Submission
|
||||
{
|
||||
return $this->_reviewSubmission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the review assignment.
|
||||
*/
|
||||
public function getReviewAssignment(): ReviewAssignment
|
||||
{
|
||||
return $this->_reviewAssignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the review step.
|
||||
*/
|
||||
public function getStep(): int
|
||||
{
|
||||
return $this->_step;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement protected template methods from Form
|
||||
//
|
||||
/**
|
||||
* @copydoc Form::fetch()
|
||||
*
|
||||
* @param null|mixed $template
|
||||
*/
|
||||
public function fetch($request, $template = null, $display = false)
|
||||
{
|
||||
$templateMgr = TemplateManager::getManager($request);
|
||||
$templateMgr->assign([
|
||||
'submission' => $this->getReviewSubmission(),
|
||||
'reviewAssignment' => $this->getReviewAssignment(),
|
||||
'reviewIsClosed' => $this->getReviewAssignment()->getDateCompleted() || $this->getReviewAssignment()->getCancelled(),
|
||||
'step' => $this->getStep(),
|
||||
]);
|
||||
return parent::fetch($request, $template, $display);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Protected helper methods
|
||||
//
|
||||
/**
|
||||
* Set the review step of the submission to the given
|
||||
* value if it is not already set to a higher value. Then
|
||||
* update the given reviewer submission.
|
||||
*/
|
||||
public function updateReviewStepAndSaveSubmission(ReviewAssignment $reviewAssignment)
|
||||
{
|
||||
// Update the review step.
|
||||
$nextStep = $this->getStep() + 1;
|
||||
if ($reviewAssignment->getStep() < $nextStep) {
|
||||
$reviewAssignment->setStep($nextStep);
|
||||
}
|
||||
|
||||
// Save the reviewer submission.
|
||||
/** @var ReviewAssignmentDAO */
|
||||
$reviewAssignmentDAO = DAORegistry::getDAO('ReviewAssignmentDAO');
|
||||
$reviewAssignmentDAO->updateObject($reviewAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\submission\reviewer\form\ReviewerReviewForm', '\ReviewerReviewForm');
|
||||
}
|
||||
Reference in New Issue
Block a user