first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,282 @@
<?php
/**
* @file classes/submissionFile/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 submission files
*/
namespace PKP\submissionFile;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\LazyCollection;
use PKP\core\interfaces\CollectorInterface;
use PKP\plugins\Hook;
/**
* @template T of SubmissionFile
*/
class Collector implements CollectorInterface
{
/** @var DAO */
public $dao;
/** @var null|array get submission files for one or more file stages */
protected $fileStages = null;
/** @var null|array get submission files for one or more genres */
protected $genreIds = null;
/** @var null|array get submission files for one or more review rounds */
protected $reviewRoundIds = null;
/** @var null|array get submission files for one or more review assignments */
protected $reviewIds = null;
/** @var null|array get submission files for one or more submissions */
protected $submissionIds = null;
/** @var null|array get submission files matching one or more files */
protected $fileIds = null;
/** @var null|string get submission files matching one ASSOC_TYPE */
protected $assocType = null;
/** @var null|array get submission files matching an ASSOC_ID with one of the assocTypes */
protected $assocIds = null;
/** @var bool include submission files in the SubmissionFile::SUBMISSION_FILE_DEPENDENT stage */
protected $includeDependentFiles = false;
/** @var null|array get submission files matching one or more uploader users id */
protected $uploaderUserIds = null;
/** @var null|int */
public $count = null;
/** @var null|int */
public $offset = 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);
}
/**
* Set fileStages filter
*/
public function filterByFileStages(?array $fileStages): self
{
$this->fileStages = $fileStages;
return $this;
}
/**
* Set genreIds filter
*/
public function filterByGenreIds(?array $genreIds): self
{
$this->genreIds = $genreIds;
return $this;
}
/**
* Set review rounds filter
*/
public function filterByReviewRoundIds(?array $reviewRoundIds): self
{
$this->reviewRoundIds = $reviewRoundIds;
return $this;
}
/**
* Set review assignments filter
*/
public function filterByReviewIds(?array $reviewIds): self
{
$this->reviewIds = $reviewIds;
return $this;
}
/**
* Set submissionIds filter
*/
public function filterBySubmissionIds(?array $submissionIds): self
{
$this->submissionIds = $submissionIds;
return $this;
}
/**
* Set fileIds filter
*/
public function filterByFileIds(?array $fileIds): self
{
$this->fileIds = $fileIds;
return $this;
}
/**
* Set assocType and assocId filters
*
* @param null|int $assocType One of the Application::ASSOC_TYPE_ constants
* @param null|array $assocIds Match for the specified assoc type
*/
public function filterByAssoc(?int $assocType, ?array $assocIds = null): self
{
$this->assocType = $assocType;
$this->assocIds = $assocIds;
return $this;
}
/**
* Set uploaderUserIds filter
*/
public function filterByUploaderUserIds(?array $uploaderUserIds): self
{
$this->uploaderUserIds = $uploaderUserIds;
return $this;
}
/**
* Whether or not to include dependent files in the results
*/
public function includeDependentFiles(bool $includeDependentFiles = true): self
{
$this->includeDependentFiles = $includeDependentFiles;
return $this;
}
/**
* Limit the number of objects retrieved
*/
public function limit(?int $count): self
{
$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): self
{
$this->offset = $offset;
return $this;
}
/**
* @copydoc CollectorInterface::getQueryBuilder()
*/
public function getQueryBuilder(): Builder
{
$qb = DB::table($this->dao->table . ' as sf')
->join('submissions as s', 's.submission_id', '=', 'sf.submission_id')
->join('files as f', 'f.file_id', '=', 'sf.file_id')
->select(['sf.*', 'f.*', 's.locale as locale']);
if ($this->submissionIds !== null) {
$qb->whereIn('sf.submission_id', $this->submissionIds);
}
if ($this->fileStages !== null) {
$qb->whereIn('sf.file_stage', $this->fileStages);
}
if ($this->genreIds !== null) {
$qb->whereIn('sf.genre_id', $this->genreIds);
}
if ($this->fileIds !== null) {
$qb->whereIn('sf.submission_file_id', function ($query) {
return $query->select('submission_file_id')
->from('submission_file_revisions')
->whereIn('file_id', $this->fileIds);
});
}
if ($this->reviewRoundIds !== null) {
$qb->whereIn('sf.submission_file_id', function ($query) {
return $query->select('submission_file_id')
->from('review_round_files')
->whereIn('review_round_id', $this->reviewRoundIds);
});
}
if ($this->reviewIds !== null) {
$qb->join('review_files as rf', 'rf.submission_file_id', '=', 'sf.submission_file_id')
->whereIn('rf.review_id', $this->reviewIds);
}
if ($this->assocType !== null) {
$qb->where('sf.assoc_type', $this->assocType);
if ($this->assocIds !== null) {
$qb->whereIn('sf.assoc_id', $this->assocIds);
}
}
if ($this->uploaderUserIds !== null) {
$qb->whereIn('sf.uploader_user_id', $this->uploaderUserIds);
}
if ($this->includeDependentFiles !== true && $this->fileStages !== null && !in_array(SubmissionFile::SUBMISSION_FILE_DEPENDENT, $this->fileStages)) {
$qb->where('sf.file_stage', '!=', SubmissionFile::SUBMISSION_FILE_DEPENDENT);
}
$qb->orderBy('sf.created_at', 'desc');
if ($this->count !== null) {
$qb->limit($this->count);
}
if ($this->offset !== null) {
$qb->offset($this->offset);
}
Hook::call('SubmissionFile::Collector::getQueryBuilder', [&$qb, $this]);
return $qb;
}
}
+459
View File
@@ -0,0 +1,459 @@
<?php
/**
* @file classes/submissionFile/DAO.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 DAO
*
* @ingroup submissionFile
*
* @see SubmissionFile
*
* @brief Operations for retrieving and modifying submission files
*/
namespace PKP\submissionFile;
use APP\core\Application;
use Exception;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\LazyCollection;
use PKP\core\EntityDAO;
use PKP\db\DAORegistry;
use PKP\plugins\PKPPubIdPluginDAO;
use PKP\services\PKPSchemaService;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\ReviewFilesDAO;
use PKP\submission\reviewRound\ReviewRound;
use PKP\submission\reviewRound\ReviewRoundDAO;
/**
* @template T of SubmissionFile
* @extends EntityDAO<T>
*/
class DAO extends EntityDAO implements PKPPubIdPluginDAO
{
/** @copydoc EntityDAO::$schema */
public $schema = PKPSchemaService::SCHEMA_SUBMISSION_FILE;
/** @copydoc EntityDAO::$table */
public $table = 'submission_files';
/** @copydoc EntityDAO::$settingsTable */
public $settingsTable = 'submission_file_settings';
/** @copydoc EntityDAO::$primaryKeyColumn */
public $primaryKeyColumn = 'submission_file_id';
/** @copydoc EntityDAO::$primaryTableColumns */
public $primaryTableColumns = [
'assocId' => 'assoc_id',
'assocType' => 'assoc_type',
'createdAt' => 'created_at',
'fileId' => 'file_id',
'fileStage' => 'file_stage',
'genreId' => 'genre_id',
'id' => 'submission_file_id',
'sourceSubmissionFileId' => 'source_submission_file_id',
'submissionId' => 'submission_id',
'updatedAt' => 'updated_at',
'uploaderUserId' => 'uploader_user_id',
'viewable' => 'viewable',
];
/**
* Get the parent object ID column name
*/
public function getParentColumn(): string
{
return 'submission_id';
}
/**
* Instantiate a new SubmissionFile
*/
public function newDataObject(): SubmissionFile
{
return app(SubmissionFile::class);
}
/**
* Get a submission file.
*
* Optionally, pass the submission ID to only get a submission file
* if it exists and is assigned to that submission.
*/
public function get(int $id, int $submissionId = null): ?SubmissionFile
{
$query = new Collector($this);
$row = $query
->getQueryBuilder()
->where($this->primaryKeyColumn, '=', $id)
->when($submissionId !== null, fn (Builder $query) => $query->where('sf.submission_id', $submissionId))
->first();
return $row ? $this->fromRow($row) : null;
}
/**
* Check if a submission file exists.
*
* Optionally, pass the submission ID to check if the submission file
* exists and is assigned to that submission.
*/
public function exists(int $id, int $submissionId = null): bool
{
return DB::table($this->table)
->where($this->primaryKeyColumn, '=', $id)
->when($submissionId !== null, fn (Builder $query) => $query->where($this->getParentColumn(), $submissionId))
->exists();
}
/**
* Get the number of announcements 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('sf.' . $this->primaryKeyColumn)
->pluck('sf.' . $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_file_id => $this->fromRow($row);
}
});
}
/**
* @copydoc EntityDAO::fromRow()
*/
public function fromRow(object $primaryRow): SubmissionFile
{
$submissionFile = parent::fromRow($primaryRow);
$submissionFile->setData('locale', $primaryRow->locale);
$submissionFile->setData('path', $primaryRow->path);
$submissionFile->setData('mimetype', $primaryRow->mimetype);
return $submissionFile;
}
/**
* @copydoc EntityDAO::insert()
*/
public function insert(SubmissionFile $submissionFile): int
{
parent::_insert($submissionFile);
DB::table('submission_file_revisions')->insert([
'submission_file_id' => $submissionFile->getId(),
'file_id' => $submissionFile->getData('fileId'),
]);
$this->insertReviewRound($submissionFile);
return $submissionFile->getId();
}
/**
* Update a Submission File
*/
public function update(SubmissionFile $submissionFile): void
{
parent::_update($submissionFile);
$hasSubmissionFileRevision = DB::table('submission_file_revisions')
->where([
'submission_file_id' => $submissionFile->getId(),
'file_id' => $submissionFile->getData('fileId')
])
->exists();
if ($hasSubmissionFileRevision) {
return;
}
DB::table('submission_file_revisions')->insert([
'submission_file_id' => $submissionFile->getId(),
'file_id' => $submissionFile->getData('fileId'),
]);
}
/**
* @copydoc EntityDAO::delete()
*/
public function delete(SubmissionFile $submissionFile)
{
parent::_delete($submissionFile);
}
/**
* @copydoc EntityDao::deleteById()
*/
public function deleteById(int $submissionFileId)
{
DB::table('submission_file_revisions')
->where('submission_file_id', '=', $submissionFileId)
->delete();
DB::table('review_round_files')
->where('submission_file_id', '=', $submissionFileId)
->delete();
$reviewFilesDao = DAORegistry::getDAO('ReviewFilesDAO'); /** @var ReviewFilesDAO $reviewFilesDao */
$reviewFilesDao->revokeBySubmissionFileId($submissionFileId);
parent::deleteById($submissionFileId);
}
/**
* Retrieve file by public file ID
*
* $pubIdType it is 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 $submissionId
* @param null|mixed $contextId
*/
public function getByPubId(
$pubIdType,
$pubId,
$submissionId = null,
$contextId = null
): ?SubmissionFile {
if (empty($pubId)) {
return null;
}
$submissionFileId = DB::table('submission_files as sf')
->where('sf.submission_id', '=', $submissionId)
->whereIn('sf.submission_file_id', function ($q) use ($pubIdType, $pubId) {
return $q->select('sfs.submission_file_id')
->from($this->settingsTable . ' as sfs')
->where('sfs.setting_name', '=', 'pub-id::' . $pubIdType)
->where('sfs.setting_value', '=', $pubId);
})
->select('sf.*')
->value('sf.submission_file_id');
if (empty($submissionFileId)) {
return null;
}
$submissionFile = $this->get($submissionFileId);
if ($submissionFile->getData('fileStage') === SubmissionFile::SUBMISSION_FILE_PROOF) {
return $submissionFile;
}
return null;
}
/**
* Retrieve file by public ID or submissionFileId
*
* @param string|int $bestId Publisher id or submissionFileId
*/
public function getByBestId(
$bestId,
int $submissionId
): ?SubmissionFile {
$submissionFile = null;
if ($bestId != '') {
$submissionFile = $this->getByPubId('publisher-id', $bestId, $submissionId, null);
}
if (!isset($submissionFile)) {
$submissionFile = $this->get($bestId);
}
if (
$submissionFile &&
in_array(
$submissionFile->getData('fileStage'),
[
SubmissionFile::SUBMISSION_FILE_PROOF,
SubmissionFile::SUBMISSION_FILE_DEPENDENT
]
)
) {
return $submissionFile;
}
return null;
}
/**
* Assign file to a review round.
*/
public function assignRevisionToReviewRound(
SubmissionFile $submissionFile,
ReviewRound $reviewRound
): void {
DB::table('review_round_files')->updateOrInsert(
[
'submission_id' => $reviewRound->getSubmissionId(),
'review_round_id' => $reviewRound->getId(),
'submission_file_id' => $submissionFile->getId(),
],
[
'submission_id' => $reviewRound->getSubmissionId(),
'review_round_id' => $reviewRound->getId(),
'stage_id' => $reviewRound->getStageId(),
'submission_file_id' => $submissionFile->getId(),
],
);
}
/**
* Checks if public identifier exists (other than for the specified
* submission file ID, which is treated as an exception).
*
* $pubIdType it is 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>).
*/
public function pubIdExists(
$pubIdType,
$pubId,
$excludePubObjectId,
$contextId
): bool {
$result = DB::table($this->settingsTable . ' as sfs')
->join('submission_files AS sf', 'sfs.submission_file_id', '=', 'sf.submission_file_id')
->join('submissions AS s', 'sf.submission_id', '=', 's.submission_id')
->where([
'sfs.setting_name' => 'pub-id::' . (string) $pubIdType,
'sfs.setting_value' => (string) $pubId,
'sfs.submission_file_id' => (int) $excludePubObjectId,
's.context_id' => (int) $contextId
])->count();
return (bool) $result > 0;
}
/**
* @copydoc PKPPubIdPluginDAO::changePubId()
*/
public function changePubId($pubObjectId, $pubIdType, $pubId)
{
DB::table($this->settingsTable)
->updateOrInsert(
[
'submission_file_id' => (int) $pubObjectId,
'setting_name' => 'pub-id::' . (string) $pubIdType,
'setting_value' => (string) $pubId
],
['locale' => '']
);
}
/**
* @copydoc PKPPubIdPluginDAO::deletePubId()
*/
public function deletePubId($pubObjectId, $pubIdType)
{
DB::table($this->settingsTable)
->where([
'submission_file_id' => (int) $pubObjectId,
'setting_name' => 'pub-id::' . (string) $pubIdType
])->delete();
}
/**
* @copydoc PKPPubIdPluginDAO::deleteAllPubIds()
*/
public function deleteAllPubIds($contextId, $pubIdType)
{
return DB::table('publication_settings as ps')
->leftJoin('publications as p', 'p.publication_id', '=', 'ps.publication_id')
->leftJoin('submissions as s', 's.submission_id', '=', 'p.submission_id')
->where('ps.setting_name', '=', 'pub-id::' . $pubIdType)
->where('s.context_id', '=', $contextId)
->delete();
}
/**
* Insert a review round for Submission File
*
* @throws Exception If we couldn't find the review Round, throws an exception.
*/
protected function insertReviewRound(SubmissionFile $submissionFile): void
{
if (
!in_array(
$submissionFile->getData('assocType'),
[
Application::ASSOC_TYPE_REVIEW_ROUND,
Application::ASSOC_TYPE_REVIEW_ASSIGNMENT
]
)
) {
return;
}
$reviewRound = $this->getReviewRound($submissionFile);
if (!$reviewRound) {
throw new Exception('Review round not found for adding submission file.');
}
DB::table('review_round_files')->insert([
'submission_id' => $submissionFile->getData('submissionId'),
'review_round_id' => $reviewRound->getId(),
'stage_id' => $reviewRound->getStageId(),
'submission_file_id' => $submissionFile->getId(),
]);
}
/**
* Retrieve the review round for a SubmissionFile, otherwise returns null
*/
protected function getReviewRound(SubmissionFile $submissionFile): ?ReviewRound
{
if ($submissionFile->getData('assocType') === Application::ASSOC_TYPE_REVIEW_ROUND) {
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
return $reviewRoundDao->getById($submissionFile->getData('assocId'));
}
if ($submissionFile->getData('assocType') === Application::ASSOC_TYPE_REVIEW_ASSIGNMENT) {
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
$reviewAssignment = $reviewAssignmentDao->getById($submissionFile->getData('assocId'));
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
return $reviewRoundDao->getById($reviewAssignment->getReviewRoundId());
}
return null;
}
}
@@ -0,0 +1,842 @@
<?php
/**
* @file classes/submissionFile/Repository.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 Repository
*
* @brief A repository to find and manage submission files.
*/
namespace PKP\submissionFile;
use APP\core\Application;
use APP\core\Request;
use APP\core\Services;
use APP\facades\Repo;
use APP\notification\Notification;
use APP\notification\NotificationManager;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\log\SubmissionEmailLogDAO;
use PKP\log\SubmissionEmailLogEntry;
use PKP\log\event\SubmissionFileEventLogEntry;
use PKP\mail\mailables\RevisedVersionNotify;
use PKP\note\NoteDAO;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\query\QueryDAO;
use PKP\security\authorization\SubmissionFileAccessPolicy;
use PKP\security\Role;
use PKP\security\Validation;
use PKP\services\PKPSchemaService;
use PKP\stageAssignment\StageAssignmentDAO;
use PKP\submission\reviewRound\ReviewRoundDAO;
use PKP\submissionFile\maps\Schema;
use PKP\validation\ValidatorFactory;
abstract class Repository
{
public DAO $dao;
public string $schemaMap = Schema::class;
protected Request $request;
/** @var PKPSchemaService<SubmissionFile> */
protected PKPSchemaService $schemaService;
/** @var array<int> $reviewFileStages The file stages that are part of a review workflow stage */
public array $reviewFileStages = [];
public function __construct(DAO $dao, Request $request, PKPSchemaService $schemaService)
{
$this->schemaService = $schemaService;
$this->dao = $dao;
$this->request = $request;
}
/** @copydoc DAO::newDataObject() */
public function newDataObject(array $params = []): SubmissionFile
{
$object = $this->dao->newDataObject();
if (!empty($params)) {
$object->setAllData($params);
}
return $object;
}
/** @copydoc DAO::get() */
public function get(int $id, int $submissionId = null): ?SubmissionFile
{
return $this->dao->get($id, $submissionId);
}
/** @copydoc DAO::exists() */
public function exists(int $id, int $submissionId = null): bool
{
return $this->dao->exists($id, $submissionId);
}
/** @copydoc DAO::getCollector() */
public function getCollector(): Collector
{
return app(Collector::class);
}
/**
* Get an instance of the map class for mapping
* submission Files to their schema
*/
public function getSchemaMap(): Schema
{
return app('maps')->withExtensions($this->schemaMap);
}
/**
* Validate properties for a submission file
*
* Perform validation checks on data used to add or edit a submission file.
*
* @param array $props A key/value array with the new data to validate
* @param array $allowedLocales The context's supported locales
* @param string $primaryLocale The context's primary locale
*
* @return array A key/value array with validation errors. Empty if no errors
*/
public function validate(
?SubmissionFile $object,
array $props,
array $allowedLocales,
string $primaryLocale
): array {
$validator = ValidatorFactory::make(
$props,
$this->schemaService->getValidationRules($this->dao->schema, $allowedLocales),
[]
);
// Check required fields
ValidatorFactory::required(
$validator,
$object,
$this->schemaService->getRequiredProps($this->dao->schema),
$this->schemaService->getMultilingualProps($this->dao->schema),
$allowedLocales,
$primaryLocale
);
// Check for input from disallowed locales
ValidatorFactory::allowedLocales($validator, $this->schemaService->getMultilingualProps($this->dao->schema), $allowedLocales);
// Do not allow the uploaderUserId or createdAt properties to be modified
if ($object) {
$validator->after(function ($validator) use ($props) {
if (
!empty($props['uploaderUserId']) &&
!$validator->errors()->get('uploaderUserId')
) {
$validator
->errors()
->add(
'uploaderUserId',
__('submission.file.notAllowedUploaderUserId')
);
}
if (
!empty($props['createdAt']) &&
!$validator->errors()->get('createdAt')
) {
$validator
->errors()
->add(
'createdAt',
__('api.files.400.notAllowedCreatedAt')
);
}
});
}
// Make sure that file stage and assocType match
if (isset($props['assocType'])) {
$validator->after(function ($validator) use ($props) {
if (
$props['assocType'] === PKPApplication::ASSOC_TYPE_REVIEW_ROUND &&
!in_array(
$props['fileStage'],
[SubmissionFile::SUBMISSION_FILE_REVIEW_FILE, SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION, SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE, SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION]
)
) {
$validator
->errors()
->add(
'assocType',
__('api.submissionFiles.400.badReviewRoundAssocType')
);
}
if ($props['assocType'] === PKPApplication::ASSOC_TYPE_REVIEW_ASSIGNMENT && $props['fileStage'] !== SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT) {
$validator
->errors()
->add(
'assocType',
__('api.submissionFiles.400.badReviewAssignmentAssocType')
);
}
if (
$props['assocType'] === PKPApplication::ASSOC_TYPE_SUBMISSION_FILE &&
$props['fileStage'] !== SubmissionFile::SUBMISSION_FILE_DEPENDENT
) {
$validator
->errors()
->add(
'assocType',
__('api.submissionFiles.400.badDependentFileAssocType')
);
}
if (
$props['assocType'] === PKPApplication::ASSOC_TYPE_NOTE &&
$props['fileStage'] !== SubmissionFile::SUBMISSION_FILE_NOTE
) {
$validator
->errors()
->add(
'assocType',
__('api.submissionFiles.400.badNoteAssocType')
);
}
if (
$props['assocType'] === PKPApplication::ASSOC_TYPE_REPRESENTATION &&
$props['fileStage'] !== SubmissionFile::SUBMISSION_FILE_PROOF
) {
$validator
->errors()
->add(
'assocType',
__('api.submissionFiles.400.badRepresentationAssocType')
);
}
});
}
$errors = [];
if ($validator->fails()) {
$errors = $this->schemaService->formatValidationErrors($validator->errors());
}
Hook::call(
'SubmissionFile::validate',
[
&$errors,
$object,
$props,
$allowedLocales,
$primaryLocale
]
);
return $errors;
}
/** @copydoc DAO::insert() */
public function add(SubmissionFile $submissionFile): int
{
$submissionFile->setData('createdAt', Core::getCurrentDate());
$submissionFile->setData('updatedAt', Core::getCurrentDate());
$submissionFileId = $this->dao->insert($submissionFile);
$submissionFile = $this->get($submissionFileId);
Hook::call('SubmissionFile::add', [$submissionFile]);
$logData = $this->getSubmissionFileLogData($submissionFile);
$logEntry = Repo::eventLog()->newDataObject(array_merge(
$logData,
[
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION_FILE,
'assocId' => $submissionFile->getId(),
'eventType' => SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_UPLOAD,
'dateLogged' => Core::getCurrentDate(),
'message' => 'submission.event.fileUploaded',
'isTranslated' => false,
]
));
Repo::eventLog()->add($logEntry);
$submission = Repo::submission()->get($submissionFile->getData('submissionId'));
$logEntry = Repo::eventLog()->newDataObject(array_merge(
$logData,
[
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_REVISION_UPLOAD,
'dateLogged' => Core::getCurrentDate(),
'message' => 'submission.event.fileRevised',
'isTranslated' => false,
]
));
Repo::eventLog()->add($logEntry);
// Update status and notifications when revisions have been uploaded
if ($submissionFile->getData('fileStage') === SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION ||
$submissionFile->getData('fileStage') === SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION) {
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$reviewRound = $reviewRoundDao->getById($submissionFile->getData('assocId'));
if (!$reviewRound) {
throw new Exception('Submission file added to review round that does not exist.');
}
$reviewRoundDao->updateStatus($reviewRound);
// Update author notifications
$authorUserIds = [];
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$authorAssignments = $stageAssignmentDao->getBySubmissionAndRoleIds($submissionFile->getData('submissionId'), [Role::ROLE_ID_AUTHOR]);
while ($assignment = $authorAssignments->next()) {
if ($assignment->getStageId() == $reviewRound->getStageId()) {
$authorUserIds[] = (int) $assignment->getUserId();
}
}
$notificationMgr = new NotificationManager();
$notificationMgr->updateNotification(
$this->request,
[PKPNotification::NOTIFICATION_TYPE_PENDING_INTERNAL_REVISIONS, PKPNotification::NOTIFICATION_TYPE_PENDING_EXTERNAL_REVISIONS],
$authorUserIds,
PKPApplication::ASSOC_TYPE_SUBMISSION,
$submissionFile->getData('submissionId')
);
// Notify editors if the file is uploaded by an author
if (in_array($submissionFile->getData('uploaderUserId'), $authorUserIds)) {
if (!$submission) {
throw new Exception('Submission file added to submission that does not exist.');
}
$this->notifyEditorsRevisionsUploaded($submissionFile);
}
}
return $submissionFileId;
}
/** @copydoc DAO::update() */
public function edit(
SubmissionFile $submissionFile,
array $params
): void {
$newSubmissionFile = clone $submissionFile;
$newSubmissionFile->setAllData(array_merge($newSubmissionFile->_data, $params));
Hook::call(
'SubmissionFile::edit',
[
$newSubmissionFile,
$submissionFile,
$params
]
);
$newSubmissionFile->setData('updatedAt', Core::getCurrentDate());
$this->dao->update($newSubmissionFile);
$newFileUploaded = !empty($params['fileId']) && $params['fileId'] !== $submissionFile->getData('fileId');
$logData = $this->getSubmissionFileLogData($submissionFile);
$logEntry = Repo::eventLog()->newDataObject(array_merge(
$logData,
[
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION_FILE,
'assocId' => $submissionFile->getId(),
'eventType' => $newFileUploaded ? SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_REVISION_UPLOAD : SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_EDIT,
'message' => $newFileUploaded ? 'submission.event.revisionUploaded' : 'submission.event.fileEdited',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
]
));
Repo::eventLog()->add($logEntry);
$submission = Repo::submission()->get($submissionFile->getData('submissionId'));
Repo::eventLog()->newDataObject(array_merge(
$logData,
[
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
'assocId' => $submission->getId(),
'eventType' => $newFileUploaded ? SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_REVISION_UPLOAD : SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_EDIT,
'message' => $newFileUploaded ? 'submission.event.revisionUploaded' : 'submission.event.fileEdited',
'isTranslate' => false,
'dateLogged' => Core::getCurrentDate(),
]
));
}
/**
* Copy a submission file to another stage
*
* @return int ID of the new submission file
*/
public function copy(SubmissionFile $submissionFile, int $toFileStage, ?int $reviewRoundId = null): int
{
$newSubmissionFile = clone $submissionFile;
$newSubmissionFile->setData('fileStage', $toFileStage);
$newSubmissionFile->setData('sourceSubmissionFileId', $submissionFile->getId());
$newSubmissionFile->setData('assocType', null);
$newSubmissionFile->setData('assocId', null);
if ($reviewRoundId) {
$newSubmissionFile->setData('assocType', Application::ASSOC_TYPE_REVIEW_ROUND);
$newSubmissionFile->setData('assocId', $reviewRoundId);
}
return Repo::submissionFile()->add($newSubmissionFile);
}
/** @copydoc DAO::delete() */
public function delete(SubmissionFile $submissionFile): void
{
Hook::call('SubmissionFile::delete::before', [$submissionFile]);
// Delete dependent files
$this
->getCollector()
->includeDependentFiles(true)
->filterByFileStages([SubmissionFile::SUBMISSION_FILE_DEPENDENT])
->filterByAssoc(Application::ASSOC_TYPE_SUBMISSION_FILE, [$submissionFile->getId()])
->getMany()
->each(function (SubmissionFile $dependentFile) {
$this->delete($dependentFile);
});
// Delete notes for this submission file
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
$noteDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION_FILE, $submissionFile->getId());
// Update tasks
$notificationMgr = new NotificationManager();
switch ($submissionFile->getData('fileStage')) {
case SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION:
$authorUserIds = [];
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
$submitterAssignments = $stageAssignmentDao->getBySubmissionAndRoleIds($submissionFile->getData('submissionId'), [Role::ROLE_ID_AUTHOR]);
while ($assignment = $submitterAssignments->next()) {
$authorUserIds[] = $assignment->getUserId();
}
$notificationMgr->updateNotification(
Application::get()->getRequest(),
[
Notification::NOTIFICATION_TYPE_PENDING_INTERNAL_REVISIONS,
Notification::NOTIFICATION_TYPE_PENDING_EXTERNAL_REVISIONS
],
$authorUserIds,
Application::ASSOC_TYPE_SUBMISSION,
$submissionFile->getData('submissionId')
);
break;
case SubmissionFile::SUBMISSION_FILE_COPYEDIT:
$notificationMgr->updateNotification(
Application::get()->getRequest(),
[
Notification::NOTIFICATION_TYPE_ASSIGN_COPYEDITOR,
Notification::NOTIFICATION_TYPE_AWAITING_COPYEDITS
],
null,
Application::ASSOC_TYPE_SUBMISSION,
$submissionFile->getData('submissionId')
);
break;
}
// Get all revision file ids before they are deleted
$revisions = $this->getRevisions($submissionFile->getId());
// Get the review round before review round files are deleted
if ($submissionFile->getData('fileStage') === SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION) {
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$reviewRound = $reviewRoundDao->getBySubmissionFileId($submissionFile->getId());
}
$this->dao->delete($submissionFile);
// Delete all files that are not referenced by other submission files
foreach ($revisions as $revision) {
$countFileShares = $this
->getCollector()
->filterByFileIds([$revision->fileId])
->includeDependentFiles(true)
->getCount();
if (!$countFileShares) {
Services::get('file')->delete($revision->fileId);
}
}
// Update the review round status after deletion
if ($submissionFile->getData('fileStage') === SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION) {
$reviewRoundDao->updateStatus($reviewRound);
}
// Log the deletion
$logEntry = Repo::eventLog()->newDataObject(array_merge(
$this->getSubmissionFileLogData($submissionFile),
[
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION_FILE,
'assocId' => $submissionFile->getId(),
'eventType' => SubmissionFileEventLogEntry::SUBMISSION_LOG_FILE_DELETE,
'message' => 'submission.event.fileDeleted',
'isTranslated' => false,
'dateLogged' => Core::getCurrentDate(),
]
));
Repo::eventLog()->add($logEntry);
Hook::call('SubmissionFile::delete', [$submissionFile]);
}
/**
* Get the file stage ids that a user can access based on their
* stage assignments
*
* This does not return file stages for ROLE_ID_REVIEWER or ROLE_ID_READER.
* These roles are not granted stage assignments and this method should not
* be used for these roles.
*
* This method does not define access to review attachments, discussion
* files or dependent files. Access to these files are not determined by
* stage assignment.
*
* In some cases it may be necessary to apply additional restrictions. For example,
* authors are granted write access to submission files or revisions only when other
* conditions are met. This method only considers these an assigned file stage for
* authors when read access is requested.
*
* $stageAssignments it's an array holding the stage assignments of this user.
* Each key is a workflow stage and value is an array of assigned roles
* $action it's an integer holding a flag to read or write to file stages. One of SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_
*
* @return array List of file stages (SubmissionFile::SUBMISSION_FILE_*)
*/
public function getAssignedFileStages(
array $stageAssignments,
int $action
): array {
$allowedRoles = [
Role::ROLE_ID_MANAGER,
Role::ROLE_ID_SITE_ADMIN,
Role::ROLE_ID_SUB_EDITOR,
Role::ROLE_ID_ASSISTANT,
Role::ROLE_ID_AUTHOR
];
$notAuthorRoles = array_diff($allowedRoles, [Role::ROLE_ID_AUTHOR]);
$allowedFileStages = [];
if (
array_key_exists(WORKFLOW_STAGE_ID_SUBMISSION, $stageAssignments) &&
!empty(array_intersect($allowedRoles, $stageAssignments[WORKFLOW_STAGE_ID_SUBMISSION]))
) {
$hasEditorialAssignment = !empty(array_intersect($notAuthorRoles, $stageAssignments[WORKFLOW_STAGE_ID_SUBMISSION]));
// Authors only have read access
if ($action === SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ || $hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_SUBMISSION;
}
}
if (array_key_exists(WORKFLOW_STAGE_ID_INTERNAL_REVIEW, $stageAssignments)) {
$hasEditorialAssignment = !empty(array_intersect($notAuthorRoles, $stageAssignments[WORKFLOW_STAGE_ID_INTERNAL_REVIEW]));
// Authors can only write revision files under specific conditions
if ($action === SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ || $hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION;
}
// Authors can never access review files
if ($hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE;
}
}
if (array_key_exists(WORKFLOW_STAGE_ID_EXTERNAL_REVIEW, $stageAssignments)) {
$hasEditorialAssignment = !empty(array_intersect($notAuthorRoles, $stageAssignments[WORKFLOW_STAGE_ID_EXTERNAL_REVIEW]));
// Authors can only write revision files under specific conditions
if ($action === SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ || $hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION;
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_ATTACHMENT;
}
// Authors can never access review files
if ($hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_REVIEW_FILE;
}
}
if (
array_key_exists(WORKFLOW_STAGE_ID_EDITING, $stageAssignments) &&
!empty(array_intersect($allowedRoles, $stageAssignments[WORKFLOW_STAGE_ID_EDITING]))
) {
$hasEditorialAssignment = !empty(array_intersect($notAuthorRoles, $stageAssignments[WORKFLOW_STAGE_ID_EDITING]));
// Authors only have read access
if ($action === SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ || $hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_COPYEDIT;
}
if ($hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_FINAL;
}
}
if (array_key_exists(WORKFLOW_STAGE_ID_PRODUCTION, $stageAssignments) &&
!empty(array_intersect($allowedRoles, $stageAssignments[WORKFLOW_STAGE_ID_PRODUCTION]))
) {
$hasEditorialAssignment = !empty(array_intersect($notAuthorRoles, $stageAssignments[WORKFLOW_STAGE_ID_PRODUCTION]));
// Authors only have read access
if ($action === SubmissionFileAccessPolicy::SUBMISSION_FILE_ACCESS_READ || $hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_PROOF;
}
if ($hasEditorialAssignment) {
$allowedFileStages[] = SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY;
}
}
return $allowedFileStages;
}
/**
* Get all valid file stages
*
* Valid file stages should be passed through
* the hook SubmissionFile::fileStages.
*/
abstract public function getFileStages(): array;
/**
* Get the path to a submission's file directory
*
* This returns the relative path from the files_dir set in the config.
*/
public function getSubmissionDir(
int $contextId,
int $submissionId
): string {
$dirNames = Application::getFileDirectories();
return sprintf(
'%s/%d/%s/%d',
str_replace('/', '', $dirNames['context']),
$contextId,
str_replace('/', '', $dirNames['submission']),
$submissionId
);
}
/**
* Get the workflow stage for a submission file
*/
public function getWorkflowStageId(SubmissionFile $submissionFile): ?int
{
$fileStage = $submissionFile->getData('fileStage');
if ($fileStage === SubmissionFile::SUBMISSION_FILE_SUBMISSION) {
return WORKFLOW_STAGE_ID_SUBMISSION;
}
if (
$fileStage === SubmissionFile::SUBMISSION_FILE_FINAL ||
$fileStage === SubmissionFile::SUBMISSION_FILE_COPYEDIT
) {
return WORKFLOW_STAGE_ID_EDITING;
}
if (
$fileStage === SubmissionFile::SUBMISSION_FILE_PROOF ||
$fileStage === SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY
) {
return WORKFLOW_STAGE_ID_PRODUCTION;
}
if (
$fileStage === SubmissionFile::SUBMISSION_FILE_DEPENDENT
) {
$parentFile = $this->get($submissionFile->getData('assocId'));
return $parentFile ? $this->getWorkflowStageId($parentFile) : null;
}
if (
$fileStage === SubmissionFile::SUBMISSION_FILE_REVIEW_FILE ||
$fileStage === SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE ||
$fileStage === SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT ||
$fileStage === SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION ||
$fileStage === SubmissionFile::SUBMISSION_FILE_ATTACHMENT ||
$fileStage === SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION
) {
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$reviewRound = $reviewRoundDao->getBySubmissionFileId($submissionFile->getId());
return $reviewRound?->getStageId();
}
if ($fileStage === SubmissionFile::SUBMISSION_FILE_QUERY) {
// This file should be associated with a note. If not, fail.
if ($submissionFile->getData('assocType') != PKPApplication::ASSOC_TYPE_NOTE) {
return null;
}
// Get the associated note.
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
$note = $noteDao->getById($submissionFile->getData('assocId'));
// The note should be associated with a query. If not, fail.
if ($note?->getAssocType() != PKPApplication::ASSOC_TYPE_QUERY) {
return null;
}
// Get the associated query.
$queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
$query = $queryDao->getById($note->getAssocId());
// The query will have an associated file stage.
return $query ? $query->getStageId() : null;
}
throw new Exception('Could not determine the workflow stage id from submission file ' . $submissionFile->getId() . ' with file stage ' . $submissionFile->getData('fileStage'));
}
/**
* Check if a submission file supports dependent files
*/
public function supportsDependentFiles(SubmissionFile $submissionFile): bool
{
$fileStage = $submissionFile->getData('fileStage');
$excludedFileStages = [
SubmissionFile::SUBMISSION_FILE_DEPENDENT,
SubmissionFile::SUBMISSION_FILE_QUERY,
];
$allowedMimetypes = [
'text/html',
'application/xml',
'text/xml',
];
$result = !in_array($fileStage, $excludedFileStages) && in_array($submissionFile->getData('mimetype'), $allowedMimetypes);
Hook::call('SubmissionFile::supportsDependentFiles', [&$result, $submissionFile]);
return $result;
}
/**
* Get the files for each revision of a submission file
*/
public function getRevisions(int $submissionFileId): Collection
{
return DB::table('submission_file_revisions as sfr')
->leftJoin('files as f', 'f.file_id', '=', 'sfr.file_id')
->where('submission_file_id', '=', $submissionFileId)
->orderBy('revision_id', 'desc')
->select(['f.file_id as fileId', 'f.path', 'f.mimetype', 'sfr.revision_id'])
->get();
}
/**
* Sends email to notify editors about new revision of a submission file
*/
protected function notifyEditorsRevisionsUploaded(SubmissionFile $submissionFile): void
{
$submission = Repo::submission()->get($submissionFile->getData('submissionId'));
$context = Services::get('context')->get($submission->getData('contextId'));
$uploader = Repo::user()->get($submissionFile->getData('uploaderUserId'));
$user = $this->request->getUser();
// Fetch the latest notification email timestamp
$submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO');
/** @var SubmissionEmailLogDAO $submissionEmailLogDao */
$submissionEmails = $submissionEmailLogDao->getByEventType(
$submission->getId(),
SubmissionEmailLogEntry::SUBMISSION_EMAIL_AUTHOR_NOTIFY_REVISED_VERSION
);
$lastNotification = null;
$sentDates = [];
if ($submissionEmails) {
while ($email = $submissionEmails->next()) {
if ($email->getDateSent()) {
$sentDates[] = $email->getDateSent();
}
}
if (!empty($sentDates)) {
$lastNotification = max(array_map('strtotime', $sentDates));
}
}
// Get editors assigned to the submission, consider also the recommendOnly editors
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao*/
$reviewRound = $reviewRoundDao->getById($submissionFile->getData('assocId'));
$editorsStageAssignments = $stageAssignmentDao->getEditorsAssignedToStage(
$submission->getId(),
$reviewRound->getStageId()
);
$recipients = [];
foreach ($editorsStageAssignments as $editorsStageAssignment) {
$editor = Repo::user()->get($editorsStageAssignment->getUserId());
// IF no prior notification exists
// OR if editor has logged in after the last revision upload
// OR the last upload and notification was sent more than a day ago,
// THEN send a new notification
if (is_null($lastNotification) || strtotime($editor->getDateLastLogin()) > $lastNotification || strtotime('-1 day') > $lastNotification) {
$recipients[] = $editor;
}
}
if (empty($recipients)) {
return;
}
$mailable = new RevisedVersionNotify($context, $submission, $uploader, $reviewRound);
$template = Repo::emailTemplate()->getByKey($context->getId(), RevisedVersionNotify::getEmailTemplateKey());
$mailable->body($template->getLocalizedData('body'))
->subject($template->getLocalizedData('subject'))
->sender($user)
->recipients($recipients)
->replyTo($context->getData('contactEmail'), $context->getData('contactName'));
Mail::send($mailable);
$submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $submissionEmailLogDao */
$submissionEmailLogDao->logMailable(
SubmissionEmailLogEntry::SUBMISSION_EMAIL_AUTHOR_NOTIFY_REVISED_VERSION,
$mailable,
$submission,
$user
);
}
/**
* Derive data from the submission file to record in the event log
*/
protected function getSubmissionFileLogData(SubmissionFile $submissionFile): array
{
$user = $this->request->getUser();
return [
'userId' => Validation::loggedInAs() ?: $user?->getId(),
'fileStage' => $submissionFile->getData('fileStage'),
'submissionFileId' => $submissionFile->getId(),
'sourceSubmissionFileId' => $submissionFile->getData('sourceSubmissionFileId'),
'fileId' => $submissionFile->getData('fileId'),
'submissionId' => $submissionFile->getData('submissionId'),
'filename' => $submissionFile->getData('name'),
'username' => $user?->getUsername(),
];
}
}
@@ -0,0 +1,407 @@
<?php
/**
* @file classes/submissionFile/SubmissionFile.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 SubmissionFile
*
* @ingroup submission
*
* @brief Submission file class.
*/
namespace PKP\submissionFile;
use APP\facades\Repo;
use PKP\facades\Locale;
/**
* @extends \PKP\core\DataObject<DAO>
*/
class SubmissionFile extends \PKP\core\DataObject
{
// Define the file stage identifiers.
public const SUBMISSION_FILE_SUBMISSION = 2;
public const SUBMISSION_FILE_NOTE = 3;
public const SUBMISSION_FILE_REVIEW_FILE = 4;
public const SUBMISSION_FILE_REVIEW_ATTACHMENT = 5;
public const SUBMISSION_FILE_FINAL = 6;
public const SUBMISSION_FILE_COPYEDIT = 9;
public const SUBMISSION_FILE_PROOF = 10;
public const SUBMISSION_FILE_PRODUCTION_READY = 11;
public const SUBMISSION_FILE_ATTACHMENT = 13;
public const SUBMISSION_FILE_REVIEW_REVISION = 15;
public const SUBMISSION_FILE_DEPENDENT = 17;
public const SUBMISSION_FILE_QUERY = 18;
public const SUBMISSION_FILE_INTERNAL_REVIEW_FILE = 19;
public const SUBMISSION_FILE_INTERNAL_REVIEW_REVISION = 20;
public const INTERNAL_REVIEW_STAGES = [
SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE,
SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION,
];
public const EXTERNAL_REVIEW_STAGES = [
SubmissionFile::SUBMISSION_FILE_REVIEW_FILE,
SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION,
];
/**
* Get the default/fall back locale the values should exist for
*/
public function getDefaultLocale(): ?string
{
return $this->getData('locale');
}
/**
* Get the locale of the submission.
* This is not properly a property of the submission file
* (e.g. it won't be persisted to the DB with the update function)
* It helps solve submission locale requirement for file's multilingual metadata
*
* @deprecated 3.3.0.0
*
* @return string
*/
public function getSubmissionLocale()
{
return $this->getData('locale');
}
/**
* Set the locale of the submission.
* This is not properly a property of the submission file
* (e.g. it won't be persisted to the DB with the update function)
* It helps solve submission locale requirement for file's multilingual metadata
*
* @deprecated 3.3.0.0
*
* @param string $submissionLocale
*/
public function setSubmissionLocale($submissionLocale)
{
$this->setData('locale', $submissionLocale);
}
/**
* Get stored public ID of the file.
*
* @param string $pubIdType @literal 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>). @endliteral
*
* @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 file.
*
* @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)
{
if ($pubIdType == 'doi') {
if ($doiObject = $this->getData('doiObject')) {
Repo::doi()->edit($doiObject, ['doi' => $pubId]);
} else {
$newDoiObject = Repo::doi()->newDataObject(
[
'doi' => $pubId,
'contextId' => Repo::submission()->get($this->getData('submissionId'))->getData('contextId')
]
);
$doiId = Repo::doi()->add($newDoiObject);
$this->setData('doiId', $doiId);
}
} else {
$this->setData('pub-id::' . $pubIdType, $pubId);
}
}
/**
* Get price of submission file.
* A null return indicates "not available"; 0 is free.
*
* @return float|null
*/
public function getDirectSalesPrice()
{
return $this->getData('directSalesPrice');
}
/**
* Set direct sales price.
* A null return indicates "not available"; 0 is free.
*
* @param float|null $directSalesPrice
*/
public function setDirectSalesPrice($directSalesPrice)
{
$this->setData('directSalesPrice', $directSalesPrice);
}
/**
* Get sales type of submission file.
*
* @return string
*/
public function getSalesType()
{
return $this->getData('salesType');
}
/**
* Set sales type.
*
* @param string $salesType
*/
public function setSalesType($salesType)
{
$this->setData('salesType', $salesType);
}
/**
* Set the genre id of this file (i.e. referring to Manuscript, Index, etc)
* Foreign key into genres table
*
* @deprecated 3.3.0.0
*
* @param int $genreId
*/
public function setGenreId($genreId)
{
$this->setData('genreId', $genreId);
}
/**
* Get the genre id of this file (i.e. referring to Manuscript, Index, etc)
* Foreign key into genres table
*
* @deprecated 3.3.0.0
*
* @return int
*/
public function getGenreId()
{
return $this->getData('genreId');
}
/**
* Return the "best" file ID -- If a public ID is set,
* use it; otherwise use the internal ID and revision.
*
* @return string
*/
public function getBestId()
{
return strlen($publisherId = (string) $this->getStoredPubId('publisher-id')) ? $publisherId : $this->getId();
}
/**
* Get file stage of the file.
*
* @deprecated 3.3.0.0
*
* @return int SubmissionFile::SUBMISSION_FILE_...
*/
public function getFileStage()
{
return $this->getData('fileStage');
}
/**
* Set file stage of the file.
*
* @deprecated 3.3.0.0
*
* @param int $fileStage SubmissionFile::SUBMISSION_FILE_...
*/
public function setFileStage($fileStage)
{
$this->setData('fileStage', $fileStage);
}
/**
* Get modified date of file.
*
* @deprecated 3.3.0.0
*
* @return string
*/
public function getDateModified()
{
return $this->getData('updatedAt');
}
/**
* Set modified date of file.
*
* @deprecated 3.3.0.0
*
* @param string $updatedAt
*/
public function setDateModified($updatedAt)
{
return $this->setData('updatedAt', $updatedAt);
}
/**
* Get viewable.
*
* @deprecated 3.3.0.0
*
* @return bool
*/
public function getViewable()
{
return $this->getData('viewable');
}
/**
* Set viewable.
*
* @deprecated 3.3.0.0
*
* @param bool $viewable
*/
public function setViewable($viewable)
{
return $this->setData('viewable', $viewable);
}
/**
* Set the uploader's user id.
*
* @deprecated 3.3.0.0
*
* @param int $uploaderUserId
*/
public function setUploaderUserId($uploaderUserId)
{
$this->setData('uploaderUserId', $uploaderUserId);
}
/**
* Get the uploader's user id.
*
* @deprecated 3.3.0.0
*
* @return int
*/
public function getUploaderUserId()
{
return $this->getData('uploaderUserId');
}
/**
* Get type that is associated with this file.
*
* @deprecated 3.3.0.0
*
* @return int
*/
public function getAssocType()
{
return $this->getData('assocType');
}
/**
* Set type that is associated with this file.
*
* @deprecated 3.3.0.0
*
* @param int $assocType
*/
public function setAssocType($assocType)
{
$this->setData('assocType', $assocType);
}
/**
* Get the submission chapter id.
*
* @deprecated 3.3.0.0
*
* @return int
*/
public function getChapterId()
{
return $this->getData('chapterId');
}
/**
* Set the submission chapter id.
*
* @deprecated 3.3.0.0
*
* @param int $chapterId
*/
public function setChapterId($chapterId)
{
$this->setData('chapterId', $chapterId);
}
/**
* Helper method to fetch current DOI
*
*/
public function getDoi(): ?string
{
$doiObject = $this->getData('doiObject');
if (empty($doiObject)) {
return null;
} else {
return $doiObject->getData('doi');
}
}
/**
* @copydoc \PKP\core\DataObject::getDAO()
*/
public function getDAO(): DAO
{
return Repo::submissionFile()->dao;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\submissionFile\SubmissionFile', '\SubmissionFile');
foreach ([
'SUBMISSION_FILE_SUBMISSION',
'SUBMISSION_FILE_NOTE',
'SUBMISSION_FILE_REVIEW_FILE',
'SUBMISSION_FILE_REVIEW_ATTACHMENT',
'SUBMISSION_FILE_FINAL',
'SUBMISSION_FILE_COPYEDIT',
'SUBMISSION_FILE_PROOF',
'SUBMISSION_FILE_PRODUCTION_READY',
'SUBMISSION_FILE_ATTACHMENT',
'SUBMISSION_FILE_REVIEW_REVISION',
'SUBMISSION_FILE_DEPENDENT',
'SUBMISSION_FILE_QUERY',
'SUBMISSION_FILE_INTERNAL_REVIEW_FILE',
'SUBMISSION_FILE_INTERNAL_REVIEW_REVISION',
] as $constantName) {
define($constantName, constant('\SubmissionFile::' . $constantName));
}
}
@@ -0,0 +1,249 @@
<?php
/**
* @file classes/submissionFile/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 submissionFiles to the properties defined in the submission file schema
*/
namespace PKP\submissionFile\maps;
use APP\core\Application;
use APP\core\Request;
use APP\core\Services;
use APP\facades\Repo;
use Illuminate\Support\Enumerable;
use PKP\context\Context;
use PKP\core\maps\Schema as BaseSchema;
use PKP\services\PKPSchemaService;
use PKP\submission\Genre;
use PKP\submissionFile\SubmissionFile;
class Schema extends BaseSchema
{
/** */
public Enumerable $collection;
/** */
public string $schema = PKPSchemaService::SCHEMA_SUBMISSION_FILE;
/** @var Genre[] File genres in this context */
public array $genres;
public function __construct(Request $request, Context $context, PKPSchemaService $schemaService)
{
parent::__construct($request, $context, $schemaService);
}
/**
* Map a submission file
*
* Includes all properties in the submission file schema.
*
* @param Genre[] $genres
*/
public function map(SubmissionFile $item, array $genres): array
{
$this->genres = $genres;
return $this->mapByProperties($this->getProps(), $item);
}
/**
* Summarize a submission file
*
* Includes properties with the apiSummary flag in the submission file schema.
*
* @param Genre[] $genres
*/
public function summarize(SubmissionFile $item, array $genres): array
{
$this->genres = $genres;
return $this->mapByProperties($this->getSummaryProps(), $item);
}
/**
* Map a collection of submission files
*
* @see self::map
*
* @param Genre[] $genres
*/
public function mapMany(Enumerable $collection, array $genres): Enumerable
{
$this->collection = $collection;
return $collection->map(function ($item) use ($genres) {
return $this->map($item, $genres);
});
}
/**
* Summarize a collection of submission files
*
* @see self::summarize
*
* @param Genre[] $genres
*/
public function summarizeMany(Enumerable $collection, array $genres): Enumerable
{
$this->collection = $collection;
return $collection->map(function ($item) use ($genres) {
return $this->summarize($item, $genres);
});
}
/**
* Map schema properties of a submission file to an assoc array
*/
protected function mapByProperties(array $props, SubmissionFile $submissionFile): array
{
$output = [];
foreach ($props as $prop) {
if ($prop === '_href') {
$output[$prop] = $this->getApiUrl(
'submissions/' . $submissionFile->getData('submissionId') . '/files/' . $submissionFile->getId(),
$this->context->getData('urlPath')
);
continue;
}
if ($prop === 'dependentFiles') {
$dependentFiles = Repo::submissionFile()
->getCollector()
->filterByAssoc(Application::ASSOC_TYPE_SUBMISSION_FILE, [$submissionFile->getId()])
->filterBySubmissionIds([$submissionFile->getData('submissionId')])
->filterByFileStages([SubmissionFile::SUBMISSION_FILE_DEPENDENT])
->includeDependentFiles()
->getMany();
$output[$prop] = $this->summarizeMany($dependentFiles, $this->genres)->values();
continue;
}
if ($prop === 'documentType') {
$output[$prop] = Services::get('file')->getDocumentType($submissionFile->getData('mimetype'));
continue;
}
if ($prop === 'genreId') {
$genre = $this->getGenre($submissionFile);
$output[$prop] = $genre ? $genre->getId() : null;
continue;
}
if ($prop === 'genreName') {
$genre = $this->getGenre($submissionFile);
$output[$prop] = $genre ? $genre->getName(null) : null;
continue;
}
if ($prop === 'genreIsDependent') {
$genre = $this->getGenre($submissionFile);
$output[$prop] = $genre ? (bool) $genre->getDependent() : null;
continue;
}
if ($prop === 'genreIsSupplementary') {
$genre = $this->getGenre($submissionFile);
$output[$prop] = $genre ? (bool) $genre->getSupplementary() : null;
continue;
}
if ($prop === 'revisions') {
$files = [];
$revisions = Repo::submissionFile()->getRevisions($submissionFile->getId());
foreach ($revisions as $revision) {
if ($revision->fileId === $submissionFile->getData('fileId')) {
continue;
}
$files[] = [
'documentType' => Services::get('file')->getDocumentType($revision->mimetype),
'fileId' => $revision->fileId,
'mimetype' => $revision->mimetype,
'path' => $revision->path,
'url' => $this->request->getDispatcher()->url(
$this->request,
Application::ROUTE_COMPONENT,
$this->context->getData('urlPath'),
'api.file.FileApiHandler',
'downloadFile',
null,
[
'fileId' => $revision->fileId,
'submissionFileId' => $submissionFile->getId(),
'submissionId' => $submissionFile->getData('submissionId'),
'stageId' => Repo::submissionFile()->getWorkflowStageId($submissionFile),
]
),
];
}
$output[$prop] = $files;
continue;
}
if ($prop === 'uploaderUserName') {
$userId = $submissionFile->getData('uploaderUserId');
$user = !is_null($userId) ? Repo::user()->get($userId) : null; // userId can be null, see pkp/pkp-lib#8493
$output[$prop] = $user?->getUsername() ?? '';
continue;
}
if ($prop === 'url') {
$output[$prop] = $this->request->getDispatcher()->url(
$this->request,
Application::ROUTE_COMPONENT,
$this->context->getData('urlPath'),
'api.file.FileApiHandler',
'downloadFile',
null,
[
'submissionFileId' => $submissionFile->getId(),
'submissionId' => $submissionFile->getData('submissionId'),
'stageId' => Repo::submissionFile()->getWorkflowStageId($submissionFile),
]
);
continue;
}
$output[$prop] = $submissionFile->getData($prop);
}
$output = $this->schemaService->addMissingMultilingualValues(
$this->schema,
$output,
$this->context->getSupportedFormLocales()
);
ksort($output);
return $this->withExtensions($output, $submissionFile);
}
protected function getGenre(SubmissionFile $submissionFile): ?Genre
{
foreach ($this->genres as $genre) {
if ($genre->getId() === $submissionFile->getData('genreId')) {
return $genre;
}
}
return null;
}
}