diff --git a/src/app/Console/Development/TemplateRender.php b/src/app/Console/Development/TemplateRender.php index 2e9f5856..2f9c68bb 100644 --- a/src/app/Console/Development/TemplateRender.php +++ b/src/app/Console/Development/TemplateRender.php @@ -1,59 +1,61 @@ argument('template'); $template = str_replace("/", "\\", $template); $class = "\\App\\{$template}"; // Invalid template, list all templates if (!class_exists($class)) { $this->info("Invalid template name. Available templates:"); foreach (glob(app_path() . '/Documents/*.php') as $file) { $file = basename($file, '.php'); $this->info("Documents/$file"); } foreach (glob(app_path() . '/Mail/*.php') as $file) { $file = basename($file, '.php'); $this->info("Mail/$file"); } return 1; } $mode = 'html'; if (!empty($this->option('pdf'))) { $mode = 'pdf'; + } elseif (!empty($this->option('text'))) { + $mode = 'text'; } echo $class::fakeRender($mode); } } diff --git a/src/app/Documents/Receipt.php b/src/app/Documents/Receipt.php index 4fc11e4d..b00504a8 100644 --- a/src/app/Documents/Receipt.php +++ b/src/app/Documents/Receipt.php @@ -1,241 +1,245 @@ wallet = $wallet; $this->year = $year; $this->month = $month; } /** * Render the mail template with fake data * + * @param string $type Output format ('html' or 'pdf') + * * @return string HTML or PDF output */ public static function fakeRender(string $type = 'html'): string { $wallet = new Wallet(); $wallet->id = \App\Utils::uuidStr(); $wallet->owner = new User(['id' => 123456789]); // @phpstan-ignore-line $receipt = new self($wallet, date('Y'), date('n')); self::$fakeMode = true; if ($type == 'pdf') { return $receipt->pdfOutput(); + } elseif ($type !== 'html') { + throw new \Exception("Unsupported output format"); } return $receipt->htmlOutput(); } /** * Render the receipt in HTML format. * * @return string HTML content */ public function htmlOutput(): string { return $this->build()->render(); } /** * Render the receipt in PDF format. * * @return string PDF content */ public function pdfOutput(): string { // Parse ther HTML template $html = $this->build()->render(); // Link fonts from public/fonts to storage/fonts so DomPdf can find them if (!is_link(storage_path('fonts/Roboto-Regular.ttf'))) { symlink( public_path('fonts/Roboto-Regular.ttf'), storage_path('fonts/Roboto-Regular.ttf') ); symlink( public_path('fonts/Roboto-Bold.ttf'), storage_path('fonts/Roboto-Bold.ttf') ); } // Fix font and image paths $html = str_replace('url(/fonts/', 'url(fonts/', $html); $html = str_replace('src="/images/', 'src="images/', $html); // TODO: The output file is about ~200KB, we could probably slim it down // by using separate font files with small subset of languages when // there are no Unicode characters used, e.g. only ASCII or Latin. // Load PDF generator $pdf = \PDF::loadHTML($html)->setPaper('a4', 'portrait'); return $pdf->output(); } /** * Build the document * * @return \Illuminate\View\View The template object */ protected function build() { $appName = \config('app.name'); $start = Carbon::create($this->year, $this->month, 1, 0, 0, 0); $end = $start->copy()->endOfMonth(); $month = \trans('documents.month' . intval($this->month)); $title = \trans('documents.receipt-title', ['year' => $this->year, 'month' => $month]); $company = $this->companyData(); if (self::$fakeMode) { $customer = [ 'id' => $this->wallet->owner->id, 'wallet_id' => $this->wallet->id, 'customer' => 'Freddie Krüger
7252 Westminster Lane
Forest Hills, NY 11375', ]; $items = collect([ (object) [ 'amount' => 1234, 'updated_at' => $start->copy()->next(Carbon::MONDAY), ], (object) [ 'amount' => 10000, // @phpstan-ignore-next-line 'updated_at' => $start->copy()->next()->next(), ], (object) [ 'amount' => 1234, // @phpstan-ignore-next-line 'updated_at' => $start->copy()->next()->next()->next(Carbon::MONDAY), ], (object) [ 'amount' => 99, // @phpstan-ignore-next-line 'updated_at' => $start->copy()->next()->next()->next(), ], ]); } else { $customer = $this->customerData(); $items = $this->wallet->payments() ->where('status', PaymentProvider::STATUS_PAID) ->where('updated_at', '>=', $start) ->where('updated_at', '<', $end) ->where('amount', '>', 0) ->orderBy('updated_at') ->get(); } $total = 0; $items = $items->map(function ($item) use (&$total, $appName) { $total += $item->amount; return [ 'amount' => sprintf('%.2f %s', $item->amount / 100, $this->wallet->currency), 'description' => \trans('documents.receipt-item-desc', ['site' => $appName]), 'date' => $item->updated_at->toDateString(), ]; }); $total = sprintf('%.2f %s', $total / 100, $this->wallet->currency); // Load the template $view = view('documents.receipt') ->with([ 'site' => $appName, 'title' => $title, 'company' => $company, 'customer' => $customer, 'items' => $items, 'total' => $total, ]); return $view; } /** * Prepare customer data for the template * * @return array Customer data for the template */ protected function customerData(): array { $user = $this->wallet->owner; $name = $user->name(); $organization = $user->getSetting('organization'); $address = $user->getSetting('billing_address'); $customer = trim(($organization ?: $name) . "\n$address"); $customer = str_replace("\n", '
', htmlentities($customer)); return [ 'id' => $this->wallet->owner->id, 'wallet_id' => $this->wallet->id, 'customer' => $customer, ]; } /** * Prepare company data for the template * * @return array Company data for the template */ protected function companyData(): array { $header = \config('app.company.name') . "\n" . \config('app.company.address'); $header = str_replace("\n", '
', htmlentities($header)); $footerLineLength = 110; $footer = \config('app.company.details'); $contact = \config('app.company.email'); $logo = \config('app.company.logo'); if ($contact) { $length = strlen($footer) + strlen($contact) + 3; $contact = htmlentities($contact); $footer .= ($length > $footerLineLength ? "\n" : ' | ') . sprintf('%s', $contact, $contact); } return [ 'logo' => $logo ? "" : '', 'header' => $header, 'footer' => $footer, ]; } } diff --git a/src/app/Mail/Helper.php b/src/app/Mail/Helper.php new file mode 100644 index 00000000..756b6b8b --- /dev/null +++ b/src/app/Mail/Helper.php @@ -0,0 +1,33 @@ +build(); // @phpstan-ignore-line + + $mailer = \Illuminate\Container\Container::getInstance()->make('mailer'); + + return $mailer->render(['text' => $mail->textView], $mail->buildViewData()); + } elseif ($type != 'html') { + throw new \Exception("Unsupported output format"); + } + + // HTML output + return $mail->build()->render(); // @phpstan-ignore-line + } +} diff --git a/src/app/Mail/NegativeBalance.php b/src/app/Mail/NegativeBalance.php index 54ff3371..dfac7f47 100644 --- a/src/app/Mail/NegativeBalance.php +++ b/src/app/Mail/NegativeBalance.php @@ -1,69 +1,72 @@ account = $account; } /** * Build the message. * * @return $this */ public function build() { $user = $this->account; $subject = \trans('mail.negativebalance-subject', ['site' => \config('app.name')]); - $this->view('emails.negative_balance') + $this->view('emails.html.negative_balance') + ->text('emails.plain.negative_balance') ->subject($subject) ->with([ 'site' => \config('app.name'), 'subject' => $subject, 'username' => $user->name(true), 'supportUrl' => \config('app.support_url'), 'walletUrl' => Utils::serviceUrl('/wallet'), ]); return $this; } /** * Render the mail template with fake data * - * @return string HTML output + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output */ - public static function fakeRender(): string + public static function fakeRender(string $type = 'html'): string { $user = new User(); $mail = new self($user); - return $mail->build()->render(); + return Helper::render($mail, $type); } } diff --git a/src/app/Mail/PasswordReset.php b/src/app/Mail/PasswordReset.php index 24549869..4544a192 100644 --- a/src/app/Mail/PasswordReset.php +++ b/src/app/Mail/PasswordReset.php @@ -1,78 +1,81 @@ code = $code; } /** * Build the message. * * @return $this */ public function build() { $href = Utils::serviceUrl( sprintf('/login/reset/%s-%s', $this->code->short_code, $this->code->code) ); - $this->view('emails.password_reset') + $this->view('emails.html.password_reset') + ->text('emails.plain.password_reset') ->subject(__('mail.passwordreset-subject', ['site' => \config('app.name')])) ->with([ 'site' => \config('app.name'), 'code' => $this->code->code, 'short_code' => $this->code->short_code, 'link' => sprintf('%s', $href, $href), 'username' => $this->code->user->name(true) ]); return $this; } /** * Render the mail template with fake data * - * @return string HTML output + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output */ - public static function fakeRender(): string + public static function fakeRender(string $type = 'html'): string { $code = new VerificationCode([ 'code' => Str::random(VerificationCode::CODE_LENGTH), 'short_code' => VerificationCode::generateShortCode(), ]); $code->user = new User([ 'email' => 'test@' . \config('app.domain'), ]); $mail = new self($code); - return $mail->build()->render(); + return Helper::render($mail, $type); } } diff --git a/src/app/Mail/PaymentFailure.php b/src/app/Mail/PaymentFailure.php index 56f36d4c..9bb8e700 100644 --- a/src/app/Mail/PaymentFailure.php +++ b/src/app/Mail/PaymentFailure.php @@ -1,82 +1,81 @@ payment = $payment; $this->user = $user; } /** * Build the message. * * @return $this */ public function build() { $user = $this->user; $subject = \trans('mail.paymentfailure-subject', ['site' => \config('app.name')]); - $this->view('emails.payment_failure') + $this->view('emails.html.payment_failure') + ->text('emails.plain.payment_failure') ->subject($subject) ->with([ 'site' => \config('app.name'), 'subject' => $subject, 'username' => $user->name(true), 'walletUrl' => Utils::serviceUrl('/wallet'), 'supportUrl' => \config('app.support_url'), ]); return $this; } /** * Render the mail template with fake data * - * @return string HTML output + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output */ - public static function fakeRender(): string + public static function fakeRender(string $type = 'mail'): string { $payment = new Payment(); $user = new User([ 'email' => 'test@' . \config('app.domain'), ]); - if (!\config('app.support_url')) { - \config(['app.support_url' => 'https://not-configured-support.url']); - } - $mail = new self($payment, $user); - return $mail->build()->render(); + return Helper::render($mail, $type); } } diff --git a/src/app/Mail/PaymentMandateDisabled.php b/src/app/Mail/PaymentMandateDisabled.php index 78014170..a5e5fccc 100644 --- a/src/app/Mail/PaymentMandateDisabled.php +++ b/src/app/Mail/PaymentMandateDisabled.php @@ -1,82 +1,81 @@ wallet = $wallet; $this->user = $user; } /** * Build the message. * * @return $this */ public function build() { $user = $this->user; $subject = \trans('mail.paymentmandatedisabled-subject', ['site' => \config('app.name')]); - $this->view('emails.payment_mandate_disabled') + $this->view('emails.html.payment_mandate_disabled') + ->text('emails.plain.payment_mandate_disabled') ->subject($subject) ->with([ 'site' => \config('app.name'), 'subject' => $subject, 'username' => $user->name(true), 'walletUrl' => Utils::serviceUrl('/wallet'), 'supportUrl' => \config('app.support_url'), ]); return $this; } /** * Render the mail template with fake data * - * @return string HTML output + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output */ - public static function fakeRender(): string + public static function fakeRender(string $type = 'html'): string { $wallet = new Wallet(); $user = new User([ 'email' => 'test@' . \config('app.domain'), ]); - if (!\config('app.support_url')) { - \config(['app.support_url' => 'https://not-configured-support.url']); - } - $mail = new self($wallet, $user); - return $mail->build()->render(); + return Helper::render($mail, $type); } } diff --git a/src/app/Mail/PaymentSuccess.php b/src/app/Mail/PaymentSuccess.php index 2ba3a661..c211e73c 100644 --- a/src/app/Mail/PaymentSuccess.php +++ b/src/app/Mail/PaymentSuccess.php @@ -1,82 +1,81 @@ payment = $payment; $this->user = $user; } /** * Build the message. * * @return $this */ public function build() { $user = $this->user; $subject = \trans('mail.paymentsuccess-subject', ['site' => \config('app.name')]); - $this->view('emails.payment_success') + $this->view('emails.html.payment_success') + ->text('emails.plain.payment_success') ->subject($subject) ->with([ 'site' => \config('app.name'), 'subject' => $subject, 'username' => $user->name(true), 'walletUrl' => Utils::serviceUrl('/wallet'), 'supportUrl' => \config('app.support_url'), ]); return $this; } /** * Render the mail template with fake data * - * @return string HTML output + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output */ - public static function fakeRender(): string + public static function fakeRender(string $type = 'html'): string { $payment = new Payment(); $user = new User([ 'email' => 'test@' . \config('app.domain'), ]); - if (!\config('app.support_url')) { - \config(['app.support_url' => 'https://not-configured-support.url']); - } - $mail = new self($payment, $user); - return $mail->build()->render(); + return Helper::render($mail, $type); } } diff --git a/src/app/Mail/SignupVerification.php b/src/app/Mail/SignupVerification.php index 397c0f41..039263a1 100644 --- a/src/app/Mail/SignupVerification.php +++ b/src/app/Mail/SignupVerification.php @@ -1,85 +1,87 @@ code = $code; } /** * Build the message. * * @return $this */ public function build() { $href = Utils::serviceUrl( sprintf('/signup/%s-%s', $this->code->short_code, $this->code->code) ); $username = $this->code->data['first_name'] ?? ''; if (!empty($this->code->data['last_name'])) { $username .= ' ' . $this->code->data['last_name']; } $username = trim($username); - $this->view('emails.signup_code') + $this->view('emails.html.signup_code') + ->text('emails.plain.signup_code') ->subject(__('mail.signupcode-subject', ['site' => \config('app.name')])) ->with([ 'site' => \config('app.name'), 'username' => $username ?: 'User', 'code' => $this->code->code, 'short_code' => $this->code->short_code, - 'link' => sprintf('%s', $href, $href), + 'href' => $href, ]); return $this; } /** * Render the mail template with fake data * - * @return string HTML output + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output */ - public static function fakeRender(): string + public static function fakeRender(string $type = 'html'): string { $code = new SignupCode([ 'code' => Str::random(SignupCode::CODE_LENGTH), 'short_code' => SignupCode::generateShortCode(), 'data' => [ 'email' => 'test@' . \config('app.domain'), 'first_name' => 'Firstname', 'last_name' => 'Lastname', ], ]); - $mail = new self($code); - return $mail->build()->render(); + return Helper::render($mail, $type); } } diff --git a/src/app/Mail/SuspendedDebtor.php b/src/app/Mail/SuspendedDebtor.php index 45b268b5..4ffb1803 100644 --- a/src/app/Mail/SuspendedDebtor.php +++ b/src/app/Mail/SuspendedDebtor.php @@ -1,89 +1,83 @@ account = $account; } /** * Build the message. * * @return $this */ public function build() { $user = $this->account; $subject = \trans('mail.suspendeddebtor-subject', ['site' => \config('app.name')]); - $moreInfo = null; + $moreInfoHtml = null; + $moreInfoText = null; if ($moreInfoUrl = \config('app.kb.account_suspended')) { - $moreInfo = \trans('mail.more-info-html', ['href' => $moreInfoUrl]); + $moreInfoHtml = \trans('mail.more-info-html', ['href' => $moreInfoUrl]); + $moreInfoText = \trans('mail.more-info-text', ['href' => $moreInfoUrl]); } - $this->view('emails.suspended_debtor') + $this->view('emails.html.suspended_debtor') + ->text('emails.plain.suspended_debtor') ->subject($subject) ->with([ 'site' => \config('app.name'), 'subject' => $subject, 'username' => $user->name(true), 'cancelUrl' => \config('app.kb.account_delete'), 'supportUrl' => \config('app.support_url'), 'walletUrl' => Utils::serviceUrl('/wallet'), - 'moreInfo' => $moreInfo, + 'moreInfoHtml' => $moreInfoHtml, + 'moreInfoText' => $moreInfoText, 'days' => 14 // TODO: Configurable ]); return $this; } /** * Render the mail template with fake data * - * @return string HTML output + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output */ - public static function fakeRender(): string + public static function fakeRender(string $type = 'html'): string { $user = new User(); - if (!\config('app.support_url')) { - \config(['app.support_url' => 'https://not-configured-support.url']); - } - - if (!\config('app.kb.account_delete')) { - \config(['app.kb.account_delete' => 'https://not-configured-kb.url']); - } - - if (!\config('app.kb.account_suspended')) { - \config(['app.kb.account_suspended' => 'https://not-configured-kb.url']); - } - $mail = new self($user); - return $mail->build()->render(); + return Helper::render($mail, $type); } } diff --git a/src/composer.json b/src/composer.json index 6a50c33e..8bf0793e 100644 --- a/src/composer.json +++ b/src/composer.json @@ -1,87 +1,88 @@ { "name": "laravel/laravel", "type": "project", "description": "The Laravel Framework.", "keywords": [ "framework", "laravel" ], "license": "MIT", "repositories": [ { "type": "vcs", "url": "https://git.kolab.org/diffusion/PNL/php-net_ldap3.git" } ], "require": { "php": "^7.1.3", "barryvdh/laravel-dompdf": "^0.8.6", "doctrine/dbal": "^2.9", "fideloper/proxy": "^4.0", "geoip2/geoip2": "^2.9", "iatstuti/laravel-nullable-fields": "*", "kolab/net_ldap3": "dev-master", "laravel/framework": "6.*", - "laravel/tinker": "^1.0", + "laravel/tinker": "^2.4", "mollie/laravel-mollie": "^2.9", "morrislaptop/laravel-queue-clear": "^1.2", "silviolleite/laravelpwa": "^1.0", "spatie/laravel-translatable": "^4.2", "spomky-labs/otphp": "~4.0.0", "stripe/stripe-php": "^7.29", "swooletw/laravel-swoole": "^2.6", "torann/currency": "^1.0", "torann/geoip": "^1.0", "tymon/jwt-auth": "^1.0" }, "require-dev": { "beyondcode/laravel-dump-server": "^1.0", "beyondcode/laravel-er-diagram-generator": "^1.3", "filp/whoops": "^2.0", "fzaninotto/faker": "^1.4", + "kirschbaum-development/mail-intercept": "^0.2.4", "laravel/dusk": "~5.11.0", "mockery/mockery": "^1.0", "nunomaduro/larastan": "^0.5", - "phpstan/phpstan": "^0.12", + "phpstan/phpstan": "0.12.25", "phpunit/phpunit": "^8" }, "config": { "optimize-autoloader": true, "preferred-install": "dist", "sort-packages": true }, "extra": { "laravel": { "dont-discover": [] } }, "autoload": { "psr-4": { "App\\": "app/" }, "classmap": [ "database/seeds", "database/factories", "include" ] }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "minimum-stability": "dev", "prefer-stable": true, "scripts": { "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" ], "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ "@php artisan key:generate --ansi" ] } } diff --git a/src/resources/lang/en/mail.php b/src/resources/lang/en/mail.php index df5a67d6..fc49baa9 100644 --- a/src/resources/lang/en/mail.php +++ b/src/resources/lang/en/mail.php @@ -1,63 +1,64 @@ "Dear :name,", 'footer' => "Best regards,\nYour :site Team", 'more-info-html' => "See here for more information.", + 'more-info-text' => "See :href for more information.", 'negativebalance-subject' => ":site Payment Reminder", 'negativebalance-body' => "It has probably skipped your attention that you are behind on paying for your :site account. " - . "Consider setting up auto-payment to avoid messages like this in the future.\n\n" - . "Settle up to keep your account running.", + . "Consider setting up auto-payment to avoid messages like this in the future.", + 'negativebalance-body-extd' => "Settle up to keep your account running.", 'passwordreset-subject' => ":site Password Reset", 'passwordreset-body' => "Someone recently asked to change your :site password.\n" . "If this was you, use this verification code to complete the process: :code.\n" . "You can also click the link below.\n" . "If you did not make such a request, you can either ignore this message or get in touch with us about this incident.", 'paymentmandatedisabled-subject' => ":site Auto-payment Problem", 'paymentmandatedisabled-body' => "Your :site account balance is negative " . "and the configured amount for automatically topping up the balance does not cover " - . "the costs of subscriptions consumed.\n\n" - . "Charging you multiple times for the same amount in short succession " + . "the costs of subscriptions consumed.", + 'paymentmandatedisabled-body-ext' => "Charging you multiple times for the same amount in short succession " . "could lead to issues with the payment provider. " . "In order to not cause any problems, we suspended auto-payment for your account. " . "To resolve this issue, login to your account settings and adjust your auto-payment amount.", 'paymentfailure-subject' => ":site Payment Failed", 'paymentfailure-body' => "Something went wrong with auto-payment for your :site account.\n" - . "We tried to charge you via your preferred payment method, but the charge did not go through.\n\n" - . "In order to not cause any further issues, we suspended auto-payment for your account. " + . "We tried to charge you via your preferred payment method, but the charge did not go through.", + 'paymentfailure-body-ext' => "In order to not cause any further issues, we suspended auto-payment for your account. " . "To resolve this issue, login to your account settings at", 'paymentfailure-body-rest' => "There you can pay manually for your account and " . "change your auto-payment settings.", 'paymentsuccess-subject' => ":site Payment Succeeded", 'paymentsuccess-body' => "The auto-payment for your :site account went through without issues. " . "You can check your new account balance and more details here:", - 'support' => "Special circumstances? Something wrong with a charge?\n" - . " :site Support is here to help:", + 'support' => "Special circumstances? Something is wrong with a charge?\n" + . ":site Support is here to help.", 'signupcode-subject' => ":site Registration", 'signupcode-body' => "This is your verification code for the :site registration process: :code.\n" . "You can also click the link below to continue the registration process:", 'suspendeddebtor-subject' => ":site Account Suspended", 'suspendeddebtor-body' => "You have been behind on paying for your :site account " ."for over :days days. Your account has been suspended.", 'suspendeddebtor-middle' => "Settle up now to reactivate your account.", 'suspendeddebtor-cancel' => "Don't want to be our customer anymore? " . "Here is how you can cancel your account:", ]; diff --git a/src/resources/views/documents/receipt.blade.php b/src/resources/views/documents/receipt.blade.php index f920df64..bdded528 100644 --- a/src/resources/views/documents/receipt.blade.php +++ b/src/resources/views/documents/receipt.blade.php @@ -1,56 +1,56 @@

