first commit
This commit is contained in:
@@ -0,0 +1,464 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/issue/Collector.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 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 issues
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use Exception;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use InvalidArgumentException;
|
||||
use PKP\core\interfaces\CollectorInterface;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
class Collector implements CollectorInterface
|
||||
{
|
||||
public const ORDERBY_DATE_PUBLISHED = 'datePublished';
|
||||
public const ORDERBY_LAST_MODIFIED = 'lastModified';
|
||||
public const ORDERBY_SEQUENCE = 'seq';
|
||||
public const ORDERBY_PUBLISHED_ISSUES = 'publishedIssues';
|
||||
public const ORDERBY_UNPUBLISHED_ISSUES = 'unpublishedIssues';
|
||||
public const ORDERBY_SHELF = 'shelf';
|
||||
public const ORDER_DIR_ASC = 'ASC';
|
||||
public const ORDER_DIR_DESC = 'DESC';
|
||||
private const ORDER_CURRENT_ISSUE = 'currentIssue';
|
||||
|
||||
public DAO $dao;
|
||||
|
||||
public ?int $count = null;
|
||||
|
||||
public ?int $offset = null;
|
||||
|
||||
/** @var array|null Context ID or PKPApplication::CONTEXT_ID_ALL to get from all contexts */
|
||||
public ?array $contextIds = null;
|
||||
|
||||
/** @var array|null List of issue IDs to include */
|
||||
public ?array $issueIds = null;
|
||||
|
||||
/** @var array|null order and direction pairing for queries */
|
||||
public ?array $resultOrderings = null;
|
||||
|
||||
/** @var bool|null return published issues */
|
||||
public ?bool $isPublished = null;
|
||||
|
||||
/** @var array|null return issues in volume(s) */
|
||||
public ?array $volumes = null;
|
||||
|
||||
/** @var array|null return issues with number(s) */
|
||||
public ?array $numbers = null;
|
||||
|
||||
/** @var array|null return issues with year(s) */
|
||||
public ?array $years = null;
|
||||
|
||||
/** @var array|null return issues that match a title */
|
||||
public ?array $titles = null;
|
||||
|
||||
public ?array $doiStatuses = null;
|
||||
|
||||
public ?bool $hasDois = null;
|
||||
|
||||
/** @var array Which DOI types should be considered when checking if a submission has DOIs set */
|
||||
public array $enabledDoiTypes = [];
|
||||
|
||||
/** @var string|null Returns Issue by URL path */
|
||||
public ?string $urlPath = null;
|
||||
|
||||
/** @var string|null return issues which match words from this search phrase */
|
||||
public ?string $searchPhrase = null;
|
||||
|
||||
public function __construct(DAO $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::getCount() */
|
||||
public function getCount(): int
|
||||
{
|
||||
return $this->dao->getCount($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc DAO::getIds()
|
||||
*
|
||||
* @return Collection<int,int>
|
||||
*/
|
||||
public function getIds(): Collection
|
||||
{
|
||||
return $this->dao->getIds($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc DAO::getMany()
|
||||
*
|
||||
* @return LazyCollection<int,Issue>
|
||||
*/
|
||||
public function getMany(): LazyCollection
|
||||
{
|
||||
return $this->dao->getMany($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set context issues filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByContextIds(?array $contextIds): static
|
||||
{
|
||||
$this->contextIds = $contextIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set issue ID filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByIssueIds(?array $issueIds): static
|
||||
{
|
||||
$this->issueIds = $issueIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set result order and direction based on an ORDERBY_* constant
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function orderBy(string $orderByConstant): static
|
||||
{
|
||||
$this->resultOrderings = match ($orderByConstant) {
|
||||
static::ORDERBY_LAST_MODIFIED => [
|
||||
['orderBy' => 'i.last_modified', 'direction' => static::ORDER_DIR_DESC]
|
||||
],
|
||||
static::ORDERBY_SEQUENCE => [
|
||||
['orderBy' => 'o.seq', 'direction' => static::ORDER_DIR_ASC]
|
||||
],
|
||||
static::ORDERBY_PUBLISHED_ISSUES => [
|
||||
['orderBy' => 'o.seq', 'direction' => static::ORDER_DIR_ASC],
|
||||
['orderBy' => 'currentIssue', 'direction' => static::ORDER_DIR_DESC],
|
||||
['orderBy' => 'i.date_published', 'direction' => static::ORDER_DIR_DESC]
|
||||
],
|
||||
static::ORDERBY_UNPUBLISHED_ISSUES => [
|
||||
['orderBy' => 'i.year', 'direction' => static::ORDER_DIR_ASC],
|
||||
['orderBy' => 'i.volume', 'direction' => static::ORDER_DIR_ASC],
|
||||
['orderBy' => 'i.number', 'direction' => static::ORDER_DIR_ASC]
|
||||
],
|
||||
static::ORDERBY_SHELF => [
|
||||
['orderBy' => static::ORDER_CURRENT_ISSUE, 'direction' => static::ORDER_DIR_DESC],
|
||||
['orderBy' => 'i.year', 'direction' => static::ORDER_DIR_ASC],
|
||||
['orderBy' => 'i.volume', 'direction' => static::ORDER_DIR_ASC],
|
||||
['orderBy' => 'i.number', 'direction' => static::ORDER_DIR_ASC]
|
||||
],
|
||||
default => throw new InvalidArgumentException('One of ORDERBY_* constants must be provided')
|
||||
};
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set published filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByPublished(bool $isPublished): static
|
||||
{
|
||||
$this->isPublished = $isPublished;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set volumes filter
|
||||
*
|
||||
* @param int[]|null $volumes
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByVolumes(?array $volumes): static
|
||||
{
|
||||
$this->volumes = $volumes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set volumes filter
|
||||
*
|
||||
* @param int[]|null $numbers
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByNumbers(?array $numbers): static
|
||||
{
|
||||
$this->numbers = $numbers;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set volumes filter
|
||||
*
|
||||
* @param int[]|null $years
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByYears(?array $years): static
|
||||
{
|
||||
$this->years = $years;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set urlPath filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByUrlPath(string $urlPath): static
|
||||
{
|
||||
$this->urlPath = $urlPath;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set titles filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByTitles(array $titles): static
|
||||
{
|
||||
$this->titles = $titles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to issues with these statuses
|
||||
*
|
||||
* @param array|null $statuses One or more of DOI::STATUS_* constants
|
||||
*
|
||||
*/
|
||||
public function filterByDoiStatuses(?array $statuses): static
|
||||
{
|
||||
$this->doiStatuses = $statuses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to issues that do/don't have any DOIs assign to their sub objects
|
||||
*
|
||||
* @param array|null $enabledDoiTypes TYPE_* constants to consider when checking issue has DOIs
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function filterByHasDois(?bool $hasDois, ?array $enabledDoiTypes = null): static
|
||||
{
|
||||
$this->hasDois = $hasDois;
|
||||
$this->enabledDoiTypes = $enabledDoiTypes === null ? [Repo::doi()::TYPE_ISSUE] : $enabledDoiTypes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set query search phrase
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function searchPhrase(?string $phrase): static
|
||||
{
|
||||
$this->searchPhrase = $phrase;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the number of objects retrieved
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function limit(?int $count): static
|
||||
{
|
||||
$this->count = $count;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset the number of objects retrieved, for example to
|
||||
* retrieve the second page of contents
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function offset(?int $offset): static
|
||||
{
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getQueryBuilder(): Builder
|
||||
{
|
||||
$q = DB::table($this->dao->table, 'i')
|
||||
->select('i.*')
|
||||
->leftJoin('custom_issue_orders as o', 'o.issue_id', '=', 'i.issue_id');
|
||||
|
||||
// Issue titles (exact matches)
|
||||
$q->when(
|
||||
$this->titles !== null,
|
||||
fn (Builder $q) =>
|
||||
$q->whereIn(
|
||||
'i.issue_id',
|
||||
fn (Builder $q) =>
|
||||
$q->select('issue_id')
|
||||
->from($this->dao->settingsTable)
|
||||
->where('setting_name', '=', 'title')
|
||||
->whereIn('setting_value', $this->titles)
|
||||
)
|
||||
);
|
||||
|
||||
// Context
|
||||
// Never permit a query without a context_id unless the PKPApplication::CONTEXT_ID_ALL wildcard
|
||||
// has been set explicitly.
|
||||
if (!isset($this->contextIds)) {
|
||||
throw new Exception('Submissions can not be retrieved without a context id. Pass the Application::CONTEXT_ID_ALL wildcard to get submissions from any context.');
|
||||
} elseif (!in_array(PKPApplication::CONTEXT_ID_ALL, $this->contextIds)) {
|
||||
$q->whereIn('i.journal_id', $this->contextIds);
|
||||
}
|
||||
|
||||
// Issue IDs
|
||||
$q->when($this->issueIds !== null, fn (Builder $q) => $q->whereIn('i.issue_id', $this->issueIds));
|
||||
// Published
|
||||
$q->when($this->isPublished !== null, fn (Builder $q) => $q->where('i.published', '=', $this->isPublished ? 1 : 0));
|
||||
// Volumes
|
||||
$q->when($this->volumes !== null, fn (Builder $q) => $q->whereIn('i.volume', $this->volumes));
|
||||
// Numbers
|
||||
$q->when($this->numbers !== null, fn (Builder $q) => $q->whereIn('i.number', $this->numbers));
|
||||
// Years
|
||||
$q->when($this->years !== null, fn (Builder $q) => $q->whereIn('i.year', $this->years));
|
||||
// URL path
|
||||
$q->when($this->urlPath !== null, fn (Builder $q) => $q->where('i.url_path', '=', $this->urlPath));
|
||||
|
||||
// DOI statuses
|
||||
$q->when(
|
||||
$this->doiStatuses !== null,
|
||||
fn (Builder $q) =>
|
||||
$q->whereIn(
|
||||
'i.issue_id',
|
||||
fn (Builder $q) =>
|
||||
$q->select('i.issue_id')
|
||||
->from('issues as i')
|
||||
->leftJoin('dois as d', 'd.doi_id', '=', 'i.doi_id')
|
||||
->whereIn('d.status', $this->doiStatuses)
|
||||
)
|
||||
);
|
||||
|
||||
// By whether issue has DOI assigned
|
||||
$q->when(
|
||||
$this->hasDois !== null,
|
||||
fn (Builder $q) =>
|
||||
$q->whereIn(
|
||||
'i.issue_id',
|
||||
fn (Builder $q) =>
|
||||
$q->select('current_i.issue_id')
|
||||
->from('issues', 'current_i')
|
||||
->when(
|
||||
in_array(Repo::doi()::TYPE_ISSUE, $this->enabledDoiTypes),
|
||||
fn (Builder $q) =>
|
||||
$this->hasDois ? $q->whereNotNull('current_i.doi_id') : $q->whereNull('current_i.doi_id')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Search phrase
|
||||
if ($this->searchPhrase !== null) {
|
||||
$searchPhrase = $this->searchPhrase;
|
||||
|
||||
// Add support for searching for the volume, number and year
|
||||
// using the localized issue identification formats. In
|
||||
// en this will match Vol. 1. No. 1 (2018) against:
|
||||
// i.volume = 1 AND i.number = 1 AND i.year = 2018
|
||||
$volume = '';
|
||||
$volumeRegex = '/\b' . preg_quote(__('issue.vol'), '/') . '\s+(\d+)/';
|
||||
if (preg_match($volumeRegex, $searchPhrase, $matches)) {
|
||||
[$found, $volume] = $matches;
|
||||
$searchPhrase = str_replace($found, '', $searchPhrase);
|
||||
}
|
||||
$number = '';
|
||||
$numberRegex = '/\b' . preg_quote(__('issue.no'), '/') . '\s+(\S+)\b/';
|
||||
if (preg_match($numberRegex, $searchPhrase, $matches)) {
|
||||
[$found, $number] = $matches;
|
||||
$searchPhrase = str_replace($found, '', $searchPhrase);
|
||||
}
|
||||
$year = '';
|
||||
if (preg_match('/\((\d{4})\)/', $searchPhrase, $matches)) {
|
||||
[$found, $year] = $matches;
|
||||
$searchPhrase = str_replace($found, '', $searchPhrase);
|
||||
}
|
||||
$q->when(
|
||||
strlen($volume) || $number !== '' || $year !== '',
|
||||
fn (Builder $q) => $q->where(
|
||||
fn (Builder $q) => $q
|
||||
->when($volume !== '', fn (Builder $q) => $q->where('i.volume', '=', $volume))
|
||||
->when($number !== '', fn (Builder $q) => $q->where('i.number', '=', $number))
|
||||
->when($year !== '', fn (Builder $q) => $q->where('i.year', '=', $year))
|
||||
)
|
||||
);
|
||||
|
||||
$words = array_filter(array_unique(explode(' ', $searchPhrase)), 'strlen');
|
||||
if (count($words)) {
|
||||
$likePattern = DB::raw("CONCAT('%', LOWER(?), '%')");
|
||||
foreach ($words as $word) {
|
||||
$q->where(
|
||||
fn (Builder $q) => $q
|
||||
->whereIn(
|
||||
'i.issue_id',
|
||||
fn (Builder $q) =>
|
||||
$q->select('iss_t.issue_id')
|
||||
->from($this->dao->settingsTable, 'iss_t')
|
||||
->where('iss_t.setting_name', '=', 'title')
|
||||
->where(DB::raw('LOWER(iss_t.setting_value)'), 'LIKE', $likePattern)->addBinding($word)
|
||||
)
|
||||
->orWhereIn(
|
||||
'i.issue_id',
|
||||
fn (Builder $q) =>
|
||||
$q->select('iss_d.issue_id')
|
||||
->from($this->dao->settingsTable, 'iss_d')
|
||||
->where('iss_d.setting_name', '=', 'name')
|
||||
->where(DB::raw('LOWER(iss_d.setting_value)'), 'LIKE', $likePattern)->addBinding($word)
|
||||
)
|
||||
// Match any four-digit number to the year
|
||||
->when(ctype_digit($word) && strlen($word) === 4, fn (Builder $q) => $q->orWhere('i.year', '=', $word))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ordering for query-builder-based and legacy-based orderings
|
||||
$q->when($this->resultOrderings !== null, function (Builder $q) {
|
||||
foreach ($this->resultOrderings as $resultOrdering) {
|
||||
if ($resultOrdering['orderBy'] === static::ORDER_CURRENT_ISSUE) {
|
||||
// Custom query to order by current issue status from the journals table
|
||||
$q->leftJoin('journals as j', 'j.current_issue_id', '=', 'i.issue_id')
|
||||
->orderByRaw('CASE WHEN j.current_issue_id IS NOT NULL then 1 else 0 END ' . $resultOrdering['direction']);
|
||||
} else {
|
||||
$q->orderBy($resultOrdering['orderBy'], $resultOrdering['direction']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Limit and offset results for pagination
|
||||
$q->when($this->count !== null, fn (Builder $q) => $q->limit($this->count));
|
||||
$q->when($this->offset !== null, fn (Builder $q) => $q->offset($this->offset));
|
||||
|
||||
// Add app-specific query statements
|
||||
Hook::call('Issue::getMany::queryObject', [&$q, $this]);
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
<?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')));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,723 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @defgroup issue Issue
|
||||
* Implement journal issues.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file classes/issue/Issue.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 Issue
|
||||
*
|
||||
* @ingroup issue
|
||||
*
|
||||
* @see \APP\issue\DAO
|
||||
*
|
||||
* @brief Class for Issue.
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\file\PublicFileManager;
|
||||
use PKP\core\Core;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\submission\PKPSubmission;
|
||||
|
||||
class Issue extends \PKP\core\DataObject
|
||||
{
|
||||
public const ISSUE_ACCESS_OPEN = 1;
|
||||
public const ISSUE_ACCESS_SUBSCRIPTION = 2;
|
||||
|
||||
/**
|
||||
* get journal id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getJournalId()
|
||||
{
|
||||
return $this->getData('journalId');
|
||||
}
|
||||
|
||||
/**
|
||||
* set journal id
|
||||
*
|
||||
* @param int $journalId
|
||||
*/
|
||||
public function setJournalId($journalId)
|
||||
{
|
||||
return $this->setData('journalId', $journalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalizedTitle()
|
||||
{
|
||||
return $this->getLocalizedData('title');
|
||||
}
|
||||
|
||||
/**
|
||||
* get title
|
||||
*
|
||||
* @param string $locale
|
||||
*
|
||||
* @return string|array<string,string>
|
||||
*/
|
||||
public function getTitle($locale)
|
||||
{
|
||||
return $this->getData('title', $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* set title
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setTitle($title, $locale)
|
||||
{
|
||||
return $this->setData('title', $title, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* get volume
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getVolume()
|
||||
{
|
||||
return $this->getData('volume');
|
||||
}
|
||||
|
||||
/**
|
||||
* set volume
|
||||
*
|
||||
* @param int $volume
|
||||
*/
|
||||
public function setVolume($volume)
|
||||
{
|
||||
return $this->setData('volume', $volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* get number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNumber()
|
||||
{
|
||||
return $this->getData('number');
|
||||
}
|
||||
|
||||
/**
|
||||
* set number
|
||||
*
|
||||
* @param string $number
|
||||
*/
|
||||
public function setNumber($number)
|
||||
{
|
||||
return $this->setData('number', $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* get year
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getYear()
|
||||
{
|
||||
return $this->getData('year');
|
||||
}
|
||||
|
||||
/**
|
||||
* set year
|
||||
*
|
||||
* @param int $year
|
||||
*/
|
||||
public function setYear($year)
|
||||
{
|
||||
return $this->setData('year', $year);
|
||||
}
|
||||
|
||||
/**
|
||||
* get published
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPublished()
|
||||
{
|
||||
return $this->getData('published');
|
||||
}
|
||||
|
||||
/**
|
||||
* set published
|
||||
*
|
||||
* @param int $published
|
||||
*/
|
||||
public function setPublished($published)
|
||||
{
|
||||
return $this->setData('published', $published);
|
||||
}
|
||||
|
||||
/**
|
||||
* get date published
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDatePublished()
|
||||
{
|
||||
return $this->getData('datePublished');
|
||||
}
|
||||
|
||||
/**
|
||||
* set date published
|
||||
*
|
||||
* @param string $datePublished
|
||||
*/
|
||||
public function setDatePublished($datePublished)
|
||||
{
|
||||
return $this->setData('datePublished', $datePublished);
|
||||
}
|
||||
|
||||
/**
|
||||
* get date the users were last notified
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateNotified()
|
||||
{
|
||||
return $this->getData('dateNotified');
|
||||
}
|
||||
|
||||
/**
|
||||
* set date the users were last notified
|
||||
*
|
||||
* @param string $dateNotified
|
||||
*/
|
||||
public function setDateNotified($dateNotified)
|
||||
{
|
||||
return $this->setData('dateNotified', $dateNotified);
|
||||
}
|
||||
|
||||
/**
|
||||
* get date the issue was last modified
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->getData('lastModified');
|
||||
}
|
||||
|
||||
/**
|
||||
* set date the issue was last modified
|
||||
*
|
||||
* @param string $lastModified
|
||||
*/
|
||||
public function setLastModified($lastModified)
|
||||
{
|
||||
return $this->setData('lastModified', $lastModified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stamp the date of the last modification to the current time.
|
||||
*/
|
||||
public function stampModified()
|
||||
{
|
||||
return $this->setLastModified(Core::getCurrentDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* get access status (ISSUE_ACCESS_...)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAccessStatus()
|
||||
{
|
||||
return $this->getData('accessStatus');
|
||||
}
|
||||
|
||||
/**
|
||||
* set access status (ISSUE_ACCESS_...)
|
||||
*
|
||||
* @param int $accessStatus
|
||||
*/
|
||||
public function setAccessStatus($accessStatus)
|
||||
{
|
||||
return $this->setData('accessStatus', $accessStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* get open access date
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOpenAccessDate()
|
||||
{
|
||||
return $this->getData('openAccessDate');
|
||||
}
|
||||
|
||||
/**
|
||||
* set open access date
|
||||
*
|
||||
* @param string $openAccessDate
|
||||
*/
|
||||
public function setOpenAccessDate($openAccessDate)
|
||||
{
|
||||
return $this->setData('openAccessDate', $openAccessDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalizedDescription()
|
||||
{
|
||||
return $this->getLocalizedData('description');
|
||||
}
|
||||
|
||||
/**
|
||||
* get description
|
||||
*
|
||||
* @param string $locale
|
||||
*
|
||||
* @return string|array<string,string>
|
||||
*/
|
||||
public function getDescription($locale)
|
||||
{
|
||||
return $this->getData('description', $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* set description
|
||||
*
|
||||
* @param string $description
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setDescription($description, $locale)
|
||||
{
|
||||
return $this->setData('description', $description, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current DOI
|
||||
*
|
||||
*/
|
||||
public function getDoi(): ?string
|
||||
{
|
||||
$doiObject = $this->getData('doiObject');
|
||||
|
||||
if (empty($doiObject)) {
|
||||
return null;
|
||||
} else {
|
||||
return $doiObject->getData('doi');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored public ID of the issue.
|
||||
*
|
||||
* This helper function is required by PKPPubIdPlugins.
|
||||
* NB: To maintain backwards compatibility, getDoi() is called from here
|
||||
*
|
||||
* @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>).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStoredPubId($pubIdType)
|
||||
{
|
||||
if ($pubIdType == 'doi') {
|
||||
return $this->getDoi();
|
||||
} else {
|
||||
return $this->getData('pub-id::' . $pubIdType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set stored public issue id.
|
||||
*
|
||||
* @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' => $this->getJournalId()
|
||||
]
|
||||
);
|
||||
$doiId = Repo::doi()->add($newDoiObject);
|
||||
$this->setData('doiId', $doiId);
|
||||
}
|
||||
} else {
|
||||
$this->setData('pub-id::' . $pubIdType, $pubId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get show issue volume
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getShowVolume()
|
||||
{
|
||||
return $this->getData('showVolume');
|
||||
}
|
||||
|
||||
/**
|
||||
* set show issue volume
|
||||
*
|
||||
* @param int $showVolume
|
||||
*/
|
||||
public function setShowVolume($showVolume)
|
||||
{
|
||||
return $this->setData('showVolume', $showVolume);
|
||||
}
|
||||
|
||||
/**
|
||||
* get show issue number
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getShowNumber()
|
||||
{
|
||||
return $this->getData('showNumber');
|
||||
}
|
||||
|
||||
/**
|
||||
* set show issue number
|
||||
*
|
||||
* @param int $showNumber
|
||||
*/
|
||||
public function setShowNumber($showNumber)
|
||||
{
|
||||
return $this->setData('showNumber', $showNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* get show issue year
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getShowYear()
|
||||
{
|
||||
return $this->getData('showYear');
|
||||
}
|
||||
|
||||
/**
|
||||
* set show issue year
|
||||
*
|
||||
* @param int $showYear
|
||||
*/
|
||||
public function setShowYear($showYear)
|
||||
{
|
||||
return $this->setData('showYear', $showYear);
|
||||
}
|
||||
|
||||
/**
|
||||
* get show issue title
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getShowTitle()
|
||||
{
|
||||
return $this->getData('showTitle');
|
||||
}
|
||||
|
||||
/**
|
||||
* set show issue title
|
||||
*
|
||||
* @param int $showTitle
|
||||
*/
|
||||
public function setShowTitle($showTitle)
|
||||
{
|
||||
return $this->setData('showTitle', $showTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized issue cover image file name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalizedCoverImage()
|
||||
{
|
||||
return $this->getLocalizedData('coverImage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get issue cover image file name
|
||||
*
|
||||
* @param string $locale
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function getCoverImage($locale)
|
||||
{
|
||||
return $this->getData('coverImage', $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set issue cover image file name
|
||||
*
|
||||
* @param string|array $coverImage
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setCoverImage($coverImage, $locale)
|
||||
{
|
||||
return $this->setData('coverImage', $coverImage, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized issue cover image alternate text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalizedCoverImageAltText()
|
||||
{
|
||||
return $this->getLocalizedData('coverImageAltText');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get issue cover image alternate text
|
||||
*
|
||||
* @param string $locale
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCoverImageAltText($locale)
|
||||
{
|
||||
return $this->getData('coverImageAltText', $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full URL to the localized cover image
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalizedCoverImageUrl()
|
||||
{
|
||||
$coverImage = $this->getLocalizedCoverImage();
|
||||
if (!$coverImage) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$request = Application::get()->getRequest();
|
||||
|
||||
$publicFileManager = new PublicFileManager();
|
||||
|
||||
return $request->getBaseUrl() . '/' . $publicFileManager->getContextFilesPath($this->getJournalId()) . '/' . $coverImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL to all localized cover images
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCoverImageUrls()
|
||||
{
|
||||
$coverImages = $this->getCoverImage(null);
|
||||
if (empty($coverImages)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$request = Application::get()->getRequest();
|
||||
$publicFileManager = new PublicFileManager();
|
||||
|
||||
$urls = [];
|
||||
|
||||
foreach ($coverImages as $locale => $coverImage) {
|
||||
$urls[$locale] = sprintf('%s/%s/%s', $request->getBaseUrl(), $publicFileManager->getContextFilesPath($this->getJournalId()), $coverImage);
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set issue cover image alternate text
|
||||
*
|
||||
* @param string $coverImageAltText
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setCoverImageAltText($coverImageAltText, $locale)
|
||||
{
|
||||
return $this->setData('coverImageAltText', $coverImageAltText, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string of the issue identification based label format
|
||||
*
|
||||
* @param array $force force show/hide of data components
|
||||
* @param string $locale use specific non-default locale
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIssueIdentification($force = [], $locale = null)
|
||||
{
|
||||
$displayOptions = [
|
||||
'showVolume' => $this->getData('showVolume'),
|
||||
'showNumber' => $this->getData('showNumber'),
|
||||
'showYear' => $this->getData('showYear'),
|
||||
'showTitle' => $this->getData('showTitle'),
|
||||
];
|
||||
|
||||
$displayOptions = array_merge($displayOptions, $force);
|
||||
if (is_null($locale)) {
|
||||
$locale = Locale::getLocale();
|
||||
}
|
||||
|
||||
$volLabel = __('issue.vol', [], $locale);
|
||||
$numLabel = __('issue.no', [], $locale);
|
||||
|
||||
$vol = $this->getData('volume');
|
||||
$num = $this->getData('number');
|
||||
$year = $this->getData('year');
|
||||
$title = $this->getTitle($locale);
|
||||
if (empty($title)) {
|
||||
$title = $this->getLocalizedTitle();
|
||||
}
|
||||
|
||||
$identification = [];
|
||||
foreach ($displayOptions as $opt => $val) {
|
||||
if (empty($val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($opt == 'showVolume') {
|
||||
$identification[] = "{$volLabel} {$vol}";
|
||||
} elseif ($opt == 'showNumber') {
|
||||
$identification[] = "{$numLabel} {$num}";
|
||||
} elseif ($opt == 'showYear') {
|
||||
$identification[] = !empty($identification) ? "({$year})" : $year;
|
||||
} elseif ($opt == 'showTitle') {
|
||||
if (!empty($title)) {
|
||||
// Append a separator to the last key
|
||||
if (!empty($identification)) {
|
||||
end($identification);
|
||||
$identification[key($identification)] .= ':';
|
||||
}
|
||||
$identification[] = $title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we've got an empty title, re-run the function and force a result
|
||||
if (empty($identification)) {
|
||||
return $this->getIssueIdentification(
|
||||
[
|
||||
'showVolume' => true,
|
||||
'showNumber' => true,
|
||||
'showYear' => true,
|
||||
'showTitle' => false,
|
||||
],
|
||||
$locale
|
||||
);
|
||||
}
|
||||
|
||||
return join(' ', $identification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string of the issue series identification
|
||||
* eg: Vol 1 No 1 (2000)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIssueSeries()
|
||||
{
|
||||
if ($this->getShowVolume() || $this->getShowNumber() || $this->getShowYear()) {
|
||||
return $this->getIssueIdentification(['showTitle' => false]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of articles in this issue.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNumArticles()
|
||||
{
|
||||
return Repo::submission()->getCollector()
|
||||
->filterByContextIds([$this->getData('journalId')])
|
||||
->filterByIssueIds([$this->getId()])
|
||||
->filterByStatus([PKPSubmission::STATUS_SCHEDULED, PKPSubmission::STATUS_PUBLISHED])
|
||||
->getCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "best" issue ID -- If a public issue ID is set,
|
||||
* use it; otherwise use the internal issue Id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBestIssueId()
|
||||
{
|
||||
return strlen($urlPath = (string) $this->getData('urlPath')) ? $urlPath : $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a description exists for this issue
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDescription()
|
||||
{
|
||||
$description = $this->getLocalizedDescription();
|
||||
return !empty($description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether issue had DOI assigned to it
|
||||
*
|
||||
*/
|
||||
public function hasDoi(): bool
|
||||
{
|
||||
return (bool) $this->getData('doiObject');
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \PKP\core\DataObject::getDAO()
|
||||
*/
|
||||
public function getDAO(): DAO
|
||||
{
|
||||
return Repo::issue()->dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the object in Import/Export results
|
||||
*
|
||||
* @return string A string that Identifies the object
|
||||
*/
|
||||
public function getUIDisplayString()
|
||||
{
|
||||
return __('plugins.importexport.issue.cli.display', ['issueId' => $this->getId(), 'issueIdentification' => $this->getIssueIdentification()]);
|
||||
}
|
||||
}
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\APP\issue\Issue', '\Issue');
|
||||
foreach ([
|
||||
'ISSUE_ACCESS_OPEN',
|
||||
'ISSUE_ACCESS_SUBSCRIPTION',
|
||||
] as $constantName) {
|
||||
define($constantName, constant('\Issue::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/issue/IssueAction.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 IssueAction
|
||||
*
|
||||
* @ingroup issue
|
||||
*
|
||||
* @see Issue
|
||||
*
|
||||
* @brief IssueAction class.
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use APP\subscription\IndividualSubscriptionDAO;
|
||||
use APP\subscription\InstitutionalSubscriptionDAO;
|
||||
use APP\subscription\Subscription;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\security\Role;
|
||||
use PKP\security\RoleDAO;
|
||||
use PKP\submission\PKPSubmission;
|
||||
|
||||
class IssueAction
|
||||
{
|
||||
/**
|
||||
* Actions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if subscription is required for viewing the issue
|
||||
*
|
||||
* @param Issue $issue
|
||||
* @param \APP\journal\Journal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function subscriptionRequired($issue, $journal)
|
||||
{
|
||||
assert($issue instanceof \APP\issue\Issue);
|
||||
assert($journal instanceof \APP\journal\Journal);
|
||||
assert($journal->getId() == $issue->getJournalId());
|
||||
|
||||
// Check subscription state.
|
||||
$result = $journal->getData('publishingMode') == \APP\journal\Journal::PUBLISHING_MODE_SUBSCRIPTION &&
|
||||
$issue->getAccessStatus() != \APP\issue\Issue::ISSUE_ACCESS_OPEN && (
|
||||
is_null($issue->getOpenAccessDate()) ||
|
||||
strtotime($issue->getOpenAccessDate()) > time()
|
||||
);
|
||||
Hook::call('IssueAction::subscriptionRequired', [&$journal, &$issue, &$result]);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this user is granted access to pre-publication issue galleys
|
||||
* based on their roles in the journal (i.e. Manager, Editor, etc).
|
||||
*
|
||||
* @param \APP\journal\Journal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allowedIssuePrePublicationAccess($journal, $user)
|
||||
{
|
||||
/** @var RoleDAO */
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO');
|
||||
if ($user && $journal) {
|
||||
$journalId = $journal->getId();
|
||||
$userId = $user->getId();
|
||||
$subscriptionAssumedRoles = [
|
||||
Role::ROLE_ID_MANAGER,
|
||||
Role::ROLE_ID_SUB_EDITOR,
|
||||
Role::ROLE_ID_ASSISTANT,
|
||||
Role::ROLE_ID_SUBSCRIPTION_MANAGER
|
||||
];
|
||||
|
||||
$roles = $roleDao->getByUserId($userId, $journalId);
|
||||
foreach ($roles as $role) {
|
||||
if (in_array($role->getRoleId(), $subscriptionAssumedRoles)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user has subscription
|
||||
*
|
||||
* @param \PKP\user\User $user
|
||||
* @param \APP\journal\Journal $journal
|
||||
* @param int $issueId Issue ID (optional)
|
||||
* @param int $articleId Article ID (optional)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function subscribedUser($user, $journal, $issueId = null, $articleId = null)
|
||||
{
|
||||
$subscriptionDao = DAORegistry::getDAO('IndividualSubscriptionDAO'); /** @var IndividualSubscriptionDAO $subscriptionDao */
|
||||
$submission = Repo::submission()->get((int) $articleId);
|
||||
$result = false;
|
||||
if (isset($user) && isset($journal)) {
|
||||
if ($submission && Repo::submission()->canPreview($user, $submission)) {
|
||||
$result = true;
|
||||
} else {
|
||||
$result = $subscriptionDao->isValidIndividualSubscription($user->getId(), $journal->getId());
|
||||
}
|
||||
|
||||
// If no valid subscription, check if there is an expired subscription
|
||||
// that was valid during publication date of any one of the submission's
|
||||
// publications
|
||||
if (!$result && $journal->getData('subscriptionExpiryPartial')) {
|
||||
if (isset($submission) && !empty($submission->getData('publications'))) {
|
||||
foreach ($submission->getData('publications') as $publication) {
|
||||
if ($subscriptionDao->isValidIndividualSubscription($user->getId(), $journal->getId(), Subscription::SUBSCRIPTION_DATE_END, $publication->getData('datePublished'))) {
|
||||
$result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif (isset($issueId)) {
|
||||
$issue = Repo::issue()->get($issueId);
|
||||
if (isset($issue) && $issue->getPublished()) {
|
||||
$result = $subscriptionDao->isValidIndividualSubscription($user->getId(), $journal->getId(), Subscription::SUBSCRIPTION_DATE_END, $issue->getDatePublished());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Hook::call('IssueAction::subscribedUser', [&$user, &$journal, &$issueId, &$articleId, &$result]);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if remote client domain or ip is allowed
|
||||
*
|
||||
* @param \APP\core\Request $request
|
||||
* @param \APP\journal\Journal $journal
|
||||
* @param int $issueId Issue ID (optional)
|
||||
* @param int $articleId Article ID (optional)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function subscribedDomain($request, $journal, $issueId = null, $articleId = null)
|
||||
{
|
||||
$subscriptionDao = DAORegistry::getDAO('InstitutionalSubscriptionDAO'); /** @var InstitutionalSubscriptionDAO $subscriptionDao */
|
||||
$result = false;
|
||||
if (isset($journal)) {
|
||||
$result = $subscriptionDao->isValidInstitutionalSubscription($request->getRemoteDomain(), $request->getRemoteAddr(), $journal->getId());
|
||||
|
||||
// If no valid subscription, check if there is an expired subscription
|
||||
// that was valid during publication date of requested content
|
||||
if (!$result && $journal->getData('subscriptionExpiryPartial')) {
|
||||
if (isset($articleId)) {
|
||||
$submission = Repo::submission()->get($articleId);
|
||||
if ($submission->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
|
||||
$result = $subscriptionDao->isValidInstitutionalSubscription($request->getRemoteDomain(), $request->getRemoteAddr(), $journal->getId(), Subscription::SUBSCRIPTION_DATE_END, $submission->getDatePublished());
|
||||
}
|
||||
} elseif (isset($issueId)) {
|
||||
$issue = Repo::issue()->get($issueId);
|
||||
if (isset($issue) && $issue->getPublished()) {
|
||||
$result = $subscriptionDao->isValidInstitutionalSubscription($request->getRemoteDomain(), $request->getRemoteAddr(), $journal->getId(), Subscription::SUBSCRIPTION_DATE_END, $issue->getDatePublished());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Hook::call('IssueAction::subscribedDomain', [&$request, &$journal, &$issueId, &$articleId, &$result]);
|
||||
return (bool) $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\APP\issue\IssueAction', '\IssueAction');
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/issue/IssueFile.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 IssueFile
|
||||
*
|
||||
* @ingroup issue
|
||||
*
|
||||
* @brief Issue file class.
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use PKP\file\PKPFile;
|
||||
|
||||
class IssueFile extends PKPFile
|
||||
{
|
||||
/** @var int File content type IDs */
|
||||
public const ISSUE_FILE_PUBLIC = 1;
|
||||
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get ID of issue.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIssueId()
|
||||
{
|
||||
return $this->getData('issueId');
|
||||
}
|
||||
|
||||
/**
|
||||
* set ID of issue.
|
||||
*
|
||||
* @param int $issueId
|
||||
*/
|
||||
public function setIssueId($issueId)
|
||||
{
|
||||
return $this->setData('issueId', $issueId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content type of the file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return $this->getData('contentType');
|
||||
}
|
||||
|
||||
/**
|
||||
* set type of the file.
|
||||
*/
|
||||
public function setContentType($contentType)
|
||||
{
|
||||
return $this->setData('contentType', $contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modified date of file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDateModified()
|
||||
{
|
||||
return $this->getData('dateModified');
|
||||
}
|
||||
|
||||
/**
|
||||
* set modified date of file.
|
||||
*
|
||||
* @param string $dateModified
|
||||
*/
|
||||
public function setDateModified($dateModified)
|
||||
{
|
||||
return $this->setData('dateModified', $dateModified);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\APP\issue\IssueFile', '\IssueFile');
|
||||
define('ISSUE_FILE_PUBLIC', IssueFile::ISSUE_FILE_PUBLIC);
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/issue/IssueFileDAO.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 IssueFileDAO
|
||||
*
|
||||
* @ingroup issue
|
||||
*
|
||||
* @see IssueFile
|
||||
*
|
||||
* @brief Operations for retrieving and modifying IssueFile objects.
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use PKP\db\DAO;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
class IssueFileDAO extends DAO
|
||||
{
|
||||
/** @var array MIME types that can be displayed inline in a browser */
|
||||
public $_inlineableTypes = null;
|
||||
|
||||
|
||||
/**
|
||||
* Get inlineable file types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getInlineableTypes()
|
||||
{
|
||||
return $this->_inlineableTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set inlineable file types.
|
||||
*
|
||||
* @param array $inlineableTypes
|
||||
*/
|
||||
public function setInlineableTypes($inlineableTypes)
|
||||
{
|
||||
$this->_inlineableTypes = $inlineableTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an issue file by ID.
|
||||
*
|
||||
* @param int $fileId
|
||||
* @param int $issueId optional
|
||||
*
|
||||
* @return IssueFile
|
||||
*/
|
||||
public function getById($fileId, $issueId = null)
|
||||
{
|
||||
$params = [(int) $fileId];
|
||||
if ($issueId) {
|
||||
$params[] = (int) $issueId;
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT f.*
|
||||
FROM issue_files f
|
||||
WHERE f.file_id = ?
|
||||
' . ($issueId ? ' AND f.issue_id = ?' : ''),
|
||||
$params
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? $this->_fromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new IssueFile data object.
|
||||
*
|
||||
* @return IssueFile
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new IssueFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to return an IssueFile object from a row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return IssueFile
|
||||
*/
|
||||
public function _fromRow($row)
|
||||
{
|
||||
$issueFile = $this->newDataObject();
|
||||
$issueFile->setId($row['file_id']);
|
||||
$issueFile->setIssueId($row['issue_id']);
|
||||
$issueFile->setServerFileName($row['file_name']);
|
||||
$issueFile->setFileType($row['file_type']);
|
||||
$issueFile->setFileSize($row['file_size']);
|
||||
$issueFile->setContentType($row['content_type']);
|
||||
$issueFile->setOriginalFileName($row['original_file_name']);
|
||||
$issueFile->setDateUploaded($this->datetimeFromDB($row['date_uploaded']));
|
||||
$issueFile->setDateModified($this->datetimeFromDB($row['date_modified']));
|
||||
Hook::call('IssueFileDAO::_returnIssueFileFromRow', [&$issueFile, &$row]);
|
||||
return $issueFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new IssueFile.
|
||||
*
|
||||
* @param IssueFile $issueFile
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function insertObject($issueFile)
|
||||
{
|
||||
$this->update(
|
||||
sprintf(
|
||||
'INSERT INTO issue_files
|
||||
(issue_id,
|
||||
file_name,
|
||||
file_type,
|
||||
file_size,
|
||||
content_type,
|
||||
original_file_name,
|
||||
date_uploaded,
|
||||
date_modified)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, %s, %s)',
|
||||
$this->datetimeToDB($issueFile->getDateUploaded()),
|
||||
$this->datetimeToDB($issueFile->getDateModified())
|
||||
),
|
||||
[
|
||||
(int) $issueFile->getIssueId(),
|
||||
$issueFile->getServerFileName(),
|
||||
$issueFile->getFileType(),
|
||||
$issueFile->getFileSize(),
|
||||
$issueFile->getContentType(),
|
||||
$issueFile->getOriginalFileName()
|
||||
]
|
||||
);
|
||||
|
||||
$issueFile->setId($this->getInsertId());
|
||||
return $issueFile->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing issue file.
|
||||
*/
|
||||
public function updateObject($issueFile)
|
||||
{
|
||||
$this->update(
|
||||
sprintf(
|
||||
'UPDATE issue_files
|
||||
SET
|
||||
issue_id = ?,
|
||||
file_name = ?,
|
||||
file_type = ?,
|
||||
file_size = ?,
|
||||
content_type = ?,
|
||||
original_file_name = ?,
|
||||
date_uploaded = %s,
|
||||
date_modified = %s
|
||||
WHERE file_id = ?',
|
||||
$this->datetimeToDB($issueFile->getDateUploaded()),
|
||||
$this->datetimeToDB($issueFile->getDateModified())
|
||||
),
|
||||
[
|
||||
(int) $issueFile->getIssueId(),
|
||||
$issueFile->getServerFileName(),
|
||||
$issueFile->getFileType(),
|
||||
$issueFile->getFileSize(),
|
||||
$issueFile->getContentType(),
|
||||
$issueFile->getOriginalFileName(),
|
||||
(int) $issueFile->getId()
|
||||
]
|
||||
);
|
||||
|
||||
return $issueFile->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an issue file.
|
||||
*/
|
||||
public function deleteObject($issueFile)
|
||||
{
|
||||
$this->deleteById($issueFile->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an issue file by ID.
|
||||
*/
|
||||
public function deleteById($fileId)
|
||||
{
|
||||
$this->update('DELETE FROM issue_files WHERE file_id = ?', [(int) $fileId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all issue files for an issue.
|
||||
*
|
||||
* @param int $issueId
|
||||
*/
|
||||
public function deleteByIssueId($issueId)
|
||||
{
|
||||
$this->update('DELETE FROM issue_files WHERE issue_id = ?', [(int) $issueId]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\APP\issue\IssueFileDAO', '\IssueFileDAO');
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* @defgroup issue_galley Issue Galleys
|
||||
* Issue galleys allow for the representation of an entire journal issue with
|
||||
* a single file, typically a PDF.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file classes/issue/IssueGalley.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 IssueGalley
|
||||
*
|
||||
* @ingroup issue_galley
|
||||
*
|
||||
* @see IssueGalleyDAO
|
||||
*
|
||||
* @brief A galley is a final presentation version of the full-text of an issue.
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Services;
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\facades\Locale;
|
||||
|
||||
class IssueGalley extends IssueFile
|
||||
{
|
||||
/** @var IssueFile */
|
||||
public $_issueFile;
|
||||
|
||||
|
||||
/**
|
||||
* Check if galley is a PDF galley.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPdfGalley()
|
||||
{
|
||||
switch ($this->getFileType()) {
|
||||
case 'application/pdf':
|
||||
case 'application/x-pdf':
|
||||
case 'text/pdf':
|
||||
case 'text/x-pdf':
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
/**
|
||||
* Get views count.
|
||||
*
|
||||
* @deprecated 3.4
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getViews()
|
||||
{
|
||||
$filters = [
|
||||
'dateStart' => StatisticsHelper::STATISTICS_EARLIEST_DATE,
|
||||
'dateEnd' => date('Y-m-d', strtotime('yesterday')),
|
||||
'contextIds' => [Application::get()->getRequest()->getContext()->getId()],
|
||||
'issueGalleyIds' => [$this->getId()],
|
||||
];
|
||||
$metrics = Services::get('issueStats')
|
||||
->getQueryBuilder($filters)
|
||||
->getSum([])
|
||||
->value('metric');
|
||||
return $metrics ? $metrics : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the localized value of the galley label.
|
||||
*
|
||||
* @return $string
|
||||
*/
|
||||
public function getGalleyLabel()
|
||||
{
|
||||
$label = $this->getLabel();
|
||||
if ($this->getLocale() != Locale::getLocale()) {
|
||||
$label .= ' (' . Locale::getMetadata($this->getLocale())->getDisplayName() . ')';
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get label/title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel()
|
||||
{
|
||||
return $this->getData('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set label/title.
|
||||
*
|
||||
* @param string $label
|
||||
*/
|
||||
public function setLabel($label)
|
||||
{
|
||||
return $this->setData('label', $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocale()
|
||||
{
|
||||
return $this->getData('locale');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set locale.
|
||||
*
|
||||
* @param string $locale
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
{
|
||||
return $this->setData('locale', $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sequence order.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getSequence()
|
||||
{
|
||||
return $this->getData('sequence');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sequence order.
|
||||
*
|
||||
* @param float $sequence
|
||||
*/
|
||||
public function setSequence($sequence)
|
||||
{
|
||||
return $this->setData('sequence', $sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getFileId()
|
||||
{
|
||||
return $this->getData('fileId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set file ID.
|
||||
*
|
||||
* @param int $fileId
|
||||
*/
|
||||
public function setFileId($fileId)
|
||||
{
|
||||
return $this->setData('fileId', $fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored public ID of the galley.
|
||||
*
|
||||
* @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>).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStoredPubId($pubIdType)
|
||||
{
|
||||
return $this->getData('pub-id::' . $pubIdType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set stored public galley id.
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
return $this->setData('pub-id::' . $pubIdType, $pubId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "best" issue galley ID -- If a urlPath is set,
|
||||
* use it; otherwise use the internal article Id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBestGalleyId()
|
||||
{
|
||||
return strlen($urlPath = (string) $this->getData('urlPath')) ? $urlPath : $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file corresponding to this galley.
|
||||
*
|
||||
* @return IssueFile
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
if (!isset($this->_issueFile)) {
|
||||
$issueFileDao = DAORegistry::getDAO('IssueFileDAO'); /** @var IssueFileDAO $issueFileDao */
|
||||
$this->_issueFile = $issueFileDao->getById($this->getFileId());
|
||||
}
|
||||
return $this->_issueFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\APP\issue\IssueGalley', '\IssueGalley');
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/issue/IssueGalleyDAO.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 IssueGalleyDAO
|
||||
*
|
||||
* @ingroup issue_galley
|
||||
*
|
||||
* @see IssueGalley
|
||||
*
|
||||
* @brief Operations for retrieving and modifying IssueGalley objects.
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use APP\file\IssueFileManager;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
class IssueGalleyDAO extends \PKP\db\DAO
|
||||
{
|
||||
/**
|
||||
* Retrieve a galley by ID.
|
||||
*
|
||||
* @param int $galleyId
|
||||
* @param int $issueId optional
|
||||
*
|
||||
* @return IssueGalley
|
||||
*/
|
||||
public function getById($galleyId, $issueId = null)
|
||||
{
|
||||
$params = [(int) $galleyId];
|
||||
if ($issueId !== null) {
|
||||
$params[] = (int) $issueId;
|
||||
}
|
||||
$result = $this->retrieve(
|
||||
'SELECT
|
||||
g.*,
|
||||
f.file_name,
|
||||
f.original_file_name,
|
||||
f.file_type,
|
||||
f.file_size,
|
||||
f.content_type,
|
||||
f.date_uploaded,
|
||||
f.date_modified
|
||||
FROM issue_galleys g
|
||||
LEFT JOIN issue_files f ON (g.file_id = f.file_id)
|
||||
WHERE g.galley_id = ?' .
|
||||
($issueId !== null ? ' AND g.issue_id = ?' : ''),
|
||||
$params
|
||||
);
|
||||
|
||||
$returner = null;
|
||||
if ($row = $result->current()) {
|
||||
$returner = $this->_fromRow((array) $row);
|
||||
} else {
|
||||
Hook::call('IssueGalleyDAO::getById', [&$galleyId, &$issueId, &$returner]);
|
||||
}
|
||||
return $returner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if public identifier exists (other than for the specified
|
||||
* galley ID, which is treated as an exception).
|
||||
*
|
||||
* @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
|
||||
* @param int $galleyId An ID to be excluded from the search.
|
||||
* @param int $journalId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function pubIdExists($pubIdType, $pubId, $galleyId, $journalId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT COUNT(*) AS row_count
|
||||
FROM issue_galley_settings igs
|
||||
INNER JOIN issue_galleys ig ON igs.galley_id = ig.galley_id
|
||||
INNER JOIN issues i ON ig.issue_id = i.issue_id
|
||||
WHERE igs.setting_name = ? AND igs.setting_value = ? AND igs.galley_id <> ? AND i.journal_id = ?',
|
||||
[
|
||||
'pub-id::' . $pubIdType,
|
||||
$pubId,
|
||||
(int) $galleyId,
|
||||
(int) $journalId
|
||||
]
|
||||
);
|
||||
$row = $result->current();
|
||||
return $row ? (bool) $row->row_count : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a galley by ID.
|
||||
*
|
||||
* @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
|
||||
* @param int $issueId
|
||||
*
|
||||
* @return IssueGalley
|
||||
*/
|
||||
public function getByPubId($pubIdType, $pubId, $issueId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT
|
||||
g.*,
|
||||
f.file_name,
|
||||
f.original_file_name,
|
||||
f.file_type,
|
||||
f.file_size,
|
||||
f.content_type,
|
||||
f.date_uploaded,
|
||||
f.date_modified
|
||||
FROM issue_galleys g
|
||||
INNER JOIN issue_galley_settings gs ON g.galley_id = gs.galley_id
|
||||
LEFT JOIN issue_files f ON (g.file_id = f.file_id)
|
||||
WHERE gs.setting_name = ? AND
|
||||
gs.setting_value = ? AND
|
||||
g.issue_id = ?',
|
||||
['pub-id::' . $pubIdType, (string) $pubId, (int) $issueId]
|
||||
);
|
||||
$row = $result->current();
|
||||
$returner = null;
|
||||
if ($row) {
|
||||
$returner = $this->_fromRow((array) $row);
|
||||
} else {
|
||||
Hook::call('IssueGalleyDAO::getByPubId', [&$pubIdType, &$pubId, &$issueId, &$returner]);
|
||||
}
|
||||
return $returner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all galleys for an issue.
|
||||
*
|
||||
* @param int $issueId
|
||||
*
|
||||
* @return array<int,IssueGalley> IssueGalleys
|
||||
*/
|
||||
public function getByIssueId($issueId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT
|
||||
g.*,
|
||||
f.file_name,
|
||||
f.original_file_name,
|
||||
f.file_type,
|
||||
f.file_size,
|
||||
f.content_type,
|
||||
f.date_uploaded,
|
||||
f.date_modified
|
||||
FROM issue_galleys g
|
||||
LEFT JOIN issue_files f ON (g.file_id = f.file_id)
|
||||
WHERE g.issue_id = ? ORDER BY g.seq',
|
||||
[(int) $issueId]
|
||||
);
|
||||
|
||||
$galleys = [];
|
||||
foreach ($result as $row) {
|
||||
$issueGalley = $this->_fromRow((array) $row);
|
||||
$galleys[$issueGalley->getId()] = $issueGalley;
|
||||
}
|
||||
Hook::call('IssueGalleyDAO::getGalleysByIssue', [&$galleys, &$issueId]);
|
||||
return $galleys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve issue galley by urlPath or, failing that,
|
||||
* internal galley ID; urlPath takes precedence.
|
||||
*
|
||||
* @param string $galleyId
|
||||
* @param int $issueId
|
||||
*
|
||||
* @return IssueGalley object
|
||||
*/
|
||||
public function getByBestId($galleyId, $issueId)
|
||||
{
|
||||
$result = $this->retrieve(
|
||||
'SELECT
|
||||
g.*,
|
||||
f.file_name,
|
||||
f.original_file_name,
|
||||
f.file_type,
|
||||
f.file_size,
|
||||
f.content_type,
|
||||
f.date_uploaded,
|
||||
f.date_modified
|
||||
FROM issue_galleys g
|
||||
LEFT JOIN issue_files f ON (g.file_id = f.file_id)
|
||||
WHERE g.url_path = ? AND
|
||||
g.issue_id = ?',
|
||||
[
|
||||
$galleyId,
|
||||
(int) $issueId,
|
||||
]
|
||||
);
|
||||
if ($row = $result->current()) {
|
||||
return $this->_fromRow((array) $row);
|
||||
}
|
||||
return $this->getById($galleyId, $issueId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of fields for which data is localized.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLocaleFieldNames()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of additional fields that do not have
|
||||
* dedicated accessors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalFieldNames()
|
||||
{
|
||||
$additionalFields = parent::getAdditionalFieldNames();
|
||||
// FIXME: Move this to a PID plug-in.
|
||||
$additionalFields[] = 'pub-id::publisher-id';
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the localized fields for this galley.
|
||||
*
|
||||
* @param IssueGalley $galley
|
||||
*/
|
||||
public function updateLocaleFields($galley)
|
||||
{
|
||||
$this->updateDataObjectSettings('issue_galley_settings', $galley, [
|
||||
'galley_id' => $galley->getId()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new issue galley.
|
||||
*
|
||||
* @return IssueGalley
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
return new IssueGalley();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to return an IssueGalley object from a row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return IssueGalley
|
||||
*/
|
||||
public function _fromRow($row)
|
||||
{
|
||||
$galley = $this->newDataObject();
|
||||
|
||||
$galley->setId($row['galley_id']);
|
||||
$galley->setIssueId($row['issue_id']);
|
||||
$galley->setLocale($row['locale']);
|
||||
$galley->setFileId($row['file_id']);
|
||||
$galley->setLabel($row['label']);
|
||||
$galley->setSequence($row['seq']);
|
||||
$galley->setData('urlPath', $row['url_path']);
|
||||
|
||||
// IssueFile set methods
|
||||
$galley->setServerFileName($row['file_name']);
|
||||
$galley->setOriginalFileName($row['original_file_name']);
|
||||
$galley->setFileType($row['file_type']);
|
||||
$galley->setFileSize($row['file_size']);
|
||||
$galley->setContentType($row['content_type']);
|
||||
$galley->setDateModified($this->datetimeFromDB($row['date_modified']));
|
||||
$galley->setDateUploaded($this->datetimeFromDB($row['date_uploaded']));
|
||||
|
||||
$this->getDataObjectSettings('issue_galley_settings', 'galley_id', $row['galley_id'], $galley);
|
||||
|
||||
Hook::call('IssueGalleyDAO::_fromRow', [&$galley, &$row]);
|
||||
|
||||
return $galley;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new IssueGalley.
|
||||
*
|
||||
* @param IssueGalley $galley
|
||||
*/
|
||||
public function insertObject($galley)
|
||||
{
|
||||
$this->update(
|
||||
'INSERT INTO issue_galleys
|
||||
(issue_id,
|
||||
file_id,
|
||||
label,
|
||||
locale,
|
||||
seq,
|
||||
url_path)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
(int) $galley->getIssueId(),
|
||||
(int) $galley->getFileId(),
|
||||
$galley->getLabel(),
|
||||
$galley->getLocale(),
|
||||
$galley->getSequence() == null ? $this->getNextGalleySequence($galley->getIssueId()) : $galley->getSequence(),
|
||||
$galley->getData('urlPath'),
|
||||
]
|
||||
);
|
||||
$galley->setId($this->getInsertId());
|
||||
$this->updateLocaleFields($galley);
|
||||
|
||||
Hook::call('IssueGalleyDAO::insertObject', [&$galley, $galley->getId()]);
|
||||
|
||||
return $galley->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing IssueGalley.
|
||||
*
|
||||
* @param IssueGalley $galley
|
||||
*/
|
||||
public function updateObject($galley)
|
||||
{
|
||||
$this->update(
|
||||
'UPDATE issue_galleys
|
||||
SET
|
||||
file_id = ?,
|
||||
label = ?,
|
||||
locale = ?,
|
||||
seq = ?,
|
||||
url_path = ?
|
||||
WHERE galley_id = ?',
|
||||
[
|
||||
(int) $galley->getFileId(),
|
||||
$galley->getLabel(),
|
||||
$galley->getLocale(),
|
||||
$galley->getSequence(),
|
||||
$galley->getData('urlPath'),
|
||||
(int) $galley->getId()
|
||||
]
|
||||
);
|
||||
$this->updateLocaleFields($galley);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an IssueGalley.
|
||||
*
|
||||
* @param IssueGalley $galley
|
||||
*/
|
||||
public function deleteObject($galley)
|
||||
{
|
||||
return $this->deleteById($galley->getId(), $galley->getIssueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a galley by ID.
|
||||
*
|
||||
* @param int $galleyId
|
||||
* @param int $issueId optional
|
||||
*/
|
||||
public function deleteById($galleyId, $issueId = null)
|
||||
{
|
||||
Hook::call('IssueGalleyDAO::deleteById', [&$galleyId, &$issueId]);
|
||||
|
||||
if (isset($issueId)) {
|
||||
// Delete the file
|
||||
$issueGalley = $this->getById($galleyId);
|
||||
$issueFileManager = new IssueFileManager($issueId);
|
||||
$issueFileManager->deleteById($issueGalley->getFileId());
|
||||
|
||||
$affectedRows = $this->update(
|
||||
'DELETE FROM issue_galleys WHERE galley_id = ? AND issue_id = ?',
|
||||
[(int) $galleyId, (int) $issueId]
|
||||
);
|
||||
} else {
|
||||
$affectedRows = $this->update(
|
||||
'DELETE FROM issue_galleys WHERE galley_id = ?',
|
||||
[(int) $galleyId]
|
||||
);
|
||||
}
|
||||
if ($affectedRows) {
|
||||
$this->update('DELETE FROM issue_galley_settings WHERE galley_id = ?', [(int) $galleyId]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete galleys by issue.
|
||||
* NOTE that this will not delete issue_file entities or the respective files.
|
||||
*
|
||||
* @param int $issueId
|
||||
*/
|
||||
public function deleteByIssueId($issueId)
|
||||
{
|
||||
$galleys = $this->getByIssueId($issueId);
|
||||
foreach ($galleys as $galley) {
|
||||
$this->deleteById($galley->getId(), $issueId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequentially renumber galleys for an issue in their sequence order.
|
||||
*
|
||||
* @param int $issueId
|
||||
*/
|
||||
public function resequence($issueId)
|
||||
{
|
||||
$result = $this->retrieve('SELECT galley_id FROM issue_galleys WHERE issue_id = ? ORDER BY seq', [(int) $issueId]);
|
||||
for ($i = 1; $row = $result->current(); $i++) {
|
||||
$this->update('UPDATE issue_galleys SET seq = ? WHERE galley_id = ?', [$i, $row->galley_id]);
|
||||
$result->next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the the next sequence number for an issue's galleys (i.e., current max + 1).
|
||||
*
|
||||
* @param int $issueId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNextGalleySequence($issueId)
|
||||
{
|
||||
$result = $this->retrieve('SELECT COALESCE(MAX(seq), 0) + 1 AS next_sequence FROM issue_galleys WHERE issue_id = ?', [(int) $issueId]);
|
||||
$row = $result->current();
|
||||
return $row->next_sequence;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\APP\issue\IssueGalleyDAO', '\IssueGalleyDAO');
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/issue/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 issues.
|
||||
*/
|
||||
|
||||
namespace APP\issue;
|
||||
|
||||
use APP\core\Request;
|
||||
use APP\facades\Repo;
|
||||
use APP\file\IssueFileManager;
|
||||
use APP\file\PublicFileManager;
|
||||
use APP\journal\JournalDAO;
|
||||
use Illuminate\Support\Collection;
|
||||
use PKP\context\Context;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\doi\exceptions\DoiException;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\services\PKPSchemaService;
|
||||
use PKP\validation\ValidatorFactory;
|
||||
|
||||
class Repository
|
||||
{
|
||||
/** @var DAO $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 $request */
|
||||
protected $request;
|
||||
|
||||
/** @var PKPSchemaService $schemaService */
|
||||
protected $schemaService;
|
||||
|
||||
// TODO: Explicitly excluding caching for now as it wasn't actually setting data to cache
|
||||
|
||||
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 = []): Issue
|
||||
{
|
||||
$object = $this->dao->newDataObject();
|
||||
if (!empty($params)) {
|
||||
$object->setAllData($params);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::exists() */
|
||||
public function exists(int $id, ?int $journalId = null): bool
|
||||
{
|
||||
return $this->dao->exists($id, $journalId);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::get()
|
||||
* TODO: Function signature should stick with ID, but previous DAO expected $useCache = false as default
|
||||
*/
|
||||
public function get(int $id, ?int $journalId = null): ?Issue
|
||||
{
|
||||
// TODO: Caching as currently setup never properly caches objects and always fires a _cacheMiss()
|
||||
// if ($useCache) {
|
||||
// $cache = $this->dao->_getCache('issues');
|
||||
// $returner = $cache->get($id);
|
||||
// if ($returner && $contextId != null && $contextId != $returner->getJournalId()) {
|
||||
// $returner = null;
|
||||
// }
|
||||
// return $returner;
|
||||
// }
|
||||
|
||||
return $this->dao->get($id, $journalId);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::getCollector() */
|
||||
public function getCollector(): Collector
|
||||
{
|
||||
return app(Collector::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the map class for mapping
|
||||
* announcements to their schema
|
||||
*/
|
||||
public function getSchemaMap(): maps\Schema
|
||||
{
|
||||
return app('maps')->withExtensions($this->schemaMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate properties for an issue
|
||||
*
|
||||
* Perform validation checks on data used to add or edit an issue.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return array A key/value array with validation errors. Empty if no errors
|
||||
*/
|
||||
public function validate(?Issue $object, array $props, array $allowedLocales, string $primaryLocale): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
$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);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $this->schemaService->formatValidationErrors($validator->errors());
|
||||
}
|
||||
|
||||
Hook::call('Issue::validate', [&$errors, $object, $props, $allowedLocales, $primaryLocale]);
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::insert() */
|
||||
public function add(Issue $issue): int
|
||||
{
|
||||
return $this->dao->insert($issue);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::update() */
|
||||
public function edit(Issue $issue, array $params)
|
||||
{
|
||||
$newIssue = $this->newDataObject(array_merge($issue->_data, $params));
|
||||
|
||||
Hook::call('Issue::edit', [&$newIssue, $issue, $params]);
|
||||
|
||||
$this->dao->update($newIssue);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::delete() */
|
||||
public function delete(Issue $issue)
|
||||
{
|
||||
$publicFileManager = new PublicFileManager();
|
||||
|
||||
if (is_array($issue->getCoverImage(null))) {
|
||||
foreach ($issue->getCoverImage(null) as $coverImage) {
|
||||
if ($coverImage != '') {
|
||||
$publicFileManager->removeContextFile($issue->getJournalId(), $coverImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$issueId = $issue->getId();
|
||||
|
||||
// Delete issue-specific ordering if it exists.
|
||||
Repo::section()->deleteCustomSectionOrdering($issueId);
|
||||
|
||||
// Delete published issue galleys and issue files
|
||||
$issueGalleyDao = DAORegistry::getDAO('IssueGalleyDAO'); /** @var IssueGalleyDAO $issueGalleyDao */
|
||||
$issueGalleyDao->deleteByIssueId($issueId);
|
||||
|
||||
$issueFileDao = DAORegistry::getDAO('IssueFileDAO'); /** @var IssueFileDAO $issueFileDao */
|
||||
$issueFileDao->deleteByIssueId($issueId);
|
||||
|
||||
$issueFileManager = new IssueFileManager($issueId);
|
||||
$issueFileManager->deleteIssueTree();
|
||||
|
||||
$this->dao->deleteCustomIssueOrdering($issueId);
|
||||
|
||||
$this->dao->delete($issue);
|
||||
}
|
||||
|
||||
public function deleteMany(Collector $collector)
|
||||
{
|
||||
foreach ($collector->getIds() as $issueId) {
|
||||
$this->dao->deleteById($issueId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve current issue
|
||||
*
|
||||
* @param bool $useCache TODO: Not currently implemented. Adding to preserved desired cache usage in future
|
||||
*/
|
||||
public function getCurrent(int $contextId, bool $useCache = false): ?Issue
|
||||
{
|
||||
// TODO: Caching as currently setup never properly caches objects and always fires a _cacheMiss()
|
||||
// if ($useCache) {
|
||||
// $cache = $this->dao->_getCache('current');
|
||||
// return $cache->get($contextId);
|
||||
// }
|
||||
|
||||
/** @var JournalDAO $journalDao */
|
||||
$journalDao = DAORegistry::getDAO('JournalDAO');
|
||||
|
||||
$journal = $journalDao->getById($contextId);
|
||||
$issueId = $journal->getData('currentIssueId');
|
||||
|
||||
return $issueId != null ? $this->get($issueId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current issue for the journal.
|
||||
*
|
||||
*/
|
||||
public function updateCurrent(int $contextId, ?Issue $newCurrentIssue = null)
|
||||
{
|
||||
/** @var JournalDAO $journalDao */
|
||||
$journalDao = DAORegistry::getDAO('JournalDAO');
|
||||
$journal = $journalDao->getById($contextId);
|
||||
|
||||
if ($newCurrentIssue) {
|
||||
$journal->setData('currentIssueId', $newCurrentIssue->getId());
|
||||
$this->edit($newCurrentIssue, []);
|
||||
$journalDao->updateObject($journal);
|
||||
} else {
|
||||
$journalDao->removeCurrentIssue($journal->getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get issue by a submission id
|
||||
*
|
||||
*/
|
||||
public function getBySubmissionId(int $submissionId): ?Issue
|
||||
{
|
||||
$issueId = Repo::submission()
|
||||
->get($submissionId)
|
||||
?->getCurrentPublication()
|
||||
?->getData('issueId');
|
||||
return $issueId ? $this->get($issueId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Issue by "best" issue id -- url path if it exists,
|
||||
* falling back on the internal issue ID otherwise.
|
||||
*
|
||||
* @param bool $useCache TODO: Carryover from IssueDAO—was not in use
|
||||
*/
|
||||
public function getByBestId(string $idOrUrlPath, ?int $contextId = null, bool $useCache = false): ?Issue
|
||||
{
|
||||
// Get the issue that matches the requested urlPath (if $idOrUrlPath is one)
|
||||
return ctype_digit((string) $idOrUrlPath)
|
||||
? $this->get((int) $idOrUrlPath, $contextId)
|
||||
: $this->getByUrlPath($idOrUrlPath, $contextId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an issue by its urlPath
|
||||
*
|
||||
*/
|
||||
public function getByUrlPath(string $urlPath, int $contextId): ?Issue
|
||||
{
|
||||
$issueId = $this->dao->getIdByUrlPath($urlPath, $contextId);
|
||||
return $issueId ? $this->get($issueId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the years in which issues have been published
|
||||
*
|
||||
*/
|
||||
public function getYearsIssuesPublished(int $contextId): Collection
|
||||
{
|
||||
return $this->dao->getYearsIssuesPublished($contextId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all Issues for a given context
|
||||
*
|
||||
*/
|
||||
public function deleteByContextId(int $contextId)
|
||||
{
|
||||
$collector = $this->getCollector()->filterByContextIds([$contextId]);
|
||||
$this->deleteMany($collector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DOI for the given issue
|
||||
*
|
||||
* @return DoiException[]
|
||||
*/
|
||||
public function createDoi(Issue $issue): array
|
||||
{
|
||||
/** @var JournalDAO $contextDao */
|
||||
$contextDao = DAORegistry::getDAO('JournalDAO');
|
||||
$context = $contextDao->getById($issue->getData('journalId'));
|
||||
|
||||
$doiCreationFailures = [];
|
||||
|
||||
if ($context->isDoiTypeEnabled(Repo::doi()::TYPE_ISSUE) && empty($issue->getData('doiId'))) {
|
||||
try {
|
||||
$doiId = Repo::doi()->mintIssueDoi($issue, $context);
|
||||
$issue->setData('doiId', $doiId);
|
||||
$this->dao->update($issue);
|
||||
} catch (DoiException $exception) {
|
||||
$doiCreationFailures[] = $exception;
|
||||
}
|
||||
}
|
||||
|
||||
return $doiCreationFailures;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/issue/maps/Schema.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2003-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Schema
|
||||
*
|
||||
* @brief Map sections to the properties defined in the issue schema
|
||||
*/
|
||||
|
||||
namespace APP\issue\maps;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\issue\Issue;
|
||||
use APP\issue\IssueGalleyDAO;
|
||||
use APP\journal\Journal;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\services\PKPSchemaService;
|
||||
use PKP\submission\Genre;
|
||||
use PKP\userGroup\UserGroup;
|
||||
|
||||
class Schema extends \PKP\core\maps\Schema
|
||||
{
|
||||
/** @copydoc \PKP\core\maps\Schema::$collection */
|
||||
public Enumerable $collection;
|
||||
|
||||
/** @copydoc \PKP\core\maps\Schema::$schema */
|
||||
public string $schema = PKPSchemaService::SCHEMA_ISSUE;
|
||||
|
||||
/** @var LazyCollection<int,UserGroup> The user groups for this context. */
|
||||
public LazyCollection $userGroups;
|
||||
|
||||
/** @var Genre[] The genres for this context. */
|
||||
public array $genres;
|
||||
|
||||
/**
|
||||
* Map an Issue
|
||||
*
|
||||
* Includes all properties in the Issue schema
|
||||
*
|
||||
* @param LazyCollection<int,UserGroup> $userGroups The user groups of this content
|
||||
* @param Genre[] $genres The genres of this context
|
||||
*/
|
||||
public function map(Issue $item, Journal $context, LazyCollection $userGroups, array $genres): array
|
||||
{
|
||||
$this->userGroups = $userGroups;
|
||||
$this->genres = $genres;
|
||||
return $this->mapByProperties($this->getProps(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize an Issue
|
||||
*
|
||||
* Includes properties with the apiSummary flag in the Issue schema.
|
||||
*
|
||||
*/
|
||||
public function summarize(Issue $item, Journal $context): array
|
||||
{
|
||||
$this->context = $context;
|
||||
return $this->mapByProperties($this->getSummaryProps(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a collection of Issues
|
||||
*
|
||||
* @see self::map
|
||||
*
|
||||
* @param LazyCollection<int,UserGroup> $userGroups The user groups of this content
|
||||
* @param Genre[] $genres The genres of this context
|
||||
*/
|
||||
public function mapMany(Enumerable $collection, Journal $context, LazyCollection $userGroups, array $genres): Enumerable
|
||||
{
|
||||
$this->collection = $collection;
|
||||
return $collection->map(function ($item) use ($context, $userGroups, $genres) {
|
||||
return $this->map($item, $context, $userGroups, $genres);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize a collection of Issues
|
||||
*
|
||||
* @see self::summarize
|
||||
*/
|
||||
public function summarizeMany(Enumerable $collection, Journal $context): Enumerable
|
||||
{
|
||||
$this->collection = $collection;
|
||||
return $collection->map(function ($item) use ($context) {
|
||||
return $this->summarize($item, $context);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Map schema properties of an Issue to an assoc array
|
||||
*
|
||||
*/
|
||||
private function mapByProperties(array $props, Issue $issue): array
|
||||
{
|
||||
$output = [];
|
||||
|
||||
foreach ($props as $prop) {
|
||||
switch ($prop) {
|
||||
case '_href':
|
||||
$output[$prop] = $this->getApiUrl('issues/' . $issue->getId(), $this->context->getData('urlPath'));
|
||||
break;
|
||||
case 'articles':
|
||||
$data = [];
|
||||
|
||||
$submissions = Repo::submission()
|
||||
->getCollector()
|
||||
->filterByContextIds([$issue->getJournalId()])
|
||||
->filterByIssueIds([$issue->getId()])
|
||||
->getMany();
|
||||
|
||||
foreach ($submissions as $submission) {
|
||||
$data[] = Repo::submission()->getSchemaMap()->summarize($submission, $this->userGroups, $this->genres);
|
||||
}
|
||||
|
||||
$output[$prop] = $data;
|
||||
break;
|
||||
case 'coverImageUrl':
|
||||
$output[$prop] = $issue->getCoverImageUrls();
|
||||
break;
|
||||
case 'doiObject':
|
||||
if ($issue->getData('doiObject')) {
|
||||
$retVal = Repo::doi()->getSchemaMap()->summarize($issue->getData('doiObject'));
|
||||
} else {
|
||||
$retVal = null;
|
||||
}
|
||||
|
||||
$output[$prop] = $retVal;
|
||||
break;
|
||||
case 'galleys':
|
||||
$data = [];
|
||||
/** @var IssueGalleyDAO $issueGalleyDao */
|
||||
$issueGalleyDao = DAORegistry::getDAO('IssueGalleyDAO');
|
||||
$galleys = $issueGalleyDao->getByIssueId($issue->getId());
|
||||
if (!empty($galleys)) {
|
||||
$request = Application::get()->getRequest();
|
||||
foreach ($galleys as $galley) {
|
||||
$data[] = [
|
||||
'fileId' => $galley->getData('fileId'),
|
||||
'label' => $galley->getData('label'),
|
||||
'locale' => $galley->getData('locale'),
|
||||
'pub-id::publisher-id' => $galley->getData('pub-id::publisher-id'),
|
||||
'sequence' => $galley->getData('sequence'),
|
||||
'urlPublished' => $request->getDispatcher()->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
$this->context->getPath(),
|
||||
'issue',
|
||||
'view',
|
||||
[
|
||||
$galley->getIssueId(),
|
||||
$galley->getId()
|
||||
]
|
||||
),
|
||||
'urlRemote' => $galley->getData('urlRemote'),
|
||||
];
|
||||
}
|
||||
}
|
||||
$output[$prop] = $data;
|
||||
break;
|
||||
case 'publishedUrl':
|
||||
$output['publishedUrl'] = $this->request->getDispatcher()->url(
|
||||
$this->request,
|
||||
Application::ROUTE_PAGE,
|
||||
$this->context->getPath(),
|
||||
'issue',
|
||||
'view',
|
||||
$issue->getId()
|
||||
);
|
||||
break;
|
||||
case 'sections':
|
||||
$data = [];
|
||||
$sections = Repo::section()->getByIssueId($issue->getId());
|
||||
if (!empty($sections)) {
|
||||
$seq = 1;
|
||||
foreach ($sections as $section) {
|
||||
$sectionProperties = Repo::section()->getSchemaMap()->summarize($section);
|
||||
$sectionProperties['seq'] = $seq;
|
||||
$seq++;
|
||||
$data[] = $sectionProperties;
|
||||
}
|
||||
}
|
||||
$output[$prop] = $data;
|
||||
break;
|
||||
case 'identification':
|
||||
$output[$prop] = $issue->getIssueIdentification();
|
||||
break;
|
||||
|
||||
default:
|
||||
$output[$prop] = $issue->getData($prop);
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an issue with only the issue identification for the stats list
|
||||
*/
|
||||
public function mapToStats(Issue $issue): array
|
||||
{
|
||||
$props = $this->mapByProperties([
|
||||
'_href',
|
||||
'id',
|
||||
'identification',
|
||||
'publishedUrl'
|
||||
], $issue);
|
||||
return $props;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user