549 lines
17 KiB
PHP
549 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* @file classes/publication/DAO.php
|
|
*
|
|
* Copyright (c) 2014-2021 Simon Fraser University
|
|
* Copyright (c) 2000-2021 John Willinsky
|
|
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
|
*
|
|
* @class DAO
|
|
*
|
|
* @brief Read and write publications to the database.
|
|
*/
|
|
|
|
namespace PKP\publication;
|
|
|
|
use APP\facades\Repo;
|
|
use APP\publication\Publication;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Enumerable;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\LazyCollection;
|
|
use PKP\citation\CitationDAO;
|
|
use PKP\core\EntityDAO;
|
|
use PKP\core\traits\EntityWithParent;
|
|
use PKP\services\PKPSchemaService;
|
|
use PKP\submission\SubmissionAgencyDAO;
|
|
use PKP\submission\SubmissionDisciplineDAO;
|
|
use PKP\submission\SubmissionKeywordDAO;
|
|
use PKP\submission\SubmissionLanguageDAO;
|
|
use PKP\submission\SubmissionSubjectDAO;
|
|
|
|
/**
|
|
* @template T of Publication
|
|
* @extends EntityDAO<T>
|
|
*/
|
|
class DAO extends EntityDAO
|
|
{
|
|
use EntityWithParent;
|
|
|
|
/** @copydoc EntityDAO::$schema */
|
|
public $schema = PKPSchemaService::SCHEMA_PUBLICATION;
|
|
|
|
/** @copydoc EntityDAO::$table */
|
|
public $table = 'publications';
|
|
|
|
/** @copydoc EntityDAO::$settingsTable */
|
|
public $settingsTable = 'publication_settings';
|
|
|
|
/** @copydoc EntityDAO::$primaryKeyColumn */
|
|
public $primaryKeyColumn = 'publication_id';
|
|
|
|
/** @var SubmissionKeywordDAO */
|
|
public $submissionKeywordDao;
|
|
|
|
/** @var SubmissionSubjectDAO */
|
|
public $submissionSubjectDao;
|
|
|
|
/** @var SubmissionDisciplineDAO */
|
|
public $submissionDisciplineDao;
|
|
|
|
/** @var SubmissionLanguageDAO */
|
|
public $submissionLanguageDao;
|
|
|
|
/** @var SubmissionAgencyDAO */
|
|
public $submissionAgencyDao;
|
|
|
|
/** @var CitationDAO */
|
|
public $citationDao;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct(
|
|
SubmissionKeywordDAO $submissionKeywordDao,
|
|
SubmissionSubjectDAO $submissionSubjectDao,
|
|
SubmissionDisciplineDAO $submissionDisciplineDao,
|
|
SubmissionLanguageDAO $submissionLanguageDao,
|
|
SubmissionAgencyDAO $submissionAgencyDao,
|
|
CitationDAO $citationDao,
|
|
PKPSchemaService $schemaService
|
|
) {
|
|
parent::__construct($schemaService);
|
|
|
|
$this->submissionKeywordDao = $submissionKeywordDao;
|
|
$this->submissionSubjectDao = $submissionSubjectDao;
|
|
$this->submissionDisciplineDao = $submissionDisciplineDao;
|
|
$this->submissionLanguageDao = $submissionLanguageDao;
|
|
$this->submissionAgencyDao = $submissionAgencyDao;
|
|
$this->citationDao = $citationDao;
|
|
}
|
|
|
|
/**
|
|
* Get the parent object ID column name
|
|
*/
|
|
public function getParentColumn(): string
|
|
{
|
|
return 'submission_id';
|
|
}
|
|
|
|
/**
|
|
* Instantiate a new DataObject
|
|
*/
|
|
public function newDataObject(): Publication
|
|
{
|
|
return app(Publication::class);
|
|
}
|
|
|
|
/**
|
|
* Get the total count of rows 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('p.' . $this->primaryKeyColumn)
|
|
->pluck('p.' . $this->primaryKeyColumn);
|
|
}
|
|
|
|
/**
|
|
* Get a collection of publications 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->publication_id => $this->fromRow($row);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the publication dates of the first and last publications
|
|
* matching the passed query
|
|
*
|
|
* @return object self::$min_date_published, self::$max_date_published
|
|
*/
|
|
public function getDateBoundaries(Collector $query): object
|
|
{
|
|
return $query
|
|
->getQueryBuilder()
|
|
->reorder()
|
|
->select([
|
|
DB::raw('MIN(p.date_published) AS min_date_published, MAX(p.date_published) AS max_date_published')
|
|
])
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* Is the urlPath a duplicate?
|
|
*
|
|
* Checks if the urlPath is used in any other submission than the one
|
|
* passed
|
|
*
|
|
* A urlPath may be duplicated across more than one publication of the
|
|
* same submission. But two publications in two different submissions
|
|
* can not share the same urlPath.
|
|
*
|
|
* This is only applied within a single context.
|
|
*/
|
|
public function isDuplicateUrlPath(string $urlPath, int $submissionId, int $contextId): bool
|
|
{
|
|
if (!strlen($urlPath)) {
|
|
return false;
|
|
}
|
|
return (bool) DB::table('publications as p')
|
|
->leftJoin('submissions as s', 's.submission_id', '=', 'p.submission_id')
|
|
->where('url_path', '=', $urlPath)
|
|
->where('p.submission_id', '!=', $submissionId)
|
|
->where('s.context_id', '=', $contextId)
|
|
->count();
|
|
}
|
|
|
|
/**
|
|
* @copydoc EntityDAO::fromRow()
|
|
*/
|
|
public function fromRow(object $row): Publication
|
|
{
|
|
$publication = parent::fromRow($row);
|
|
|
|
$this->setDoiObject($publication);
|
|
|
|
// Set the primary locale from the submission
|
|
$locale = DB::table('submissions as s')
|
|
->where('s.submission_id', '=', $publication->getData('submissionId'))
|
|
->value('locale');
|
|
$publication->setData('locale', $locale);
|
|
|
|
$this->setAuthors($publication);
|
|
$this->setCategories($publication);
|
|
$this->setControlledVocab($publication);
|
|
|
|
return $publication;
|
|
}
|
|
|
|
/**
|
|
* @copydoc EntityDAO::insert()
|
|
*/
|
|
public function insert(Publication $publication): int
|
|
{
|
|
$vocabs = $this->extractControlledVocab($publication);
|
|
|
|
$id = parent::_insert($publication);
|
|
|
|
$this->saveControlledVocab($vocabs, $id);
|
|
$this->saveCategories($publication);
|
|
|
|
// Parse the citations
|
|
if ($publication->getData('citationsRaw')) {
|
|
$this->saveCitations($publication);
|
|
}
|
|
|
|
return $id;
|
|
}
|
|
|
|
/**
|
|
* @copydoc EntityDAO::update()
|
|
*/
|
|
public function update(Publication $publication, Publication $oldPublication = null)
|
|
{
|
|
$vocabs = $this->extractControlledVocab($publication);
|
|
|
|
parent::_update($publication);
|
|
|
|
$this->saveControlledVocab($vocabs, $publication->getId());
|
|
$this->saveCategories($publication);
|
|
|
|
if ($oldPublication && $oldPublication->getData('citationsRaw') != $publication->getData('citationsRaw')) {
|
|
$this->saveCitations($publication);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @copydoc EntityDAO::delete()
|
|
*/
|
|
public function delete(Publication $publication)
|
|
{
|
|
parent::_delete($publication);
|
|
}
|
|
|
|
/**
|
|
* @copydoc EntityDAO::deleteById()
|
|
*/
|
|
public function deleteById(int $publicationId)
|
|
{
|
|
parent::deleteById($publicationId);
|
|
|
|
$this->deleteAuthors($publicationId);
|
|
$this->deleteCategories($publicationId);
|
|
$this->deleteControlledVocab($publicationId);
|
|
$this->deleteCitations($publicationId);
|
|
}
|
|
|
|
/**
|
|
* Get publication ids that have a matching setting
|
|
*/
|
|
public function getIdsBySetting(string $settingName, $settingValue, int $contextId): Enumerable
|
|
{
|
|
$q = DB::table($this->table . ' as p')
|
|
->join($this->settingsTable . ' as ps', 'p.publication_id', '=', 'ps.publication_id')
|
|
->join('submissions as s', 'p.submission_id', '=', 's.submission_id')
|
|
->where('ps.setting_name', '=', $settingName)
|
|
->where('ps.setting_value', '=', $settingValue)
|
|
->where('s.context_id', '=', (int) $contextId);
|
|
|
|
return $q->select('p.publication_id')
|
|
->pluck('p.publication_id');
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::pubIdExists()
|
|
*/
|
|
public function pubIdExists($pubIdType, $pubId, $excludePubObjectId, int $contextId)
|
|
{
|
|
$result = $this->deprecatedDao->retrieve(
|
|
'SELECT COUNT(*) AS row_count
|
|
FROM publication_settings ps
|
|
LEFT JOIN publications p ON p.publication_id = ps.publication_id
|
|
LEFT JOIN submissions s ON p.submission_id = s.submission_id
|
|
WHERE ps.setting_name = ? and ps.setting_value = ? and s.submission_id <> ? AND s.context_id = ?',
|
|
[
|
|
'pub-id::' . $pubIdType,
|
|
$pubId,
|
|
// The excludePubObjectId refers to the submission id
|
|
// because multiple versions of the same submission
|
|
// are allowed to share a DOI.
|
|
(int) $excludePubObjectId,
|
|
(int) $contextId
|
|
]
|
|
);
|
|
$row = $result->current();
|
|
return $row ? (bool) $row->row_count : false;
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::changePubId()
|
|
*/
|
|
public function changePubId($pubObjectId, $pubIdType, $pubId)
|
|
{
|
|
DB::table($this->settingsTable)
|
|
->update([
|
|
'publication_id' => (int) $pubObjectId,
|
|
'locale' => '',
|
|
'setting_name' => 'pub-id::' . $pubIdType,
|
|
'setting_value' => (string) $pubId
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::deletePubId()
|
|
*/
|
|
public function deletePubId($pubObjectId, $pubIdType)
|
|
{
|
|
DB::table($this->settingsTable)
|
|
->where('publication_id', (int) $pubObjectId)
|
|
->where('setting_name', '=', 'pub-id::' . $pubIdType)
|
|
->delete();
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::deleteAllPubIds()
|
|
*/
|
|
public function deleteAllPubIds($contextId, $pubIdType)
|
|
{
|
|
switch (DB::getDriverName()) {
|
|
case 'mysql':
|
|
$this->deprecatedDao->update(
|
|
'DELETE ps FROM publication_settings ps
|
|
LEFT JOIN publications p ON p.publication_id = ps.publication_id
|
|
LEFT JOIN submissions s ON s.submission_id = p.submission_id
|
|
WHERE ps.setting_name = ?
|
|
AND s.context_id = ?',
|
|
[
|
|
'pub-id::' . $pubIdType,
|
|
$contextId,
|
|
]
|
|
);
|
|
break;
|
|
case 'pgsql':
|
|
$this->deprecatedDao->update(
|
|
'DELETE FROM publication_settings
|
|
USING publication_settings ps
|
|
LEFT JOIN publications p ON p.publication_id = ps.publication_id
|
|
LEFT JOIN submissions s ON s.submission_id = p.submission_id
|
|
WHERE ps.setting_name = ?
|
|
AND s.context_id = ?
|
|
AND ps.publication_id = publication_settings.publication_id
|
|
AND ps.locale = publication_settings.locale
|
|
AND ps.setting_name = publication_settings.setting_name',
|
|
[
|
|
'pub-id::' . $pubIdType,
|
|
$contextId,
|
|
]
|
|
);
|
|
break;
|
|
default: fatalError('Unknown database type!');
|
|
}
|
|
$this->deprecatedDao->flushCache();
|
|
}
|
|
|
|
/**
|
|
* Set a publication's author properties
|
|
*/
|
|
protected function setAuthors(Publication $publication)
|
|
{
|
|
$publication->setData(
|
|
'authors',
|
|
Repo::author()
|
|
->getCollector()
|
|
->filterByPublicationIds([$publication->getId()])
|
|
->orderBy(\PKP\author\Collector::ORDERBY_SEQUENCE)
|
|
->getMany()
|
|
->remember()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Delete a publication's authors
|
|
*/
|
|
protected function deleteAuthors(int $publicationId)
|
|
{
|
|
$authors = Repo::author()
|
|
->getCollector()
|
|
->filterByPublicationIds([$publicationId])
|
|
->getMany();
|
|
|
|
foreach ($authors as $author) {
|
|
Repo::author()->delete($author);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a publication's controlled vocabulary properties
|
|
*/
|
|
protected function setControlledVocab(Publication $publication)
|
|
{
|
|
$publication->setData('keywords', $this->submissionKeywordDao->getKeywords($publication->getId()));
|
|
$publication->setData('subjects', $this->submissionSubjectDao->getSubjects($publication->getId()));
|
|
$publication->setData('disciplines', $this->submissionDisciplineDao->getDisciplines($publication->getId()));
|
|
$publication->setData('languages', $this->submissionLanguageDao->getLanguages($publication->getId()));
|
|
$publication->setData('supportingAgencies', $this->submissionAgencyDao->getAgencies($publication->getId()));
|
|
}
|
|
|
|
/**
|
|
* Remove controlled vocabulary from a publication's data
|
|
*
|
|
* Controlled vocabulary includes keywords, subjects, and similar
|
|
* metadata that shouldn't be saved in the publications table.
|
|
*
|
|
* @see self::saveControlledVocab()
|
|
*
|
|
* @return array Key/value of controlled vocabulary properties
|
|
*/
|
|
protected function extractControlledVocab(Publication $publication): array
|
|
{
|
|
$controlledVocabKeyedArray = array_flip([
|
|
'disciplines',
|
|
'keywords',
|
|
'languages',
|
|
'subjects',
|
|
'supportingAgencies',
|
|
]);
|
|
|
|
$values = array_intersect_key($publication->_data, $controlledVocabKeyedArray);
|
|
$publication->setAllData(array_diff_key($publication->_data, $controlledVocabKeyedArray));
|
|
|
|
return $values;
|
|
}
|
|
|
|
/**
|
|
* Save controlled vocabulary properties
|
|
*
|
|
* @see self::extractControlledVocab()
|
|
*/
|
|
protected function saveControlledVocab(array $values, int $publicationId)
|
|
{
|
|
// Update controlled vocabularly for which we have props
|
|
foreach ($values as $prop => $value) {
|
|
switch ($prop) {
|
|
case 'keywords':
|
|
$this->submissionKeywordDao->insertKeywords($value, $publicationId);
|
|
break;
|
|
case 'subjects':
|
|
$this->submissionSubjectDao->insertSubjects($value, $publicationId);
|
|
break;
|
|
case 'disciplines':
|
|
$this->submissionDisciplineDao->insertDisciplines($value, $publicationId);
|
|
break;
|
|
case 'languages':
|
|
$this->submissionLanguageDao->insertLanguages($value, $publicationId);
|
|
break;
|
|
case 'supportingAgencies':
|
|
$this->submissionAgencyDao->insertAgencies($value, $publicationId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete controlled vocab entries for a publication
|
|
*/
|
|
protected function deleteControlledVocab(int $publicationId)
|
|
{
|
|
$this->submissionKeywordDao->insertKeywords([], $publicationId);
|
|
$this->submissionSubjectDao->insertSubjects([], $publicationId);
|
|
$this->submissionDisciplineDao->insertDisciplines([], $publicationId);
|
|
$this->submissionLanguageDao->insertLanguages([], $publicationId);
|
|
$this->submissionAgencyDao->insertAgencies([], $publicationId);
|
|
}
|
|
|
|
/**
|
|
* Set a publication's category property
|
|
*/
|
|
protected function setCategories(Publication $publication)
|
|
{
|
|
$publication->setData(
|
|
'categoryIds',
|
|
Repo::category()->getCollector()
|
|
->filterByPublicationIds([$publication->getId()])
|
|
->getIds()
|
|
->toArray()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Save the assigned categories
|
|
*/
|
|
protected function saveCategories(Publication $publication)
|
|
{
|
|
Repo::category()->dao->deletePublicationAssignments($publication->getId());
|
|
if (!empty($publication->getData('categoryIds'))) {
|
|
foreach ($publication->getData('categoryIds') as $categoryId) {
|
|
Repo::category()->dao->insertPublicationAssignment($categoryId, $publication->getId());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete the category assignments
|
|
*/
|
|
protected function deleteCategories(int $publicationId)
|
|
{
|
|
Repo::category()->dao->deletePublicationAssignments($publicationId);
|
|
}
|
|
|
|
/**
|
|
* Save the citations
|
|
*/
|
|
protected function saveCitations(Publication $publication)
|
|
{
|
|
$this->citationDao->importCitations($publication->getId(), $publication->getData('citationsRaw'));
|
|
}
|
|
|
|
/**
|
|
* Delete the citations
|
|
*/
|
|
protected function deleteCitations(int $publicationId)
|
|
{
|
|
$this->citationDao->deleteByPublicationId($publicationId);
|
|
}
|
|
|
|
/**
|
|
* Set the DOI object
|
|
*
|
|
*/
|
|
protected function setDoiObject(Publication $publication)
|
|
{
|
|
if (!empty($publication->getData('doiId'))) {
|
|
$publication->setData('doiObject', Repo::doi()->get($publication->getData('doiId')));
|
|
}
|
|
}
|
|
}
|