Added email notification on new news
This commit is contained in:
parent
814cafd05d
commit
149155fbda
|
@ -65,5 +65,6 @@ return [
|
|||
// callable like [$instance, 'method] 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 = '';
|
||||
$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', [
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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)'),
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
$isNewNews = !$news->id;
|
||||
$news->save();
|
||||
|
||||
if ($isNewNews) {
|
||||
event('news.created', ['news' => $news]);
|
||||
}
|
||||
|
||||
$this->log->info(
|
||||
'Updated {pinned}{type} "{news}": {text}',
|
||||
[
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -107,6 +107,9 @@ class Stats
|
|||
case 'humans':
|
||||
$query = Settings::whereEmailHuman(true);
|
||||
break;
|
||||
case 'news':
|
||||
$query = Settings::whereEmailNews(true);
|
||||
break;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
$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) {
|
||||
|
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>',
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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\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
|
||||
*/
|
||||
|
|
|
@ -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']);
|
||||
|
|
Loading…
Reference in New Issue