[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
lib
/
pkp
/
classes
/
submissionFile
/
[
Home
]
File: Repository.php
<?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(), ]; } }