{{ $title }}

{!! $customer['customer'] !!} {{ __('documents.account-id') }} {{ $customer['wallet_id'] }}
{{ __('documents.customer-no') }} {{ $customer['id'] }}
@foreach ($items as $item) @endforeach
{{ __('documents.date') }} {{ __('documents.description') }} {{ __('documents.amount') }}
{{ $item['date'] }} {{ $item['description'] }} {{ $item['amount'] }}
{{ __('documents.total') }} {{ $total }}
diff --git a/src/resources/views/emails/negative_balance.blade.php b/src/resources/views/emails/html/negative_balance.blade.php similarity index 79% rename from src/resources/views/emails/negative_balance.blade.php rename to src/resources/views/emails/html/negative_balance.blade.php index 1294aa9e..3d32f390 100644 --- a/src/resources/views/emails/negative_balance.blade.php +++ b/src/resources/views/emails/html/negative_balance.blade.php @@ -1,19 +1,20 @@

{{ __('mail.header', ['name' => $username]) }}

{{ __('mail.negativebalance-body', ['site' => $site]) }}

+

{{ __('mail.negativebalance-body-ext', ['site' => $site]) }}

{{ $walletUrl }}

@if ($supportUrl)

{{ __('mail.support', ['site' => $site]) }}

{{ $supportUrl }}

