Skip to content

Commit

Permalink
#9253 Add featured image to site announcements
Browse files Browse the repository at this point in the history
  • Loading branch information
NateWr authored and asmecher committed Nov 8, 2023
1 parent 17a9e6f commit 31d3b23
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 5 deletions.
23 changes: 22 additions & 1 deletion api/v1/announcements/PKPAnnouncementHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use PKP\announcement\Collector;
use PKP\config\Config;
use PKP\context\Context;
use PKP\core\exceptions\StoryTemporaryFileException;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\handler\APIHandler;
Expand Down Expand Up @@ -220,6 +221,19 @@ public function add($slimRequest, $response, $args)

$announcement = Repo::announcement()->newDataObject($params);
$announcementId = Repo::announcement()->add($announcement);

try {
$announcement = Repo::announcement()->add($announcementId);
} catch (StoryTemporaryFileException $e) {
$announcement = Repo::announcement()->get($announcementId);
Repo::announcement()->delete($announcement);
return $response->withStatus(400)->withJson([
'image' => __('api.400.errorUploadingImage')
]);
}

$announcement = Repo::announcement()->get($announcementId);

$sendEmail = (bool) filter_var($params['sendEmail'], FILTER_VALIDATE_BOOLEAN);

if ($context) {
Expand Down Expand Up @@ -270,7 +284,14 @@ public function edit($slimRequest, $response, $args)
return $response->withStatus(400)->withJson($errors);
}

Repo::announcement()->edit($announcement, $params);
try {
Repo::announcement()->edit($announcement, $params);
} catch (StoryTemporaryFileException $e) {
Repo::announcement()->delete($announcement);
return $response->withStatus(400)->withJson([
'image' => __('api.400.errorUploadingImage')
]);
}

$announcement = Repo::announcement()->get($announcement->getId());

Expand Down
64 changes: 64 additions & 0 deletions classes/announcement/Announcement.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

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
Expand Down Expand Up @@ -275,6 +278,67 @@ 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) {
Expand Down
156 changes: 156 additions & 0 deletions classes/announcement/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@

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\StoryTemporaryFileException;
use PKP\file\FileManager;
use PKP\file\TemporaryFile;
use PKP\file\TemporaryFileManager;
use PKP\plugins\Hook;
use PKP\services\PKPSchemaService;
use PKP\validation\ValidatorFactory;
Expand Down Expand Up @@ -128,6 +135,12 @@ 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;
Expand All @@ -142,13 +155,28 @@ public function edit(Announcement $announcement, array $params)
Hook::call('Announcement::edit', [$newAnnouncement, $announcement, $params]);

$this->dao->update($newAnnouncement);

if ($announcement->getImage()) {
$this->deleteImage($announcement);
}

$image = $newAnnouncement->getImage();
if ($image && $image['temporaryFileId']) {
$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]);
}

Expand All @@ -161,4 +189,132 @@ public function deleteMany(Collector $collector)
$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->storeTemporaryFile($temporaryFile, $filePath, $user?->getId(), $announcement)) {
$announcement->setImage(
$this->getImageData($announcement, $temporaryFile)
);
$this->dao->update($announcement);
} else {
$this->delete($announcement);
throw new StoryTemporaryFileException($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'],
])
);
}
}
}
10 changes: 9 additions & 1 deletion classes/components/forms/announcement/PKPAnnouncementForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PKP\components\forms\FieldOptions;
use PKP\components\forms\FieldRichTextarea;
use PKP\components\forms\FieldText;
use PKP\components\forms\FieldUploadImage;
use PKP\components\forms\FormComponent;
use PKP\context\Context;
use PKP\db\DAORegistry;
Expand All @@ -42,7 +43,7 @@ class PKPAnnouncementForm extends FormComponent
* @param string $action URL to submit the form to
* @param array $locales Supported locales
*/
public function __construct($action, $locales, ?Context $context = null)
public function __construct($action, $locales, string $baseUrl, string $temporaryFileApiUrl, ?Context $context = null)
{
$this->action = $action;
$this->locales = $locales;
Expand All @@ -68,6 +69,13 @@ public function __construct($action, $locales, ?Context $context = null)
'toolbar' => 'bold italic superscript subscript | link | blockquote bullist numlist',
'plugins' => 'paste,link,lists',
]))
->addField(new FieldUploadImage('image', [
'label' => __('manager.image'),
'baseUrl' => $baseUrl,
'options' => [
'url' => $temporaryFileApiUrl,
],
]))
->addField(new FieldText('dateExpire', [
'label' => __('manager.announcements.form.dateExpire'),
'description' => __('manager.announcements.form.dateExpireInstructions'),
Expand Down
2 changes: 1 addition & 1 deletion classes/core/exceptions/StoreTemporaryFileException.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ public function __construct(public TemporaryFile $temporaryFile, public string $
}
parent::__construct($message);
}
}
}
13 changes: 13 additions & 0 deletions classes/file/PKPPublicFileManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ public function uploadSiteFile($fileName, $destFileName)
return $this->uploadFile($fileName, $this->getSiteFilesPath() . '/' . $destFileName);
}

/**
* Copy a file to the site's public directory.
*
* @param string $sourceFile the source of the file to copy
* @param string $destFileName the destination file name
*
* @return bool
*/
public function copySiteFile($sourceFile, $destFileName)
{
return $this->copyFile($sourceFile, $this->getSiteFilesPath() . '/' . $destFileName);
}

/**
* Copy a file to a context's public directory.
*
Expand Down
3 changes: 3 additions & 0 deletions locale/en/manager.po
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,9 @@ msgstr "No users were found."
msgid "manager.groups.title"
msgstr "Title"

msgid "manager.image"
msgstr "Image"

msgid "manager.importExport"
msgstr "Import/Export Data"

Expand Down
2 changes: 1 addition & 1 deletion pages/admin/AdminHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public function settings($args, $request)
$siteStatisticsForm = new \PKP\components\forms\site\PKPSiteStatisticsForm($apiUrl, $locales, $site);
$highlightsListPanel = $this->getHighlightsListPanel();
$announcementSettingsForm = new PKPAnnouncementSettingsForm($apiUrl, $locales, $site);
$announcementsForm = new PKPAnnouncementForm($announcementsApiUrl, $locales);
$announcementsForm = new PKPAnnouncementForm($announcementsApiUrl, $locales, Repo::announcement()->getFileUploadBaseUrl(), $temporaryFileApiUrl);
$announcementsListPanel = $this->getAnnouncementsListPanel($announcementsApiUrl, $announcementsForm);

$templateMgr = TemplateManager::getManager($request);
Expand Down
Loading

0 comments on commit 31d3b23

Please sign in to comment.