Added email notification on new news

This commit is contained in:
Igor Scheller 2020-12-28 16:04:05 +01:00 committed by msquare
parent 814cafd05d
commit 149155fbda
22 changed files with 327 additions and 5 deletions

View File

@ -65,5 +65,6 @@ return [
// callable like [$instance, 'method] or 'function'
// or $function
// ]
'news.created' => \Engelsystem\Events\Listener\News::class . '@created',
],
];

View File

@ -0,0 +1,37 @@
<?php
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddEmailNewsToUsersSettings extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up()
{
$this->schema->table(
'users_settings',
function (Blueprint $table) {
$table->boolean('email_news')->default(false)->after('email_shiftinfo');
}
);
}
/**
* Reverse the migration
*/
public function down()
{
$this->schema->table(
'users_settings',
function (Blueprint $table) {
$table->dropColumn('email_news');
}
);
}
}

View File

@ -46,6 +46,7 @@ function guest_register()
$pronoun = '';
$email_shiftinfo = false;
$email_by_human_allowed = false;
$email_news = false;
$tshirt_size = '';
$password_hash = '';
$selected_angel_types = [];
@ -113,6 +114,10 @@ function guest_register()
$email_by_human_allowed = true;
}
if ($request->has('email_news')) {
$email_news = true;
}
if ($enable_tshirt_size) {
if ($request->has('tshirt_size') && isset($tshirt_sizes[$request->input('tshirt_size')])) {
$tshirt_size = $request->input('tshirt_size');
@ -211,6 +216,7 @@ function guest_register()
'theme' => config('theme'),
'email_human' => $email_by_human_allowed,
'email_shiftinfo' => $email_shiftinfo,
'email_news' => $email_news,
]);
$settings->user()
->associate($user)
@ -352,11 +358,16 @@ function guest_register()
),
$email_shiftinfo
),
form_checkbox(
'email_news',
__('Notify me of new news'),
$email_news
),
form_checkbox(
'email_by_human_allowed',
__('Humans are allowed to send me an email (e.g. for ticket vouchers)'),
$email_by_human_allowed
)
),
])
]),
div('row', [

View File

@ -38,6 +38,7 @@ function user_settings_main($user_source, $enable_tshirt_size, $tshirt_sizes)
$user_source->settings->email_shiftinfo = $request->has('email_shiftinfo');
$user_source->settings->email_human = $request->has('email_by_human_allowed');
$user_source->settings->email_news = $request->has('email_news');
if ($request->has('tshirt_size') && isset($tshirt_sizes[$request->input('tshirt_size')])) {
$user_source->personalData->shirt_size = $request->input('tshirt_size');

View File

@ -93,6 +93,11 @@ function User_settings_view(
),
$user_source->settings->email_shiftinfo
),
form_checkbox(
'email_news',
__('Notify me of new news'),
$user_source->settings->email_news
),
form_checkbox(
'email_by_human_allowed',
__('Humans are allowed to send me an email (e.g. for ticket vouchers)'),

View File

@ -123,3 +123,12 @@ msgstr "Frage erstellt."
msgid "question.edit.success"
msgstr "Frage erfolgreich bearbeitet."
msgid "notification.news.new"
msgstr "Neue News: %s"
msgid "notification.news.new.introduction"
msgstr "Es gibt eine neue News: %1$s"
msgid "notification.news.new.text"
msgstr "Du kannst sie dir unter %3$s anschauen."

View File

@ -1613,6 +1613,9 @@ msgstr ""
msgid "The %s is allowed to send me an email (e.g. when my shifts change)"
msgstr "Das %s darf mir E-Mails senden (z.B. wenn sich meine Schichten ändern)"
msgid "Notify me of new news"
msgstr "Benachrichtige mich bei neuen News"
#: includes/pages/guest_login.php:291 includes/view/User_view.php:73
msgid "Humans are allowed to send me an email (e.g. for ticket vouchers)"
msgstr "Menschen dürfen mir eine E-Mail senden (z.B. für Ticket Gutscheine)"

View File

@ -119,3 +119,12 @@ msgstr "Question added successfully."
msgid "question.edit.success"
msgstr "Question updated successfully."
msgid "notification.news.new"
msgstr "New news: %s"
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"

View File

@ -1,6 +1,7 @@
{% block title %}{{ __('Hi %s,', [username]) }}{% endblock %}
{% block introduction %}{{ __('here is a message for you from the %s:', [config('app_name')]) }}{% endblock %}
{% block message %}{{ message|raw }}{% endblock %}
{% block footer %}{{ __('This email is autogenerated and has not been signed. You got this email because you are registered in the %s.', [config('app_name')]) }}{% endblock %}

View File

@ -0,0 +1,9 @@
{% extends "emails/mail.twig" %}
{% block introduction %}
{{ __('notification.news.new.introduction', [news.title, news.text, url('/news/' ~ news.id)]) }}
{% endblock %}
{% block message %}
{{ __('notification.news.new.text', [news.title, news.text, url('/news/' ~ news.id)]) }}
{% endblock %}

View File

@ -154,8 +154,13 @@ class NewsController extends BaseController
return $this->showEdit($news);
}
$isNewNews = !$news->id;
$news->save();
if ($isNewNews) {
event('news.created', ['news' => $news]);
}
$this->log->info(
'Updated {pinned}{type} "{news}": {text}',
[

View File

@ -119,6 +119,7 @@ class Controller extends BaseController
'type' => 'gauge',
['labels' => ['type' => 'system'], 'value' => $this->stats->email('system')],
['labels' => ['type' => 'humans'], 'value' => $this->stats->email('humans')],
['labels' => ['type' => 'news'], 'value' => $this->stats->email('news')],
],
'users_working' => [
'type' => 'gauge',

View File

@ -107,6 +107,9 @@ class Stats
case 'humans':
$query = Settings::whereEmailHuman(true);
break;
case 'news':
$query = Settings::whereEmailNews(true);
break;
default:
return 0;
}

View File

@ -0,0 +1,77 @@
<?php
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;
use Swift_SwiftException as SwiftException;
class News
{
/** @var LoggerInterface */
protected $log;
/** @var EngelsystemMailer */
protected $mailer;
/** @var UserSettings */
protected $settings;
/**
* @param LoggerInterface $log
* @param EngelsystemMailer $mailer
* @param UserSettings $settings
*/
public function __construct(
LoggerInterface $log,
EngelsystemMailer $mailer,
UserSettings $settings
) {
$this->log = $log;
$this->mailer = $mailer;
$this->settings = $settings;
}
/**
* @param NewsModel $news
*/
public function created(NewsModel $news)
{
/** @var UserSettings[]|Collection $recipients */
$recipients = $this->settings
->whereEmailNews(true)
->with('user')
->get();
foreach ($recipients as $recipient) {
$this->sendMail($news, $recipient->user, 'notification.news.new', 'emails/news-new');
}
}
/**
* @param NewsModel $news
* @param User $user
* @param string $subject
* @param string $template
*/
protected function sendMail(NewsModel $news, User $user, string $subject, string $template)
{
try {
$this->mailer->sendViewTranslated(
$user,
$subject,
$template,
['title' => $news->title, 'news' => $news, 'username' => $user->name]
);
} catch (SwiftException $e) {
$this->log->error(
'Unable to send email "{title}" to user {user} with {exception}',
['title' => $subject, 'user' => $user->name, 'exception' => $e]
);
}
}
}

View File

@ -61,7 +61,7 @@ class EngelsystemMailer extends Mailer
$this->translation->setLocale($locale);
}
$subject = $this->translation ? $this->translation->translate($subject) : $subject;
$subject = $this->translation ? $this->translation->translate($subject, $data) : $subject;
$sentMails = $this->sendView($to, $subject, $template, $data);
if ($activeLocale) {

View File

@ -9,11 +9,13 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @property int $theme
* @property bool $email_human
* @property bool $email_shiftinfo
* @property bool $email_news
*
* @method static QueryBuilder|Settings[] whereLanguage($value)
* @method static QueryBuilder|Settings[] whereTheme($value)
* @method static QueryBuilder|Settings[] whereEmailHuman($value)
* @method static QueryBuilder|Settings[] whereEmailShiftinfo($value)
* @method static QueryBuilder|Settings[] whereEmailNews($value)
*/
class Settings extends HasUserModel
{
@ -27,5 +29,6 @@ class Settings extends HasUserModel
'theme',
'email_human',
'email_shiftinfo',
'email_news',
];
}

View File

@ -2,6 +2,7 @@
use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Events\EventDispatcher;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector;
@ -89,6 +90,24 @@ function config_path($path = ''): string
return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
}
/**
* @param string|object|null $event
* @param array $payload
*
* @return EventDispatcher
*/
function event($event = null, $payload = [])
{
/** @var EventDispatcher $dispatcher */
$dispatcher = app('events.dispatcher');
if (!is_null($event)) {
return $dispatcher->dispatch($event, $payload);
}
return $dispatcher;
}
/**
* @param string $path
* @param int $status

View File

@ -3,6 +3,7 @@
namespace Engelsystem\Test\Unit\Controllers\Admin;
use Engelsystem\Controllers\Admin\NewsController;
use Engelsystem\Events\EventDispatcher;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Validation\Validator;
@ -307,6 +308,9 @@ class NewsControllerTest extends ControllerTest
$this->auth = $this->createMock(Authenticator::class);
$this->app->instance(Authenticator::class, $this->auth);
$eventDispatcher = $this->createMock(EventDispatcher::class);
$this->app->instance('events.dispatcher', $eventDispatcher);
(new News([
'title' => 'Foo',
'text' => '<b>foo</b>',

View File

@ -249,6 +249,7 @@ class StatsTest extends TestCase
$this->assertEquals(0, $stats->email('not-available-option'));
$this->assertEquals(2, $stats->email('system'));
$this->assertEquals(3, $stats->email('humans'));
$this->assertEquals(1, $stats->email('news'));
}
/**
@ -378,7 +379,7 @@ class StatsTest extends TestCase
{
$this->addUser();
$this->addUser([], ['shirt_size' => 'L'], ['email_human' => true, 'email_shiftinfo' => true]);
$this->addUser(['arrived' => 1], [], ['email_human' => true]);
$this->addUser(['arrived' => 1], [], ['email_human' => true, 'email_news' => true]);
$this->addUser(['arrived' => 1], [], ['language' => 'lo_RM', 'email_shiftinfo' => true]);
$this->addUser(['arrived' => 1, 'got_voucher' => 2], ['shirt_size' => 'XXL'], ['language' => 'lo_RM']);
$this->addUser(['arrived' => 1, 'got_voucher' => 9, 'force_active' => true], [], ['theme' => 1]);

View File

@ -0,0 +1,101 @@
<?php
namespace Engelsystem\Test\Unit\Events\Listener;
use Engelsystem\Events\Listener\News;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Mail\EngelsystemMailer;
use Engelsystem\Models\News as NewsModel;
use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Psr\Log\Test\TestLogger;
use Swift_SwiftException as SwiftException;
class NewsTest extends TestCase
{
use HasDatabase;
/** @var TestLogger */
protected $log;
/** @var EngelsystemMailer|MockObject */
protected $mailer;
/** @var User */
protected $user;
/**
* @covers \Engelsystem\Events\Listener\News::created
* @covers \Engelsystem\Events\Listener\News::__construct
* @covers \Engelsystem\Events\Listener\News::sendMail
*/
public function testCreated()
{
$news = new NewsModel([
'title' => 'Foo',
'text' => 'Bar',
'user_id' => 1,
]);
$news->save();
$i = 0;
$this->mailer->expects($this->exactly(2))
->method('sendViewTranslated')
->willReturnCallback(function (User $user, string $subject, string $template, array $data) use (&$i) {
$this->assertEquals(1, $user->id);
$this->assertEquals('notification.news.new', $subject);
$this->assertEquals('emails/news-new', $template);
$this->assertEquals('Foo', array_values($data)[0]);
if ($i++ > 0) {
throw new SwiftException('Oops');
}
return 1;
});
/** @var News $listener */
$listener = $this->app->make(News::class);
$error = 'Unable to send email';
$listener->created($news);
$this->assertFalse($this->log->hasErrorThatContains($error));
$listener->created($news);
$this->assertTrue($this->log->hasErrorThatContains($error));
}
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
$this->log = new TestLogger();
$this->app->instance(LoggerInterface::class, $this->log);
$this->mailer = $this->createMock(EngelsystemMailer::class);
$this->app->instance(EngelsystemMailer::class, $this->mailer);
$this->user = new User([
'name' => 'test',
'password' => '',
'email' => 'foo@bar.baz',
'api_key' => '',
]);
$this->user->save();
$settings = new Settings([
'language' => '',
'theme' => 1,
'email_news' => true,
]);
$settings->user()
->associate($this->user)
->save();
}
}

