diff --git a/app/Http/Controllers/ClientPortal/EmailPreferencesController.php b/app/Http/Controllers/ClientPortal/EmailPreferencesController.php
new file mode 100644
index 000000000000..6fd5063756aa
--- /dev/null
+++ b/app/Http/Controllers/ClientPortal/EmailPreferencesController.php
@@ -0,0 +1,61 @@
+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'));
+ }
+}
+
diff --git a/app/Mail/Admin/ClientUnsubscribedObject.php b/app/Mail/Admin/ClientUnsubscribedObject.php
new file mode 100644
index 000000000000..eb0190ab161f
--- /dev/null
+++ b/app/Mail/Admin/ClientUnsubscribedObject.php
@@ -0,0 +1,58 @@
+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;
+ }
+}
diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php
index 1530ce9a4f8a..f9f4b1562a21 100644
--- a/app/Mail/TemplateEmail.php
+++ b/app/Mail/TemplateEmail.php
@@ -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) {
diff --git a/app/Models/ClientContact.php b/app/Models/ClientContact.php
index 0acdfb09cf42..2f5a948feae8 100644
--- a/app/Models/ClientContact.php
+++ b/app/Models/ClientContact.php
@@ -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}";
+ }
+
+
}
diff --git a/app/Models/CreditInvitation.php b/app/Models/CreditInvitation.php
index 545585bcc5e3..83656270280f 100644
--- a/app/Models/CreditInvitation.php
+++ b/app/Models/CreditInvitation.php
@@ -97,6 +97,11 @@ class CreditInvitation extends BaseModel
return self::class;
}
+ public function getEntityString(): string
+ {
+ return 'credit';
+ }
+
public function entityType()
{
return Credit::class;
diff --git a/app/Models/InvoiceInvitation.php b/app/Models/InvoiceInvitation.php
index 2f5c6c08a1df..58c191fc4a41 100644
--- a/app/Models/InvoiceInvitation.php
+++ b/app/Models/InvoiceInvitation.php
@@ -97,6 +97,11 @@ class InvoiceInvitation extends BaseModel
return self::class;
}
+ public function getEntityString(): string
+ {
+ return 'invoice';
+ }
+
public function entityType()
{
return Invoice::class;
diff --git a/app/Models/PurchaseOrderInvitation.php b/app/Models/PurchaseOrderInvitation.php
index a1702ea6b926..7584256f72c9 100644
--- a/app/Models/PurchaseOrderInvitation.php
+++ b/app/Models/PurchaseOrderInvitation.php
@@ -97,6 +97,11 @@ class PurchaseOrderInvitation extends BaseModel
return self::class;
}
+ public function getEntityString(): string
+ {
+ return 'purchase_order';
+ }
+
public function entityType()
{
return PurchaseOrder::class;
diff --git a/app/Models/QuoteInvitation.php b/app/Models/QuoteInvitation.php
index 65aef2e2556a..e1c3c726973c 100644
--- a/app/Models/QuoteInvitation.php
+++ b/app/Models/QuoteInvitation.php
@@ -78,6 +78,11 @@ class QuoteInvitation extends BaseModel
return self::class;
}
+ public function getEntityString(): string
+ {
+ return 'quote';
+ }
+
public function entityType()
{
return Quote::class;
diff --git a/app/Models/RecurringInvoiceInvitation.php b/app/Models/RecurringInvoiceInvitation.php
index 34928a155c1d..a5fe7868db4e 100644
--- a/app/Models/RecurringInvoiceInvitation.php
+++ b/app/Models/RecurringInvoiceInvitation.php
@@ -91,6 +91,12 @@ class RecurringInvoiceInvitation extends BaseModel
return self::class;
}
+
+ public function getEntityString(): string
+ {
+ return 'recurring_invoice';
+ }
+
public function entityType()
{
return RecurringInvoice::class;
diff --git a/app/Services/Email/EmailMailable.php b/app/Services/Email/EmailMailable.php
index 2599d7d30161..ada43bab730d 100644
--- a/app/Services/Email/EmailMailable.php
+++ b/app/Services/Email/EmailMailable.php
@@ -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,
]
);
}
diff --git a/lang/en/texts.php b/lang/en/texts.php
index b7df2cdddbbd..ceb0e221993e 100644
--- a/lang/en/texts.php
+++ b/lang/en/texts.php
@@ -5213,6 +5213,12 @@ $lang = array(
'nordigen_requisition_body' => 'Access to bank account feeds has expired as set in End User Agreement.
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;
diff --git a/resources/views/email/template/client.blade.php b/resources/views/email/template/client.blade.php
index 4d9462b2b370..2986b35345fb 100644
--- a/resources/views/email/template/client.blade.php
+++ b/resources/views/email/template/client.blade.php
@@ -163,7 +163,7 @@
{{ $slot ?? '' }}
{!! $body ?? '' !!}
-
+
+ + {{ ctrans('texts.email_preferences') }} + +
+@endif + @endcomponent diff --git a/resources/views/email/template/light.blade.php b/resources/views/email/template/light.blade.php index 43b495cf4452..9a82f9c9e21a 100644 --- a/resources/views/email/template/light.blade.php +++ b/resources/views/email/template/light.blade.php @@ -14,4 +14,12 @@ @endif +@isset($email_preferences) ++ + {{ ctrans('texts.email_preferences') }} + +
+@endif + @endcomponent diff --git a/resources/views/email/template/plain.blade.php b/resources/views/email/template/plain.blade.php index 270144bf7931..1ae5fe70b167 100644 --- a/resources/views/email/template/plain.blade.php +++ b/resources/views/email/template/plain.blade.php @@ -44,6 +44,14 @@ @endif @endisset + +@if(isset($email_preferences)) ++ {{ ctrans('texts.email_preferences') }} +
+@endif + + @if(isset($unsubscribe_link)){{ ctrans('texts.unsubscribe') }}
@endif \ No newline at end of file diff --git a/resources/views/portal/ninja2020/generic/email_preferences.blade.php b/resources/views/portal/ninja2020/generic/email_preferences.blade.php new file mode 100644 index 000000000000..af73164212da --- /dev/null +++ b/resources/views/portal/ninja2020/generic/email_preferences.blade.php @@ -0,0 +1,43 @@ +@extends('portal.ninja2020.layout.clean') @section('meta_title', +ctrans('texts.preferences')) @section('body') +