@endif -

{{ __('mail.footer', ['site' => $site, 'appurl' => config('app.url')]) }}

+

{{ __('mail.footer', ['site' => $site]) }}

diff --git a/src/resources/views/emails/password_reset.blade.php b/src/resources/views/emails/html/password_reset.blade.php similarity index 79% rename from src/resources/views/emails/password_reset.blade.php rename to src/resources/views/emails/html/password_reset.blade.php index 85751758..31ee02ae 100644 --- a/src/resources/views/emails/password_reset.blade.php +++ b/src/resources/views/emails/html/password_reset.blade.php @@ -1,15 +1,15 @@

{{ __('mail.header', ['name' => $username]) }}

{{ __('mail.passwordreset-body', ['code' => $short_code, 'site' => $site]) }}

{!! $link !!}

-

{{ __('mail.footer', ['site' => $site, 'appurl' => config('app.url')]) }}

+

{{ __('mail.footer', ['site' => $site]) }}

diff --git a/src/resources/views/emails/payment_failure.blade.php b/src/resources/views/emails/html/payment_failure.blade.php similarity index 81% rename from src/resources/views/emails/payment_failure.blade.php rename to src/resources/views/emails/html/payment_failure.blade.php index 4bf28128..3a272320 100644 --- a/src/resources/views/emails/payment_failure.blade.php +++ b/src/resources/views/emails/html/payment_failure.blade.php @@ -1,20 +1,21 @@

