mirror of
https://github.com/flarum/core.git
synced 2025-08-05 16:07:34 +02:00
feat: notification unsubscribe & email overhaul with HTML multipart (#3872)
This commit is contained in:
@@ -70,9 +70,8 @@ flarum-mentions:
|
|||||||
# These translations are used in emails sent when a post is replied to
|
# These translations are used in emails sent when a post is replied to
|
||||||
post_mentioned:
|
post_mentioned:
|
||||||
subject: "{replier_display_name} replied to your post in {title}"
|
subject: "{replier_display_name} replied to your post in {title}"
|
||||||
|
plain:
|
||||||
body: |
|
body: |
|
||||||
Hey {recipient_display_name}!
|
|
||||||
|
|
||||||
{replier_display_name} replied to your post (#{post_number}) in {title}.
|
{replier_display_name} replied to your post (#{post_number}) in {title}.
|
||||||
|
|
||||||
{url}
|
{url}
|
||||||
@@ -80,13 +79,14 @@ flarum-mentions:
|
|||||||
---
|
---
|
||||||
|
|
||||||
{content}
|
{content}
|
||||||
|
html:
|
||||||
|
body: "{replier_display_name} replied to your post (#{post_number}) in [{title}]({url})."
|
||||||
|
|
||||||
# These translations are used in emails sent when a user is mentioned
|
# These translations are used in emails sent when a user is mentioned
|
||||||
user_mentioned:
|
user_mentioned:
|
||||||
subject: "{mentioner_display_name} mentioned you in {title}"
|
subject: "{mentioner_display_name} mentioned you in {title}"
|
||||||
|
plain:
|
||||||
body: |
|
body: |
|
||||||
Hey {recipient_display_name}!
|
|
||||||
|
|
||||||
{mentioner_display_name} mentioned you in a post in {title}.
|
{mentioner_display_name} mentioned you in a post in {title}.
|
||||||
|
|
||||||
{url}
|
{url}
|
||||||
@@ -94,12 +94,13 @@ flarum-mentions:
|
|||||||
---
|
---
|
||||||
|
|
||||||
{content}
|
{content}
|
||||||
|
html:
|
||||||
|
body: "{mentioner_display_name} mentioned you in a post in [{title}]({url})."
|
||||||
# These translations are used in emails sent when a group is mentioned
|
# These translations are used in emails sent when a group is mentioned
|
||||||
group_mentioned:
|
group_mentioned:
|
||||||
subject: "{mentioner_display_name} mentioned a group you're a member of in {title}"
|
subject: "{mentioner_display_name} mentioned a group you're a member of in {title}"
|
||||||
|
plain:
|
||||||
body: |
|
body: |
|
||||||
Hey {recipient_display_name}!
|
|
||||||
|
|
||||||
{mentioner_display_name} mentioned a group you're a member of in {title}.
|
{mentioner_display_name} mentioned a group you're a member of in {title}.
|
||||||
|
|
||||||
{url}
|
{url}
|
||||||
@@ -107,3 +108,5 @@ flarum-mentions:
|
|||||||
---
|
---
|
||||||
|
|
||||||
{content}
|
{content}
|
||||||
|
html:
|
||||||
|
body: "{mentioner_display_name} mentioned a group you're a member of in [{title}]({url})."
|
||||||
|
@@ -38,9 +38,11 @@ class GroupMentionedBlueprint implements BlueprintInterface, MailableInterface
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailView(): string|array
|
public function getEmailViews(): array
|
||||||
{
|
{
|
||||||
return ['text' => 'flarum-mentions::emails.groupMentioned'];
|
return [
|
||||||
|
'text' => 'flarum-mentions::emails.plain.groupMentioned',
|
||||||
|
'html' => 'flarum-mentions::emails.html.groupMentioned', ];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailSubject(TranslatorInterface $translator): string
|
public function getEmailSubject(TranslatorInterface $translator): string
|
||||||
|
@@ -39,9 +39,12 @@ class PostMentionedBlueprint implements BlueprintInterface, MailableInterface
|
|||||||
return ['replyNumber' => (int) $this->reply->number];
|
return ['replyNumber' => (int) $this->reply->number];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailView(): string|array
|
public function getEmailViews(): array
|
||||||
{
|
{
|
||||||
return ['text' => 'flarum-mentions::emails.postMentioned'];
|
return [
|
||||||
|
'text' => 'flarum-mentions::emails.plain.postMentioned',
|
||||||
|
'html' => 'flarum-mentions::emails.html.postMentioned',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailSubject(TranslatorInterface $translator): string
|
public function getEmailSubject(TranslatorInterface $translator): string
|
||||||
|
@@ -38,9 +38,12 @@ class UserMentionedBlueprint implements BlueprintInterface, MailableInterface
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailView(): string|array
|
public function getEmailViews(): array
|
||||||
{
|
{
|
||||||
return ['text' => 'flarum-mentions::emails.userMentioned'];
|
return [
|
||||||
|
'text' => 'flarum-mentions::emails.plain.userMentioned',
|
||||||
|
'html' => 'flarum-mentions::emails.html.userMentioned'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailSubject(TranslatorInterface $translator): string
|
public function getEmailSubject(TranslatorInterface $translator): string
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
@extends('flarum.forum::email.html.notification.base')
|
||||||
|
|
||||||
|
@section('notificationContent')
|
||||||
|
{!! $formatter->convert($translator->trans('flarum-mentions.email.group_mentioned.html.body', [
|
||||||
|
'{mentioner_display_name}' => $blueprint->post->user->display_name,
|
||||||
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number])
|
||||||
|
])) !!}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('contentPreview')
|
||||||
|
{!! $blueprint->post->formatContent() !!}
|
||||||
|
@endsection
|
@@ -0,0 +1,14 @@
|
|||||||
|
@extends('flarum.forum::email.html.notification.base')
|
||||||
|
|
||||||
|
@section('notificationContent')
|
||||||
|
{!! $formatter->convert($translator->trans('flarum-mentions.email.post_mentioned.html.body', [
|
||||||
|
'{replier_display_name}' => $blueprint->reply->user->display_name,
|
||||||
|
'{post_number}' => $blueprint->post->number,
|
||||||
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->reply->discussion_id, 'near' => $blueprint->reply->number])
|
||||||
|
])) !!}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('contentPreview')
|
||||||
|
{!! $blueprint->reply->formatContent() !!}
|
||||||
|
@endsection
|
@@ -0,0 +1,13 @@
|
|||||||
|
@extends('flarum.forum::email.html.notification.base')
|
||||||
|
|
||||||
|
@section('notificationContent')
|
||||||
|
{!! $formatter->convert($translator->trans('flarum-mentions.email.user_mentioned.html.body', [
|
||||||
|
'{mentioner_display_name}' => $blueprint->post->user->display_name,
|
||||||
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number])
|
||||||
|
])) !!}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('contentPreview')
|
||||||
|
{!! $blueprint->post->formatContent() !!}
|
||||||
|
@endsection
|
@@ -1,7 +1,10 @@
|
|||||||
{!! $translator->trans('flarum-mentions.email.group_mentioned.body', [
|
@extends('flarum.forum::email.plain.notification.base')
|
||||||
'{recipient_display_name}' => $user->display_name,
|
|
||||||
|
@section('content')
|
||||||
|
{!! $translator->trans('flarum-mentions.email.group_mentioned.plain.body', [
|
||||||
'{mentioner_display_name}' => $blueprint->post->user->display_name,
|
'{mentioner_display_name}' => $blueprint->post->user->display_name,
|
||||||
'{title}' => $blueprint->post->discussion->title,
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number]),
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number]),
|
||||||
'{content}' => $blueprint->post->content
|
'{content}' => $blueprint->post->content
|
||||||
]) !!}
|
]) !!}
|
||||||
|
@endsection
|
@@ -1,8 +1,11 @@
|
|||||||
{!! $translator->trans('flarum-mentions.email.post_mentioned.body', [
|
@extends('flarum.forum::email.plain.notification.base')
|
||||||
'{recipient_display_name}' => $user->display_name,
|
|
||||||
|
@section('content')
|
||||||
|
{!! $translator->trans('flarum-mentions.email.post_mentioned.plain.body', [
|
||||||
'{replier_display_name}' => $blueprint->reply->user->display_name,
|
'{replier_display_name}' => $blueprint->reply->user->display_name,
|
||||||
'{post_number}' => $blueprint->post->number,
|
'{post_number}' => $blueprint->post->number,
|
||||||
'{title}' => $blueprint->post->discussion->title,
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->reply->discussion_id, 'near' => $blueprint->reply->number]),
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->reply->discussion_id, 'near' => $blueprint->reply->number]),
|
||||||
'{content}' => $blueprint->reply->content
|
'{content}' => $blueprint->reply->content
|
||||||
]) !!}
|
]) !!}
|
||||||
|
@endsection
|
@@ -1,7 +1,10 @@
|
|||||||
{!! $translator->trans('flarum-mentions.email.user_mentioned.body', [
|
@extends('flarum.forum::email.plain.notification.base')
|
||||||
'{recipient_display_name}' => $user->display_name,
|
|
||||||
|
@section('content')
|
||||||
|
{!! $translator->trans('flarum-mentions.email.user_mentioned.plain.body', [
|
||||||
'{mentioner_display_name}' => $blueprint->post->user->display_name,
|
'{mentioner_display_name}' => $blueprint->post->user->display_name,
|
||||||
'{title}' => $blueprint->post->discussion->title,
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number]),
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number]),
|
||||||
'{content}' => $blueprint->post->content
|
'{content}' => $blueprint->post->content
|
||||||
]) !!}
|
]) !!}
|
||||||
|
@endsection
|
@@ -51,10 +51,9 @@ flarum-subscriptions:
|
|||||||
# These translations are used in emails sent when a post is made in a subscribed discussion
|
# These translations are used in emails sent when a post is made in a subscribed discussion
|
||||||
new_post:
|
new_post:
|
||||||
subject: "[New Post] {title}"
|
subject: "[New Post] {title}"
|
||||||
|
plain:
|
||||||
body: |
|
body: |
|
||||||
Hey {recipient_display_name}!
|
{poster_display_name} just posted in a discussion you're following: {title}.
|
||||||
|
|
||||||
{poster_display_name} made a post in a discussion you're following: {title}.
|
|
||||||
|
|
||||||
To view the new activity, check out the following link:
|
To view the new activity, check out the following link:
|
||||||
{url}
|
{url}
|
||||||
@@ -66,6 +65,11 @@ flarum-subscriptions:
|
|||||||
---
|
---
|
||||||
|
|
||||||
You won't receive any more notifications about this discussion until you're up-to-date.
|
You won't receive any more notifications about this discussion until you're up-to-date.
|
||||||
|
html:
|
||||||
|
body: |
|
||||||
|
{poster_display_name} just posted in a discussion you're following: [{title}]({url}).
|
||||||
|
|
||||||
|
You won't recieve any more notifications about this discussion until you're up-to-date.
|
||||||
|
|
||||||
##
|
##
|
||||||
# REUSED TRANSLATIONS - These keys should not be used directly in code!
|
# REUSED TRANSLATIONS - These keys should not be used directly in code!
|
||||||
|
@@ -39,9 +39,11 @@ class NewPostBlueprint implements BlueprintInterface, MailableInterface
|
|||||||
return ['postNumber' => (int) $this->post->number];
|
return ['postNumber' => (int) $this->post->number];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailView(): string|array
|
public function getEmailViews(): array
|
||||||
{
|
{
|
||||||
return ['text' => 'flarum-subscriptions::emails.newPost'];
|
return [
|
||||||
|
'text' => 'flarum-subscriptions::emails.plain.newPost',
|
||||||
|
'html' => 'flarum-subscriptions::emails.html.newPost', ];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailSubject(TranslatorInterface $translator): string
|
public function getEmailSubject(TranslatorInterface $translator): string
|
||||||
|
13
extensions/subscriptions/views/emails/html/newPost.blade.php
Normal file
13
extensions/subscriptions/views/emails/html/newPost.blade.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@extends('flarum.forum::email.html.notification.base')
|
||||||
|
|
||||||
|
@section('notificationContent')
|
||||||
|
{!! $formatter->convert($translator->trans('flarum-subscriptions.email.new_post.html.body', [
|
||||||
|
'{poster_display_name}' => $blueprint->post->user->display_name,
|
||||||
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number])
|
||||||
|
])) !!}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('contentPreview')
|
||||||
|
{!! $blueprint->post->formatContent() !!}
|
||||||
|
@endsection
|
@@ -1,7 +1,10 @@
|
|||||||
{!! $translator->trans('flarum-subscriptions.email.new_post.body', [
|
@extends('flarum.forum::email.plain.notification.base')
|
||||||
'{recipient_display_name}' => $user->display_name,
|
|
||||||
|
@section('content')
|
||||||
|
{!! $translator->trans('flarum-subscriptions.email.new_post.plain.body', [
|
||||||
'{poster_display_name}' => $blueprint->post->user->display_name,
|
'{poster_display_name}' => $blueprint->post->user->display_name,
|
||||||
'{title}' => $blueprint->post->discussion->title,
|
'{title}' => $blueprint->post->discussion->title,
|
||||||
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number]),
|
'{url}' => $url->to('forum')->route('discussion', ['id' => $blueprint->post->discussion_id, 'near' => $blueprint->post->number]),
|
||||||
'{content}' => $blueprint->post->content
|
'{content}' => $blueprint->post->content
|
||||||
]) !!}
|
]) !!}
|
||||||
|
@endsection
|
@@ -52,20 +52,22 @@ flarum-suspend:
|
|||||||
no_reason_given: No reason was given for this suspension.
|
no_reason_given: No reason was given for this suspension.
|
||||||
suspended:
|
suspended:
|
||||||
subject: Your account has been suspended
|
subject: Your account has been suspended
|
||||||
|
plain:
|
||||||
body: |
|
body: |
|
||||||
Hey {recipient_display_name},
|
|
||||||
|
|
||||||
You have been suspended for the following reason:
|
You have been suspended for the following reason:
|
||||||
|
|
||||||
---
|
---
|
||||||
{suspension_message}
|
{suspension_message}
|
||||||
---
|
---
|
||||||
|
html:
|
||||||
|
body: "You have been suspended from {forumTitle} for the following reason:"
|
||||||
|
|
||||||
unsuspended:
|
unsuspended:
|
||||||
subject: Your account has been unsuspended
|
subject: Your account has been unsuspended
|
||||||
|
plain:
|
||||||
body: |
|
body: |
|
||||||
Hey {recipient_display_name},
|
|
||||||
|
|
||||||
You have been unsuspended. You can head back to the forum by clicking on the following link:
|
You have been unsuspended. You can head back to the forum by clicking on the following link:
|
||||||
|
|
||||||
{forum_url}
|
{forum_url}
|
||||||
|
html:
|
||||||
|
body: "You have been unsuspended. You can head back to [{forumTitle}]({forum_url}) when you are ready."
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Suspend\Notification;
|
namespace Flarum\Suspend\Notification;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Carbon\CarbonInterface;
|
use Carbon\CarbonInterface;
|
||||||
use Flarum\Database\AbstractModel;
|
use Flarum\Database\AbstractModel;
|
||||||
use Flarum\Locale\TranslatorInterface;
|
use Flarum\Locale\TranslatorInterface;
|
||||||
@@ -35,7 +36,7 @@ class UserSuspendedBlueprint implements BlueprintInterface, MailableInterface
|
|||||||
|
|
||||||
public function getData(): CarbonInterface
|
public function getData(): CarbonInterface
|
||||||
{
|
{
|
||||||
return $this->user->suspended_until;
|
return Carbon::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getType(): string
|
public static function getType(): string
|
||||||
@@ -48,9 +49,11 @@ class UserSuspendedBlueprint implements BlueprintInterface, MailableInterface
|
|||||||
return User::class;
|
return User::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailView(): string|array
|
public function getEmailViews(): array
|
||||||
{
|
{
|
||||||
return ['text' => 'flarum-suspend::emails.suspended'];
|
return [
|
||||||
|
'text' => 'flarum-suspend::emails.plain.suspended',
|
||||||
|
'html' => 'flarum-suspend::emails.html.suspended', ];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailSubject(TranslatorInterface $translator): string
|
public function getEmailSubject(TranslatorInterface $translator): string
|
||||||
|
@@ -49,9 +49,11 @@ class UserUnsuspendedBlueprint implements BlueprintInterface, MailableInterface
|
|||||||
return User::class;
|
return User::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailView(): string|array
|
public function getEmailViews(): array
|
||||||
{
|
{
|
||||||
return ['text' => 'flarum-suspend::emails.unsuspended'];
|
return [
|
||||||
|
'text' => 'flarum-suspend::emails.plain.unsuspended',
|
||||||
|
'html' => 'flarum-suspend::emails.html.unsuspended', ];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailSubject(TranslatorInterface $translator): string
|
public function getEmailSubject(TranslatorInterface $translator): string
|
||||||
|
@@ -50,7 +50,7 @@ class SuspendUserTest extends TestCase
|
|||||||
{
|
{
|
||||||
$response = $this->sendSuspensionRequest($authenticatedAs, $targetUserId);
|
$response = $this->sendSuspensionRequest($authenticatedAs, $targetUserId);
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
11
extensions/suspend/views/emails/html/suspended.blade.php
Normal file
11
extensions/suspend/views/emails/html/suspended.blade.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
@extends('flarum.forum::email.html.information.base')
|
||||||
|
|
||||||
|
@section('informationContent')
|
||||||
|
{!! $formatter->convert($translator->trans('flarum-suspend.email.suspended.html.body', [
|
||||||
|
'{forumTitle}' => $settings->get('forum_title')
|
||||||
|
])) !!}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('contentPreview')
|
||||||
|
{!! $formatter->convert($blueprint->user->suspend_message ?? $translator->trans('flarum-suspend.email.no_reason_given')) !!}
|
||||||
|
@endsection
|
@@ -0,0 +1,8 @@
|
|||||||
|
@extends('flarum.forum::email.html.information.base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
{!! $formatter->convert($translator->trans('flarum-suspend.email.unsuspended.html.body', [
|
||||||
|
'{forumTitle}' => $settings->get('forum_title'),
|
||||||
|
'{forum_url}' => $url->to('forum')->base(),
|
||||||
|
])) !!}
|
||||||
|
@endsection
|
@@ -0,0 +1,7 @@
|
|||||||
|
@extends('flarum.forum::email.plain.information.base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
{!! $translator->trans('flarum-suspend.email.suspended.plain.body', [
|
||||||
|
'{suspension_message}' => $blueprint->user->suspend_message ?? $translator->trans('flarum-suspend.email.no_reason_given'),
|
||||||
|
]) !!}
|
||||||
|
@endsection
|
@@ -0,0 +1,7 @@
|
|||||||
|
@extends('flarum.forum::email.plain.information.base')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
{!! $translator->trans('flarum-suspend.email.unsuspended.plain.body', [
|
||||||
|
'{forum_url}' => $url->to('forum')->base(),
|
||||||
|
]) !!}
|
||||||
|
@endsection
|
@@ -1,4 +0,0 @@
|
|||||||
{!! $translator->trans('flarum-suspend.email.suspended.body', [
|
|
||||||
'{recipient_display_name}' => $user->display_name,
|
|
||||||
'{suspension_message}' => $blueprint->user->suspend_message ?? $translator->trans('flarum-suspend.email.no_reason_given'),
|
|
||||||
]) !!}
|
|
@@ -1,4 +0,0 @@
|
|||||||
{!! $translator->trans('flarum-suspend.email.unsuspended.body', [
|
|
||||||
'{recipient_display_name}' => $user->display_name,
|
|
||||||
'{forum_url}' => $url->to('forum')->base(),
|
|
||||||
]) !!}
|
|
@@ -8,6 +8,7 @@ import type { IPageAttrs } from '../../common/components/Page';
|
|||||||
import type { AlertIdentifier } from '../../common/states/AlertManagerState';
|
import type { AlertIdentifier } from '../../common/states/AlertManagerState';
|
||||||
import type Mithril from 'mithril';
|
import type Mithril from 'mithril';
|
||||||
import type { SaveSubmitEvent } from './AdminPage';
|
import type { SaveSubmitEvent } from './AdminPage';
|
||||||
|
import ItemList from '../../common/utils/ItemList';
|
||||||
|
|
||||||
export interface MailSettings {
|
export interface MailSettings {
|
||||||
data: {
|
data: {
|
||||||
@@ -65,16 +66,58 @@ export default class MailPage<CustomAttrs extends IPageAttrs = IPageAttrs> exten
|
|||||||
return <LoadingIndicator />;
|
return <LoadingIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = this.driverFields![this.setting('mail_driver')()];
|
const mailSettings = this.mailSettingItems().toArray();
|
||||||
const fieldKeys = Object.keys(fields);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Form">
|
<div className="Form">
|
||||||
{this.buildSettingComponent({
|
{mailSettings.map((settingComponent) => settingComponent)}
|
||||||
|
{this.submitButton()}
|
||||||
|
|
||||||
|
<FieldSet label={app.translator.trans('core.admin.email.send_test_mail_heading')} className="MailPage-MailSettings">
|
||||||
|
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user!.email() })}</div>
|
||||||
|
<Button className="Button Button--primary" disabled={this.sendingTest || this.isChanged()} onclick={() => this.sendTestEmail()}>
|
||||||
|
{app.translator.trans('core.admin.email.send_test_mail_button')}
|
||||||
|
</Button>
|
||||||
|
</FieldSet>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mailSettingItems(): ItemList<Mithril.Children> {
|
||||||
|
const items = new ItemList<Mithril.Children>();
|
||||||
|
|
||||||
|
const fields = this.driverFields![this.setting('mail_driver')()];
|
||||||
|
const fieldKeys = Object.keys(fields);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'mail_from',
|
||||||
|
this.buildSettingComponent({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
setting: 'mail_from',
|
setting: 'mail_from',
|
||||||
label: app.translator.trans('core.admin.email.addresses_heading'),
|
label: app.translator.trans('core.admin.email.addresses_heading'),
|
||||||
})}
|
}),
|
||||||
|
80
|
||||||
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'mail_format',
|
||||||
|
this.buildSettingComponent({
|
||||||
|
type: 'select',
|
||||||
|
setting: 'mail_format',
|
||||||
|
options: {
|
||||||
|
multipart: app.translator.trans('core.admin.email.format.multipart_option'),
|
||||||
|
plain: app.translator.trans('core.admin.email.format.plain_option'),
|
||||||
|
html: app.translator.trans('core.admin.email.format.html_option'),
|
||||||
|
},
|
||||||
|
label: app.translator.trans('core.admin.email.format_heading'),
|
||||||
|
help: app.translator.trans('core.admin.email.format_help'),
|
||||||
|
}),
|
||||||
|
70
|
||||||
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'mail_driver',
|
||||||
|
<div>
|
||||||
{this.buildSettingComponent({
|
{this.buildSettingComponent({
|
||||||
type: 'select',
|
type: 'select',
|
||||||
setting: 'mail_driver',
|
setting: 'mail_driver',
|
||||||
@@ -104,16 +147,11 @@ export default class MailPage<CustomAttrs extends IPageAttrs = IPageAttrs> exten
|
|||||||
</div>
|
</div>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
)}
|
)}
|
||||||
{this.submitButton()}
|
</div>,
|
||||||
|
60
|
||||||
<FieldSet label={app.translator.trans('core.admin.email.send_test_mail_heading')} className="MailPage-MailSettings">
|
|
||||||
<div className="helpText">{app.translator.trans('core.admin.email.send_test_mail_text', { email: app.session.user!.email() })}</div>
|
|
||||||
<Button className="Button Button--primary" disabled={this.sendingTest || this.isChanged()} onclick={() => this.sendTestEmail()}>
|
|
||||||
{app.translator.trans('core.admin.email.send_test_mail_button')}
|
|
||||||
</Button>
|
|
||||||
</FieldSet>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTestEmail() {
|
sendTestEmail() {
|
||||||
|
@@ -126,6 +126,12 @@ core:
|
|||||||
description: "Configure the driver, settings and addresses your forum will use to send email."
|
description: "Configure the driver, settings and addresses your forum will use to send email."
|
||||||
driver_heading: Choose a Driver
|
driver_heading: Choose a Driver
|
||||||
driver_label: Driver
|
driver_label: Driver
|
||||||
|
format:
|
||||||
|
multipart_option: Multipart (recommended)
|
||||||
|
plain_option: Plain Text
|
||||||
|
html_option: HTML
|
||||||
|
format_heading: Outgoing Email Format
|
||||||
|
format_help: "Choose the format that outgoing emails will be sent in. The recommended option is <code>multipart</code>, as this will allow your user's email client to display the most appropriate format."
|
||||||
from_label: Sender
|
from_label: Sender
|
||||||
mail_encryption_label: Encryption
|
mail_encryption_label: Encryption
|
||||||
mail_host_label: Host
|
mail_host_label: Host
|
||||||
@@ -727,6 +733,16 @@ core:
|
|||||||
submit_button: => core.ref.save_changes
|
submit_button: => core.ref.save_changes
|
||||||
title: => core.ref.reset_your_password
|
title: => core.ref.reset_your_password
|
||||||
|
|
||||||
|
# Translations in this namespace are displayed by the email unsubscribe interface.
|
||||||
|
unsubscribe_email:
|
||||||
|
title: Unsubscribe Confirmation
|
||||||
|
return_to_forum: Back to {forumTitle}
|
||||||
|
confirm_button: Confirm
|
||||||
|
immediate_helptext: You will be immediately unsubscribed once you confirm.
|
||||||
|
confirm_message: "You have requested to unsubscribe from \"{type}\" email notifications from {forumTitle}."
|
||||||
|
success_message: "You have successfully unsubscribed from \"{type}\" notification from {forumTitle}. If you wish to receive them again, please [update your settings]({settingsLink})."
|
||||||
|
invalid_message: "This unsubscribe link is invalid or has already been used. For any changes in your email notifications from {forumTitle}, please [check your settings]({settingsLink})."
|
||||||
|
|
||||||
# Translations in this namespace are used in messages output by the API.
|
# Translations in this namespace are used in messages output by the API.
|
||||||
api:
|
api:
|
||||||
invalid_username_message: "The username may only contain letters, numbers, and dashes."
|
invalid_username_message: "The username may only contain letters, numbers, and dashes."
|
||||||
@@ -737,13 +753,30 @@ core:
|
|||||||
|
|
||||||
# Translations in this namespace are used in emails sent by the forum.
|
# Translations in this namespace are used in emails sent by the forum.
|
||||||
email:
|
email:
|
||||||
|
greeting: "Hey {displayName},"
|
||||||
|
signoff: "The {forumTitle} team"
|
||||||
|
|
||||||
|
# These translations are used by the "informational" email template.
|
||||||
|
informational:
|
||||||
|
default_title: "Information"
|
||||||
|
footer: "This email was sent to {userEmail} as an informational service related to your account on [{forumTitle}]({forumUrl})."
|
||||||
|
footer_plain: "This email was sent to {userEmail} as an informational service related to your account on {forumTitle}."
|
||||||
|
|
||||||
|
# These translations are used by the "notification" email template.
|
||||||
|
notification:
|
||||||
|
default_title: "Notification"
|
||||||
|
footer:
|
||||||
|
main_text: "This email was sent to {email} because you are subscribed to \"{type}\" notifications on [{forumTitle}]({forumUrl})."
|
||||||
|
main_text_plain: "This email was sent to {email} because you are subscribed to \"{type}\" notifications on {forumTitle}."
|
||||||
|
unsubscribe_text: "If you'd like to stop receiving this type of notification, [unsubscribe here]({unsubscribeLink})."
|
||||||
|
unsubscribe_text_plain: "If you'd like to stop receiving this type of notification, unsubscribe here: {unsubscribeLink}"
|
||||||
|
settings_text: "Manage your notification settings [here]({settingsLink})."
|
||||||
|
settings_text_plain: "Manage your notification settings here: {settingsLink}"
|
||||||
|
|
||||||
# These translations are used in emails sent when users register new accounts.
|
# These translations are used in emails sent when users register new accounts.
|
||||||
activate_account:
|
activate_account:
|
||||||
subject: Activate Your New Account
|
subject: Activate Your New Account
|
||||||
body: |
|
body: |
|
||||||
Hey {username}!
|
|
||||||
|
|
||||||
Someone (hopefully you!) has signed up to {forum} with this email address.
|
Someone (hopefully you!) has signed up to {forum} with this email address.
|
||||||
|
|
||||||
If this was you, simply click the following link and your account will be activated:
|
If this was you, simply click the following link and your account will be activated:
|
||||||
@@ -755,8 +788,6 @@ core:
|
|||||||
confirm_email:
|
confirm_email:
|
||||||
subject: Confirm Your New Email Address
|
subject: Confirm Your New Email Address
|
||||||
body: |
|
body: |
|
||||||
Hey {username}!
|
|
||||||
|
|
||||||
Someone (hopefully you!) has changed their email address on {forum} to this one.
|
Someone (hopefully you!) has changed their email address on {forum} to this one.
|
||||||
|
|
||||||
If this was you, simply click the following link and your email will be confirmed:
|
If this was you, simply click the following link and your email will be confirmed:
|
||||||
@@ -768,8 +799,6 @@ core:
|
|||||||
reset_password:
|
reset_password:
|
||||||
subject: => core.ref.reset_your_password
|
subject: => core.ref.reset_your_password
|
||||||
body: |
|
body: |
|
||||||
Hey {username}!
|
|
||||||
|
|
||||||
Someone (hopefully you!) has submitted a forgotten password request for your account on {forum}.
|
Someone (hopefully you!) has submitted a forgotten password request for your account on {forum}.
|
||||||
|
|
||||||
If this was you, click the following link to reset your password:
|
If this was you, click the following link to reset your password:
|
||||||
@@ -781,8 +810,6 @@ core:
|
|||||||
send_test:
|
send_test:
|
||||||
subject: Flarum Email Test
|
subject: Flarum Email Test
|
||||||
body: |
|
body: |
|
||||||
Hey {username}!
|
|
||||||
|
|
||||||
This is a test email to confirm that your Flarum email configuration is working properly.
|
This is a test email to confirm that your Flarum email configuration is working properly.
|
||||||
|
|
||||||
If this was you, this email means that your configuration works!
|
If this was you, this email means that your configuration works!
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Flarum\Database\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
return Migration::createTable(
|
||||||
|
'unsubscribe_tokens',
|
||||||
|
function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedInteger('user_id');
|
||||||
|
$table->string('email_type');
|
||||||
|
$table->string('token', 100)->unique();
|
||||||
|
$table->timestamp('unsubscribed_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
|
||||||
|
$table->index('user_id');
|
||||||
|
$table->index('email_type');
|
||||||
|
$table->index('token');
|
||||||
|
$table->index(['user_id', 'email_type']);
|
||||||
|
}
|
||||||
|
);
|
@@ -11,8 +11,10 @@ namespace Flarum\Api\Controller;
|
|||||||
|
|
||||||
use Flarum\Http\RequestUtil;
|
use Flarum\Http\RequestUtil;
|
||||||
use Flarum\Locale\TranslatorInterface;
|
use Flarum\Locale\TranslatorInterface;
|
||||||
|
use Flarum\Mail\Job\SendInformationalEmailJob;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
use Illuminate\Contracts\Mail\Mailer;
|
use Illuminate\Contracts\Mail\Mailer;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Contracts\Queue\Factory;
|
||||||
use Laminas\Diactoros\Response\EmptyResponse;
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
@@ -22,7 +24,9 @@ class SendTestMailController implements RequestHandlerInterface
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected Mailer $mailer,
|
protected Mailer $mailer,
|
||||||
protected TranslatorInterface $translator
|
protected TranslatorInterface $translator,
|
||||||
|
protected SettingsRepositoryInterface $settings,
|
||||||
|
protected Factory $queue
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,12 +35,16 @@ class SendTestMailController implements RequestHandlerInterface
|
|||||||
$actor = RequestUtil::getActor($request);
|
$actor = RequestUtil::getActor($request);
|
||||||
$actor->assertAdmin();
|
$actor->assertAdmin();
|
||||||
|
|
||||||
$body = $this->translator->trans('core.email.send_test.body', ['username' => $actor->username]);
|
$this->queue->connection('sync')->push(
|
||||||
|
new SendInformationalEmailJob(
|
||||||
$this->mailer->raw($body, function (Message $message) use ($actor) {
|
email: $actor->email,
|
||||||
$message->to($actor->email);
|
displayName: $actor->display_name,
|
||||||
$message->subject($this->translator->trans('core.email.send_test.subject'));
|
subject: $this->translator->trans('core.email.send_test.subject'),
|
||||||
});
|
body: $this->translator->trans('core.email.send_test.body'),
|
||||||
|
forumTitle: $this->settings->get('forum_title'),
|
||||||
|
bodyTitle: $this->translator->trans('core.email.send_test.subject')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return new EmptyResponse();
|
return new EmptyResponse();
|
||||||
}
|
}
|
||||||
|
@@ -204,4 +204,19 @@ class Formatter
|
|||||||
return $attributes;
|
return $attributes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a plain text string (with or without Markdown) to it's HTML equivalent.
|
||||||
|
*
|
||||||
|
* @param ?string $content
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function convert(?string $content): string
|
||||||
|
{
|
||||||
|
if (! $content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getRenderer()->render($this->getParser()->parse($content));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\Forum\Controller;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Flarum\Http\UrlGenerator;
|
||||||
|
use Flarum\Notification\UnsubscribeToken;
|
||||||
|
use Flarum\User\User;
|
||||||
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Laminas\Diactoros\Response\RedirectResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class UnsubscribeActionController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected Dispatcher $bus,
|
||||||
|
protected UrlGenerator $url
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Request $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$parsedBody = $request->getParsedBody();
|
||||||
|
$token = Arr::get($parsedBody, 'token');
|
||||||
|
$userId = Arr::get($parsedBody, 'userId');
|
||||||
|
|
||||||
|
/** @var UnsubscribeToken|null $unsubscribeRecord */
|
||||||
|
$unsubscribeRecord = UnsubscribeToken::where('user_id', $userId)
|
||||||
|
->where('token', $token)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($unsubscribeRecord && empty($unsubscribeRecord->unsubscribed_at)) {
|
||||||
|
$unsubscribeRecord->unsubscribed_at = Carbon::now();
|
||||||
|
$unsubscribeRecord->save();
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = User::find($userId);
|
||||||
|
$user->setNotificationPreference($unsubscribeRecord->email_type, 'email', false);
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RedirectResponse($this->url->to('forum')->base());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\Forum\Controller;
|
||||||
|
|
||||||
|
use Flarum\Http\Controller\AbstractHtmlController;
|
||||||
|
use Flarum\Http\UrlGenerator;
|
||||||
|
use Flarum\Locale\TranslatorInterface;
|
||||||
|
use Flarum\Notification\UnsubscribeToken;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Illuminate\Contracts\View\Factory;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
|
||||||
|
class UnsubscribeViewController extends AbstractHtmlController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected Factory $view,
|
||||||
|
protected UrlGenerator $url,
|
||||||
|
protected TranslatorInterface $translator,
|
||||||
|
protected SettingsRepositoryInterface $settings
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(Request $request): View
|
||||||
|
{
|
||||||
|
$userId = Arr::get($request->getQueryParams(), 'userId');
|
||||||
|
$token = Arr::get($request->getQueryParams(), 'token');
|
||||||
|
|
||||||
|
// Fetch the unsubscribe token record
|
||||||
|
/** @var UnsubscribeToken|null $unsubscribeRecord */
|
||||||
|
$unsubscribeRecord = UnsubscribeToken::where('user_id', $userId)
|
||||||
|
->where('token', $token)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$settingsLink = $this->url->to('forum')->route('settings');
|
||||||
|
$forumTitle = $this->settings->get('forum_title');
|
||||||
|
|
||||||
|
// If record exists and has not been used before
|
||||||
|
if ($unsubscribeRecord && empty($unsubscribeRecord->unsubscribed_at)) {
|
||||||
|
$view = 'flarum.forum::unsubscribe-confirmation';
|
||||||
|
$message = $this->translator->trans('core.views.unsubscribe_email.confirm_message', [
|
||||||
|
'settingsLink' => $settingsLink,
|
||||||
|
'forumTitle' => $forumTitle,
|
||||||
|
'type' => $unsubscribeRecord->email_type
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// If the token doesn't exist or has already been used
|
||||||
|
$view = 'flarum.forum::unsubscribe-error';
|
||||||
|
$message = $this->translator->trans('core.views.unsubscribe_email.invalid_message', [
|
||||||
|
'settingsLink' => $settingsLink,
|
||||||
|
'forumTitle' => $forumTitle
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->view
|
||||||
|
->make($view)
|
||||||
|
->with('message', $message)
|
||||||
|
->with('userId', $userId)
|
||||||
|
->with('token', $token)
|
||||||
|
->with('csrfToken', $request->getAttribute('session')->token());
|
||||||
|
}
|
||||||
|
}
|
@@ -147,7 +147,8 @@ class ForumServiceProvider extends AbstractServiceProvider
|
|||||||
|
|
||||||
$view->share([
|
$view->share([
|
||||||
'translator' => $container->make(TranslatorInterface::class),
|
'translator' => $container->make(TranslatorInterface::class),
|
||||||
'settings' => $container->make(SettingsRepositoryInterface::class)
|
'settings' => $container->make(SettingsRepositoryInterface::class),
|
||||||
|
'formatter' => $container->make(Formatter::class),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$events->listen(
|
$events->listen(
|
||||||
|
@@ -43,6 +43,18 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
|||||||
$route->toForum(Content\AssertRegistered::class)
|
$route->toForum(Content\AssertRegistered::class)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$map->get(
|
||||||
|
'/notifications/unsubscribe/{userId}/{token}',
|
||||||
|
'notifications.unsubscribe',
|
||||||
|
$route->toController(Controller\UnsubscribeViewController::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$map->post(
|
||||||
|
'/notifications/unsubscribe/confirm',
|
||||||
|
'notifications.unsubscribe.confirm',
|
||||||
|
$route->toController(Controller\UnsubscribeActionController::class)
|
||||||
|
);
|
||||||
|
|
||||||
$map->get(
|
$map->get(
|
||||||
'/logout',
|
'/logout',
|
||||||
'logout',
|
'logout',
|
||||||
|
@@ -58,6 +58,7 @@ class WriteSettings implements Step
|
|||||||
'forum_title' => 'A new Flarum forum',
|
'forum_title' => 'A new Flarum forum',
|
||||||
'forum_description' => '',
|
'forum_description' => '',
|
||||||
'mail_driver' => 'mail',
|
'mail_driver' => 'mail',
|
||||||
|
'mail_format' => 'multipart',
|
||||||
'mail_from' => 'noreply@localhost',
|
'mail_from' => 'noreply@localhost',
|
||||||
'slug_driver_Flarum\User\User' => 'default',
|
'slug_driver_Flarum\User\User' => 'default',
|
||||||
'theme_colored_header' => '0',
|
'theme_colored_header' => '0',
|
||||||
|
49
framework/core/src/Mail/Job/SendInformationalEmailJob.php
Normal file
49
framework/core/src/Mail/Job/SendInformationalEmailJob.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\Mail\Job;
|
||||||
|
|
||||||
|
use Flarum\Queue\AbstractJob;
|
||||||
|
use Illuminate\Contracts\Mail\Mailer;
|
||||||
|
use Illuminate\Mail\Message;
|
||||||
|
|
||||||
|
class SendInformationalEmailJob extends AbstractJob
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $email,
|
||||||
|
private readonly string $displayName,
|
||||||
|
private readonly string $subject,
|
||||||
|
private readonly string $body,
|
||||||
|
private readonly string $forumTitle,
|
||||||
|
private readonly ?string $bodyTitle = null,
|
||||||
|
protected array $views = [
|
||||||
|
'text' => 'flarum.forum::email.plain.information.base',
|
||||||
|
'html' => 'flarum.forum::email.html.information.base'
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Mailer $mailer): void
|
||||||
|
{
|
||||||
|
$forumTitle = $this->forumTitle;
|
||||||
|
$infoContent = $this->body;
|
||||||
|
$userEmail = $this->email;
|
||||||
|
$title = $this->bodyTitle;
|
||||||
|
$username = $this->displayName;
|
||||||
|
|
||||||
|
$mailer->send(
|
||||||
|
$this->views,
|
||||||
|
compact('forumTitle', 'infoContent', 'userEmail', 'title', 'username'),
|
||||||
|
function (Message $message) {
|
||||||
|
$message->to($this->email);
|
||||||
|
$message->subject($this->subject);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Flarum.
|
|
||||||
*
|
|
||||||
* For detailed copyright and license information, please view the
|
|
||||||
* LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Flarum\Mail\Job;
|
|
||||||
|
|
||||||
use Flarum\Queue\AbstractJob;
|
|
||||||
use Illuminate\Contracts\Mail\Mailer;
|
|
||||||
use Illuminate\Mail\Message;
|
|
||||||
|
|
||||||
class SendRawEmailJob extends AbstractJob
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly string $email,
|
|
||||||
private readonly string $subject,
|
|
||||||
private readonly string $body
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Mailer $mailer): void
|
|
||||||
{
|
|
||||||
$mailer->raw($this->body, function (Message $message) {
|
|
||||||
$message->to($this->email);
|
|
||||||
$message->subject($this->subject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,7 +14,6 @@ use Flarum\Settings\SettingsRepositoryInterface;
|
|||||||
use Illuminate\Contracts\Container\Container;
|
use Illuminate\Contracts\Container\Container;
|
||||||
use Illuminate\Contracts\Mail\Mailer as MailerContract;
|
use Illuminate\Contracts\Mail\Mailer as MailerContract;
|
||||||
use Illuminate\Contracts\Validation\Factory;
|
use Illuminate\Contracts\Validation\Factory;
|
||||||
use Illuminate\Mail\Mailer;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Symfony\Component\Mailer\Transport\TransportInterface;
|
use Symfony\Component\Mailer\Transport\TransportInterface;
|
||||||
|
|
||||||
@@ -62,18 +61,20 @@ class MailServiceProvider extends AbstractServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
$this->container->singleton('mailer', function (Container $container): MailerContract {
|
$this->container->singleton('mailer', function (Container $container): MailerContract {
|
||||||
|
$settings = $container->make(SettingsRepositoryInterface::class);
|
||||||
|
|
||||||
$mailer = new Mailer(
|
$mailer = new Mailer(
|
||||||
'flarum',
|
'flarum',
|
||||||
$container['view'],
|
$container['view'],
|
||||||
$container['symfony.mailer.transport'],
|
$container['symfony.mailer.transport'],
|
||||||
$container['events']
|
$container['events'],
|
||||||
|
$settings,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($container->bound('queue')) {
|
if ($container->bound('queue')) {
|
||||||
$mailer->setQueue($container->make('queue'));
|
$mailer->setQueue($container->make('queue'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$settings = $container->make(SettingsRepositoryInterface::class);
|
|
||||||
$mailer->alwaysFrom($settings->get('mail_from'), $settings->get('forum_title'));
|
$mailer->alwaysFrom($settings->get('mail_from'), $settings->get('forum_title'));
|
||||||
|
|
||||||
return $mailer;
|
return $mailer;
|
||||||
|
46
framework/core/src/Mail/Mailer.php
Normal file
46
framework/core/src/Mail/Mailer.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\Mail;
|
||||||
|
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Illuminate\Contracts\View\Factory;
|
||||||
|
use Illuminate\Mail\Mailer as SymfonyMailer;
|
||||||
|
use Symfony\Component\Mailer\Transport\TransportInterface;
|
||||||
|
|
||||||
|
class Mailer extends SymfonyMailer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
string $name,
|
||||||
|
Factory $views,
|
||||||
|
TransportInterface $transport,
|
||||||
|
Dispatcher $events = null,
|
||||||
|
protected SettingsRepositoryInterface $settings
|
||||||
|
) {
|
||||||
|
parent::__construct($name, $views, $transport, $events);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send($view, array $data = [], $callback = null)
|
||||||
|
{
|
||||||
|
$emailType = $this->settings->get('mail_format');
|
||||||
|
|
||||||
|
switch ($emailType) {
|
||||||
|
case 'html':
|
||||||
|
unset($view['text']);
|
||||||
|
break;
|
||||||
|
case 'plain':
|
||||||
|
unset($view['html']);
|
||||||
|
break;
|
||||||
|
// case 'multipart' is the default, where Flarum will send both HTML and text versions of emails, so that the recipient's email client can choose which one to display.
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::send($view, $data, $callback);
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace Flarum\Notification\Job;
|
namespace Flarum\Notification\Job;
|
||||||
|
|
||||||
|
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||||
use Flarum\Notification\MailableInterface;
|
use Flarum\Notification\MailableInterface;
|
||||||
use Flarum\Notification\NotificationMailer;
|
use Flarum\Notification\NotificationMailer;
|
||||||
use Flarum\Queue\AbstractJob;
|
use Flarum\Queue\AbstractJob;
|
||||||
@@ -17,7 +18,7 @@ use Flarum\User\User;
|
|||||||
class SendEmailNotificationJob extends AbstractJob
|
class SendEmailNotificationJob extends AbstractJob
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly MailableInterface $blueprint,
|
private readonly MailableInterface&BlueprintInterface $blueprint,
|
||||||
private readonly User $recipient
|
private readonly User $recipient
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@@ -14,9 +14,16 @@ use Flarum\Locale\TranslatorInterface;
|
|||||||
interface MailableInterface
|
interface MailableInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get the name of the view to construct a notification email with.
|
* Get the names of the views to construct a notification email with.
|
||||||
|
*
|
||||||
|
* To provide the best experience for the user, Flarum expects both a `text` and `html` view.
|
||||||
|
*
|
||||||
|
* @return array{
|
||||||
|
* text: string,
|
||||||
|
* html: string
|
||||||
|
* }
|
||||||
*/
|
*/
|
||||||
public function getEmailView(): string|array;
|
public function getEmailViews(): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the subject line for a notification email.
|
* Get the subject line for a notification email.
|
||||||
|
@@ -9,34 +9,70 @@
|
|||||||
|
|
||||||
namespace Flarum\Notification;
|
namespace Flarum\Notification;
|
||||||
|
|
||||||
|
use Flarum\Http\UrlGenerator;
|
||||||
use Flarum\Locale\TranslatorInterface;
|
use Flarum\Locale\TranslatorInterface;
|
||||||
|
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
use Flarum\User\User;
|
use Flarum\User\User;
|
||||||
use Illuminate\Contracts\Mail\Mailer;
|
use Illuminate\Contracts\Mail\Mailer;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class NotificationMailer
|
class NotificationMailer
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected Mailer $mailer,
|
protected Mailer $mailer,
|
||||||
protected TranslatorInterface $translator,
|
protected TranslatorInterface $translator,
|
||||||
protected SettingsRepositoryInterface $settings
|
protected SettingsRepositoryInterface $settings,
|
||||||
|
protected UrlGenerator $url
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function send(MailableInterface $blueprint, User $user): void
|
public function send(MailableInterface&BlueprintInterface $blueprint, User $user): void
|
||||||
{
|
{
|
||||||
// Ensure that notifications are delivered to the user in their default language, if they've selected one.
|
// Ensure that notifications are delivered to the user in their default language, if they've selected one.
|
||||||
// If the selected locale is no longer available, the forum default will be used instead.
|
|
||||||
$this->translator->setLocale($user->getPreference('locale') ?? $this->settings->get('default_locale'));
|
$this->translator->setLocale($user->getPreference('locale') ?? $this->settings->get('default_locale'));
|
||||||
|
|
||||||
|
// Generate and save the unsubscribe token:
|
||||||
|
$unsubscribeRecord = UnsubscribeToken::generate($user->id, $blueprint::getType());
|
||||||
|
$unsubscribeRecord->save();
|
||||||
|
|
||||||
|
$unsubscribeLink = $this->url->to('forum')->route('notifications.unsubscribe', ['userId' => $user->id, 'token' => $unsubscribeRecord->token]);
|
||||||
|
$settingsLink = $this->url->to('forum')->route('settings');
|
||||||
|
$type = $blueprint::getType();
|
||||||
|
$forumTitle = $this->settings->get('forum_title');
|
||||||
|
$username = $user->display_name;
|
||||||
|
$userEmail = $user->email;
|
||||||
|
|
||||||
$this->mailer->send(
|
$this->mailer->send(
|
||||||
$blueprint->getEmailView(),
|
$this->getEmailViews($blueprint),
|
||||||
compact('blueprint', 'user'),
|
compact('blueprint', 'user', 'unsubscribeLink', 'settingsLink', 'type', 'forumTitle', 'username', 'userEmail'),
|
||||||
function (Message $message) use ($blueprint, $user) {
|
function (Message $message) use ($blueprint, $user) {
|
||||||
$message->to($user->email, $user->display_name)
|
$message->to($user->email, $user->display_name)
|
||||||
->subject($blueprint->getEmailSubject($this->translator));
|
->subject($blueprint->getEmailSubject($this->translator));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrives the email views from the blueprint, and enforces that both a
|
||||||
|
* plain text and HTML view are provided.
|
||||||
|
*
|
||||||
|
* @param MailableInterface&BlueprintInterface $blueprint
|
||||||
|
* @return array{
|
||||||
|
* text: string,
|
||||||
|
* html: string
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
protected function getEmailViews(MailableInterface&BlueprintInterface $blueprint): array
|
||||||
|
{
|
||||||
|
$views = $blueprint->getEmailViews();
|
||||||
|
|
||||||
|
// check that both text and html views are provided
|
||||||
|
if (! Arr::has($views, ['text', 'html'])) {
|
||||||
|
throw new \InvalidArgumentException('Both text and html views must be provided to send an email notification of type'.$blueprint::getType().'.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $views;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
57
framework/core/src/Notification/UnsubscribeToken.php
Normal file
57
framework/core/src/Notification/UnsubscribeToken.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\Notification;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Flarum\Database\AbstractModel;
|
||||||
|
use Flarum\User\User;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $user_id
|
||||||
|
* @property string $email_type
|
||||||
|
* @property string $token
|
||||||
|
* @property \Carbon\Carbon $unsubscribed_at
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property-read \Flarum\User\User|null $user
|
||||||
|
*/
|
||||||
|
class UnsubscribeToken extends AbstractModel
|
||||||
|
{
|
||||||
|
protected $table = 'unsubscribe_tokens';
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'user_id' => 'int',
|
||||||
|
'unsubscribed_at' => 'datetime'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $fillable = ['user_id', 'email_type', 'token'];
|
||||||
|
|
||||||
|
const TOKEN_LENGTH = 60;
|
||||||
|
|
||||||
|
public static function generate(int $userId, string $emailType): static
|
||||||
|
{
|
||||||
|
$token = new static;
|
||||||
|
|
||||||
|
$token->token = Str::random(self::TOKEN_LENGTH);
|
||||||
|
$token->user_id = $userId;
|
||||||
|
$token->email_type = $emailType;
|
||||||
|
$token->created_at = Carbon::now();
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
@@ -18,7 +18,7 @@ class QueueFactory implements Factory
|
|||||||
/**
|
/**
|
||||||
* The cached queue instance.
|
* The cached queue instance.
|
||||||
*/
|
*/
|
||||||
private ?Queue $queue;
|
private ?Queue $queue = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expects a callback that will be called to instantiate the queue adapter,
|
* Expects a callback that will be called to instantiate the queue adapter,
|
||||||
|
@@ -24,6 +24,7 @@ class SettingsServiceProvider extends AbstractServiceProvider
|
|||||||
return new Collection([
|
return new Collection([
|
||||||
'theme_primary_color' => '#4D698E',
|
'theme_primary_color' => '#4D698E',
|
||||||
'theme_secondary_color' => '#4D698E',
|
'theme_secondary_color' => '#4D698E',
|
||||||
|
'mail_format' => 'multipart',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -9,7 +9,8 @@
|
|||||||
|
|
||||||
namespace Flarum\User;
|
namespace Flarum\User;
|
||||||
|
|
||||||
use Flarum\Mail\Job\SendRawEmailJob;
|
use Flarum\Mail\Job\SendInformationalEmailJob;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
trait AccountActivationMailerTrait
|
trait AccountActivationMailerTrait
|
||||||
{
|
{
|
||||||
@@ -38,6 +39,12 @@ trait AccountActivationMailerTrait
|
|||||||
$body = $this->translator->trans('core.email.activate_account.body', $data);
|
$body = $this->translator->trans('core.email.activate_account.body', $data);
|
||||||
$subject = $this->translator->trans('core.email.activate_account.subject');
|
$subject = $this->translator->trans('core.email.activate_account.subject');
|
||||||
|
|
||||||
$this->queue->push(new SendRawEmailJob($user->email, $subject, $body));
|
$this->queue->push(new SendInformationalEmailJob(
|
||||||
|
email: $user->email,
|
||||||
|
subject: $subject,
|
||||||
|
body: $body,
|
||||||
|
forumTitle: Arr::get($data, 'forum'),
|
||||||
|
displayName: Arr::get($data, 'username')
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,10 +11,11 @@ namespace Flarum\User;
|
|||||||
|
|
||||||
use Flarum\Http\UrlGenerator;
|
use Flarum\Http\UrlGenerator;
|
||||||
use Flarum\Locale\TranslatorInterface;
|
use Flarum\Locale\TranslatorInterface;
|
||||||
use Flarum\Mail\Job\SendRawEmailJob;
|
use Flarum\Mail\Job\SendInformationalEmailJob;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
use Flarum\User\Event\EmailChangeRequested;
|
use Flarum\User\Event\EmailChangeRequested;
|
||||||
use Illuminate\Contracts\Queue\Queue;
|
use Illuminate\Contracts\Queue\Queue;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class EmailConfirmationMailer
|
class EmailConfirmationMailer
|
||||||
{
|
{
|
||||||
@@ -34,7 +35,13 @@ class EmailConfirmationMailer
|
|||||||
$body = $this->translator->trans('core.email.confirm_email.body', $data);
|
$body = $this->translator->trans('core.email.confirm_email.body', $data);
|
||||||
$subject = $this->translator->trans('core.email.confirm_email.subject');
|
$subject = $this->translator->trans('core.email.confirm_email.subject');
|
||||||
|
|
||||||
$this->queue->push(new SendRawEmailJob($email, $subject, $body));
|
$this->queue->push(new SendInformationalEmailJob(
|
||||||
|
email: $email,
|
||||||
|
subject:$subject,
|
||||||
|
body: $body,
|
||||||
|
forumTitle: Arr::get($data, 'forum'),
|
||||||
|
displayName: Arr::get($data, 'username')
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generateToken(User $user, string $email): EmailToken
|
protected function generateToken(User $user, string $email): EmailToken
|
||||||
|
@@ -11,12 +11,13 @@ namespace Flarum\User\Job;
|
|||||||
|
|
||||||
use Flarum\Http\UrlGenerator;
|
use Flarum\Http\UrlGenerator;
|
||||||
use Flarum\Locale\TranslatorInterface;
|
use Flarum\Locale\TranslatorInterface;
|
||||||
use Flarum\Mail\Job\SendRawEmailJob;
|
use Flarum\Mail\Job\SendInformationalEmailJob;
|
||||||
use Flarum\Queue\AbstractJob;
|
use Flarum\Queue\AbstractJob;
|
||||||
use Flarum\Settings\SettingsRepositoryInterface;
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
use Flarum\User\PasswordToken;
|
use Flarum\User\PasswordToken;
|
||||||
use Flarum\User\UserRepository;
|
use Flarum\User\UserRepository;
|
||||||
use Illuminate\Contracts\Queue\Queue;
|
use Illuminate\Contracts\Queue\Queue;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class RequestPasswordResetJob extends AbstractJob
|
class RequestPasswordResetJob extends AbstractJob
|
||||||
{
|
{
|
||||||
@@ -50,6 +51,13 @@ class RequestPasswordResetJob extends AbstractJob
|
|||||||
$body = $translator->trans('core.email.reset_password.body', $data);
|
$body = $translator->trans('core.email.reset_password.body', $data);
|
||||||
$subject = $translator->trans('core.email.reset_password.subject');
|
$subject = $translator->trans('core.email.reset_password.subject');
|
||||||
|
|
||||||
$queue->push(new SendRawEmailJob($user->email, $subject, $body));
|
$queue->push(new SendInformationalEmailJob(
|
||||||
|
email: $user->email,
|
||||||
|
displayName: Arr::get($data, 'username'),
|
||||||
|
subject: $subject,
|
||||||
|
body: $body,
|
||||||
|
forumTitle: Arr::get($data, 'forum'),
|
||||||
|
bodyTitle: $subject
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -687,4 +687,14 @@ class User extends AbstractModel
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of a notification preference.
|
||||||
|
*/
|
||||||
|
public function setNotificationPreference(string $type, string $method, bool $value): static
|
||||||
|
{
|
||||||
|
$this->setPreference(static::getNotificationPreferenceKey($type, $method), $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
84
framework/core/views/email/html/base.blade.php
Normal file
84
framework/core/views/email/html/base.blade.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@inject('url', 'Flarum\Http\UrlGenerator')
|
||||||
|
|
||||||
|
@php
|
||||||
|
$primaryColor = $settings->get('theme_primary_color');
|
||||||
|
$secondaryColor = $settings->get('theme_secondary_color');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ $translator->getLocale() }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ $title ?? 'Flarum Email' }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e1e1e1;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
.content-preview {
|
||||||
|
background-color: #f7f7f7; /* Light gray background */
|
||||||
|
padding: 15px;
|
||||||
|
margin: 15px 0;
|
||||||
|
border: 1px solid #e1e1e1;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.signoff {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="Header-title">
|
||||||
|
<a href="{{ $url->to('forum')->base() }}" id="home-link">
|
||||||
|
@if ($settings->get('logo_path'))
|
||||||
|
<img src="{{ $url->to('forum')->base() . '/assets/' . $settings->get('logo_path') }}" alt="{{ $settings->get('forum_title') }}" class="Header-logo">
|
||||||
|
@else
|
||||||
|
{{ $settings->get('forum_title') }}
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@yield('header')
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
@if(!isset($greeting) || $greeting !== false)
|
||||||
|
<div class="greeting">
|
||||||
|
<p>{!! $translator->trans('core.email.greeting', ['displayName' => $username]) !!}</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="main-content">
|
||||||
|
@yield('content')
|
||||||
|
</div>
|
||||||
|
@if(!isset($signoff) || $signoff !== false)
|
||||||
|
<div class="signoff">
|
||||||
|
<p>{!! $translator->trans('core.email.signoff', ['forumTitle' => $settings->get('forum_title')]) !!}</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
@yield('footer')
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
22
framework/core/views/email/html/information/base.blade.php
Normal file
22
framework/core/views/email/html/information/base.blade.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@extends('flarum.forum::email.html.base')
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
<h2>{{ $title ?? $translator->trans('core.email.informational.default_title') }}</h2>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@if(isset($infoContent))
|
||||||
|
<p>{!! $formatter->convert($infoContent) !!}</p>
|
||||||
|
@else
|
||||||
|
@yield('informationContent')
|
||||||
|
@endif
|
||||||
|
@hasSection('contentPreview')
|
||||||
|
<div class="content-preview">
|
||||||
|
@yield('contentPreview')
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p>{!! $formatter->convert($translator->trans('core.email.informational.footer', ['userEmail' => $userEmail, 'forumUrl' => $url->to('forum')->base(), 'forumTitle' => $settings->get('forum_title')])) !!}</p>
|
||||||
|
@endsection
|
20
framework/core/views/email/html/notification/base.blade.php
Normal file
20
framework/core/views/email/html/notification/base.blade.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@extends('flarum.forum::email.html.base')
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
<h2>{{ $title ?? $translator->trans('core.email.notification.default_title') }}</h2>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@yield('notificationContent')
|
||||||
|
@hasSection('contentPreview')
|
||||||
|
<div class="content-preview">
|
||||||
|
@yield('contentPreview')
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
<p>{!! $formatter->convert($translator->trans('core.email.notification.footer.main_text', ['email' => $user->email, 'type' => $type, 'forumUrl' => $url->to('forum')->base(), 'forumTitle' => $settings->get('forum_title')])) !!}</p>
|
||||||
|
<p>{!! $formatter->convert($translator->trans('core.email.notification.footer.unsubscribe_text', ['unsubscribeLink' => $unsubscribeLink])) !!}</p>
|
||||||
|
<p>{!! $formatter->convert($translator->trans('core.email.notification.footer.settings_text', ['settingsLink' => $settingsLink])) !!}</p>
|
||||||
|
@endsection
|
14
framework/core/views/email/plain/base.blade.php
Normal file
14
framework/core/views/email/plain/base.blade.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@yield('header')
|
||||||
|
|
||||||
|
@if(!isset($greeting) || $greeting !== false)
|
||||||
|
{{ $translator->trans('core.email.greeting', ['displayName' => $username]) }}
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@yield('content')
|
||||||
|
|
||||||
|
@if(!isset($signoff) || $signoff !== false)
|
||||||
|
- {{ $translator->trans('core.email.signoff', ['forumTitle' => $settings->get('forum_title')]) }} -
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
|
@yield('footer')
|
13
framework/core/views/email/plain/information/base.blade.php
Normal file
13
framework/core/views/email/plain/information/base.blade.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@extends('flarum.forum::email.plain.base')
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ $title ?? $translator->trans('core.email.informational.default_title') }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
{{ $infoContent ?? '' }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
{!! $translator->trans('core.email.informational.footer_plain', ['userEmail' => $userEmail, 'forumTitle' => $forumTitle]) !!}
|
||||||
|
@endsection
|
17
framework/core/views/email/plain/notification/base.blade.php
Normal file
17
framework/core/views/email/plain/notification/base.blade.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@extends('flarum.forum::email.plain.base')
|
||||||
|
|
||||||
|
@section('header')
|
||||||
|
{{ $title ?? $translator->trans('core.email.notification.default_title') }}
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@yield('notificationContent')
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('footer')
|
||||||
|
{!! $translator->trans('core.email.notification.footer.main_text_plain', ['email' => $user->email, 'type' => $type, 'forumTitle' => $forumTitle]) !!}
|
||||||
|
|
||||||
|
{!! $translator->trans('core.email.notification.footer.unsubscribe_text_plain', ['unsubscribeLink' => $unsubscribeLink]) !!}
|
||||||
|
|
||||||
|
{!! $translator->trans('core.email.notification.footer.settings_text_plain', ['settingsLink' => $settingsLink]) !!}
|
||||||
|
@endsection
|
22
framework/core/views/unsubscribe-confirmation.blade.php
Normal file
22
framework/core/views/unsubscribe-confirmation.blade.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@extends('flarum.forum::layouts.basic')
|
||||||
|
|
||||||
|
@section('title', $translator->trans('core.views.unsubscribe_email.title'))
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h2>{{ $translator->trans('core.views.unsubscribe_email.title') }}</h2>
|
||||||
|
<p>{!! $formatter->convert($message) !!}</p>
|
||||||
|
<p>{{ $translator->trans('core.views.unsubscribe_email.immediate_helptext') }}</p>
|
||||||
|
|
||||||
|
<form action="{{ $url->to('forum')->route('notifications.unsubscribe.confirm') }}" method="post">
|
||||||
|
<input type="hidden" name="userId" value="{{ $userId }}">
|
||||||
|
<input type="hidden" name="token" value="{{ $token }}">
|
||||||
|
<input type="hidden" name="csrfToken" value="{{ $csrfToken }}">
|
||||||
|
<button type="submit" class="button Button Button--primary">
|
||||||
|
{{ $translator->trans('core.views.unsubscribe_email.confirm_button') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
<a href="{{ $url->to('forum')->base() }}">
|
||||||
|
{{ $translator->trans('core.views.unsubscribe_email.return_to_forum', ['forumTitle' => $settings->get('forum_title')]) }}
|
||||||
|
</a>
|
||||||
|
@endsection
|
12
framework/core/views/unsubscribe-error.blade.php
Normal file
12
framework/core/views/unsubscribe-error.blade.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
@extends('flarum.forum::layouts.basic')
|
||||||
|
|
||||||
|
@section('title', $translator->trans('core.views.unsubscribe_email_error.title'))
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h2>{{ $translator->trans('core.views.unsubscribe_email_error.title') }}</h2>
|
||||||
|
<p>{!! $message !!}</p>
|
||||||
|
|
||||||
|
<a href="{{ $url->to('forum')->base() }}">
|
||||||
|
{{ $translator->trans('core.views.unsubscribe_email.return_to_forum', ['forumTitle' => $settings->get('forum_title')]) }}
|
||||||
|
</a>
|
||||||
|
@endsection
|
Reference in New Issue
Block a user