491 lines
15 KiB
PHP
491 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* @file classes/issue/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.
|
|
*
|
|
* @ingroup issue
|
|
*
|
|
* @see Issue
|
|
*
|
|
* @brief Operations for retrieving and modifying Issue objects.
|
|
*/
|
|
|
|
namespace APP\issue;
|
|
|
|
use APP\facades\Repo;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\LazyCollection;
|
|
use PKP\cache\CacheManager;
|
|
use PKP\cache\GenericCache;
|
|
use PKP\core\EntityDAO;
|
|
use PKP\core\traits\EntityWithParent;
|
|
use PKP\db\DAOResultFactory;
|
|
use PKP\services\PKPSchemaService;
|
|
|
|
/**
|
|
* @extends EntityDAO<Issue>
|
|
*/
|
|
class DAO extends EntityDAO implements \PKP\plugins\PKPPubIdPluginDAO
|
|
{
|
|
/**
|
|
* @use EntityWithParent<Issue>
|
|
*/
|
|
use EntityWithParent;
|
|
|
|
// TODO: Needs to be addressed with refactor of caching.
|
|
public $caches;
|
|
|
|
/** @copydoc EntityDAO::$schema */
|
|
public $schema = PKPSchemaService::SCHEMA_ISSUE;
|
|
|
|
/** @copydoc EntityDAO::$table */
|
|
public $table = 'issues';
|
|
|
|
/** @copydoc EntityDAO::$settingsTable */
|
|
public $settingsTable = 'issue_settings';
|
|
|
|
/** @copydoc EntityDAO::$primaryKeyColumn */
|
|
public $primaryKeyColumn = 'issue_id';
|
|
|
|
/** @copydoc SchemaDAO::$primaryTableColumns */
|
|
public $primaryTableColumns = [
|
|
'id' => 'issue_id',
|
|
'journalId' => 'journal_id',
|
|
'volume' => 'volume',
|
|
'number' => 'number',
|
|
'year' => 'year',
|
|
'published' => 'published',
|
|
'datePublished' => 'date_published',
|
|
'dateNotified' => 'date_notified',
|
|
'lastModified' => 'last_modified',
|
|
'accessStatus' => 'access_status',
|
|
'openAccessDate' => 'open_access_date',
|
|
'showVolume' => 'show_volume',
|
|
'showNumber' => 'show_number',
|
|
'showYear' => 'show_year',
|
|
'showTitle' => 'show_title',
|
|
'styleFileName' => 'style_file_name',
|
|
'originalStyleFileName' => 'original_style_file_name',
|
|
'urlPath' => 'url_path',
|
|
'doiId' => 'doi_id'
|
|
];
|
|
|
|
/**
|
|
* Get the parent object ID column name
|
|
*/
|
|
public function getParentColumn(): string
|
|
{
|
|
return 'journal_id';
|
|
}
|
|
|
|
/**
|
|
* Handle a cache miss.
|
|
*
|
|
* TODO: Caching not currently working as expected
|
|
*
|
|
*/
|
|
public function _cacheMiss(GenericCache $cache, int $id): ?Issue
|
|
{
|
|
if ($cache->getCacheId() === 'current') {
|
|
$issue = Repo::issue()->getCurrent($id);
|
|
} else {
|
|
$issue = Repo::issue()->getByBestId($id, null, false);
|
|
}
|
|
$cache->setCache($id, $issue);
|
|
return $issue;
|
|
}
|
|
|
|
/**
|
|
* Get an issue cache by cache ID
|
|
*
|
|
* TODO: Not currently working as expected. Not used throughout current class
|
|
*
|
|
* @return mixed|object|\PKP\cache\APCCache|\PKP\cache\FileCache|GenericCache|\PKP\cache\MemcacheCache|\PKP\cache\XCacheCache
|
|
*/
|
|
public function _getCache(string $cacheId)
|
|
{
|
|
if (!isset($this->caches)) {
|
|
$this->caches = [];
|
|
}
|
|
if (!isset($this->caches[$cacheId])) {
|
|
$cacheManager = CacheManager::getManager();
|
|
$this->caches[$cacheId] = $cacheManager->getObjectCache('issues', $cacheId, [$this, '_cacheMiss']);
|
|
}
|
|
return $this->caches[$cacheId];
|
|
}
|
|
|
|
/**
|
|
* Instantiate a new DataObject
|
|
*/
|
|
public function newDataObject(): Issue
|
|
{
|
|
return app(Issue::class);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public function getIds(Collector $query): Collection
|
|
{
|
|
return $query
|
|
->getQueryBuilder()
|
|
->select('i.' . $this->primaryKeyColumn)
|
|
->pluck('i.' . $this->primaryKeyColumn);
|
|
}
|
|
|
|
/**
|
|
* Get a collection of issues matching the configured query
|
|
*
|
|
* @return LazyCollection<int,Issue>
|
|
*/
|
|
public function getMany(Collector $query): LazyCollection
|
|
{
|
|
$rows = $query
|
|
->getQueryBuilder()
|
|
->get();
|
|
|
|
return LazyCollection::make(function () use ($rows) {
|
|
foreach ($rows as $row) {
|
|
yield $row->issue_id => $this->fromRow($row);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the issue id by its url path
|
|
*
|
|
*/
|
|
public function getIdByUrlPath(string $urlPath, int $contextId): ?int
|
|
{
|
|
$issue = DB::table($this->table, 'i')
|
|
->where('i.journal_id', '=', $contextId)
|
|
->where('i.url_path', '=', $urlPath)
|
|
->first();
|
|
|
|
return $issue ? $issue->issue_id : null;
|
|
}
|
|
|
|
/** @copydoc EntityDAO::fromRow() */
|
|
public function fromRow(object $row): Issue
|
|
{
|
|
$issue = parent::fromRow($row);
|
|
$this->setDoiObject($issue);
|
|
|
|
return $issue;
|
|
}
|
|
|
|
/** @copydoc EntityDAO::_insert() */
|
|
public function insert(Issue $issue): int
|
|
{
|
|
$issueId = parent::_insert($issue);
|
|
$this->resequenceCustomIssueOrders($issue->getData('journalId'));
|
|
return $issueId;
|
|
}
|
|
|
|
/** @copydoc EntityDAO::_update() */
|
|
public function update(Issue $issue)
|
|
{
|
|
$issue->stampModified();
|
|
parent::_update($issue);
|
|
$this->resequenceCustomIssueOrders($issue->getData('journalId'));
|
|
// TODO: Flush cache
|
|
}
|
|
|
|
/** @copydoc EntityDAO::_delete() */
|
|
public function delete(Issue $issue)
|
|
{
|
|
parent::_delete($issue);
|
|
$this->resequenceCustomIssueOrders($issue->getData('journalId'));
|
|
// TODO: Flush cache
|
|
}
|
|
|
|
/**
|
|
* Deletes any custom issue ordering for a given context
|
|
*
|
|
*/
|
|
public function deleteCustomIssueOrdering(int $issueId)
|
|
{
|
|
DB::table('custom_issue_orders')
|
|
->where('issue_id', '=', $issueId)
|
|
->delete();
|
|
}
|
|
|
|
/**
|
|
* Sequentially renumber custom issue orderings in their sequence order.
|
|
*
|
|
*/
|
|
public function resequenceCustomIssueOrders(int $contextId)
|
|
{
|
|
// If no custom issue ordering already exists, there is nothing to do
|
|
if (!$this->customIssueOrderingExists($contextId)) {
|
|
return;
|
|
}
|
|
|
|
$results = DB::table($this->table, 'i')
|
|
->leftJoin('custom_issue_orders as o', 'o.issue_id', '=', 'i.issue_id')
|
|
->where('i.journal_id', '=', $contextId)
|
|
// TODO: Previous behaviour would resequence all issues, including those that haven't been published (cont.)
|
|
// artificially giving them a position in the custom_issue_orders table before they've been published.
|
|
->where('i.published', '=', 1)
|
|
->orderBy('o.seq')
|
|
->select('i.issue_id')
|
|
->get();
|
|
$results->each(function ($item, $key) use ($contextId) {
|
|
$newSeq = $key + 1;
|
|
DB::table('custom_issue_orders')
|
|
->updateOrInsert(
|
|
[
|
|
'issue_id' => $item->issue_id,
|
|
],
|
|
[
|
|
'issue_id' => $item->issue_id,
|
|
'journal_id' => (int) $contextId,
|
|
'seq' => $newSeq
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if a journal has custom issue ordering
|
|
*
|
|
*/
|
|
public function customIssueOrderingExists(int $contextId): bool
|
|
{
|
|
$resultCount = DB::table('custom_issue_orders', 'o')
|
|
->where('o.journal_id', '=', $contextId)
|
|
->count();
|
|
return $resultCount != 0;
|
|
}
|
|
|
|
/**
|
|
* Get the custom issue order of a journal
|
|
*
|
|
*/
|
|
public function getCustomIssueOrder(int $contextId, int $issueId): ?int
|
|
{
|
|
$results = DB::table('custom_issue_orders')
|
|
->where('journal_id', '=', (int) $contextId)
|
|
->where('issue_id', '=', (int) $issueId);
|
|
|
|
$row = $results->first();
|
|
return $row ? (int) $row->seq : null;
|
|
}
|
|
|
|
/**
|
|
* Gets a list of all the years in which issues have been published
|
|
*
|
|
*/
|
|
public function getYearsIssuesPublished(int $contextId): Collection
|
|
{
|
|
$collector = Repo::issue()->getCollector();
|
|
$q = $collector->filterByContextIds([$contextId])
|
|
->filterByPublished(true)
|
|
->getQueryBuilder();
|
|
|
|
return $q->select('i.year')
|
|
->groupBy('i.year')
|
|
->orderBy('i.year', 'DESC')
|
|
->pluck('i.year');
|
|
}
|
|
|
|
/**
|
|
* INTERNAL USE ONLY: Insert a custom issue ordering
|
|
* TODO: See if should be protected/private
|
|
*
|
|
*/
|
|
public function insertCustomIssueOrder(int $contextId, int $issueId, int $seq)
|
|
{
|
|
DB::table('custom_issue_orders')
|
|
->insert(
|
|
[
|
|
'issue_id' => $issueId,
|
|
'journal_id' => $contextId,
|
|
'seq' => $seq
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Move a custom issue ordering up or down, resequencing as necessary.
|
|
*
|
|
* @param int $newPos The new position (0-based) of this section
|
|
*/
|
|
public function moveCustomIssueOrder(int $contextId, int $issueId, int $newPos)
|
|
{
|
|
DB::table('custom_issue_orders')
|
|
->updateOrInsert(
|
|
[
|
|
'journal_id' => $contextId,
|
|
'issue_id' => $issueId
|
|
],
|
|
[
|
|
'seq' => $newPos
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::pubIdExists()
|
|
*
|
|
* From legacy IssueDAO
|
|
*/
|
|
public function pubIdExists($pubIdType, $pubId, $excludePubObjectId, $contextId)
|
|
{
|
|
$result = $this->deprecatedDao->retrieve(
|
|
'SELECT COUNT(*) AS row_count
|
|
FROM issue_settings ist
|
|
INNER JOIN issues i ON ist.issue_id = i.issue_id
|
|
WHERE ist.setting_name = ? AND ist.setting_value = ? AND i.issue_id <> ? AND i.journal_id = ?',
|
|
[
|
|
'pub-id::' . $pubIdType,
|
|
$pubId,
|
|
(int) $excludePubObjectId,
|
|
(int) $contextId
|
|
]
|
|
);
|
|
$row = $result->current();
|
|
return $row && $row->row_count;
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::changePubId()
|
|
*
|
|
* From legacy IssueDAO
|
|
*/
|
|
public function changePubId($pubObjectId, $pubIdType, $pubId)
|
|
{
|
|
DB::table('issue_settings')->updateOrInsert(
|
|
['issue_id' => (int) $pubObjectId, 'locale' => '', 'setting_name' => 'pub-id::' . $pubIdType],
|
|
['setting_value' => (string) $pubId]
|
|
);
|
|
// TODO: Cache not implemented
|
|
// $this->flushCache();
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::deletePubId()
|
|
*
|
|
* From legacy IssueDAO
|
|
*/
|
|
public function deletePubId($pubObjectId, $pubIdType)
|
|
{
|
|
$this->deprecatedDao->update(
|
|
'DELETE FROM issue_settings WHERE setting_name = ? AND issue_id = ?',
|
|
[
|
|
'pub-id::' . $pubIdType,
|
|
(int)$pubObjectId
|
|
]
|
|
);
|
|
// TODO: Cache not implemented
|
|
// $this->flushCache();
|
|
}
|
|
|
|
/**
|
|
* @copydoc PKPPubIdPluginDAO::deleteAllPubIds()
|
|
*
|
|
* From legacy IssueDAO
|
|
*/
|
|
public function deleteAllPubIds($contextId, $pubIdType)
|
|
{
|
|
$issues = Repo::issue()->getCollector()->filterByContextIds([$contextId])->getMany();
|
|
foreach ($issues as $issue) {
|
|
$this->deprecatedDao->update(
|
|
'DELETE FROM issue_settings WHERE setting_name = ? AND issue_id = ?',
|
|
[
|
|
'pub-id::' . $pubIdType,
|
|
(int)$issue->getId()
|
|
]
|
|
);
|
|
}
|
|
// TODO: Cache not implemented
|
|
// $this->flushCache();
|
|
}
|
|
|
|
/**
|
|
* Get all published issues (eventually with a pubId assigned and) matching the specified settings.
|
|
*
|
|
* From legacy IssueDAO
|
|
*
|
|
* @param int $contextId optional
|
|
* @param string $pubIdType
|
|
* @param string $pubIdSettingName optional
|
|
* (e.g. crossref::registeredDoi)
|
|
* @param string $pubIdSettingValue optional
|
|
* @param ?\PKP\db\DBResultRange $rangeInfo optional
|
|
*
|
|
* @return DAOResultFactory<Issue>
|
|
*/
|
|
public function getExportable($contextId, $pubIdType = null, $pubIdSettingName = null, $pubIdSettingValue = null, $rangeInfo = null)
|
|
{
|
|
$params = [];
|
|
if ($pubIdSettingName) {
|
|
$params[] = $pubIdSettingName;
|
|
}
|
|
$params[] = (int) $contextId;
|
|
if ($pubIdType) {
|
|
$params[] = 'pub-id::' . $pubIdType;
|
|
}
|
|
|
|
import('classes.plugins.PubObjectsExportPlugin'); // Constants
|
|
if ($pubIdSettingName && $pubIdSettingValue && $pubIdSettingValue != EXPORT_STATUS_NOT_DEPOSITED) {
|
|
$params[] = $pubIdSettingValue;
|
|
}
|
|
|
|
$result = $this->deprecatedDao->retrieveRange(
|
|
$sql = 'SELECT i.*
|
|
FROM issues i
|
|
LEFT JOIN custom_issue_orders o ON (o.issue_id = i.issue_id)
|
|
' . ($pubIdType != null ? ' LEFT JOIN issue_settings ist ON (i.issue_id = ist.issue_id)' : '')
|
|
. ($pubIdSettingName != null ? ' LEFT JOIN issue_settings iss ON (i.issue_id = iss.issue_id AND iss.setting_name = ?)' : '') . '
|
|
WHERE
|
|
i.published = 1 AND i.journal_id = ?
|
|
' . ($pubIdType != null ? ' AND ist.setting_name = ? AND ist.setting_value IS NOT NULL' : '')
|
|
. (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue == EXPORT_STATUS_NOT_DEPOSITED) ? ' AND iss.setting_value IS NULL' : '')
|
|
. (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue != EXPORT_STATUS_NOT_DEPOSITED) ? ' AND iss.setting_value = ?' : '')
|
|
. (($pubIdSettingName != null && is_null($pubIdSettingValue)) ? ' AND (iss.setting_value IS NULL OR iss.setting_value = \'\')' : '')
|
|
. ' ORDER BY i.date_published DESC',
|
|
$params,
|
|
$rangeInfo
|
|
);
|
|
|
|
return new DAOResultFactory($result, $this, 'fromRow', [], $sql, $params, $rangeInfo);
|
|
}
|
|
|
|
/**
|
|
* Flush the issue cache.
|
|
*
|
|
* TODO: Not currently in use. _getCache always results in cache miss.
|
|
*/
|
|
public function flushCache()
|
|
{
|
|
$this->_getCache('issues')->flush();
|
|
$this->_getCache('current')->flush();
|
|
}
|
|
|
|
/**
|
|
* Set the DOI object
|
|
*
|
|
*/
|
|
protected function setDoiObject(Issue $issue)
|
|
{
|
|
if (!empty($issue->getData('doiId'))) {
|
|
$issue->setData('doiObject', Repo::doi()->get($issue->getData('doiId')));
|
|
}
|
|
}
|
|
}
|