{{ __('mail.header', ['name' => $username]) }}

{{ __('mail.paymentfailure-body', ['site' => $site]) }}

+

{{ __('mail.paymentfailure-body-ext', ['site' => $site]) }}

{{ $walletUrl }}

{{ __('mail.paymentfailure-body-rest', ['site' => $site]) }}

@if ($supportUrl)

{{ __('mail.support', ['site' => $site]) }}

{{ $supportUrl }}

@endif -

{{ __('mail.footer', ['site' => $site, 'appurl' => config('app.url')]) }}

+

{{ __('mail.footer', ['site' => $site]) }}

diff --git a/src/resources/views/emails/payment_mandate_disabled.blade.php b/src/resources/views/emails/html/payment_mandate_disabled.blade.php similarity index 80% rename from src/resources/views/emails/payment_mandate_disabled.blade.php rename to src/resources/views/emails/html/payment_mandate_disabled.blade.php index 16b24e71..22dfd114 100644 --- a/src/resources/views/emails/payment_mandate_disabled.blade.php +++ b/src/resources/views/emails/html/payment_mandate_disabled.blade.php @@ -1,20 +1,21 @@

{{ __('mail.header', ['name' => $username]) }}

{{ __('mail.paymentmandatedisabled-body', ['site' => $site]) }}

