[ 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
/
publication
/
[
Home
]
File: Repository.php
<?php /** * @file classes/publication/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 publications. */ namespace PKP\publication; use APP\core\Application; use APP\core\Request; use APP\core\Services; use APP\facades\Repo; use APP\file\PublicFileManager; use APP\publication\DAO; use APP\publication\Publication; use APP\submission\Submission; use Illuminate\Support\Enumerable; use Illuminate\Support\LazyCollection; use PKP\context\Context; use PKP\core\Core; use PKP\core\PKPApplication; use PKP\db\DAORegistry; use PKP\file\TemporaryFileManager; use PKP\log\event\PKPSubmissionEventLogEntry; use PKP\observers\events\PublicationPublished; use PKP\observers\events\PublicationUnpublished; use PKP\plugins\Hook; use PKP\security\Validation; use PKP\services\PKPSchemaService; use PKP\submission\Genre; use PKP\submission\PKPSubmission; use PKP\userGroup\UserGroup; use PKP\validation\ValidatorFactory; abstract class Repository { /** @var 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 */ protected $request; /** @var PKPSchemaService<Publication> */ 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 = []): Publication { $object = $this->dao->newDataObject(); if (!empty($params)) { $object->setAllData($params); } return $object; } /** @copydoc DAO::exists() */ public function exists(int $id, int $submissionId = null): bool { return $this->dao->exists($id, $submissionId); } /** @copydoc DAO::get() */ public function get(int $id, int $submissionId = null): ?Publication { return $this->dao->get($id, $submissionId); } /** @copydoc DAO::getCollector() */ public function getCollector(): Collector { return app(Collector::class); } /** * Get an instance of the map class for mapping * publications to their schema * * @param LazyCollection<int,UserGroup> $userGroups * @param Genre[] $genres */ public function getSchemaMap(Submission $submission, LazyCollection $userGroups, array $genres): maps\Schema { return app('maps')->withExtensions( $this->schemaMap, [ 'submission' => $submission, 'userGroups' => $userGroups, 'genres' => $genres, ] ); } /** @copydoc DAO:: getIdsBySetting()*/ public function getIdsBySetting(string $settingName, $settingValue, int $contextId): Enumerable { return $this->dao->getIdsBySetting($settingName, $settingValue, $contextId); } /** @copydoc DAO:: getDateBoundaries()*/ public function getDateBoundaries(Collector $query): object { return $this->dao->getDateBoundaries($query); } /** * Validate properties for a publication * * Perform validation checks on data used to add or edit a publication. * * @param Publication|null $publication The publication being edited. Pass `null` if creating a new publication * @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(?Publication $publication, array $props, Submission $submission, Context $context): array { $allowedLocales = $context->getSupportedSubmissionLocales(); $primaryLocale = $submission->getLocale(); $errors = []; $validator = ValidatorFactory::make( $props, $this->schemaService->getValidationRules($this->dao->schema, $allowedLocales), $this->getErrorMessageOverrides(), ); ValidatorFactory::required( $validator, $publication, $this->schemaService->getRequiredProps($this->dao->schema), $this->schemaService->getMultilingualProps($this->dao->schema), $allowedLocales, $primaryLocale ); ValidatorFactory::allowedLocales($validator, $this->schemaService->getMultilingualProps($this->dao->schema), $allowedLocales); // The submissionId must match an existing submission if (isset($props['submissionId'])) { $validator->after(function ($validator) use ($props) { if (!$validator->errors()->get('submissionId')) { $submission = Repo::submission()->get($props['submissionId']); if (!$submission) { $validator->errors()->add('submissionId', __('publication.invalidSubmission')); } } }); } // A title must be provided if the submission is not still in progress if (!$submission->getData('submissionProgress')) { $validator->after(function ($validator) use ($props, $publication, $primaryLocale) { $title = isset($props['title']) && isset($props['title'][$primaryLocale]) ? $props['title'][$primaryLocale] : $publication?->getData('title', $primaryLocale); if (empty($title)) { $validator->errors()->add('title.' . $primaryLocale, __('validator.required')); } }); } // The urlPath must not be used in a publication attached to // any submission other than this publication's submission if (strlen($props['urlPath'] ?? '')) { $validator->after(function ($validator) use ($publication, $props) { if (!$validator->errors()->get('urlPath')) { if (ctype_digit((string) $props['urlPath'])) { $validator->errors()->add('urlPath', __('publication.urlPath.numberInvalid')); return; } // If there is no submissionId the validator will throw it back anyway if (is_null($publication) && !empty($props['submissionId'])) { $submission = Repo::submission()->get($props['submissionId']); } elseif (!is_null($publication)) { $submission = Repo::submission()->get($publication->getData('submissionId')); } // If there's no submission we can't validate but the validator should // fail anyway, so we can return without setting a separate validation // error. if (!$submission) { return; } if ($this->dao->isDuplicateUrlPath($props['urlPath'], $submission->getId(), $submission->getData('contextId'))) { $validator->errors()->add('urlPath', __('publication.urlPath.duplicate')); } } }); } // If a new file has been uploaded, check that the temporary file exists and // the current user owns it $user = Application::get()->getRequest()->getUser(); ValidatorFactory::temporaryFilesExist( $validator, ['coverImage'], ['coverImage'], $props, $allowedLocales, $user ? $user->getId() : null ); if ($validator->fails()) { $errors = $this->schemaService->formatValidationErrors($validator->errors()); } Hook::call('Publication::validate', [&$errors, $publication, $props, $allowedLocales, $primaryLocale]); return $errors; } /** * Validate a publication against publishing requirements * * This validation check should return zero errors before * publishing a publication. * * It should not be necessary to repeat validation rules from * self::validate(). These rules should be applied during all add * or edit actions. * * This additional check should be used when a journal or press * wants to enforce particular publishing requirements, such as * requiring certain metadata or other information. * * @param array $allowedLocales The context's supported submission locales * @param string $primaryLocale The submission's primary locale */ public function validatePublish(Publication $publication, Submission $submission, array $allowedLocales, string $primaryLocale): array { $errors = []; // Don't allow declined submissions to be published if ($submission->getData('status') === PKPSubmission::STATUS_DECLINED) { $errors['declined'] = __('publication.required.declined'); } // Don't allow a publication to be published before passing the review stage if ($submission->getData('stageId') <= WORKFLOW_STAGE_ID_EXTERNAL_REVIEW) { $errors['reviewStage'] = __('publication.required.reviewStage'); } Hook::call('Publication::validatePublish', [&$errors, $publication, $submission, $allowedLocales, $primaryLocale]); return $errors; } /** @copydoc DAO::insert() */ public function add(Publication $publication): int { $publication->stampModified(); $publicationId = $this->dao->insert($publication); $publication = Repo::publication()->get($publicationId); $submission = Repo::submission()->get($publication->getData('submissionId')); // Move uploaded files into place and update the settings if ($publication->getData('coverImage')) { $userId = $this->request->getUser() ? $this->request->getUser()->getId() : null; $submissionContext = $this->request->getContext(); if ($submissionContext->getId() !== $submission->getData('contextId')) { $submissionContext = Services::get('context')->get($submission->getData('contextId')); } $supportedLocales = $submissionContext->getSupportedSubmissionLocales(); foreach ($supportedLocales as $localeKey) { if (!array_key_exists($localeKey, $publication->getData('coverImage'))) { continue; } $value[$localeKey] = $this->_saveFileParam($publication, $submission, $publication->getData('coverImage', $localeKey), 'coverImage', $userId, $localeKey, true); } $this->edit($publication, ['coverImage' => $value]); } Hook::call('Publication::add', [&$publication]); // Update a submission's status based on the status of its publications Repo::submission()->updateStatus($submission); return $publication->getId(); } /** * Create a new version of a publication * * Makes a copy of an existing publication, without the datePublished, * and makes copies of all associated objects. */ public function version(Publication $publication): int { $newPublication = clone $publication; $newPublication->setData('id', null); $newPublication->setData('datePublished', null); $newPublication->setData('status', Submission::STATUS_QUEUED); $newPublication->setData('version', $publication->getData('version') + 1); $newPublication->stampModified(); $request = Application::get()->getRequest(); $context = $request->getContext(); if ($context->getData(Context::SETTING_DOI_VERSIONING)) { $newPublication->setData('doiId', null); } $newId = $this->add($newPublication); $newPublication = Repo::publication()->get($newId); $authors = $publication->getData('authors'); if (!empty($authors)) { foreach ($authors as $author) { $newAuthor = clone $author; $newAuthor->setData('id', null); $newAuthor->setData('publicationId', $newPublication->getId()); $newAuthorId = Repo::author()->add($newAuthor); if ($author->getId() === $publication->getData('primaryContactId')) { $this->edit($newPublication, ['primaryContactId' => $newAuthorId]); } } } if (!empty($newPublication->getData('citationsRaw'))) { $citationDao = DAORegistry::getDAO('CitationDAO'); /** @var \PKP\citation\CitationDAO $citationDao */ $citationDao->importCitations($newPublication->getId(), $newPublication->getData('citationsRaw')); } $newPublication = Repo::publication()->get($newPublication->getId()); Hook::call('Publication::version', [&$newPublication, $publication]); $submission = Repo::submission()->get($newPublication->getData('submissionId')); $eventLog = Repo::eventLog()->newDataObject([ 'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION, 'assocId' => $submission->getId(), 'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_CREATE_VERSION, 'userId' => Validation::loggedInAs() ?? $request->getUser()?->getId(), 'message' => 'publication.event.versionCreated', 'isTranslated' => false, 'dateLogged' => Core::getCurrentDate(), ]); Repo::eventLog()->add($eventLog); return $newPublication->getId(); } /** @copydoc DAO::update() */ public function edit(Publication $publication, array $params): Publication { $submission = Repo::submission()->get($publication->getData('submissionId')); $userId = $this->request->getUser()?->getId(); // Move uploaded files into place and update the params if (array_key_exists('coverImage', $params)) { $submissionContext = $this->request->getContext(); if ($submissionContext->getId() !== $submission->getData('contextId')) { $submissionContext = Services::get('context')->get($submission->getData('contextId')); } $supportedLocales = $submissionContext->getSupportedSubmissionLocales(); foreach ($supportedLocales as $localeKey) { if (!array_key_exists($localeKey, $params['coverImage'])) { continue; } $params['coverImage'][$localeKey] = $this->_saveFileParam($publication, $submission, $params['coverImage'][$localeKey], 'coverImage', $userId, $localeKey, true); } } $newPublication = Repo::publication()->newDataObject(array_merge($publication->_data, $params)); $newPublication->stampModified(); Hook::call('Publication::edit', [&$newPublication, $publication, $params, $this->request]); $this->dao->update($newPublication, $publication); $newPublication = Repo::publication()->get($newPublication->getId()); $submission = Repo::submission()->get($newPublication->getData('submissionId')); // Log an event when publication data is updated $eventLog = Repo::eventLog()->newDataObject([ 'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION, 'assocId' => $submission->getId(), 'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_METADATA_UPDATE, 'userId' => Validation::loggedInAs() ?? $userId, 'message' => 'submission.event.general.metadataUpdated', 'isTranslated' => false, 'dateLogged' => Core::getCurrentDate(), ]); Repo::eventLog()->add($eventLog); return $newPublication; } /** * Publish a publication * * This method performs all actions needed when publishing an item, such * as setting metadata, logging events, updating the search index, etc. * * @throws \Exception * * @see self::setStatusOnPublish() */ public function publish(Publication $publication) { $newPublication = clone $publication; $newPublication->stampModified(); $this->setStatusOnPublish($newPublication); // Set the copyright and license information $submission = Repo::submission()->get($newPublication->getData('submissionId')); $itsPublished = ($newPublication->getData('status') === PKPSubmission::STATUS_PUBLISHED); if ($itsPublished && !$newPublication->getData('copyrightHolder')) { $newPublication->setData( 'copyrightHolder', $submission->_getContextLicenseFieldValue( null, PKPSubmission::PERMISSIONS_FIELD_COPYRIGHT_HOLDER, $newPublication ) ); } if ($itsPublished && !$newPublication->getData('copyrightYear')) { $newPublication->setData( 'copyrightYear', $submission->_getContextLicenseFieldValue( null, PKPSubmission::PERMISSIONS_FIELD_COPYRIGHT_YEAR, $newPublication ) ); } if ($itsPublished && !$newPublication->getData('licenseUrl')) { $newPublication->setData( 'licenseUrl', $submission->_getContextLicenseFieldValue( null, PKPSubmission::PERMISSIONS_FIELD_LICENSE_URL, $newPublication ) ); } Hook::call('Publication::publish::before', [&$newPublication, $publication]); $this->dao->update($newPublication); $newPublication = Repo::publication()->get($newPublication->getId()); $submission = Repo::submission()->get($newPublication->getData('submissionId')); // Update a submission's status based on the status of its publications if ($newPublication->getData('status') !== $publication->getData('status')) { Repo::submission()->updateStatus($submission); $submission = Repo::submission()->get($submission->getId()); } $msg = ($newPublication->getData('status') === Submission::STATUS_SCHEDULED) ? 'publication.event.scheduled' : 'publication.event.published'; // Log an event when publication is published. Adjust the message depending // on whether this is the first publication or a subsequent version if (count($submission->getData('publications')) > 1) { $msg = ($newPublication->getData('status') === Submission::STATUS_SCHEDULED) ? 'publication.event.versionScheduled' : 'publication.event.versionPublished'; } $eventLog = Repo::eventLog()->newDataObject([ 'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION, 'assocId' => $submission->getId(), 'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_METADATA_PUBLISH, 'userId' => Validation::loggedInAs() ?? $this->request->getUser()?->getId(), 'message' => $msg, 'isTranslated' => false, 'dateLogged' => Core::getCurrentDate() ]); Repo::eventLog()->add($eventLog); // Mark DOIs stale (if applicable). if ($newPublication->getData('status') === Submission::STATUS_PUBLISHED) { $staleDoiIds = Repo::doi()->getDoisForSubmission($newPublication->getData('submissionId')); Repo::doi()->markStale($staleDoiIds); } Hook::call( 'Publication::publish', [ &$newPublication, $publication, $submission ] ); $context = $submission->getData('contextId') === Application::get()->getRequest()->getContext()?->getId() ? Application::get()->getRequest()->getContext() : Services::get('context')->get($submission->getData('contextId')); event(new PublicationPublished($newPublication, $publication, $submission, $context)); } /** * Set the status when an item is published * * Each application may handle publishing in a different way. Implement this method * in an app-specific child class by assigning `status` and `datePublished` for this * publication. * * This method should be called by self::publish(). */ abstract protected function setStatusOnPublish(Publication $publication); /** * Unpublish a publication * * This method performs all actions needed when unpublishing an item, such * as changing the status, logging events, updating the search index, etc. * * @see self::setStatusOnPublish() */ public function unpublish(Publication $publication) { $newPublication = clone $publication; $newPublication->setData('status', Submission::STATUS_QUEUED); $newPublication->stampModified(); Hook::call( 'Publication::unpublish::before', [ &$newPublication, $publication ] ); $this->dao->update($newPublication); $newPublication = Repo::publication()->get($newPublication->getId()); $submission = Repo::submission()->get($newPublication->getData('submissionId')); // Update a submission's status based on the status of its publications if ($newPublication->getData('status') !== $publication->getData('status')) { Repo::submission()->updateStatus($submission); $submission = Repo::submission()->get($submission->getId()); } // Log an event when publication is unpublished. Adjust the message depending // on whether this is the first publication or a subsequent version $msg = 'publication.event.unpublished'; if (count($submission->getData('publications')) > 1) { $msg = 'publication.event.versionUnpublished'; } // Mark DOIs stable (if applicable). if ($submission->getData('status') !== Submission::STATUS_PUBLISHED) { $staleDoiIds = Repo::doi()->getDoisForSubmission($newPublication->getData('submissionId')); Repo::doi()->markStale($staleDoiIds); } $eventLog = Repo::eventLog()->newDataObject([ 'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION, 'assocId' => $submission->getId(), 'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_METADATA_UNPUBLISH, 'userId' => Validation::loggedInAs() ?? $this->request->getUser()?->getId(), 'message' => $msg, 'isTranslated' => false, 'dateLogged' => Core::getCurrentDate() ]); Repo::eventLog()->add($eventLog); Hook::call( 'Publication::unpublish', [ &$newPublication, $publication, $submission ] ); $context = $submission->getData('contextId') === Application::get()->getRequest()->getContext()->getId() ? Application::get()->getRequest()->getContext() : Services::get('context')->get($submission->getData('contextId')); event(new PublicationUnpublished($newPublication, $publication, $submission, $context)); } /** @copydoc DAO::delete() */ public function delete(Publication $publication) { Hook::call('Publication::delete::before', [&$publication]); $submission = Repo::submission()->get($publication->getData('submissionId')); $sectionId = $publication->getData(Application::getSectionIdPropName()); $section = $sectionId ? Repo::section()->get($sectionId) : null; $this->dao->delete($publication); // Update a submission's status based on the status of its remaining publications $submission = Repo::submission()->get($publication->getData('submissionId')); Repo::submission()->updateStatus($submission, null, $section); Hook::call('Publication::delete', [&$publication]); } /** * Handle a publication setting for an uploaded file * * - Moves the temporary file to the public directory * - Resets the param value to what is expected to be stored in the db * - If a null value is passed, deletes any existing file * * This method is protected because all operations which edit publications should * go through the add and edit methods in order to ensure that * the appropriate hooks are fired. * * @param Publication $publication The publication being edited * @param Submission $submission The submission this publication is part of * @param mixed $value The param value to be saved. Contains the temporary * file ID if a new file has been uploaded. * @param string $settingName The name of the setting to save, typically used * in the filename. * @param int $userId ID of the user who owns the temporary file * @param string $localeKey Optional. Pass if the setting is multilingual * @param bool $isImage Optional. For image files which include alt text in value * * @return string|array|bool New param value or false on failure */ protected function _saveFileParam( Publication $publication, Submission $submission, $value, string $settingName, int $userId, string $localeKey = '', bool $isImage = false ) { // If the value is null, delete any existing unused file in the system if (is_null($value)) { $oldPublication = Repo::publication()->get($publication->getId()); $oldValue = $oldPublication->getData($settingName, $localeKey); $fileName = $oldValue['uploadName'] ?? null; if ($fileName) { // File may be in use by other publications $fileInUse = false; foreach ($submission->getData('publications') as $iPublication) { if ($publication->getId() === $iPublication->getId()) { continue; } $iValue = $iPublication->getData($settingName, $localeKey); if (!empty($iValue['uploadName']) && $iValue['uploadName'] === $fileName) { $fileInUse = true; continue; } } if (!$fileInUse) { $publicFileManager = new PublicFileManager(); $publicFileManager->removeContextFile($submission->getData('contextId'), $fileName); } } return null; } // Check if there is something to upload if (empty($value['temporaryFileId'])) { return $value; } // Get the submission context $submissionContext = $this->request->getContext(); if ($submissionContext->getId() !== $submission->getData('contextId')) { $submissionContext = Services::get('context')->get($submission->getData('contextId')); } $temporaryFileManager = new TemporaryFileManager(); $temporaryFile = $temporaryFileManager->getFile((int) $value['temporaryFileId'], $userId); $fileNameBase = join('_', ['submission', $submission->getId(), $publication->getId(), $settingName]); // eg - submission_1_1_coverImage $fileName = Services::get('context')->moveTemporaryFile($submissionContext, $temporaryFile, $fileNameBase, $userId, $localeKey); if ($fileName) { if ($isImage) { return [ 'altText' => !empty($value['altText']) ? $value['altText'] : '', 'dateUploaded' => Core::getCurrentDate(), 'uploadName' => $fileName, ]; } else { return [ 'dateUploaded' => Core::getCurrentDate(), 'uploadName' => $fileName, ]; } } return null; } /** * Get error message overrides for the validator */ protected function getErrorMessageOverrides(): array { return [ 'locale.regex' => __('validator.localeKey'), 'datePublished.date_format' => __('publication.datePublished.errorFormat'), 'urlPath.regex' => __('validator.alpha_dash_period'), ]; } /** * Create all DOIs associated with the publication. */ abstract protected function createDois(Publication $newPublication): void; }