Send notifications on news updates, make notifications optional
This commit is contained in:
parent
4429516a22
commit
7888dfad78
|
@ -76,6 +76,7 @@ return [
|
|||
'message.created' => \Engelsystem\Events\Listener\Messages::class . '@created',
|
||||
|
||||
'news.created' => \Engelsystem\Events\Listener\News::class . '@created',
|
||||
'news.updated' => \Engelsystem\Events\Listener\News::class . '@updated',
|
||||
|
||||
'oauth2.login' => \Engelsystem\Events\Listener\OAuth2::class . '@login',
|
||||
|
||||
|
|
|
@ -219,6 +219,9 @@ msgstr "Es gibt eine neue News: %1$s"
|
|||
msgid "notification.news.new.text"
|
||||
msgstr "Du kannst sie dir unter %3$s anschauen."
|
||||
|
||||
msgid "notification.news.updated"
|
||||
msgstr "Aktualisierte News: %s"
|
||||
|
||||
msgid "notification.messages.new"
|
||||
msgstr "Neue private Nachricht von %s"
|
||||
|
||||
|
|
|
@ -86,6 +86,9 @@ msgstr "Bitte melde dich an."
|
|||
msgid "form.submit"
|
||||
msgstr "Absenden"
|
||||
|
||||
msgid "form.send_notification"
|
||||
msgstr "Benachrichtigungen versenden"
|
||||
|
||||
msgid "credits.source"
|
||||
msgstr "Quellcode"
|
||||
|
||||
|
@ -1513,6 +1516,12 @@ msgstr "News \"%s\" löschen"
|
|||
msgid "news.comments.delete.title"
|
||||
msgstr "Kommentar \"%s\" löschen"
|
||||
|
||||
msgid "notification.news.updated.introduction"
|
||||
msgstr "Die News %1$s wurde aktualisiert"
|
||||
|
||||
msgid "notification.news.updated.text"
|
||||
msgstr "Du kannst sie dir unter %3$s anschauen."
|
||||
|
||||
msgid "form.search"
|
||||
msgstr "Suchen"
|
||||
|
||||
|
|
|
@ -216,7 +216,10 @@ msgid "notification.news.new.introduction"
|
|||
msgstr "A new news is available: %1$s"
|
||||
|
||||
msgid "notification.news.new.text"
|
||||
msgstr "You can watch it at %3$s"
|
||||
msgstr "You can view it at %3$s"
|
||||
|
||||
msgid "notification.news.updated"
|
||||
msgstr "Updated News: %s"
|
||||
|
||||
msgid "notification.messages.new"
|
||||
msgstr "New private message from %s"
|
||||
|
|
|
@ -22,6 +22,9 @@ msgstr "Your password is incorrect. Please try it again."
|
|||
msgid "form.submit"
|
||||
msgstr "Submit"
|
||||
|
||||
msgid "form.send_notification"
|
||||
msgstr "Send notifications"
|
||||
|
||||
msgid "general.login"
|
||||
msgstr "Login"
|
||||
|
||||
|
@ -244,6 +247,12 @@ msgstr "Delete news \"%s\""
|
|||
msgid "news.comments.delete.title"
|
||||
msgstr "Delete comment \"%s\""
|
||||
|
||||
msgid "notification.news.updated.introduction"
|
||||
msgstr "The news %1$s was updated"
|
||||
|
||||
msgid "notification.news.updated.text"
|
||||
msgstr "You can view it at %3$s"
|
||||
|
||||
msgid "form.search"
|
||||
msgstr "Search"
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "emails/mail.twig" %}
|
||||
|
||||
{% block introduction %}
|
||||
{{ __('notification.news.updated.introduction', [news.title, news.text, url('/news/' ~ news.id)]) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block message %}
|
||||
{{ __('notification.news.updated.text', [news.title, news.text, url('/news/' ~ news.id)]) }}
|
||||
{% endblock %}
|
|
@ -77,6 +77,8 @@
|
|||
{% if news and news.id %}
|
||||
{{ f.delete(__('form.delete'), {'confirm_title': __('news.delete.title', [news.title[:40]|e])}) }}
|
||||
{% endif %}
|
||||
|
||||
{{ f.checkbox('send_notification', __('form.send_notification'), {'checked': send_notification, 'class': 'ms-2 form-check-inline'}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -39,10 +39,10 @@ class NewsController extends BaseController
|
|||
$news = $this->news->find($newsId);
|
||||
$isMeeting = (bool) $request->get('meeting', false);
|
||||
|
||||
return $this->showEdit($news, $isMeeting);
|
||||
return $this->showEdit($news, true, $isMeeting);
|
||||
}
|
||||
|
||||
protected function showEdit(?News $news, bool $isMeetingDefault = false): Response
|
||||
protected function showEdit(?News $news, bool $sendNotification = true, bool $isMeetingDefault = false): Response
|
||||
{
|
||||
return $this->response->withView(
|
||||
'pages/news/edit.twig',
|
||||
|
@ -51,6 +51,7 @@ class NewsController extends BaseController
|
|||
'is_meeting' => $news ? $news->is_meeting : $isMeetingDefault,
|
||||
'is_pinned' => $news ? $news->is_pinned : false,
|
||||
'is_highlighted' => $news ? $news->is_highlighted : false,
|
||||
'send_notification' => $sendNotification,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -86,6 +87,7 @@ class NewsController extends BaseController
|
|||
'is_highlighted' => 'optional|checked',
|
||||
'delete' => 'optional|checked',
|
||||
'preview' => 'optional|checked',
|
||||
'send_notification' => 'optional|checked',
|
||||
]);
|
||||
|
||||
if (!$news->user) {
|
||||
|
@ -95,24 +97,27 @@ class NewsController extends BaseController
|
|||
$news->text = $data['text'];
|
||||
$news->is_meeting = !is_null($data['is_meeting']);
|
||||
$news->is_pinned = !is_null($data['is_pinned']);
|
||||
$notify = !is_null($data['send_notification']);
|
||||
|
||||
if ($this->auth->can('news.highlight')) {
|
||||
$news->is_highlighted = !is_null($data['is_highlighted']);
|
||||
}
|
||||
|
||||
if (!is_null($data['preview'])) {
|
||||
return $this->showEdit($news);
|
||||
return $this->showEdit($news, $notify);
|
||||
}
|
||||
|
||||
$isNewNews = !$news->id;
|
||||
if ($isNewNews && News::where('title', $news->title)->where('text', $news->text)->count()) {
|
||||
$this->addNotification('news.edit.duplicate', NotificationType::ERROR);
|
||||
return $this->showEdit($news);
|
||||
return $this->showEdit($news, $notify);
|
||||
}
|
||||
$news->save();
|
||||
|
||||
if ($isNewNews) {
|
||||
event('news.created', ['news' => $news]);
|
||||
event('news.created', ['news' => $news, 'sendNotification' => $notify]);
|
||||
} else {
|
||||
event('news.updated', ['news' => $news, 'sendNotification' => $notify]);
|
||||
}
|
||||
|
||||
$this->log->info(
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace Engelsystem\Events\Listener;
|
|||
use Engelsystem\Mail\EngelsystemMailer;
|
||||
use Engelsystem\Models\News as NewsModel;
|
||||
use Engelsystem\Models\User\Settings as UserSettings;
|
||||
use Engelsystem\Models\User\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
@ -20,26 +19,35 @@ class News
|
|||
) {
|
||||
}
|
||||
|
||||
public function created(NewsModel $news): void
|
||||
public function created(NewsModel $news, bool $sendNotification = true): void
|
||||
{
|
||||
$this->sendMail($news, 'notification.news.new', 'emails/news-new', $sendNotification);
|
||||
}
|
||||
|
||||
public function updated(NewsModel $news, bool $sendNotification = true): void
|
||||
{
|
||||
$this->sendMail($news, 'notification.news.updated', 'emails/news-updated', $sendNotification);
|
||||
}
|
||||
|
||||
protected function sendMail(NewsModel $news, string $subject, string $template, bool $sendNotification = true): void
|
||||
{
|
||||
if (!$sendNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var UserSettings[]|Collection $recipients */
|
||||
$recipients = $this->settings
|
||||
->whereEmailNews(true)
|
||||
->with('user')
|
||||
->with('user.personalData')
|
||||
->where('email_news', true)
|
||||
->get();
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$this->sendMail($news, $recipient->user, 'notification.news.new', 'emails/news-new');
|
||||
$this->mailer->sendViewTranslated(
|
||||
$recipient->user,
|
||||
$subject,
|
||||
$template,
|
||||
['title' => $news->title, 'news' => $news, 'username' => $recipient->user->displayName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function sendMail(NewsModel $news, User $user, string $subject, string $template): void
|
||||
{
|
||||
$this->mailer->sendViewTranslated(
|
||||
$user,
|
||||
$subject,
|
||||
$template,
|
||||
['title' => $news->title, 'news' => $news, 'username' => $user->displayName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ use Engelsystem\Helpers\Authenticator;
|
|||
use Engelsystem\Http\Exceptions\ValidationException;
|
||||
use Engelsystem\Http\Validation\Validator;
|
||||
use Engelsystem\Models\News;
|
||||
use Engelsystem\Models\User\Settings;
|
||||
use Engelsystem\Models\User\User;
|
||||
use Engelsystem\Test\Unit\Controllers\ControllerTest;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
@ -18,6 +19,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
|||
class NewsControllerTest extends ControllerTest
|
||||
{
|
||||
protected Authenticator|MockObject $auth;
|
||||
protected EventDispatcher|MockObject $eventDispatcher;
|
||||
|
||||
/** @var array */
|
||||
protected array $data = [
|
||||
|
@ -42,6 +44,7 @@ class NewsControllerTest extends ControllerTest
|
|||
$this->assertEquals('pages/news/edit.twig', $view);
|
||||
|
||||
$this->assertNotEmpty($data['news']);
|
||||
$this->assertTrue($data['send_notification']);
|
||||
|
||||
return $this->response;
|
||||
});
|
||||
|
@ -101,10 +104,13 @@ class NewsControllerTest extends ControllerTest
|
|||
public function saveCreateEditProvider(): array
|
||||
{
|
||||
return [
|
||||
// Text, isMeeting, id, sendNotification
|
||||
['Some test', true],
|
||||
['Some test', false],
|
||||
['Some test', false, 1],
|
||||
['Some test', true, 1],
|
||||
['Some test', false, null, true],
|
||||
['Some test', false, 1, true],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -117,20 +123,33 @@ class NewsControllerTest extends ControllerTest
|
|||
public function testSaveCreateEdit(
|
||||
string $text,
|
||||
bool $isMeeting,
|
||||
int $id = null
|
||||
int $id = null,
|
||||
bool $sendNotification = false
|
||||
): void {
|
||||
$this->request->attributes->set('news_id', $id);
|
||||
$id = $id ?: 2;
|
||||
$body = [
|
||||
'title' => 'Some Title',
|
||||
'text' => $text,
|
||||
'title' => 'Some Title',
|
||||
'text' => $text,
|
||||
];
|
||||
if ($isMeeting) {
|
||||
$body['is_meeting'] = '1';
|
||||
}
|
||||
if ($sendNotification) {
|
||||
$body['send_notification'] = '1';
|
||||
}
|
||||
|
||||
$this->eventDispatcher->expects($this->once())
|
||||
->method('dispatch')
|
||||
->willReturnCallback(function (string $event, array $payload) use ($id, $sendNotification) {
|
||||
$this->assertEquals($id ? 'news.updated' : 'news.created', $event);
|
||||
$this->assertEquals($sendNotification, $payload['sendNotification']);
|
||||
$this->assertInstanceOf(News::class, $payload['news']);
|
||||
|
||||
return $this->eventDispatcher;
|
||||
});
|
||||
|
||||
$this->request = $this->request->withParsedBody($body);
|
||||
$this->addUser();
|
||||
$this->request = $this->request->withParsedBody($body);
|
||||
$this->response->expects($this->once())
|
||||
->method('redirectTo')
|
||||
->with('http://localhost/news')
|
||||
|
@ -146,7 +165,7 @@ class NewsControllerTest extends ControllerTest
|
|||
|
||||
$this->assertHasNotification('news.edit.success');
|
||||
|
||||
$news = (new News())->find($id);
|
||||
$news = (new News())->find($id ?: 2);
|
||||
$this->assertEquals($text, $news->text);
|
||||
$this->assertEquals($isMeeting, (bool) $news->is_meeting);
|
||||
}
|
||||
|
@ -164,6 +183,7 @@ class NewsControllerTest extends ControllerTest
|
|||
'is_pinned' => '1',
|
||||
'is_highlighted' => '1',
|
||||
'preview' => '1',
|
||||
'send_notification' => '1',
|
||||
]);
|
||||
$this->response->expects($this->once())
|
||||
->method('withView')
|
||||
|
@ -179,6 +199,8 @@ class NewsControllerTest extends ControllerTest
|
|||
$this->assertEquals('New title', $news->title);
|
||||
$this->assertEquals('New text', $news->text);
|
||||
|
||||
$this->assertTrue($data['send_notification']);
|
||||
|
||||
return $this->response;
|
||||
});
|
||||
$this->auth->expects($this->atLeastOnce())
|
||||
|
@ -256,7 +278,9 @@ class NewsControllerTest extends ControllerTest
|
|||
*/
|
||||
protected function addUser(): void
|
||||
{
|
||||
$user = User::factory(['id' => 42])->create();
|
||||
$user = User::factory(['id' => 42])
|
||||
->has(Settings::factory(['email_news' => true]))
|
||||
->create();
|
||||
|
||||
$this->auth->expects($this->any())
|
||||
->method('user')
|
||||
|
@ -273,11 +297,11 @@ class NewsControllerTest extends ControllerTest
|
|||
$this->auth = $this->createMock(Authenticator::class);
|
||||
$this->app->instance(Authenticator::class, $this->auth);
|
||||
|
||||
$eventDispatcher = $this->createMock(EventDispatcher::class);
|
||||
$eventDispatcher->expects(self::any())
|
||||
$this->eventDispatcher = $this->createMock(EventDispatcher::class);
|
||||
$this->eventDispatcher->expects(self::any())
|
||||
->method('dispatch')
|
||||
->willReturnSelf();
|
||||
$this->app->instance('events.dispatcher', $eventDispatcher);
|
||||
$this->app->instance('events.dispatcher', $this->eventDispatcher);
|
||||
|
||||
$user = User::factory()->create();
|
||||
(new News([
|
||||
|
|
|
@ -22,7 +22,9 @@ class NewsTest extends TestCase
|
|||
|
||||
protected TestLogger $log;
|
||||
|
||||
protected EngelsystemMailer|MockObject $mailer;
|
||||
protected EngelsystemMailer | MockObject $mailer;
|
||||
|
||||
protected NewsModel $news;
|
||||
|
||||
protected User $user;
|
||||
|
||||
|
@ -33,14 +35,10 @@ class NewsTest extends TestCase
|
|||
*/
|
||||
public function testCreated(): void
|
||||
{
|
||||
$this->app->instance('config', new Config());
|
||||
/** @var NewsModel $news */
|
||||
$news = NewsModel::factory(['title' => 'Foo'])->create();
|
||||
|
||||
$this->mailer->expects($this->once())
|
||||
->method('sendViewTranslated')
|
||||
->willReturnCallback(function (User $user, string $subject, string $template, array $data): bool {
|
||||
$this->assertEquals(1, $user->id);
|
||||
$this->assertEquals($this->user->id, $user->id);
|
||||
$this->assertEquals('notification.news.new', $subject);
|
||||
$this->assertEquals('emails/news-new', $template);
|
||||
$this->assertEquals('Foo', array_values($data)[0]);
|
||||
|
@ -50,7 +48,55 @@ class NewsTest extends TestCase
|
|||
|
||||
/** @var News $listener */
|
||||
$listener = $this->app->make(News::class);
|
||||
$listener->created($news);
|
||||
$listener->created($this->news);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Events\Listener\News::created
|
||||
* @covers \Engelsystem\Events\Listener\News::sendMail
|
||||
*/
|
||||
public function testCreatedNoNotification(): void
|
||||
{
|
||||
$this->setExpects($this->mailer, 'sendViewTranslated', null, null, $this->never());
|
||||
|
||||
/** @var News $listener */
|
||||
$listener = $this->app->make(News::class);
|
||||
$listener->created($this->news, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Events\Listener\News::updated
|
||||
* @covers \Engelsystem\Events\Listener\News::sendMail
|
||||
*/
|
||||
public function testUpdated(): void
|
||||
{
|
||||
$this->mailer->expects($this->once())
|
||||
->method('sendViewTranslated')
|
||||
->willReturnCallback(function (User $user, string $subject, string $template, array $data): bool {
|
||||
$this->assertEquals($this->user->id, $user->id);
|
||||
$this->assertEquals('notification.news.updated', $subject);
|
||||
$this->assertEquals('emails/news-updated', $template);
|
||||
$this->assertEquals('Foo', array_values($data)[0]);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
/** @var News $listener */
|
||||
$listener = $this->app->make(News::class);
|
||||
$listener->updated($this->news);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Events\Listener\News::updated
|
||||
* @covers \Engelsystem\Events\Listener\News::sendMail
|
||||
*/
|
||||
public function testUpdatedNoNotification(): void
|
||||
{
|
||||
$this->setExpects($this->mailer, 'sendViewTranslated', null, null, $this->never());
|
||||
|
||||
/** @var News $listener */
|
||||
$listener = $this->app->make(News::class);
|
||||
$listener->updated($this->news, false);
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
|
@ -64,6 +110,10 @@ class NewsTest extends TestCase
|
|||
$this->mailer = $this->createMock(EngelsystemMailer::class);
|
||||
$this->app->instance(EngelsystemMailer::class, $this->mailer);
|
||||
|
||||
$this->app->instance('config', new Config());
|
||||
|
||||
$this->news = NewsModel::factory(['title' => 'Foo'])->create();
|
||||
|
||||
$this->user = User::factory()
|
||||
->has(Settings::factory([
|
||||
'language' => '',
|
||||
|
|
Loading…
Reference in New Issue