+

{{ __('mail.paymentmandatedisabled-body-ext', ['site' => $site]) }}

{{ $walletUrl }}

{{ __('mail.paymentfailure-body-rest', ['site' => $site]) }}

@if ($supportUrl)

{{ __('mail.support', ['site' => $site]) }}

{{ $supportUrl }}

@endif -

{{ __('mail.footer', ['site' => $site, 'appurl' => config('app.url')]) }}

+

{{ __('mail.footer', ['site' => $site]) }}

diff --git a/src/resources/views/emails/payment_success.blade.php b/src/resources/views/emails/html/payment_success.blade.php similarity index 85% rename from src/resources/views/emails/payment_success.blade.php rename to src/resources/views/emails/html/payment_success.blade.php index 543987bd..1def7c9d 100644 --- a/src/resources/views/emails/payment_success.blade.php +++ b/src/resources/views/emails/html/payment_success.blade.php @@ -1,19 +1,19 @@

{{ __('mail.header', ['name' => $username]) }}

{{ __('mail.paymentsuccess-body', ['site' => $site]) }}

{{ $walletUrl }}

@if ($supportUrl)

{{ __('mail.support', ['site' => $site]) }}

{{ $supportUrl }}

@endif -

{{ __('mail.footer', ['site' => $site, 'appurl' => config('app.url')]) }}

+

{{ __('mail.footer', ['site' => $site]) }}

diff --git a/src/resources/views/emails/signup_code.blade.php b/src/resources/views/emails/html/signup_code.blade.php similarity index 72% rename from src/resources/views/emails/signup_code.blade.php rename to src/resources/views/emails/html/signup_code.blade.php index e14af11a..eab5c98b 100644 --- a/src/resources/views/emails/signup_code.blade.php +++ b/src/resources/views/emails/html/signup_code.blade.php @@ -1,15 +1,15 @@

{{ __('mail.header', ['name' => $username]) }}

{{ __('mail.signupcode-body', ['code' => $short_code, 'site' => $site]) }}

-

{!! $link !!}

+

{!! $href !!}

-

{{ __('mail.footer', ['site' => $site, 'appurl' => config('app.url')]) }}

+

{{ __('mail.footer', ['site' => $site]) }}

diff --git a/src/resources/views/emails/suspended_debtor.blade.php b/src/resources/views/emails/html/suspended_debtor.blade.php similarity index 84% rename from src/resources/views/emails/suspended_debtor.blade.php rename to src/resources/views/emails/html/suspended_debtor.blade.php index ead7fc12..5c116e59 100644 --- a/src/resources/views/emails/suspended_debtor.blade.php +++ b/src/resources/views/emails/html/suspended_debtor.blade.php @@ -1,24 +1,24 @@

{{ __('mail.header', ['name' => $username]) }}

-

{{ __('mail.suspendeddebtor-body', ['site' => $site, 'days' => $days]) }} {!! $moreInfo !!}

+

{{ __('mail.suspendeddebtor-body', ['site' => $site, 'days' => $days]) }} {!! $moreInfoHtml !!}

{{ __('mail.suspendeddebtor-middle') }}

{{ $walletUrl }}

@if ($supportUrl)

{{ __('mail.support', ['site' => $site]) }}

{{ $supportUrl }}

@endif @if ($cancelUrl)

{{ __('mail.suspendeddebtor-cancel') }}

{{ $cancelUrl }}

@endif -

{{ __('mail.footer', ['site' => $site, 'appurl' => config('app.url')]) }}

+

{{ __('mail.footer', ['site' => $site]) }}

