Merge pull request #9172 from beganovich/1297

E-mail preferences links
This commit is contained in:
David Bomba 2024-01-24 14:40:05 +11:00 committed by GitHub
commit 76d159ee02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 269 additions and 12 deletions

View File

@ -0,0 +1,61 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\ClientPortal;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Models\ClientContact;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Http\Controllers\Controller;
use App\Jobs\Mail\NinjaMailerObject;
use Illuminate\Support\Facades\Cache;
use App\Mail\Admin\ClientUnsubscribedObject;
class EmailPreferencesController extends Controller
{
public function index(string $entity, string $invitation_key, Request $request): \Illuminate\View\View
{
$class = "\\App\\Models\\".ucfirst(Str::camel($entity)).'Invitation';
$invitation = $class::where('key', $invitation_key)->firstOrFail();
$data['receive_emails'] = $invitation->contact->is_locked ? false : true;
$data['company'] = $invitation->company;
return $this->render('generic.email_preferences', $data);
}
public function update(string $entity, string $invitation_key, Request $request): \Illuminate\Http\RedirectResponse
{
$class = "\\App\\Models\\" . ucfirst(Str::camel($entity)) . 'Invitation';
$invitation = $class::withTrashed()->where('key', $invitation_key)->firstOrFail();
$invitation->contact->is_locked = $request->action === 'unsubscribe' ? true : false;
$invitation->contact->push();
if ($invitation->contact->is_locked && !Cache::has("unsubscribe_notitfication_suppression:{$invitation_key}")) {
$nmo = new NinjaMailerObject();
$nmo->mailable = new NinjaMailer((new ClientUnsubscribedObject($invitation->contact, $invitation->contact->company, $invitation->contact->company->owner()->company_users()->first()->portalType() ?? true))->build());
$nmo->company = $invitation->contact->company;
$nmo->to_user = $invitation->contact->company->owner();
$nmo->settings = $invitation->contact->company->settings;
NinjaMailerJob::dispatch($nmo);
Cache::put("unsubscribe_notitfication_suppression:{$invitation_key}", true, 3600);
}
return back()->with('message', ctrans('texts.updated_settings'));
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Mail\Admin;
use App\Models\ClientContact;
use App\Models\Company;
use App\Utils\Ninja;
use Illuminate\Support\Facades\App;
class ClientUnsubscribedObject
{
public function __construct(
public ClientContact $contact,
public Company $company,
private bool $use_react_link = false
) {
}
public function build()
{
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->company->getLocale());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
$data = [
'title' => ctrans('texts.client_unsubscribed'),
'content' => ctrans('texts.client_unsubscribed_help', ['client' => $this->contact->present()->name()]),
'url' => $this->contact->getAdminLink($this->use_react_link),
'button' => ctrans('texts.view_client'),
'signature' => $this->company->settings->email_signature,
'settings' => $this->company->settings,
'logo' => $this->company->present()->logo(),
'text_body' => "\n\n".ctrans('texts.client_unsubscribed_help', ['client' => $this->contact->present()->name()])."\n\n",
];
$mail_obj = new \stdClass();
$mail_obj->subject = ctrans('texts.client_unsubscribed');
$mail_obj->data = $data;
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
$mail_obj->text_view = 'email.template.text';
return $mail_obj;
}
}

View File

@ -18,6 +18,7 @@ use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\URL;
class TemplateEmail extends Mailable
{
@ -138,6 +139,7 @@ class TemplateEmail extends Mailable
'whitelabel' => $this->client->user->account->isPaid() ? true : false,
'logo' => $this->company->present()->logo($settings),
'links' => $this->build_email->getAttachmentLinks(),
'email_preferences' => URL::signedRoute('client.email_preferences', ['entity' => $this->invitation->getEntityString(), 'invitation_key' => $this->invitation->key]),
]);
foreach ($this->build_email->getAttachments() as $file) {

View File

@ -11,22 +11,23 @@
namespace App\Models;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Mail\ClientContact\ClientContactResetPasswordObject;
use App\Models\Presenters\ClientContactPresenter;
use App\Utils\Ninja;
use Illuminate\Support\Str;
use App\Jobs\Mail\NinjaMailer;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use Illuminate\Support\Facades\Cache;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Laracasts\Presenter\PresentableTrait;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Presenters\ClientContactPresenter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Contracts\Translation\HasLocalePreference;
use App\Mail\ClientContact\ClientContactResetPasswordObject;
/**
* Class ClientContact
@ -339,4 +340,16 @@ class ClientContact extends Authenticatable implements HasLocalePreference
return '';
}
}
public function getAdminLink($use_react_link = false): string
{
return $use_react_link ? $this->getReactLink() : config('ninja.app_url');
}
private function getReactLink(): string
{
return config('ninja.react_url')."/#/clients/{$this->client->hashed_id}";
}
}

View File

@ -97,6 +97,11 @@ class CreditInvitation extends BaseModel
return self::class;
}
public function getEntityString(): string
{
return 'credit';
}
public function entityType()
{
return Credit::class;

View File

@ -97,6 +97,11 @@ class InvoiceInvitation extends BaseModel
return self::class;
}
public function getEntityString(): string
{
return 'invoice';
}
public function entityType()
{
return Invoice::class;

View File

@ -97,6 +97,11 @@ class PurchaseOrderInvitation extends BaseModel
return self::class;
}
public function getEntityString(): string
{
return 'purchase_order';
}
public function entityType()
{
return PurchaseOrder::class;

View File

@ -78,6 +78,11 @@ class QuoteInvitation extends BaseModel
return self::class;
}
public function getEntityString(): string
{
return 'quote';
}
public function entityType()
{
return Quote::class;

View File

@ -91,6 +91,12 @@ class RecurringInvoiceInvitation extends BaseModel
return self::class;
}
public function getEntityString(): string
{
return 'recurring_invoice';
}
public function entityType()
{
return RecurringInvoice::class;

View File

@ -77,6 +77,9 @@ class EmailMailable extends Mailable
'company' => $this->email_object->company,
'greeting' => '',
'links' => array_merge($this->email_object->links, $links->toArray()),
'email_preferences' => $this->email_object->invitation
? URL::signedRoute('client.email_preferences', ['entity' => $this->email_object->invitation->getEntityString(), 'invitation_key' => $this->email_object->invitation->key])
: false,
]
);
}

View File

@ -5213,6 +5213,12 @@ $lang = array(
'nordigen_requisition_body' => 'Access to bank account feeds has expired as set in End User Agreement. <br><br>Please log into Invoice Ninja and re-authenticate with your banks to continue receiving transactions.',
'participant' => 'Participant',
'participant_name' => 'Participant name',
'client_unsubscribed' => 'Client unsubscribed from emails.',
'client_unsubscribed_help' => 'Client :client has unsubscribed from your e-mails. The client needs to consent to receive future emails from you.',
'resubscribe' => 'Resubscribe',
'subscribe' => 'Subscribe',
'subscribe_help' => 'You are currently subscribed and will continue to receive email communications.',
'unsubscribe_help' => 'You are currently not subscribed, and therefore, will not receive emails at this time.',
);
return $lang;

View File

@ -163,7 +163,7 @@
{{ $slot ?? '' }}
{!! $body ?? '' !!}
<div>
<a href="#"
style="display: inline-block;background-color: {{ $primary_color }}; color: #ffffff; text-transform: uppercase;letter-spacing: 2px; text-decoration: none; font-size: 13px; font-weight: 600;">
@ -232,6 +232,21 @@
</div>
</td>
</tr>
@isset($email_preferences)
<tr>
<td bgcolor="#242424" cellpadding="20">
<div class="dark-bg-base"
style="padding-top: 10px;padding-bottom: 10px; background-color: #242424; border: 1px solid #c2c2c2; border-top-color: #242424; border-bottom-color: #242424;">
<a href="{{ $email_preferences }}">
<p style="text-align: center; color: #ffffff; font-size: 10px; font-family: Verdana, Geneva, Tahoma, sans-serif;">
{{ ctrans('texts.unsubscribe') }}
</p>
</a>
</div>
</td>
</tr>
@endisset
</table>
</td>
</tr>

View File

@ -14,4 +14,12 @@
</p>
@endif
@isset($email_preferences)
<p>
<a href="{{ $email_preferences }}">
{{ ctrans('texts.email_preferences') }}
</a>
</p>
@endif
@endcomponent

View File

@ -14,4 +14,12 @@
</p>
@endif
@isset($email_preferences)
<p>
<a href="{{ $email_preferences }}">
{{ ctrans('texts.email_preferences') }}
</a>
</p>
@endif
@endcomponent

View File

@ -44,6 +44,14 @@
</p>
@endif
@endisset
@if(isset($email_preferences))
<p>
<a href="{{ $email_preferences}} ">{{ ctrans('texts.email_preferences') }}</a>
</p>
@endif
@if(isset($unsubscribe_link))
<p><a href="{{$unsubscribe_link}}">{{ ctrans('texts.unsubscribe') }}</a></p>
@endif

View File

@ -0,0 +1,43 @@
@extends('portal.ninja2020.layout.clean') @section('meta_title',
ctrans('texts.preferences')) @section('body')
<div class="flex h-screen">
<div class="m-auto md:w-1/3 lg:w-1/5">
<div class="flex flex-col items-center">
<img
src="{{ $company->present()->logo() }}"
class="border-gray-100 h-18 pb-4"
alt="{{ $company->present()->name() }}"
/>
<h1 class="text-center text-2xl mt-10">
{{ ctrans('texts.email_preferences') }}
</h1>
<form class="my-4 flex flex-col items-center text-center" method="post">
@csrf @method('put')
@if($receive_emails)
<p>{{ ctrans('texts.subscribe_help') }}</p>
<button
name="action"
value="unsubscribe"
class="button button-secondary mt-4"
>
{{ ctrans('texts.unsubscribe') }}
</button>
@else
<p>{{ ctrans('texts.unsubscribe_help') }}</p>
<button
name="action"
value="subscribe"
class="button button-secondary mt-4"
>
{{ ctrans('texts.subscribe') }}
</button>
@endif
</form>
</div>
</div>
</div>
@stop

View File

@ -1,5 +1,6 @@
<?php
use App\Http\Controllers\ClientPortal\EmailPreferencesController;
use App\Http\Controllers\Auth\ContactForgotPasswordController;
use App\Http\Controllers\Auth\ContactLoginController;
use App\Http\Controllers\Auth\ContactRegisterController;
@ -132,6 +133,9 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie
Route::get('{entity}/{invitation_key}/download', [App\Http\Controllers\ClientPortal\InvitationController::class, 'routerForDownload'])->middleware('token_auth');
Route::get('pay/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'payInvoice'])->name('pay.invoice');
Route::get('email_preferences/{entity}/{invitation_key}', [EmailPreferencesController::class, 'index'])->name('email_preferences')->middleware('signed');
Route::put('email_preferences/{entity}/{invitation_key}', [EmailPreferencesController::class, 'update']);
Route::get('unsubscribe/{entity}/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'unsubscribe'])->name('unsubscribe');
});
@ -159,3 +163,5 @@ Route::fallback(function () {
abort(404);
})->middleware('throttle:404');
// Fix me: Move into invite_db middleware group.