Added email notification on new news

main
Igor Scheller 4 years ago committed by msquare
parent 814cafd05d
commit 149155fbda

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

@ -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');
}
);
}
}

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

@ -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_shiftinfo = $request->has('email_shiftinfo');
$user_source->settings->email_human = $request->has('email_by_human_allowed'); $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')])) { if ($request->has('tshirt_size') && isset($tshirt_sizes[$request->input('tshirt_size')])) {
$user_source->personalData->shirt_size = $request->input('tshirt_size'); $user_source->personalData->shirt_size = $request->input('tshirt_size');

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

@ -123,3 +123,12 @@ msgstr "Frage erstellt."
msgid "question.edit.success" msgid "question.edit.success"
msgstr "Frage erfolgreich bearbeitet." 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."

@ -1613,6 +1613,9 @@ msgstr ""
msgid "The %s is allowed to send me an email (e.g. when my shifts change)" 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)" 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 #: 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)" 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)" msgstr "Menschen dürfen mir eine E-Mail senden (z.B. für Ticket Gutscheine)"

@ -119,3 +119,12 @@ msgstr "Question added successfully."
msgid "question.edit.success" msgid "question.edit.success"
msgstr "Question updated successfully." 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"

@ -1,6 +1,7 @@
{% block title %}{{ __('Hi %s,', [username]) }}{% endblock %} {% block title %}{{ __('Hi %s,', [username]) }}{% endblock %}
{% block introduction %}{{ __('here is a message for you from the %s:', [config('app_name')]) }}{% endblock %} {% block introduction %}{{ __('here is a message for you from the %s:', [config('app_name')]) }}{% endblock %}
{% block message %}{{ message|raw }}{% 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 %} {% 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 %}

@ -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 %}

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

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

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

@ -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]
);
}
}
}

@ -61,7 +61,7 @@ class EngelsystemMailer extends Mailer
$this->translation->setLocale($locale); $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); $sentMails = $this->sendView($to, $subject, $template, $data);
if ($activeLocale) { if ($activeLocale) {

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

@ -2,6 +2,7 @@
use Engelsystem\Application; use Engelsystem\Application;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Events\EventDispatcher;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator; use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector; use Engelsystem\Http\Redirector;
@ -89,6 +90,24 @@ function config_path($path = ''): string
return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path); 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 string $path
* @param int $status * @param int $status

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

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

@ -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();
}
}

@ -5,6 +5,7 @@ namespace Engelsystem\Test\Unit;
use Engelsystem\Application; use Engelsystem\Application;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Container\Container; use Engelsystem\Container\Container;
use Engelsystem\Events\EventDispatcher;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator; use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector; use Engelsystem\Http\Redirector;
@ -13,7 +14,6 @@ use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface; use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Renderer\Renderer; use Engelsystem\Renderer\Renderer;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface as StorageInterface; 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')); $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 * @covers \redirect
*/ */

@ -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($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, 'getLocales', null, ['de_DE' => 'de_DE', 'en_US' => 'en_US']);
$this->setExpects($translator, 'getLocale', null, '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)) $translator->expects($this->exactly(2))
->method('setLocale') ->method('setLocale')
->withConsecutive(['de_DE'], ['en_US']); ->withConsecutive(['de_DE'], ['en_US']);

Loading…
Cancel
Save