diff --git a/src/resources/views/emails/plain/negative_balance.blade.php b/src/resources/views/emails/plain/negative_balance.blade.php new file mode 100644 index 00000000..f243c17b --- /dev/null +++ b/src/resources/views/emails/plain/negative_balance.blade.php @@ -0,0 +1,16 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.negativebalance-body', ['site' => $site]) !!} + +{!! __('mail.negativebalance-body-ext', ['site' => $site]) !!} + +{!! $walletUrl !!} + +@if ($supportUrl) +{!! __('mail.support', ['site' => $site]) !!} + +{!! $supportUrl !!} +@endif + +-- +{!! __('mail.footer', ['site' => $site]) !!} diff --git a/src/resources/views/emails/plain/password_reset.blade.php b/src/resources/views/emails/plain/password_reset.blade.php new file mode 100644 index 00000000..5e458ed9 --- /dev/null +++ b/src/resources/views/emails/plain/password_reset.blade.php @@ -0,0 +1,8 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.passwordreset-body', ['code' => $short_code, 'site' => $site]) !!} + +{!! $link !!} + +-- +{!! __('mail.footer', ['site' => $site]) !!} diff --git a/src/resources/views/emails/plain/payment_failure.blade.php b/src/resources/views/emails/plain/payment_failure.blade.php new file mode 100644 index 00000000..0994c119 --- /dev/null +++ b/src/resources/views/emails/plain/payment_failure.blade.php @@ -0,0 +1,18 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.paymentfailure-body', ['site' => $site]) !!} + +{!! __('mail.paymentfailure-body-ext', ['site' => $site]) !!} + +{!! $walletUrl !!} + +{!! __('mail.paymentfailure-body-rest', ['site' => $site]) !!} + +@if ($supportUrl) +{!! __('mail.support', ['site' => $site]) !!} + +{!! $supportUrl !!} +@endif + +-- +{!! __('mail.footer', ['site' => $site]) !!} diff --git a/src/resources/views/emails/plain/payment_mandate_disabled.blade.php b/src/resources/views/emails/plain/payment_mandate_disabled.blade.php new file mode 100644 index 00000000..40bfde85 --- /dev/null +++ b/src/resources/views/emails/plain/payment_mandate_disabled.blade.php @@ -0,0 +1,18 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.paymentmandatedisabled-body', ['site' => $site]) !!} + +{!! __('mail.paymentmandatedisabled-body-ext', ['site' => $site]) !!} + +{!! $walletUrl !!} + +{!! __('mail.paymentfailure-body-rest', ['site' => $site]) !!} + +@if ($supportUrl) +{!! __('mail.support', ['site' => $site]) !!} + +{!! $supportUrl !!} +@endif + +-- +{!! __('mail.footer', ['site' => $site]) !!} diff --git a/src/resources/views/emails/plain/payment_success.blade.php b/src/resources/views/emails/plain/payment_success.blade.php new file mode 100644 index 00000000..72e29786 --- /dev/null +++ b/src/resources/views/emails/plain/payment_success.blade.php @@ -0,0 +1,14 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.paymentsuccess-body', ['site' => $site]) !!} + +{!! $walletUrl !!} + +@if ($supportUrl) +{!! __('mail.support', ['site' => $site]) !!} + +{!! $supportUrl !!} +@endif + +-- +{!! __('mail.footer', ['site' => $site]) !!} diff --git a/src/resources/views/emails/plain/signup_code.blade.php b/src/resources/views/emails/plain/signup_code.blade.php new file mode 100644 index 00000000..38ccd283 --- /dev/null +++ b/src/resources/views/emails/plain/signup_code.blade.php @@ -0,0 +1,8 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.signupcode-body', ['code' => $short_code, 'site' => $site]) !!} + +{!! $href !!} + +-- +{!! __('mail.footer', ['site' => $site]) !!} diff --git a/src/resources/views/emails/plain/suspended_debtor.blade.php b/src/resources/views/emails/plain/suspended_debtor.blade.php new file mode 100644 index 00000000..2dccacc8 --- /dev/null +++ b/src/resources/views/emails/plain/suspended_debtor.blade.php @@ -0,0 +1,22 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.suspendeddebtor-body', ['site' => $site, 'days' => $days]) !!} {!! $moreInfoText !!} + +{!! __('mail.suspendeddebtor-middle') !!} + +{!! $walletUrl !!} + +@if ($supportUrl) +{!! __('mail.support', ['site' => $site]) !!} + +{!! $supportUrl !!} +@endif +@if ($cancelUrl) + +{!! __('mail.suspendeddebtor-cancel') !!} + +{!! $cancelUrl !!} +@endif + +-- +{!! __('mail.footer', ['site' => $site]) !!} diff --git a/src/tests/MailInterceptTrait.php b/src/tests/MailInterceptTrait.php new file mode 100644 index 00000000..544d88ad --- /dev/null +++ b/src/tests/MailInterceptTrait.php @@ -0,0 +1,79 @@ +interceptMail(); + + Mail::send($mail); + + $message = $this->interceptedMail()->first(); + + // SwiftMailer does not have methods to get the bodies, we'll parse the message + list($plain, $html) = $this->extractMailBody($message->toString()); + + return [ + 'plain' => $plain, + 'html' => $html, + 'message' => $message, + ]; + } + + /** + * Simple message parser to extract plain and html body + * + * @param string $message Email message as string + * + * @return array Plain text and HTML body + */ + protected function extractMailBody(string $message): array + { + // Note that we're not supporting every message format, we only + // support what Laravel/SwiftMailer produces + // TODO: It may stop working if we start using attachments + $plain = ''; + $html = ''; + + if (preg_match('/[\s\t]boundary="([^"]+)"/', $message, $matches)) { + // multipart message assume plain and html parts + $split = preg_split('/--' . preg_quote($matches[1]) . '/', $message); + + list($plain_head, $plain) = explode("\r\n\r\n", $split[1], 2); + list($html_head, $html) = explode("\r\n\r\n", $split[2], 2); + + if (strpos($plain_head, 'Content-Transfer-Encoding: quoted-printable') !== false) { + $plain = quoted_printable_decode($plain); + } + + if (strpos($html_head, 'Content-Transfer-Encoding: quoted-printable') !== false) { + $html = quoted_printable_decode($html); + } + } else { + list($header, $html) = explode("\r\n\r\n", $message, 2); + if (strpos($header, 'Content-Transfer-Encoding: quoted-printable') !== false) { + $html = quoted_printable_decode($html); + } + } + + return [$plain, $html]; + } +} diff --git a/src/tests/Unit/Mail/NegativeBalanceTest.php b/src/tests/Unit/Mail/NegativeBalanceTest.php index 1e173a29..5089511a 100644 --- a/src/tests/Unit/Mail/NegativeBalanceTest.php +++ b/src/tests/Unit/Mail/NegativeBalanceTest.php @@ -1,43 +1,53 @@ 'https://kolab.org/support', ]); - $mail = new NegativeBalance($user); - $html = $mail->build()->render(); + $mail = $this->fakeMail(new NegativeBalance($user)); + + $html = $mail['html']; + $plain = $mail['plain']; $walletUrl = \App\Utils::serviceUrl('/wallet'); $walletLink = sprintf('%s', $walletUrl, $walletUrl); $supportUrl = \config('app.support_url'); $supportLink = sprintf('%s', $supportUrl, $supportUrl); - $appName = \config('app.name'); - $this->assertSame("$appName Payment Reminder", $mail->subject); + $this->assertMailSubject("$appName Payment Reminder", $mail['message']); + $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $user->name(true)) > 0); $this->assertTrue(strpos($html, $walletLink) > 0); $this->assertTrue(strpos($html, $supportLink) > 0); $this->assertTrue(strpos($html, "behind on paying for your $appName account") > 0); $this->assertTrue(strpos($html, "$appName Support") > 0); $this->assertTrue(strpos($html, "$appName Team") > 0); + + $this->assertStringStartsWith('Dear ' . $user->name(true), $plain); + $this->assertTrue(strpos($plain, $walletUrl) > 0); + $this->assertTrue(strpos($plain, $supportUrl) > 0); + $this->assertTrue(strpos($plain, "behind on paying for your $appName account") > 0); + $this->assertTrue(strpos($plain, "$appName Support") > 0); + $this->assertTrue(strpos($plain, "$appName Team") > 0); } } diff --git a/src/tests/Unit/Mail/PasswordResetTest.php b/src/tests/Unit/Mail/PasswordResetTest.php index 5e8b7df4..99be9a14 100644 --- a/src/tests/Unit/Mail/PasswordResetTest.php +++ b/src/tests/Unit/Mail/PasswordResetTest.php @@ -1,43 +1,50 @@ 123456789, 'mode' => 'password-reset', 'code' => 'code', 'short_code' => 'short-code', ]); - // @phpstan-ignore-next-line $code->user = new User([ 'name' => 'User Name', ]); - $mail = new PasswordReset($code); - $html = $mail->build()->render(); + $mail = $this->fakeMail(new PasswordReset($code)); + + $html = $mail['html']; + $plain = $mail['plain']; $url = Utils::serviceUrl('/login/reset/' . $code->short_code . '-' . $code->code); $link = "$url"; + $appName = \config('app.name'); + + $this->assertMailSubject("$appName Password Reset", $mail['message']); - $this->assertSame(\config('app.name') . ' Password Reset', $mail->subject); $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $link) > 0); $this->assertTrue(strpos($html, $code->user->name(true)) > 0); + + $this->assertStringStartsWith("Dear " . $code->user->name(true), $plain); + $this->assertTrue(strpos($plain, $link) > 0); } } diff --git a/src/tests/Unit/Mail/PaymentFailureTest.php b/src/tests/Unit/Mail/PaymentFailureTest.php index 2f721536..6cf8f46f 100644 --- a/src/tests/Unit/Mail/PaymentFailureTest.php +++ b/src/tests/Unit/Mail/PaymentFailureTest.php @@ -1,44 +1,54 @@ amount = 123; \config(['app.support_url' => 'https://kolab.org/support']); - $mail = new PaymentFailure($payment, $user); - $html = $mail->build()->render(); + $mail = $this->fakeMail(new PaymentFailure($payment, $user)); + + $html = $mail['html']; + $plain = $mail['plain']; $walletUrl = \App\Utils::serviceUrl('/wallet'); $walletLink = sprintf('%s', $walletUrl, $walletUrl); $supportUrl = \config('app.support_url'); $supportLink = sprintf('%s', $supportUrl, $supportUrl); $appName = \config('app.name'); - $this->assertSame("$appName Payment Failed", $mail->subject); + $this->assertMailSubject("$appName Payment Failed", $mail['message']); + $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $user->name(true)) > 0); $this->assertTrue(strpos($html, $walletLink) > 0); $this->assertTrue(strpos($html, $supportLink) > 0); $this->assertTrue(strpos($html, "$appName Support") > 0); $this->assertTrue(strpos($html, "Something went wrong with auto-payment for your $appName account") > 0); $this->assertTrue(strpos($html, "$appName Team") > 0); + + $this->assertStringStartsWith('Dear ' . $user->name(true), $plain); + $this->assertTrue(strpos($plain, $walletUrl) > 0); + $this->assertTrue(strpos($plain, $supportUrl) > 0); + $this->assertTrue(strpos($plain, "$appName Support") > 0); + $this->assertTrue(strpos($plain, "Something went wrong with auto-payment for your $appName account") > 0); + $this->assertTrue(strpos($plain, "$appName Team") > 0); } } diff --git a/src/tests/Unit/Mail/PaymentMandateDisabledTest.php b/src/tests/Unit/Mail/PaymentMandateDisabledTest.php index 7ae936fb..4e43f475 100644 --- a/src/tests/Unit/Mail/PaymentMandateDisabledTest.php +++ b/src/tests/Unit/Mail/PaymentMandateDisabledTest.php @@ -1,43 +1,53 @@ 'https://kolab.org/support']); - $mail = new PaymentMandateDisabled($wallet, $user); - $html = $mail->build()->render(); + $mail = $this->fakeMail(new PaymentMandateDisabled($wallet, $user)); + + $html = $mail['html']; + $plain = $mail['plain']; $walletUrl = \App\Utils::serviceUrl('/wallet'); $walletLink = sprintf('%s', $walletUrl, $walletUrl); $supportUrl = \config('app.support_url'); $supportLink = sprintf('%s', $supportUrl, $supportUrl); $appName = \config('app.name'); - $this->assertSame("$appName Auto-payment Problem", $mail->subject); + $this->assertMailSubject("$appName Auto-payment Problem", $mail['message']); + $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $user->name(true)) > 0); $this->assertTrue(strpos($html, $walletLink) > 0); $this->assertTrue(strpos($html, $supportLink) > 0); $this->assertTrue(strpos($html, "$appName Support") > 0); $this->assertTrue(strpos($html, "Your $appName account balance") > 0); $this->assertTrue(strpos($html, "$appName Team") > 0); + + $this->assertStringStartsWith('Dear ' . $user->name(true), $plain); + $this->assertTrue(strpos($plain, $walletUrl) > 0); + $this->assertTrue(strpos($plain, $supportUrl) > 0); + $this->assertTrue(strpos($plain, "$appName Support") > 0); + $this->assertTrue(strpos($plain, "Your $appName account balance") > 0); + $this->assertTrue(strpos($plain, "$appName Team") > 0); } } diff --git a/src/tests/Unit/Mail/PaymentSuccessTest.php b/src/tests/Unit/Mail/PaymentSuccessTest.php index 14656169..e155d520 100644 --- a/src/tests/Unit/Mail/PaymentSuccessTest.php +++ b/src/tests/Unit/Mail/PaymentSuccessTest.php @@ -1,44 +1,54 @@ amount = 123; \config(['app.support_url' => 'https://kolab.org/support']); - $mail = new PaymentSuccess($payment, $user); - $html = $mail->build()->render(); + $mail = $this->fakeMail(new PaymentSuccess($payment, $user)); + + $html = $mail['html']; + $plain = $mail['plain']; $walletUrl = \App\Utils::serviceUrl('/wallet'); $walletLink = sprintf('%s', $walletUrl, $walletUrl); $supportUrl = \config('app.support_url'); $supportLink = sprintf('%s', $supportUrl, $supportUrl); $appName = \config('app.name'); - $this->assertSame("$appName Payment Succeeded", $mail->subject); + $this->assertMailSubject("$appName Payment Succeeded", $mail['message']); + $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $user->name(true)) > 0); $this->assertTrue(strpos($html, $walletLink) > 0); $this->assertTrue(strpos($html, $supportLink) > 0); $this->assertTrue(strpos($html, "$appName Support") > 0); $this->assertTrue(strpos($html, "The auto-payment for your $appName account") > 0); $this->assertTrue(strpos($html, "$appName Team") > 0); + + $this->assertStringStartsWith('Dear ' . $user->name(true), $plain); + $this->assertTrue(strpos($plain, $walletUrl) > 0); + $this->assertTrue(strpos($plain, $supportUrl) > 0); + $this->assertTrue(strpos($plain, "$appName Support") > 0); + $this->assertTrue(strpos($plain, "The auto-payment for your $appName account") > 0); + $this->assertTrue(strpos($plain, "$appName Team") > 0); } } diff --git a/src/tests/Unit/Mail/SignupVerificationTest.php b/src/tests/Unit/Mail/SignupVerificationTest.php index a6a843e8..c7796658 100644 --- a/src/tests/Unit/Mail/SignupVerificationTest.php +++ b/src/tests/Unit/Mail/SignupVerificationTest.php @@ -1,40 +1,48 @@ 'code', 'short_code' => 'short-code', 'data' => [ 'email' => 'test@email', 'first_name' => 'First', 'last_name' => 'Last', ], ]); - $mail = new SignupVerification($code); - $html = $mail->build()->render(); + $mail = $this->fakeMail(new SignupVerification($code)); + + $html = $mail['html']; + $plain = $mail['plain']; $url = Utils::serviceUrl('/signup/' . $code->short_code . '-' . $code->code); $link = "$url"; + $appName = \config('app.name'); + + $this->assertMailSubject("$appName Registration", $mail['message']); - $this->assertSame(\config('app.name') . ' Registration', $mail->subject); $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $link) > 0); $this->assertTrue(strpos($html, 'First Last') > 0); + + $this->assertStringStartsWith('Dear First Last', $plain); + $this->assertTrue(strpos($plain, $url) > 0); } } diff --git a/src/tests/Unit/Mail/SuspendedDebtorTest.php b/src/tests/Unit/Mail/SuspendedDebtorTest.php index a823bd71..746fddd5 100644 --- a/src/tests/Unit/Mail/SuspendedDebtorTest.php +++ b/src/tests/Unit/Mail/SuspendedDebtorTest.php @@ -1,52 +1,65 @@ 'https://kolab.org/support', 'app.kb.account_suspended' => 'https://kb.kolab.org/account-suspended', 'app.kb.account_delete' => 'https://kb.kolab.org/account-delete', ]); - $mail = new SuspendedDebtor($user); - $html = $mail->build()->render(); + $mail = $this->fakeMail(new SuspendedDebtor($user)); + + $html = $mail['html']; + $plain = $mail['plain']; $walletUrl = \App\Utils::serviceUrl('/wallet'); $walletLink = sprintf('%s', $walletUrl, $walletUrl); $supportUrl = \config('app.support_url'); $supportLink = sprintf('%s', $supportUrl, $supportUrl); $deleteUrl = \config('app.kb.account_delete'); $deleteLink = sprintf('%s', $deleteUrl, $deleteUrl); $moreUrl = \config('app.kb.account_suspended'); $moreLink = sprintf('here', $moreUrl); - $appName = \config('app.name'); - $this->assertSame("$appName Account Suspended", $mail->subject); + $this->assertMailSubject("$appName Account Suspended", $mail['message']); + $this->assertStringStartsWith('', $html); $this->assertTrue(strpos($html, $user->name(true)) > 0); $this->assertTrue(strpos($html, $walletLink) > 0); $this->assertTrue(strpos($html, $supportLink) > 0); $this->assertTrue(strpos($html, $deleteLink) > 0); $this->assertTrue(strpos($html, "You have been behind on paying for your $appName account") > 0); $this->assertTrue(strpos($html, "over 14 days") > 0); $this->assertTrue(strpos($html, "See $moreLink for more information") > 0); $this->assertTrue(strpos($html, "$appName Support") > 0); $this->assertTrue(strpos($html, "$appName Team") > 0); + + $this->assertStringStartsWith('Dear ' . $user->name(true), $plain); + $this->assertTrue(strpos($plain, $walletUrl) > 0); + $this->assertTrue(strpos($plain, $supportUrl) > 0); + $this->assertTrue(strpos($plain, $deleteUrl) > 0); + $this->assertTrue(strpos($plain, "You have been behind on paying for your $appName account") > 0); + $this->assertTrue(strpos($plain, "over 14 days") > 0); + $this->assertTrue(strpos($plain, "See $moreUrl for more information") > 0); + $this->assertTrue(strpos($plain, "$appName Support") > 0); + $this->assertTrue(strpos($plain, "$appName Team") > 0); } }