first commit
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/section/Collector.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 Collector
|
||||
*
|
||||
* @brief A helper class to configure a Query Builder to get a collection of sections
|
||||
*/
|
||||
|
||||
namespace PKP\section;
|
||||
|
||||
use APP\section\DAO;
|
||||
use APP\section\Section;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\core\interfaces\CollectorInterface;
|
||||
|
||||
/**
|
||||
* @template T of Section
|
||||
*/
|
||||
class Collector implements CollectorInterface
|
||||
{
|
||||
public DAO $dao;
|
||||
public ?array $contextIds = null;
|
||||
public ?array $titles = null;
|
||||
public ?array $abbrevs = null;
|
||||
public bool $editorOnly = false;
|
||||
public bool $excludeInactive = false;
|
||||
public bool $withPublished = false;
|
||||
public ?int $count = null;
|
||||
public ?int $offset = null;
|
||||
|
||||
public function __construct(DAO $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
public function getCount(): int
|
||||
{
|
||||
return $this->dao->getCount($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int,int>
|
||||
*/
|
||||
public function getIds(): Collection
|
||||
{
|
||||
return $this->dao->getIds($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc DAO::getMany()
|
||||
* @return LazyCollection<int,T>
|
||||
*/
|
||||
public function getMany(): LazyCollection
|
||||
{
|
||||
return $this->dao->getMany($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter sections by one or more contexts
|
||||
*/
|
||||
public function filterByContextIds(?array $contextIds): self
|
||||
{
|
||||
$this->contextIds = $contextIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter sections by one or more titles
|
||||
*/
|
||||
public function filterByTitles(?array $titles): self
|
||||
{
|
||||
$this->titles = $titles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter sections by one or more abbreviations
|
||||
*/
|
||||
public function filterByAbbrevs(?array $abbrevs): self
|
||||
{
|
||||
$this->abbrevs = $abbrevs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only include sections that all users can submit to
|
||||
*/
|
||||
public function excludeEditorOnly(bool $editorOnly = true): self
|
||||
{
|
||||
$this->editorOnly = $editorOnly;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only include active sections
|
||||
*/
|
||||
public function excludeInactive(bool $excludeInactive = true): self
|
||||
{
|
||||
$this->excludeInactive = $excludeInactive;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only include sections that contain published items
|
||||
*/
|
||||
public function withPublished(bool $withPublished = true): self
|
||||
{
|
||||
$this->withPublished = $withPublished;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the number of objects retrieved
|
||||
*/
|
||||
public function limit(?int $count): self
|
||||
{
|
||||
$this->count = $count;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset the number of objects retrieved, for example to
|
||||
* retrieve the second page of contents
|
||||
*/
|
||||
public function offset(?int $offset): self
|
||||
{
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQueryBuilder(): Builder
|
||||
{
|
||||
return DB::table($this->dao->table, 's')->select('s.*')
|
||||
->when(!is_null($this->contextIds), function (Builder $qb) {
|
||||
$qb->whereIn('s.' . $this->dao->getParentColumn(), $this->contextIds);
|
||||
})
|
||||
->when(!is_null($this->titles) || !is_null($this->abbrevs), function (Builder $qb) {
|
||||
$qb->join($this->dao->settingsTable . ' AS ss', 'ss.' . $this->dao->primaryKeyColumn, '=', 's.' . $this->dao->primaryKeyColumn)
|
||||
->when(!is_null($this->titles), function (Builder $qb) {
|
||||
$qb->where('ss.setting_name', 'title')
|
||||
->whereIn('ss.setting_value', $this->titles);
|
||||
})
|
||||
->when(!is_null($this->abbrevs), function (Builder $qb) {
|
||||
$qb->where('setting_name', 'abbrev')
|
||||
->whereIn('setting_value', $this->abbrevs);
|
||||
});
|
||||
})
|
||||
->when($this->editorOnly, function (Builder $qb) {
|
||||
$qb->where('s.editor_restricted', 0)
|
||||
->where('s.is_inactive', 0);
|
||||
})
|
||||
->when($this->excludeInactive, function (Builder $qb) {
|
||||
$qb->where('s.is_inactive', 0);
|
||||
})
|
||||
->when($this->withPublished, function (Builder $qb) {
|
||||
$qb->whereExists(function (Builder $qb) {
|
||||
$qb->select('p.*')
|
||||
->from('publications AS p')
|
||||
->whereNotNull('p.' . $this->dao->primaryKeyColumn)
|
||||
->whereColumn('p.' . $this->dao->primaryKeyColumn, '=', 's.' . $this->dao->primaryKeyColumn)
|
||||
->where('p.status', '=', Submission::STATUS_PUBLISHED);
|
||||
});
|
||||
})
|
||||
->orderBy('s.seq')
|
||||
->when(!is_null($this->count), function (Builder $qb) {
|
||||
$qb->limit($this->count);
|
||||
})
|
||||
->when(!is_null($this->offset), function (Builder $qb) {
|
||||
$qb->offset($this->offset);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/section/DAO.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 DAO
|
||||
*
|
||||
* @ingroup section
|
||||
*
|
||||
* @see Section
|
||||
*
|
||||
* @brief Operations for retrieving and modifying Section objects.
|
||||
*/
|
||||
|
||||
namespace PKP\section;
|
||||
|
||||
use APP\section\Section;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\core\EntityDAO;
|
||||
use PKP\core\traits\EntityWithParent;
|
||||
|
||||
/**
|
||||
* @template T of Section
|
||||
* @extends EntityDAO<T>
|
||||
*/
|
||||
abstract class DAO extends EntityDAO
|
||||
{
|
||||
use EntityWithParent;
|
||||
|
||||
/**
|
||||
* Get the parent object ID column name
|
||||
*/
|
||||
abstract public function getParentColumn(): string;
|
||||
|
||||
/**
|
||||
* Instantiate a new DataObject
|
||||
*/
|
||||
public function newDataObject(): Section
|
||||
{
|
||||
return App::make(Section::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of sections matching the configured query
|
||||
*/
|
||||
public function getCount(Collector $query): int
|
||||
{
|
||||
return $query
|
||||
->getQueryBuilder()
|
||||
->select($this->primaryKeyColumn)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of sections ids matching the configured query
|
||||
*
|
||||
* @return Collection<int,int>
|
||||
*/
|
||||
public function getIds(Collector $query): Collection
|
||||
{
|
||||
return $query
|
||||
->getQueryBuilder()
|
||||
->select($this->primaryKeyColumn)
|
||||
->pluck($this->primaryKeyColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of sections matching the configured query
|
||||
* @return LazyCollection<int,T>
|
||||
*/
|
||||
public function getMany(Collector $query): LazyCollection
|
||||
{
|
||||
$rows = $query
|
||||
->getQueryBuilder()
|
||||
->get();
|
||||
|
||||
return LazyCollection::make(function () use ($rows) {
|
||||
foreach ($rows as $row) {
|
||||
yield $row->{$this->primaryKeyColumn} => $this->fromRow($row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function insert(Section $section): int
|
||||
{
|
||||
return parent::_insert($section);
|
||||
}
|
||||
|
||||
public function update(Section $section)
|
||||
{
|
||||
parent::_update($section);
|
||||
}
|
||||
|
||||
public function delete(Section $section)
|
||||
{
|
||||
parent::_delete($section);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/section/Section.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 PKPSection
|
||||
*
|
||||
* @ingroup section
|
||||
*
|
||||
* @see DAO
|
||||
*
|
||||
* @brief Basic class describing a section.
|
||||
*/
|
||||
|
||||
namespace PKP\section;
|
||||
|
||||
use PKP\security\Role;
|
||||
|
||||
class PKPSection extends \PKP\core\DataObject
|
||||
{
|
||||
/**
|
||||
* What user roles are allowed to submit to sections
|
||||
* that have restricted submissions to editors
|
||||
*
|
||||
* @return int[] One or more ROLE_ID_* constants
|
||||
*/
|
||||
public static function getEditorRestrictedRoles(): array
|
||||
{
|
||||
return [
|
||||
Role::ROLE_ID_SITE_ADMIN,
|
||||
Role::ROLE_ID_MANAGER,
|
||||
Role::ROLE_ID_SUB_EDITOR
|
||||
];
|
||||
}
|
||||
|
||||
public function getContextId(): int
|
||||
{
|
||||
return $this->getData('contextId');
|
||||
}
|
||||
|
||||
public function setContextId(int $contextId): void
|
||||
{
|
||||
$this->setData('contextId', $contextId);
|
||||
}
|
||||
|
||||
public function getSequence(): float
|
||||
{
|
||||
return $this->getData('sequence');
|
||||
}
|
||||
|
||||
public function setSequence(float $sequence): void
|
||||
{
|
||||
$this->setData('sequence', $sequence);
|
||||
}
|
||||
|
||||
/* Because title is required, there must be at least one title */
|
||||
public function getLocalizedTitle(): string
|
||||
{
|
||||
return $this->getLocalizedData('title');
|
||||
}
|
||||
|
||||
public function getTitle(?string $locale): string|array|null
|
||||
{
|
||||
return $this->getData('title', $locale);
|
||||
}
|
||||
|
||||
public function setTitle(string|array $title, string $locale = null): void
|
||||
{
|
||||
$this->setData('title', $title, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return boolean indicating whether or not submissions are restricted to [sub]Editors.
|
||||
*/
|
||||
public function getEditorRestricted(): bool
|
||||
{
|
||||
return $this->getData('editorRestricted');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not submissions are restricted to [sub]Editors.
|
||||
*/
|
||||
public function setEditorRestricted(bool $editorRestricted): void
|
||||
{
|
||||
$this->setData('editorRestricted', $editorRestricted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return boolean indicating if section is not active.
|
||||
*/
|
||||
public function getIsInactive(): bool
|
||||
{
|
||||
return $this->getData('isInactive');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if section should be inactivated.
|
||||
*/
|
||||
public function setIsInactive(bool $isInactive): void
|
||||
{
|
||||
$this->setData('isInactive', $isInactive);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\section\PKPSection', '\PKPSection');
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/section/Repository.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 Repository
|
||||
*
|
||||
* @brief A repository to find and manage sections.
|
||||
*/
|
||||
|
||||
namespace PKP\section;
|
||||
|
||||
use APP\core\Request;
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use APP\section\DAO;
|
||||
use APP\section\Section;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use PKP\context\Context;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\services\PKPSchemaService;
|
||||
use PKP\validation\ValidatorFactory;
|
||||
|
||||
class Repository
|
||||
{
|
||||
public DAO $dao;
|
||||
public string $schemaMap = maps\Schema::class;
|
||||
protected Request $request;
|
||||
protected PKPSchemaService $schemaService;
|
||||
|
||||
public function __construct(DAO $dao, Request $request, PKPSchemaService $schemaService)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
$this->request = $request;
|
||||
$this->schemaService = $schemaService;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::newDataObject() */
|
||||
public function newDataObject(array $params = []): Section
|
||||
{
|
||||
$object = $this->dao->newDataObject();
|
||||
if (!empty($params)) {
|
||||
$object->setAllData($params);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::exists() */
|
||||
public function exists(int $id, int $contextId = null): bool
|
||||
{
|
||||
return $this->dao->exists($id, $contextId);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::get() */
|
||||
public function get(int $id, int $contextId = null): ?Section
|
||||
{
|
||||
return $this->dao->get($id, $contextId);
|
||||
}
|
||||
|
||||
public function getCollector(): Collector
|
||||
{
|
||||
return App::make(Collector::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the map class for mapping
|
||||
* sections to their schema
|
||||
*/
|
||||
public function getSchemaMap(): maps\Schema
|
||||
{
|
||||
return app('maps')->withExtensions($this->schemaMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate properties for a section
|
||||
*
|
||||
* Perform validation checks on data used to add or edit a section.
|
||||
*
|
||||
* @param Section|null $object Section being edited. Pass `null` if creating a new section
|
||||
* @param array $props A key/value array with the new data to validate
|
||||
*
|
||||
* @return array A key/value array with validation errors. Empty if no errors
|
||||
*/
|
||||
public function validate(?Section $object, array $props, Context $context): array
|
||||
{
|
||||
$errors = [];
|
||||
$allowedLocales = $context->getSupportedSubmissionLocales();
|
||||
$primaryLocale = $context->getPrimaryLocale();
|
||||
|
||||
$validator = ValidatorFactory::make(
|
||||
$props,
|
||||
$this->schemaService->getValidationRules($this->dao->schema, $allowedLocales)
|
||||
);
|
||||
|
||||
// Check required fields if we're adding a section
|
||||
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);
|
||||
|
||||
// The contextId must match an existing context
|
||||
$validator->after(function ($validator) use ($props) {
|
||||
if (isset($props['contextId']) && !$validator->errors()->get('contextId')) {
|
||||
$sectionContext = Services::get('context')->get($props['contextId']);
|
||||
if (!$sectionContext) {
|
||||
$validator->errors()->add('contextId', __('manager.sections.noContext'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $this->schemaService->formatValidationErrors($validator->errors());
|
||||
}
|
||||
|
||||
Hook::call('Section::validate', [&$errors, $object, $props, $allowedLocales, $primaryLocale]);
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::insert() */
|
||||
public function add(Section $section): int
|
||||
{
|
||||
$id = $this->dao->insert($section);
|
||||
Hook::call('Section::add', [$section]);
|
||||
return $id;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::update() */
|
||||
public function edit(Section $section, array $params): void
|
||||
{
|
||||
$newSection = clone $section;
|
||||
$newSection->setAllData(array_merge($newSection->_data, $params));
|
||||
Hook::call('Section::edit', [$newSection, $section, $params]);
|
||||
$this->dao->update($newSection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all sections for a given context
|
||||
*/
|
||||
public function deleteByContextId(int $contextId)
|
||||
{
|
||||
$collector = $this->getCollector()->filterByContextIds([$contextId]);
|
||||
$this->deleteMany($collector);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::delete() */
|
||||
public function delete(Section $section): void
|
||||
{
|
||||
Hook::call('Section::delete::before', [$section]);
|
||||
$this->dao->delete($section);
|
||||
Hook::call('Section::delete', [$section]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a collection of sections
|
||||
*/
|
||||
public function deleteMany(Collector $collector): void
|
||||
{
|
||||
foreach ($collector->getMany() as $section) {
|
||||
$this->delete($section);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the section has any submissions assigned to it.
|
||||
*/
|
||||
public function isEmpty(int $sectionId, int $contextId): bool
|
||||
{
|
||||
return Repo::submission()
|
||||
->getCollector()
|
||||
->filterByContextIds([$contextId])
|
||||
->filterBySectionIds([$sectionId])
|
||||
->getCount() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequentially renumber sections in their sequence order.
|
||||
*/
|
||||
public function resequence(int $contextId): void
|
||||
{
|
||||
$sections = $this->getCollector()->filterByContextIds([$contextId])->getMany();
|
||||
$seq = 0;
|
||||
foreach ($sections as $section) {
|
||||
$section->setSequence($seq);
|
||||
$this->dao->update($section);
|
||||
$seq++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of sections containing id, title and group (isInactive) key
|
||||
*/
|
||||
public function getSectionList(int $contextId, bool $excludeInactive = false): array
|
||||
{
|
||||
return $this->getCollector()
|
||||
->filterByContextIds([$contextId])
|
||||
->excludeInactive($excludeInactive)
|
||||
->getMany()
|
||||
->map(fn (Section $section) => [
|
||||
'id' => $section->getId(),
|
||||
'title' => $section->getLocalizedTitle(),
|
||||
'group' => $section->getIsInactive()
|
||||
])
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/section/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 section schema
|
||||
*/
|
||||
|
||||
namespace PKP\section\maps;
|
||||
|
||||
use APP\section\Section;
|
||||
use Illuminate\Support\Enumerable;
|
||||
use PKP\services\PKPSchemaService;
|
||||
|
||||
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_SECTION;
|
||||
|
||||
/**
|
||||
* Map a section
|
||||
*
|
||||
* Includes all properties in the section schema.
|
||||
*/
|
||||
public function map(Section $item): array
|
||||
{
|
||||
return $this->mapByProperties($this->getProps(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize an section
|
||||
*
|
||||
* Includes properties with the apiSummary flag in the section schema.
|
||||
*/
|
||||
public function summarize(Section $item): array
|
||||
{
|
||||
return $this->mapByProperties($this->getSummaryProps(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a collection of Sections
|
||||
*
|
||||
* @see self::map
|
||||
*/
|
||||
public function mapMany(Enumerable $collection): Enumerable
|
||||
{
|
||||
$this->collection = $collection;
|
||||
return $collection->map(function ($item) {
|
||||
return $this->map($item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize a collection of Sections
|
||||
*
|
||||
* @see self::summarize
|
||||
*/
|
||||
public function summarizeMany(Enumerable $collection): Enumerable
|
||||
{
|
||||
$this->collection = $collection;
|
||||
return $collection->map(function ($item) {
|
||||
return $this->summarize($item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Map schema properties of an Section to an assoc array
|
||||
*/
|
||||
protected function mapByProperties(array $props, Section $item): array
|
||||
{
|
||||
$output = [];
|
||||
foreach ($props as $prop) {
|
||||
switch ($prop) {
|
||||
case '_href':
|
||||
$output[$prop] = $this->getApiUrl('sections/' . $item->getId());
|
||||
break;
|
||||
default:
|
||||
$output[$prop] = $item->getData($prop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$output = $this->schemaService->addMissingMultilingualValues($this->schema, $output, $this->context->getSupportedFormLocales());
|
||||
ksort($output);
|
||||
return $this->withExtensions($output, $item);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user