replaced mailer library, added email templates (#1462)

This commit is contained in:
Kevin Papst
2020-02-12 16:05:13 +01:00
committed by GitHub
parent 1b4f4bba22
commit 057c824e9f
30 changed files with 2738 additions and 340 deletions

View File

@@ -2,8 +2,6 @@
# Copy this file to .env file for development, create environment variables when deploying to production # Copy this file to .env file for development, create environment variables when deploying to production
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
MAILER_FROM=kimai@example.com
###> symfony/framework-bundle ### ###> symfony/framework-bundle ###
APP_ENV=prod APP_ENV=prod
APP_SECRET=change_this_to_something_unique APP_SECRET=change_this_to_something_unique
@@ -19,13 +17,18 @@ APP_SECRET=change_this_to_something_unique
DATABASE_URL=sqlite:///%kernel.project_dir%/var/data/kimai.sqlite DATABASE_URL=sqlite:///%kernel.project_dir%/var/data/kimai.sqlite
###< doctrine/doctrine-bundle ### ###< doctrine/doctrine-bundle ###
###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:password@localhost"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###
###> nelmio/cors-bundle ### ###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$ CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
###< nelmio/cors-bundle ### ###< nelmio/cors-bundle ###
### Email configuration
# SMTP: smtp://localhost:25?encryption=&auth_mode=
# Google: gmail://username:password@default
# Amazon: ses://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
# Mailchimp: mandrill://KEY@default
# Mailgun: mailgun://KEY:DOMAIN@default
# Postmark: postmark://ID@default
# Sendgrid: sendgrid://KEY@default
# Disable emails: null://null
MAILER_URL=null://null
MAILER_FROM=kimai@example.com

View File

@@ -10,7 +10,9 @@ Perform EACH version specific task between your version and the new one, otherwi
## [1.8](https://github.com/kevinpapst/kimai2/releases/tag/1.8) ## [1.8](https://github.com/kevinpapst/kimai2/releases/tag/1.8)
New permissions: - New mailer library: check if emails are still working (eg. by using the "password forgotten" function) or if you need to adjust your configuration, [see docs at symfony.com](https://symfony.com/doc/current/components/mailer.html#transport)
Permission changes:
- `comments_create_customer` - NEW: permission that allows to add new comments for customers - `comments_create_customer` - NEW: permission that allows to add new comments for customers
- `comments_create_team_customer` - NEW: permission that allows to add new comments for team members of the current customer - `comments_create_team_customer` - NEW: permission that allows to add new comments for team members of the current customer

1348
assets/css/emails.css Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,9 @@
"ext-pdo": "*", "ext-pdo": "*",
"ext-zip": "*", "ext-zip": "*",
"beberlei/doctrineextensions": "^1.2", "beberlei/doctrineextensions": "^1.2",
"doctrine/doctrine-bundle": "^2.0",
"doctrine/doctrine-migrations-bundle": "^2.1",
"doctrine/orm": "^2.7",
"erusev/parsedown": "^1.6", "erusev/parsedown": "^1.6",
"friendsofsymfony/rest-bundle": "^2.5", "friendsofsymfony/rest-bundle": "^2.5",
"friendsofsymfony/user-bundle": "dev-master", "friendsofsymfony/user-bundle": "dev-master",
@@ -29,6 +32,7 @@
"kimai/kimai2-composer": "^0.1", "kimai/kimai2-composer": "^0.1",
"laravolt/avatar": "^3.0", "laravolt/avatar": "^3.0",
"league/csv": "^9.4", "league/csv": "^9.4",
"league/html-to-markdown": "^4.9",
"mpdf/mpdf": "^8.0", "mpdf/mpdf": "^8.0",
"nelmio/api-doc-bundle": "^3.2", "nelmio/api-doc-bundle": "^3.2",
"nelmio/cors-bundle": "^1.5", "nelmio/cors-bundle": "^1.5",
@@ -39,26 +43,33 @@
"phpoffice/phpword": "^0.17", "phpoffice/phpword": "^0.17",
"psr/log": "^1.1", "psr/log": "^1.1",
"sensio/framework-extra-bundle": "^5.2", "sensio/framework-extra-bundle": "^5.2",
"symfony/asset": "^4.0", "symfony/amazon-mailer": "^4.4",
"symfony/console": "^4.0", "symfony/asset": "^4.4",
"symfony/dotenv": "^4.0", "symfony/console": "^4.4",
"symfony/expression-language": "^4.0", "symfony/dotenv": "^4.4",
"symfony/expression-language": "^4.4",
"symfony/flex": "^1.0", "symfony/flex": "^1.0",
"symfony/form": "^4.0", "symfony/form": "^4.4",
"symfony/framework-bundle": "^4.0", "symfony/framework-bundle": "^4.4",
"symfony/intl": "^4.0", "symfony/google-mailer": "^4.4",
"symfony/intl": "^4.4",
"symfony/mailchimp-mailer": "^4.4",
"symfony/mailer": "^4.4",
"symfony/mailgun-mailer": "^4.4",
"symfony/monolog-bundle": "^3.4", "symfony/monolog-bundle": "^3.4",
"symfony/orm-pack": "^1.0", "symfony/postmark-mailer": "^4.4",
"symfony/security-bundle": "^4.0", "symfony/security-bundle": "^4.4",
"symfony/security-csrf": "^4.0", "symfony/security-csrf": "^4.4",
"symfony/serializer": "^4.0", "symfony/sendgrid-mailer": "^4.4",
"symfony/swiftmailer-bundle": "^3.2", "symfony/serializer": "^4.4",
"symfony/translation": "^4.0", "symfony/translation": "^4.4",
"symfony/twig-bundle": "^4.0", "symfony/twig-bundle": "^4.4",
"symfony/validator": "^4.0", "symfony/validator": "^4.4",
"symfony/webpack-encore-bundle": "^1.5", "symfony/webpack-encore-bundle": "^1.5",
"symfony/yaml": "^4.0", "symfony/yaml": "^4.4",
"twig/cssinliner-extra": "^3.0",
"twig/extra-bundle": "^3.0", "twig/extra-bundle": "^3.0",
"twig/inky-extra": "^3.0",
"twig/intl-extra": "^3.0", "twig/intl-extra": "^3.0",
"twig/string-extra": "^3.0" "twig/string-extra": "^3.0"
}, },
@@ -72,13 +83,13 @@
"phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-phpunit": "^0.12",
"phpstan/phpstan-symfony": "^0.12", "phpstan/phpstan-symfony": "^0.12",
"phpunit/phpunit": "^8.0", "phpunit/phpunit": "^8.0",
"symfony/browser-kit": "^4.0", "symfony/browser-kit": "^4.4",
"symfony/css-selector": "^4.0", "symfony/css-selector": "^4.4",
"symfony/debug-bundle": "^4.0", "symfony/debug-bundle": "^4.4",
"symfony/maker-bundle": "^1.12", "symfony/maker-bundle": "^1.12",
"symfony/phpunit-bridge": "^4.0", "symfony/phpunit-bridge": "^4.4",
"symfony/profiler-pack": "^1.0", "symfony/profiler-pack": "^1.0",
"symfony/var-dumper": "^4.0" "symfony/var-dumper": "^4.4"
}, },
"repositories": [ "repositories": [
{ {

1015
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,6 @@ return [
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],

View File

@@ -0,0 +1,3 @@
framework:
mailer:
dsn: 'null://null'

View File

@@ -1,4 +0,0 @@
# See https://symfony.com/doc/current/email/dev_environment.html
swiftmailer:
# send all emails to a specific address
# delivery_addresses: ['me@example.com']

View File

@@ -8,18 +8,8 @@ fos_user:
registration: registration:
confirmation: confirmation:
enabled: false enabled: false
template: '@FOSUser/Registration/email.txt.twig'
resetting: resetting:
retry_ttl: 7200 retry_ttl: 7200
token_ttl: 86400 token_ttl: 86400
email:
template: '@FOSUser/Resetting/email.txt.twig'
service: service:
mailer: fos_user.mailer.twig_swift mailer: App\Mail\UserMails
# group:
# group_class: ~ # Required when using groups
# group_manager: fos_user.group_manager.default
# form:
# type: FOS\UserBundle\Form\Type\GroupFormType # or 'fos_user_group' on Symfony < 2.8
# name: fos_user_group_form
# validation_groups: [Registration, Default]

View File

@@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_URL)%'

View File

@@ -1,3 +0,0 @@
swiftmailer:
url: '%env(MAILER_URL)%'
spool: { type: 'memory' }

View File

@@ -0,0 +1,3 @@
framework:
mailer:
dsn: 'null://null'

View File

@@ -1,2 +0,0 @@
swiftmailer:
disable_delivery: true

View File

@@ -4,4 +4,7 @@ twig:
strict_variables: '%kernel.debug%' strict_variables: '%kernel.debug%'
form_themes: form_themes:
- 'form/kimai-theme.html.twig' - 'form/kimai-theme.html.twig'
exception_controller: null exception_controller: null
paths:
# used in templates/emails/layout.html.twig
'%kernel.project_dir%/assets/css': css

View File

@@ -12,24 +12,13 @@
<ini name="max_execution_time" value="-1" /> <ini name="max_execution_time" value="-1" />
<ini name="intl.default_locale" value="en_US" /> <ini name="intl.default_locale" value="en_US" />
<env name="KERNEL_CLASS" value="App\Kernel" force="true"/> <env name="KERNEL_CLASS" value="App\Kernel" force="true"/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
<env name="APP_ENV" value="test" force="true"/> <env name="APP_ENV" value="test" force="true"/>
<env name="APP_DEBUG" value="1" force="true"/> <env name="APP_DEBUG" value="1" force="true"/>
<env name="APP_SECRET" value="5a79a1c866efef9ca1800f971d689f3e" force="true"/> <env name="APP_SECRET" value="5a79a1c866efef9ca1800f971d689f3e" force="true"/>
<!-- define your env variables for the test env here -->
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
<!-- ###+ doctrine/doctrine-bundle ### -->
<env name="DATABASE_URL" value="sqlite:///%kernel.project_dir%/var/data/kimai_test.sqlite" force="true"/> <env name="DATABASE_URL" value="sqlite:///%kernel.project_dir%/var/data/kimai_test.sqlite" force="true"/>
<!-- ###- doctrine/doctrine-bundle ### -->
<!-- ###+ symfony/swiftmailer-bundle ### -->
<env name="MAILER_URL" value="null://localhost" force="true"/>
<!-- ###- symfony/swiftmailer-bundle ### -->
<!-- ###+ nelmio/cors-bundle ### -->
<env name="CORS_ALLOW_ORIGIN" value="^https?://localhost(:[0-9]+)?$"/> <env name="CORS_ALLOW_ORIGIN" value="^https?://localhost(:[0-9]+)?$"/>
<!-- ###- nelmio/cors-bundle ### --> <env name="MAILER_URL" value="null://null"/>
</php> </php>
<testsuites> <testsuites>

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Configuration;
class MailConfiguration
{
public function getFromAddress(): ?string
{
$from = getenv('MAILER_FROM');
if ($from === false || empty($from)) {
return null;
}
return $from;
}
}

View File

@@ -214,7 +214,6 @@ class DoctorController extends AbstractController
{ {
return [ return [
'APP_ENV' => getenv('APP_ENV'), 'APP_ENV' => getenv('APP_ENV'),
'MAILER_FROM' => getenv('MAILER_FROM'),
'CORS_ALLOW_ORIGIN' => getenv('CORS_ALLOW_ORIGIN'), 'CORS_ALLOW_ORIGIN' => getenv('CORS_ALLOW_ORIGIN'),
]; ];
} }

View File

@@ -16,8 +16,10 @@ use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/** /**
* This class intercepts the registration to make sure the first-ever * This class intercepts the registration to make sure:
* registered user will get the SUPER_ADMIN role. *
* - the first-ever registered user will get the SUPER_ADMIN role
* - the user uses the current request locale as initial language setting
*/ */
class RegistrationSubscriber implements EventSubscriberInterface class RegistrationSubscriber implements EventSubscriberInterface
{ {
@@ -49,7 +51,7 @@ class RegistrationSubscriber implements EventSubscriberInterface
*/ */
public function onRegistrationSuccess(FormEvent $event) public function onRegistrationSuccess(FormEvent $event)
{ {
/** @var \FOS\UserBundle\Model\UserInterface $user */ /** @var User $user */
$user = $event->getForm()->getData(); $user = $event->getForm()->getData();
$roles = [User::ROLE_USER]; $roles = [User::ROLE_USER];
@@ -57,6 +59,7 @@ class RegistrationSubscriber implements EventSubscriberInterface
$roles = [User::ROLE_SUPER_ADMIN]; $roles = [User::ROLE_SUPER_ADMIN];
} }
$user->setLanguage($event->getRequest()->getLocale());
$user->setRoles($roles); $user->setRoles($roles);
} }
} }

43
src/Mail/KimaiMailer.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Mail;
use App\Configuration\MailConfiguration;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\RawMessage;
final class KimaiMailer implements MailerInterface
{
/**
* @var MailerInterface
*/
private $mailer;
/**
* @var MailConfiguration
*/
private $configuration;
public function __construct(MailConfiguration $configuration, MailerInterface $mailer)
{
$this->configuration = $configuration;
$this->mailer = $mailer;
}
public function send(RawMessage $message, Envelope $envelope = null): void
{
if ($message instanceof Email) {
$message->from($this->configuration->getFromAddress());
}
$this->mailer->send($message);
}
}

97
src/Mail/UserMails.php Normal file
View File

@@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Mail;
use App\Entity\User;
use FOS\UserBundle\Mailer\MailerInterface as FOSMailerInterface;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mime\Address;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class UserMails implements FOSMailerInterface
{
/**
* @var KimaiMailer
*/
private $mailer;
/**
* @var UrlGeneratorInterface
*/
private $router;
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(KimaiMailer $mailer, UrlGeneratorInterface $router, TranslatorInterface $translator)
{
$this->mailer = $mailer;
$this->router = $router;
$this->translator = $translator;
}
public function sendConfirmationEmailMessage(UserInterface $user)
{
$username = $user->getUsername();
$language = 'en';
if ($user instanceof User) {
$username = $user->getDisplayName();
$language = $user->getLanguage();
}
$url = $this->router->generate('fos_user_registration_confirm', ['token' => $user->getConfirmationToken()], UrlGeneratorInterface::ABSOLUTE_URL);
$email = (new TemplatedEmail())
->to(new Address($user->getEmail()))
->subject(
$this->translator->trans('registration.subject', ['%username%' => $username], 'email', $language)
)
->htmlTemplate('emails/confirmation.html.twig')
->context([
'user' => $user,
'username' => $username,
'confirmationUrl' => $url,
])
;
$this->mailer->send($email);
}
public function sendResettingEmailMessage(UserInterface $user)
{
$username = $user->getUsername();
$language = 'en';
if ($user instanceof User) {
$username = $user->getDisplayName();
$language = $user->getLanguage();
}
$url = $this->router->generate('fos_user_resetting_reset', ['token' => $user->getConfirmationToken()], UrlGeneratorInterface::ABSOLUTE_URL);
$email = (new TemplatedEmail())
->to(new Address($user->getEmail()))
->subject(
$this->translator->trans('reset.subject', ['%username%' => $username], 'email', $language)
)
->htmlTemplate('emails/password-reset.html.twig')
->context([
'user' => $user,
'username' => $username,
'confirmationUrl' => $url,
])
;
$this->mailer->send($email);
}
}

View File

@@ -222,6 +222,12 @@
"league/csv": { "league/csv": {
"version": "9.4.1" "version": "9.4.1"
}, },
"league/html-to-markdown": {
"version": "4.9.1"
},
"lorenzo/pinky": {
"version": "1.0.5"
},
"markbaker/complex": { "markbaker/complex": {
"version": "1.4.6" "version": "1.4.6"
}, },
@@ -417,8 +423,14 @@
"setasign/fpdi": { "setasign/fpdi": {
"version": "1.6.2" "version": "1.6.2"
}, },
"swiftmailer/swiftmailer": { "symfony/amazon-mailer": {
"version": "v6.0.2" "version": "4.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.4",
"ref": "9648db3ecae5c8a6b1a5f74715d3907124348815"
}
}, },
"symfony/asset": { "symfony/asset": {
"version": "v4.0.3" "version": "v4.0.3"
@@ -510,6 +522,15 @@
"ref": "137a14eeb6b3f5370e7147af8aff6518504f50c7" "ref": "137a14eeb6b3f5370e7147af8aff6518504f50c7"
} }
}, },
"symfony/google-mailer": {
"version": "4.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.4",
"ref": "f8fd4ddb9b477510f8f4bce2b9c054ab428c0120"
}
},
"symfony/http-foundation": { "symfony/http-foundation": {
"version": "v4.0.3" "version": "v4.0.3"
}, },
@@ -525,6 +546,36 @@
"symfony/ldap": { "symfony/ldap": {
"version": "v4.2.8" "version": "v4.2.8"
}, },
"symfony/mailchimp-mailer": {
"version": "4.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.4",
"ref": "1be490e068472fc87e226cf54bc95fb6f2e49117"
}
},
"symfony/mailer": {
"version": "4.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.3",
"ref": "15658c2a0176cda2e7dba66276a2030b52bd81b2"
},
"files": [
"config/packages/mailer.yaml"
]
},
"symfony/mailgun-mailer": {
"version": "4.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.4",
"ref": "91bc9c127983c37808481733bcf83b7651b1f941"
}
},
"symfony/maker-bundle": { "symfony/maker-bundle": {
"version": "1.0", "version": "1.0",
"recipe": { "recipe": {
@@ -552,9 +603,6 @@
"symfony/options-resolver": { "symfony/options-resolver": {
"version": "v4.0.3" "version": "v4.0.3"
}, },
"symfony/orm-pack": {
"version": "v1.0.6"
},
"symfony/phpunit-bridge": { "symfony/phpunit-bridge": {
"version": "3.3", "version": "3.3",
"recipe": { "recipe": {
@@ -588,6 +636,15 @@
"symfony/polyfill-php73": { "symfony/polyfill-php73": {
"version": "v1.11.0" "version": "v1.11.0"
}, },
"symfony/postmark-mailer": {
"version": "4.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.4",
"ref": "84842f6791eaef80618dda244d4e3bbcbbcdf543"
}
},
"symfony/process": { "symfony/process": {
"version": "v4.0.3" "version": "v4.0.3"
}, },
@@ -630,6 +687,15 @@
"symfony/security-http": { "symfony/security-http": {
"version": "v4.3.3" "version": "v4.3.3"
}, },
"symfony/sendgrid-mailer": {
"version": "4.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "4.4",
"ref": "b46e4f8f1adb2864f0aa9c0ff57aea3324a07199"
}
},
"symfony/serializer": { "symfony/serializer": {
"version": "v4.1.2" "version": "v4.1.2"
}, },
@@ -642,15 +708,6 @@
"symfony/string": { "symfony/string": {
"version": "v5.0.4" "version": "v5.0.4"
}, },
"symfony/swiftmailer-bundle": {
"version": "2.5",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "2.5",
"ref": "8daf8ede72274989fb0dc66b83cb4bc1693afbb9"
}
},
"symfony/templating": { "symfony/templating": {
"version": "v4.1.1" "version": "v4.1.1"
}, },
@@ -720,9 +777,18 @@
"theseer/tokenizer": { "theseer/tokenizer": {
"version": "1.1.0" "version": "1.1.0"
}, },
"tijsverkoyen/css-to-inline-styles": {
"version": "2.2.2"
},
"twig/cssinliner-extra": {
"version": "v3.0.1"
},
"twig/extra-bundle": { "twig/extra-bundle": {
"version": "v3.0.1" "version": "v3.0.1"
}, },
"twig/inky-extra": {
"version": "v3.0.1"
},
"twig/intl-extra": { "twig/intl-extra": {
"version": "v3.0.1" "version": "v3.0.1"
}, },

View File

@@ -0,0 +1,21 @@
{% extends 'emails/layout.html.twig' %}
{% block title %}
{{ 'registration.title'|trans({'%username%': user.displayName}, 'email') }}
{% endblock %}
{% block body %}
<p>{{ 'registration.intro'|trans({'%email%': user.email}, 'email') }}<p>
<spacer size="16"></spacer>
<button class="large primary" href="{{ confirmationUrl }}">{{ 'registration.button'|trans({'%url%': confirmationUrl}, 'email') }}</button>
<spacer size="16"></spacer>
<p><small>{{ 'button_copy_url_to_browser'|trans({'%url%': confirmationUrl}, 'email') }}</small></p>
<spacer size="16"></spacer>
<p><small>{{ 'automated_email_dont_answer'|trans({}, 'email') }}</small></p>
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% apply inky_to_html|inline_css(source('@css/emails.css')) %}
<style type="text/css">
body, html, .body {
background: #f3f3f3 !important;
}
.container.header {
background: #f3f3f3;
}
</style>
<spacer size="32"></spacer>
<container class="header">
<row>
<columns>
<h1 class="text-center">{% block title %}{% endblock %}</h1>
</columns>
</row>
</container>
<container class="body-border">
<row>
<columns>
<spacer size="32"></spacer>
{% block body %}{% endblock %}
</columns>
</row>
</container>
{% endapply %}

View File

@@ -0,0 +1,21 @@
{% extends 'emails/layout.html.twig' %}
{% block title %}
{{ 'reset.title'|trans({'%username%': user.displayName}, 'email') }}
{% endblock %}
{% block body %}
<p>{{ 'reset.intro'|trans({'%username%': user.displayName, '%email%': user.email}, 'email') }}<p>
<spacer size="16"></spacer>
<button class="large primary" href="{{ confirmationUrl }}">{{ 'reset.button'|trans({'%url%': confirmationUrl}, 'email') }}</button>
<spacer size="16"></spacer>
<p><small>{{ 'button_copy_url_to_browser'|trans({'%url%': confirmationUrl}, 'email') }}</small></p>
<spacer size="16"></spacer>
<p><small>{{ 'automated_email_dont_answer'|trans({}, 'email') }}</small></p>
{% endblock %}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Configuration;
use App\Configuration\MailConfiguration;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\Configuration\MailConfiguration
*/
class MailConfigurationTest extends TestCase
{
public function testGetFromAddress()
{
$previous = getenv('MAILER_FROM');
putenv('MAILER_FROM=foo-bar123@example.com');
$sut = new MailConfiguration();
self::assertEquals('foo-bar123@example.com', $sut->getFromAddress());
putenv('MAILER_FROM=');
self::assertNull($sut->getFromAddress());
putenv('MAILER_FROM=' . $previous);
}
}

View File

@@ -50,6 +50,7 @@ class RegistrationSubscriberTest extends TestCase
$form->method('getData')->willReturn($user); $form->method('getData')->willReturn($user);
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request->expects($this->any())->method('getLocale')->willReturn('ru');
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
@@ -57,6 +58,7 @@ class RegistrationSubscriberTest extends TestCase
$sut->onRegistrationSuccess($event); $sut->onRegistrationSuccess($event);
$this->assertEquals($expectedRoles, $user->getRoles()); $this->assertEquals($expectedRoles, $user->getRoles());
$this->assertEquals('ru', $user->getLanguage());
} }
public function getTestData() public function getTestData()

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Mail;
use App\Configuration\MailConfiguration;
use App\Entity\User;
use App\Mail\KimaiMailer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
/**
* @covers \App\Mail\KimaiMailer
*/
class KimaiMailerTest extends TestCase
{
public function getSut(): KimaiMailer
{
$config = $this->createMock(MailConfiguration::class);
$config->expects($this->any())->method('getFromAddress')->willReturn('zippel@example.com');
$mailer = $this->createMock(MailerInterface::class);
return new KimaiMailer($config, $mailer);
}
public function testSendSetsFrom()
{
$user = new User();
$user->setUsername('Testing');
$user->setEmail('foo@example.com');
$user->setAlias('Super User');
$mailer = $this->getSut();
$message = new Email();
self::assertEquals([], $message->getFrom());
$mailer->send($message);
self::assertEquals([new Address('zippel@example.com')], $message->getFrom());
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Mail;
use App\Configuration\MailConfiguration;
use App\Entity\User;
use App\Mail\KimaiMailer;
use App\Mail\UserMails;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @covers \App\Mail\UserMails
*/
class UserMailsTest extends TestCase
{
public function getSut(): UserMails
{
$config = $this->createMock(MailConfiguration::class);
$config->expects($this->any())->method('getFromAddress')->willReturn('zippel@example.com');
$mailer = $this->createMock(MailerInterface::class);
$mailer->expects($this->once())->method('send')->willReturnCallback(function (Email $message) {
self::assertEquals([new Address('zippel@example.com')], $message->getFrom());
self::assertEquals([new Address('foo@example.com')], $message->getTo());
self::assertEquals('foo', $message->getSubject());
});
$kimaiMailer = new KimaiMailer($config, $mailer);
$router = $this->createMock(UrlGeneratorInterface::class);
$translator = $this->createMock(TranslatorInterface::class);
$translator->expects($this->any())->method('trans')->willReturn('foo');
return new UserMails($kimaiMailer, $router, $translator);
}
public function testSendConfirmationEmailMessage()
{
$user = new User();
$user->setUsername('Testing');
$user->setEmail('foo@example.com');
$user->setAlias('Super User');
$mailer = $this->getSut();
$mailer->sendConfirmationEmailMessage($user);
}
public function testSendResettingEmailMessage()
{
$user = new User();
$user->setUsername('Testing');
$user->setEmail('foo@example.com');
$user->setAlias('Super User');
$mailer = $this->getSut();
$mailer->sendResettingEmailMessage($user);
}
}

47
translations/email.de.xlf Normal file
View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="de" datatype="plaintext" original="email.en.xlf">
<body>
<trans-unit id="button_copy_url_to_browser">
<source>button_copy_url_to_browser</source>
<target>Falls der Button nicht funktioniert, kopieren Sie bitte die folgende URL in ihren Browser: %url%</target>
</trans-unit>
<trans-unit id="automated_email_dont_answer">
<source>automated_email_dont_answer</source>
<target>Dies ist eine automatisch generierte E-Mail. Bitte antworten Sie nicht, die Adresse wird nicht gelesen.</target>
</trans-unit>
<trans-unit id="registration.subject">
<source>registration.subject</source>
<target>Benutzerkonto aktivieren</target>
</trans-unit>
<trans-unit id="registration.title">
<source>registration.title</source>
<target>Willkommen %username%</target>
</trans-unit>
<trans-unit id="registration.button">
<source>registration.button</source>
<target>Aktivieren Sie Ihren Zugang</target>
</trans-unit>
<trans-unit id="registration.intro">
<source>registration.intro</source>
<target>Sie haben sich für die Kimai Zeiterfassung mit der Email Adresse %email% registriert. Jetzt müssen Sie den Link in den nächsten Stunden anklicken, bevor der Link keine Gültigkeit mehr hat.</target>
</trans-unit>
<trans-unit id="reset.subject">
<source>reset.subject</source>
<target>Passwort zurücksetzen</target>
</trans-unit>
<trans-unit id="reset.title">
<source>reset.title</source>
<target>Das kann passieren ...</target>
</trans-unit>
<trans-unit id="reset.button">
<source>reset.button</source>
<target>Passwort zurücksetzen</target>
</trans-unit>
<trans-unit id="reset.intro">
<source>reset.intro</source>
<target>... Du hast also Dein Passwort vergessen? Keine Sorge: Kimai wird Dir dabei helfen ein neues zu erstellen:</target>
</trans-unit>
</body>
</file>
</xliff>

47
translations/email.en.xlf Normal file
View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="en" datatype="plaintext" original="email.en.xlf">
<body>
<trans-unit id="button_copy_url_to_browser">
<source>button_copy_url_to_browser</source>
<target>If the button doesn't work, please copy &amp; paste the following URL to your browser: %url%</target>
</trans-unit>
<trans-unit id="automated_email_dont_answer">
<source>automated_email_dont_answer</source>
<target>This is an auto generated e-mail. Please do not reply, the address will not be read.</target>
</trans-unit>
<trans-unit id="registration.subject">
<source>registration.subject</source>
<target>Activate your account</target>
</trans-unit>
<trans-unit id="registration.title">
<source>registration.title</source>
<target>Welcome %username%</target>
</trans-unit>
<trans-unit id="registration.button">
<source>registration.button</source>
<target>Activate your account</target>
</trans-unit>
<trans-unit id="registration.intro">
<source>registration.intro</source>
<target>You signed up at the Kimai time-tracker with the email %email%. Now you have to activate your account in the next few hours, before the link expires.</target>
</trans-unit>
<trans-unit id="reset.subject">
<source>reset.subject</source>
<target>Reset your password</target>
</trans-unit>
<trans-unit id="reset.title">
<source>reset.title</source>
<target>That can happen ...</target>
</trans-unit>
<trans-unit id="reset.button">
<source>reset.button</source>
<target>Reset your password</target>
</trans-unit>
<trans-unit id="reset.intro">
<source>reset.intro</source>
<target>... so you forgot your password? Don't worry: Kimai will help you to create a new one:</target>
</trans-unit>
</body>
</file>
</xliff>