View File

@ -5,6 +5,7 @@ namespace Engelsystem\Test\Unit;
use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Container\Container;
use Engelsystem\Events\EventDispatcher;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector;
@ -13,7 +14,6 @@ use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Renderer\Renderer;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface as StorageInterface;
@ -141,6 +141,28 @@ class HelpersTest extends TestCase
$this->assertEquals('/foo/conf/bar.php', config_path('bar.php'));
}
/**
* @covers \event
*/
public function testEvent()
{
/** @var Application|MockObject $app */
$app = $this->createMock(Container::class);
Application::setInstance($app);
/** @var EventDispatcher|MockObject $dispatcher */
$dispatcher = $this->createMock(EventDispatcher::class);
$this->setExpects($dispatcher, 'dispatch', ['testevent', ['some' => 'thing']], ['test']);
$app->expects($this->atLeastOnce())
->method('get')
->with('events.dispatcher')
->willReturn($dispatcher);
$this->assertEquals($dispatcher, event());
$this->assertEquals(['test'], event('testevent', ['some' => 'thing']));
}
/**
* @covers \redirect
*/

View File

@ -79,7 +79,7 @@ class EngelsystemMailerTest extends TestCase
$this->setExpects($mailer, 'sendView', ['foo@bar.baz', 'Lorem dolor', 'test/template.tpl', ['dev' => true]], 1);
$this->setExpects($translator, 'getLocales', null, ['de_DE' => 'de_DE', 'en_US' => 'en_US']);
$this->setExpects($translator, 'getLocale', null, 'en_US');
$this->setExpects($translator, 'translate', ['translatable.text'], 'Lorem dolor');
$this->setExpects($translator, 'translate', ['translatable.text', ['dev' => true]], 'Lorem dolor');
$translator->expects($this->exactly(2))
->method('setLocale')
->withConsecutive(['de_DE'], ['en_US']);