first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,346 @@
<?php
/**
* @defgroup announcement Announcement
* Implements announcements that can be presented to website visitors.
*/
/**
* @file classes/announcement/Announcement.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 Announcement
*
* @ingroup announcement
*
* @see DAO
*
* @brief Basic class describing a announcement.
*/
namespace PKP\announcement;
use APP\core\Application;
use APP\facades\Repo;
use APP\file\PublicFileManager;
use PKP\db\DAORegistry;
class Announcement extends \PKP\core\DataObject
{
//
// Get/set methods
//
/**
* Get assoc ID for this announcement.
*
* @return int
*/
public function getAssocId()
{
return $this->getData('assocId');
}
/**
* Set assoc ID for this announcement.
*
* @param int $assocId
*/
public function setAssocId($assocId)
{
$this->setData('assocId', $assocId);
}
/**
* Get assoc type for this announcement.
*
* @return int
*/
public function getAssocType()
{
return $this->getData('assocType');
}
/**
* Set assoc type for this announcement.
*
* @param int $assocType
*/
public function setAssocType($assocType)
{
$this->setData('assocType', $assocType);
}
/**
* Get the announcement type of the announcement.
*
* @return int
*/
public function getTypeId()
{
return $this->getData('typeId');
}
/**
* Set the announcement type of the announcement.
*
* @param int $typeId
*/
public function setTypeId($typeId)
{
$this->setData('typeId', $typeId);
}
/**
* Get the announcement type name of the announcement.
*
* @return string|null
*/
public function getAnnouncementTypeName()
{
$announcementTypeDao = DAORegistry::getDAO('AnnouncementTypeDAO'); /** @var AnnouncementTypeDAO $announcementTypeDao */
$announcementType = $announcementTypeDao->getById($this->getData('typeId'));
return $announcementType ? $announcementType->getLocalizedTypeName() : null;
}
/**
* Get localized announcement title
*
* @return string
*/
public function getLocalizedTitle()
{
return $this->getLocalizedData('title');
}
/**
* Get full localized announcement title including type name
*
* @return string
*/
public function getLocalizedTitleFull()
{
$typeName = $this->getAnnouncementTypeName();
if (!empty($typeName)) {
return $typeName . ': ' . $this->getLocalizedTitle();
} else {
return $this->getLocalizedTitle();
}
}
/**
* Get announcement title.
*
* @param string $locale
*
* @return string
*/
public function getTitle($locale)
{
return $this->getData('title', $locale);
}
/**
* Set announcement title.
*
* @param string $title
* @param string $locale
*/
public function setTitle($title, $locale)
{
$this->setData('title', $title, $locale);
}
/**
* Get localized short description
*
* @return string
*/
public function getLocalizedDescriptionShort()
{
return $this->getLocalizedData('descriptionShort');
}
/**
* Get announcement brief description.
*
* @param string $locale
*
* @return string
*/
public function getDescriptionShort($locale)
{
return $this->getData('descriptionShort', $locale);
}
/**
* Set announcement brief description.
*
* @param string $descriptionShort
* @param string $locale
*/
public function setDescriptionShort($descriptionShort, $locale)
{
$this->setData('descriptionShort', $descriptionShort, $locale);
}
/**
* Get localized full description
*
* @return string
*/
public function getLocalizedDescription()
{
return $this->getLocalizedData('description');
}
/**
* Get announcement description.
*
* @param string $locale
*
* @return string
*/
public function getDescription($locale)
{
return $this->getData('description', $locale);
}
/**
* Set announcement description.
*
* @param string $description
* @param string $locale
*/
public function setDescription($description, $locale)
{
$this->setData('description', $description, $locale);
}
/**
* Get announcement expiration date.
*
* @return string (YYYY-MM-DD)
*/
public function getDateExpire()
{
return $this->getData('dateExpire');
}
/**
* Set announcement expiration date.
*
* @param string $dateExpire (YYYY-MM-DD)
*/
public function setDateExpire($dateExpire)
{
$this->setData('dateExpire', $dateExpire);
}
/**
* Get announcement posted date.
*
* @return string (YYYY-MM-DD)
*/
public function getDatePosted()
{
return date('Y-m-d', strtotime($this->getData('datePosted')));
}
/**
* Get announcement posted datetime.
*
* @return string (YYYY-MM-DD HH:MM:SS)
*/
public function getDatetimePosted()
{
return $this->getData('datePosted');
}
/**
* Set announcement posted date.
*
* @param string $datePosted (YYYY-MM-DD)
*/
public function setDatePosted($datePosted)
{
$this->setData('datePosted', $datePosted);
}
/**
* Set announcement posted datetime.
*
* @param string $datetimePosted (YYYY-MM-DD HH:MM:SS)
*/
public function setDatetimePosted($datetimePosted)
{
$this->setData('datePosted', $datetimePosted);
}
/**
* Get the featured image data
*/
public function getImage(): ?array
{
return $this->getData('image');
}
/**
* Set the featured image data
*/
public function setImage(array $image): void
{
$this->setData('image', $image);
}
/**
* Get the full URL to the image
*
* @param bool $withTimestamp Pass true to include a query argument with a timestamp
* of the date the image was uploaded in order to workaround cache bugs in browsers
*/
public function getImageUrl(bool $withTimestamp = true): string
{
$image = $this->getImage();
if (!$image) {
return '';
}
$filename = $image['uploadName'];
if ($withTimestamp) {
$filename .= '?'. strtotime($image['dateUploaded']);
}
$publicFileManager = new PublicFileManager();
return join('/', [
Application::get()->getRequest()->getBaseUrl(),
$this->getAssocId()
? $publicFileManager->getContextFilesPath((int) $this->getAssocId())
: $publicFileManager->getSiteFilesPath(),
Repo::announcement()->getImageSubdirectory(),
$filename
]);
}
/**
* Get the alt text for the image
*/
public function getImageAltText(): string
{
$image = $this->getImage();
if (!$image || !$image['altText']) {
return '';
}
return $image['altText'];
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\announcement\Announcement', '\Announcement');
}
@@ -0,0 +1,82 @@
<?php
/**
* @file classes/announcement/AnnouncementType.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 AnnouncementType
*
* @ingroup announcement
*
* @see AnnouncementTypeDAO, AnnouncementTypeForm
*
* @brief Basic class describing an announcement type.
*/
namespace PKP\announcement;
class AnnouncementType extends \PKP\core\DataObject
{
//
// Get/set methods
//
/**
* Get context ID for this announcement.
*
* @return int
*/
public function getContextId()
{
return $this->getData('contextId');
}
/**
* Set context ID for this announcement.
*
* @param int $contextId
*/
public function setContextId($contextId)
{
$this->setData('contextId', $contextId);
}
/**
* Get the type of the announcement type.
*
* @return string
*/
public function getLocalizedTypeName()
{
return $this->getLocalizedData('name');
}
/**
* Get the type of the announcement type.
*
* @param string $locale
*
* @return string
*/
public function getName($locale)
{
return $this->getData('name', $locale);
}
/**
* Set the type of the announcement type.
*
* @param string $name
* @param string $locale
*/
public function setName($name, $locale)
{
$this->setData('name', $name, $locale);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\announcement\AnnouncementType', '\AnnouncementType');
}
@@ -0,0 +1,210 @@
<?php
/**
* @file classes/announcement/AnnouncementTypeDAO.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 AnnouncementTypeDAO
*
* @ingroup announcement
*
* @see AnnouncementType
*
* @brief Operations for retrieving and modifying AnnouncementType objects.
*/
namespace PKP\announcement;
use APP\facades\Repo;
class AnnouncementTypeDAO extends \PKP\db\DAO
{
/**
* Generate a new data object.
*
* @return AnnouncementType
*/
public function newDataObject()
{
return new AnnouncementType();
}
/**
* Retrieve an announcement type by announcement type ID.
*
* @param ?int $typeId Announcement type ID
* @param ?int $contextId Optional context ID
*
* @return AnnouncementType
*/
public function getById($typeId, $contextId = null)
{
$params = [(int) $typeId];
if ($contextId !== null) {
$params[] = (int) $contextId;
}
$result = $this->retrieve(
'SELECT * FROM announcement_types WHERE type_id = ?' .
($contextId !== null ? ' AND context_id = ?' : ''),
$params
);
$row = $result->current();
return $row ? $this->_fromRow((array) $row) : null;
}
/**
* Get the locale field names.
*
* @return array
*/
public function getLocaleFieldNames()
{
return ['name'];
}
/**
* Internal function to return an AnnouncementType object from a row.
*
* @param array $row
*
* @return AnnouncementType
*/
public function _fromRow($row)
{
$announcementType = $this->newDataObject();
$announcementType->setId($row['type_id']);
$announcementType->setData('contextId', $row['context_id']);
$this->getDataObjectSettings('announcement_type_settings', 'type_id', $row['type_id'], $announcementType);
return $announcementType;
}
/**
* Update the localized settings for this object
*
* @param AnnouncementType $announcementType
*/
public function updateLocaleFields($announcementType)
{
$this->updateDataObjectSettings(
'announcement_type_settings',
$announcementType,
['type_id' => (int) $announcementType->getId()]
);
}
/**
* Insert a new AnnouncementType.
*
* @param AnnouncementType $announcementType
*
* @return int
*/
public function insertObject($announcementType)
{
$this->update(
sprintf('INSERT INTO announcement_types
(context_id)
VALUES
(?)'),
[
$announcementType->getContextId()
? (int) $announcementType->getContextId()
: null
]
);
$announcementType->setId($this->getInsertId());
$this->updateLocaleFields($announcementType);
return $announcementType->getId();
}
/**
* Update an existing announcement type.
*
* @param AnnouncementType $announcementType
*
* @return bool
*/
public function updateObject($announcementType)
{
$returner = $this->update(
'UPDATE announcement_types
SET context_id = ?
WHERE type_id = ?',
[
$announcementType->getContextId(),
(int) $announcementType->getId()
]
);
$this->updateLocaleFields($announcementType);
return $returner;
}
/**
* Delete an announcement type. Note that all announcements with this type are also
* deleted.
*
* @param AnnouncementType $announcementType
*/
public function deleteObject($announcementType)
{
return $this->deleteById($announcementType->getId());
}
/**
* Delete an announcement type by announcement type ID. Note that all announcements with
* this type ID are also deleted.
*
* @param int $typeId
*/
public function deleteById($typeId)
{
$this->update('DELETE FROM announcement_type_settings WHERE type_id = ?', [(int) $typeId]);
$this->update('DELETE FROM announcement_types WHERE type_id = ?', [(int) $typeId]);
$collector = Repo::announcement()->getCollector()->filterByTypeIds([(int) $typeId]);
Repo::announcement()->deleteMany($collector);
}
/**
* Delete announcement types by context ID.
*
* @param int $contextId
*/
public function deleteByContextId($contextId)
{
foreach ($this->getByContextId($contextId) as $type) {
$this->deleteObject($type);
}
}
/**
* Retrieve an array of announcement types matching a particular context ID.
*
* @return \Generator<int,AnnouncementType> Matching AnnouncementTypes
*/
public function getByContextId(?int $contextId)
{
if ($contextId) {
$result = $this->retrieve(
'SELECT * FROM announcement_types WHERE context_id = ? ORDER BY type_id',
[$contextId]
);
} else {
$result = $this->retrieve(
'SELECT * FROM announcement_types WHERE context_id IS NULL ORDER BY type_id'
);
}
foreach ($result as $row) {
yield $row->type_id => $this->_fromRow((array) $row);
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\announcement\AnnouncementTypeDAO', '\AnnouncementTypeDAO');
}
+240
View File
@@ -0,0 +1,240 @@
<?php
/**
* @file classes/announcement/Collector.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 Collector
*
* @brief A helper class to configure a Query Builder to get a collection of announcements
*/
namespace PKP\announcement;
use APP\core\Application;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\LazyCollection;
use PKP\core\Core;
use PKP\core\interfaces\CollectorInterface;
use PKP\plugins\Hook;
/**
* @template T of Announcement
*/
class Collector implements CollectorInterface
{
public const ORDERBY_DATE_POSTED = 'date_posted';
public const ORDERBY_DATE_EXPIRE = 'date_expire';
public const ORDER_DIR_ASC = 'ASC';
public const ORDER_DIR_DESC = 'DESC';
public const SITE_ONLY = 'site';
public const SITE_AND_CONTEXTS = 'all';
public DAO $dao;
public ?array $contextIds = null;
public ?string $isActive = null;
public ?string $searchPhrase = null;
public ?array $typeIds = null;
public ?string $includeSite = null;
public ?int $count = null;
public ?int $offset = null;
public string $orderBy = self::ORDERBY_DATE_POSTED;
public string $orderDirection = self::ORDER_DIR_DESC;
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,T>
*/
public function getMany(): LazyCollection
{
return $this->dao->getMany($this);
}
/**
* Filter announcements by one or more contexts
*/
public function filterByContextIds(?array $contextIds): self
{
$this->contextIds = $contextIds;
return $this;
}
/**
* Filter announcements by those that have not expired
*
* @param string $date Optionally filter announcements by those
* not expired until $date (YYYY-MM-DD).
*/
public function filterByActive(string $date = ''): self
{
$this->isActive = empty($date)
? Core::getCurrentDate()
: $date;
return $this;
}
/**
* Filter announcements by one or more announcement types
*/
public function filterByTypeIds(array $typeIds): self
{
$this->typeIds = $typeIds;
return $this;
}
/**
* Include site-level announcements in the results
*/
public function withSiteAnnouncements(?string $includeMethod = self::SITE_AND_CONTEXTS): self
{
$this->includeSite = $includeMethod;
return $this;
}
/**
* Filter announcements by those matching a search query
*/
public function searchPhrase(?string $phrase): self
{
$this->searchPhrase = $phrase;
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;
}
/**
* Order the results
*
* Results are ordered by the date posted by default.
*
* @param string $sorter One of the self::ORDERBY_ constants
* @param string $direction One of the self::ORDER_DIR_ constants
*/
public function orderBy(?string $sorter, string $direction = self::ORDER_DIR_DESC): self
{
$this->orderBy = $sorter;
$this->orderDirection = $direction;
return $this;
}
/**
* @copydoc CollectorInterface::getQueryBuilder()
*/
public function getQueryBuilder(): Builder
{
$qb = DB::table($this->dao->table . ' as a')
->select(['a.*']);
if (isset($this->contextIds) && $this->includeSite !== self::SITE_ONLY) {
$qb->where('a.assoc_type', Application::get()->getContextAssocType());
$qb->whereIn('a.assoc_id', $this->contextIds);
if ($this->includeSite === self::SITE_AND_CONTEXTS) {
$qb->orWhereNull('a.assoc_id');
}
} elseif ($this->includeSite === self::SITE_ONLY) {
$qb->where('a.assoc_type', Application::get()->getContextAssocType());
$qb->whereNull('a.assoc_id');
}
if (isset($this->typeIds)) {
$qb->whereIn('a.type_id', $this->typeIds);
}
$qb->when($this->isActive, fn ($qb) => $qb->where(function ($qb) {
$qb->where('a.date_expire', '>', $this->isActive)
->orWhereNull('a.date_expire');
}));
if ($this->searchPhrase !== null) {
$words = explode(' ', $this->searchPhrase);
if (count($words)) {
$qb->whereIn('a.announcement_id', function ($query) use ($words) {
$query->select('announcement_id')->from($this->dao->settingsTable);
foreach ($words as $word) {
$word = strtolower(addcslashes($word, '%_'));
$query->where(function ($query) use ($word) {
$query->where(function ($query) use ($word) {
$query->where('setting_name', 'title');
$query->where(DB::raw('lower(setting_value)'), 'LIKE', "%{$word}%");
})
->orWhere(function ($query) use ($word) {
$query->where('setting_name', 'descriptionShort');
$query->where(DB::raw('lower(setting_value)'), 'LIKE', "%{$word}%");
})
->orWhere(function ($query) use ($word) {
$query->where('setting_name', 'description');
$query->where(DB::raw('lower(setting_value)'), 'LIKE', "%{$word}%");
});
});
}
});
}
}
$qb->orderByDesc('a.date_posted');
if (isset($this->count)) {
$qb->limit($this->count);
}
if (isset($this->offset)) {
$qb->offset($this->offset);
}
if (isset($this->orderBy)) {
$qb->orderBy('a.' . $this->orderBy, $this->orderDirection);
// Add a secondary sort by id to catch cases where two
// announcements share the same date
if (in_array($this->orderBy, [SELF::ORDERBY_DATE_EXPIRE, SELF::ORDERBY_DATE_POSTED])) {
$qb->orderBy('a.announcement_id', $this->orderDirection);
}
}
Hook::call('Announcement::Collector', [&$qb, $this]);
return $qb;
}
}
+142
View File
@@ -0,0 +1,142 @@
<?php
/**
* @file classes/announcement/DAO.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class DAO
*
* @brief Read and write announcements to the database.
*/
namespace PKP\announcement;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\LazyCollection;
use PKP\core\EntityDAO;
/**
* @template T of Announcement
* @extends EntityDAO<T>
*/
class DAO extends EntityDAO
{
/** @copydoc EntityDAO::$schema */
public $schema = \PKP\services\PKPSchemaService::SCHEMA_ANNOUNCEMENT;
/** @copydoc EntityDAO::$table */
public $table = 'announcements';
/** @copydoc EntityDAO::$settingsTable */
public $settingsTable = 'announcement_settings';
/** @copydoc EntityDAO::$primaryKeyColumn */
public $primaryKeyColumn = 'announcement_id';
/** @copydoc EntityDAO::$primaryTableColumns */
public $primaryTableColumns = [
'id' => 'announcement_id',
'assocId' => 'assoc_id',
'assocType' => 'assoc_type',
'typeId' => 'type_id',
'dateExpire' => 'date_expire',
'datePosted' => 'date_posted',
];
/**
* Instantiate a new DataObject
*/
public function newDataObject(): Announcement
{
return app(Announcement::class);
}
/**
* Check if an announcement exists
*/
public function exists(int $id): bool
{
return DB::table($this->table)
->where($this->primaryKeyColumn, '=', $id)
->exists();
}
/**
* Get an announcement
*/
public function get(int $id): ?Announcement
{
$row = DB::table($this->table)
->where($this->primaryKeyColumn, $id)
->first();
return $row ? $this->fromRow($row) : null;
}
/**
* Get the number of announcements matching the configured query
*/
public function getCount(Collector $query): int
{
return $query
->getQueryBuilder()
->get('a.' . $this->primaryKeyColumn)
->count();
}
/**
* Get a list of ids matching the configured query
*
* @return Collection<int,int>
*/
public function getIds(Collector $query): Collection
{
return $query
->getQueryBuilder()
->pluck('a.' . $this->primaryKeyColumn);
}
/**
* Get a collection of announcements 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->announcement_id => $this->fromRow($row);
}
});
}
/**
* @copydoc EntityDAO::insert()
*/
public function insert(Announcement $announcement): int
{
return parent::_insert($announcement);
}
/**
* @copydoc EntityDAO::update()
*/
public function update(Announcement $announcement)
{
parent::_update($announcement);
}
/**
* @copydoc EntityDAO::delete()
*/
public function delete(Announcement $announcement)
{
parent::_delete($announcement);
}
}
+352
View File
@@ -0,0 +1,352 @@
<?php
/**
* @file classes/announcement/Repository.php
*
* Copyright (c) 2014-2020 Simon Fraser University
* Copyright (c) 2000-2020 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class Repository
*
* @brief A repository to find and manage announcements.
*/
namespace PKP\announcement;
use APP\core\Application;
use APP\core\Request;
use APP\file\PublicFileManager;
use PKP\context\Context;
use PKP\core\Core;
use PKP\core\exceptions\StoreTemporaryFileException;
use PKP\core\PKPString;
use PKP\file\FileManager;
use PKP\file\TemporaryFile;
use PKP\file\TemporaryFileManager;
use PKP\plugins\Hook;
use PKP\services\PKPSchemaService;
use PKP\user\User;
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<Announcement> $schemaService */
protected $schemaService;
public function __construct(DAO $dao, Request $request, PKPSchemaService $schemaService)
{
$this->dao = $dao;
$this->request = $request;
$this->schemaService = $schemaService;
}
/** @copydoc DAO::newDataObject() */
public function newDataObject(array $params = []): Announcement
{
$object = $this->dao->newDataObject();
if (!empty($params)) {
$object->setAllData($params);
}
return $object;
}
/** @copydoc DAO::get() */
public function get(int $id): ?Announcement
{
return $this->dao->get($id);
}
/** @copydoc DAO::exists() */
public function exists(int $id): bool
{
return $this->dao->exists($id);
}
/** @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 announcement
*
* Perform validation checks on data used to add or edit an announcement.
*
* @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
*
* @return array A key/value array with validation errors. Empty if no errors
*/
public function validate(?Announcement $object, array $props, array $allowedLocales, string $primaryLocale): array
{
$validator = ValidatorFactory::make(
$props,
$this->schemaService->getValidationRules($this->dao->schema, $allowedLocales),
[
'dateExpire.date_format' => __('stats.dateRange.invalidDate'),
]
);
// 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);
$errors = [];
if ($validator->fails()) {
$errors = $this->schemaService->formatValidationErrors($validator->errors());
}
Hook::call('Announcement::validate', [&$errors, $object, $props, $allowedLocales, $primaryLocale]);
return $errors;
}
/** @copydoc DAO::insert() */
public function add(Announcement $announcement): int
{
$announcement->setData('datePosted', Core::getCurrentDate());
$id = $this->dao->insert($announcement);
$announcement = $this->get($id);
if ($announcement->getImage()) {
$this->handleImageUpload($announcement);
}
Hook::call('Announcement::add', [$announcement]);
return $id;
}
/**
* Update an object in the database
*
* Deletes the old image if it has been removed, or a new image has
* been uploaded.
*/
public function edit(Announcement $announcement, array $params)
{
$newAnnouncement = clone $announcement;
$newAnnouncement->setAllData(array_merge($newAnnouncement->_data, $params));
Hook::call('Announcement::edit', [$newAnnouncement, $announcement, $params]);
$this->dao->update($newAnnouncement);
$image = $newAnnouncement->getImage();
$hasNewImage = $image && $image['temporaryFileId'];
if ((!$image || $hasNewImage) && $announcement->getImage()) {
$this->deleteImage($announcement);
}
if ($hasNewImage) {
$this->handleImageUpload($newAnnouncement);
}
}
/** @copydoc DAO::delete() */
public function delete(Announcement $announcement)
{
Hook::call('Announcement::delete::before', [$announcement]);
if ($announcement->getImage()) {
$this->deleteImage($announcement);
}
$this->dao->delete($announcement);
Hook::call('Announcement::delete', [$announcement]);
}
/**
* Delete a collection of announcements
*/
public function deleteMany(Collector $collector)
{
foreach ($collector->getMany() as $announcement) {
$this->delete($announcement);
}
}
/**
* The subdirectory where announcement images are stored
*/
public function getImageSubdirectory(): string
{
return 'announcements';
}
/**
* Get the base URL for announcement file uploads
*/
public function getFileUploadBaseUrl(?Context $context = null): string
{
return join('/', [
Application::get()->getRequest()->getPublicFilesUrl($context),
$this->getImageSubdirectory(),
]);
}
/**
* Handle image uploads
*
* @throws StoreTemporaryFileException Unable to store temporary file upload
*/
protected function handleImageUpload(Announcement $announcement): void
{
$image = $announcement->getImage();
if ($image && $image['temporaryFileId']) {
$user = Application::get()->getRequest()->getUser();
$image = $announcement->getImage();
$temporaryFileManager = new TemporaryFileManager();
$temporaryFile = $temporaryFileManager->getFile((int) $image['temporaryFileId'], $user?->getId());
$filePath = $this->getImageSubdirectory() . '/' . $this->getImageFilename($announcement, $temporaryFile);
if (!$this->isValidImage($temporaryFile, $filePath, $user, $announcement)) {
throw new StoreTemporaryFileException($temporaryFile, $filePath, $user, $announcement);
}
if ($this->storeTemporaryFile($temporaryFile, $filePath, $user->getId(), $announcement)) {
$announcement->setImage(
$this->getImageData($announcement, $temporaryFile)
);
$this->dao->update($announcement);
} else {
$this->delete($announcement);
throw new StoreTemporaryFileException($temporaryFile, $filePath, $user, $announcement);
}
}
}
/**
* Store a temporary file upload in the public files directory
*
* @param string $newPath The new filename with the path relative to the public files directoruy
* @return bool Whether or not the operation was successful
*/
protected function storeTemporaryFile(TemporaryFile $temporaryFile, string $newPath, int $userId, Announcement $announcement): bool
{
$publicFileManager = new PublicFileManager();
$temporaryFileManager = new TemporaryFileManager();
if ($announcement->getAssocId()) {
$result = $publicFileManager->copyContextFile(
$announcement->getAssocId(),
$temporaryFile->getFilePath(),
$newPath
);
} else {
$result = $publicFileManager->copySiteFile(
$temporaryFile->getFilePath(),
$newPath
);
}
if (!$result) {
return false;
}
$temporaryFileManager->deleteById($temporaryFile->getId(), $userId);
return $result;
}
/**
* Get the data array for a temporary file that has just been stored
*
* @return array Data about the image, like the upload name, alt text, and date uploaded
*/
protected function getImageData(Announcement $announcement, TemporaryFile $temporaryFile): array
{
$image = $announcement->getImage();
return [
'name' => $temporaryFile->getOriginalFileName(),
'uploadName' => $this->getImageFilename($announcement, $temporaryFile),
'dateUploaded' => Core::getCurrentDate(),
'altText' => !empty($image['altText']) ? $image['altText'] : '',
];
}
/**
* Get the filename of the image upload
*/
protected function getImageFilename(Announcement $announcement, TemporaryFile $temporaryFile): string
{
$fileManager = new FileManager();
return $announcement->getId()
. $fileManager->getImageExtension($temporaryFile->getFileType());
}
/**
* Delete the image related to announcement
*/
protected function deleteImage(Announcement $announcement): void
{
$image = $announcement->getImage();
if ($image && $image['uploadName']) {
$publicFileManager = new PublicFileManager();
$filesPath = $announcement->getAssocId()
? $publicFileManager->getContextFilesPath($announcement->getAssocId())
: $publicFileManager->getSiteFilesPath();
$publicFileManager->deleteByPath(
join('/', [
$filesPath,
$this->getImageSubdirectory(),
$image['uploadName'],
])
);
}
}
/**
* Check that temporary file is an image
*/
protected function isValidImage(TemporaryFile $temporaryFile): bool
{
if (getimagesize($temporaryFile->getFilePath()) === false) {
return false;
}
$extension = pathinfo($temporaryFile->getOriginalFileName(), PATHINFO_EXTENSION);
$fileManager = new FileManager();
$extensionFromMimeType = $fileManager->getImageExtension(
PKPString::mime_content_type($temporaryFile->getFilePath())
);
if ($extensionFromMimeType !== '.' . $extension) {
return false;
}
return true;
}
}
@@ -0,0 +1,124 @@
<?php
/**
* @file classes/announcement/maps/Schema.php
*
* Copyright (c) 2014-2020 Simon Fraser University
* Copyright (c) 2000-2020 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class Schema
*
* @brief Map announcements to the properties defined in the announcement schema
*/
namespace PKP\announcement\maps;
use APP\core\Application;
use APP\core\Request;
use Illuminate\Support\Enumerable;
use PKP\announcement\Announcement;
use PKP\core\PKPApplication;
use PKP\services\PKPSchemaService;
class Schema extends \PKP\core\maps\Schema
{
public Enumerable $collection;
public string $schema = PKPSchemaService::SCHEMA_ANNOUNCEMENT;
/**
* Map an announcement
*
* Includes all properties in the announcement schema.
*/
public function map(Announcement $item): array
{
return $this->mapByProperties($this->getProps(), $item);
}
/**
* Summarize an announcement
*
* Includes properties with the apiSummary flag in the announcement schema.
*/
public function summarize(Announcement $item): array
{
return $this->mapByProperties($this->getSummaryProps(), $item);
}
/**
* Map a collection of Announcements
*
* @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 Announcements
*
* @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 Announcement to an assoc array
*/
protected function mapByProperties(array $props, Announcement $item): array
{
$output = [];
foreach ($props as $prop) {
switch ($prop) {
case '_href':
$output[$prop] = $this->getApiUrl('announcements/' . $item->getId());
break;
case 'url':
$output[$prop] = $this->request->getDispatcher()->url(
$this->request,
PKPApplication::ROUTE_PAGE,
$this->getUrlPath(),
'announcement',
'view',
$item->getId()
);
break;
default:
$output[$prop] = $item->getData($prop);
break;
}
}
$output = $this->schemaService->addMissingMultilingualValues($this->schema, $output, $this->getSupportedLocales());
ksort($output);
return $this->withExtensions($output, $item);
}
protected function getUrlPath(): string
{
if (isset($this->context)) {
return $this->context->getData('urlPath');
}
return 'index';
}
protected function getSupportedLocales(): array
{
if (isset($this->context)) {
return $this->context->getSupportedFormLocales();
}
return Application::get()->getRequest()->getSite()->getSupportedLocales();
}
}