diff --git a/app/Http/Controllers/V1/Admin/MailSender/GetAllMailSendersController.php b/app/Http/Controllers/V1/Admin/MailSender/GetAllMailSendersController.php new file mode 100644 index 000000000..60cf26590 --- /dev/null +++ b/app/Http/Controllers/V1/Admin/MailSender/GetAllMailSendersController.php @@ -0,0 +1,24 @@ +get(); + + return MailSenderResource::collection($mailSenders); + } +} diff --git a/app/Http/Controllers/V1/Admin/MailSender/MailSenderController.php b/app/Http/Controllers/V1/Admin/MailSender/MailSenderController.php new file mode 100644 index 000000000..38f44c326 --- /dev/null +++ b/app/Http/Controllers/V1/Admin/MailSender/MailSenderController.php @@ -0,0 +1,98 @@ +authorize('viewAny', MailSender::class); + + $limit = $request->has('limit') ? $request->limit : 10; + + $mailSenders = MailSender::whereCompany() + ->applyFilters($request->all()) + ->paginateData($limit); + + return (MailSenderResource::collection($mailSenders)) + ->additional(['meta' => [ + 'mail_sender_total_count' => MailSender::whereCompany()->count(), + ]]); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(MailSenderRequest $request) + { + $this->authorize('create', MailSender::class); + + $mailSender = MailSender::createFromRequest($request); + + return new MailSenderResource($mailSender); + } + + /** + * Display the specified resource. + * + * @param \Crater\Models\SenderMail $senderMail + * @return \Illuminate\Http\Response + */ + public function show(MailSender $mailSender) + { + $this->authorize('view', $mailSender); + + return new MailSenderResource($mailSender); + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param \Crater\Models\SenderMail $senderMail + * @return \Illuminate\Http\Response + */ + public function update(MailSenderRequest $request, MailSender $mailSender) + { + $this->authorize('update', $mailSender); + + $mailSender->updateFromRequest($request); + + return new MailSenderResource($mailSender); + } + + /** + * Remove the specified resource from storage. + * + * @param \Crater\Models\SenderMail $senderMail + * @return \Illuminate\Http\Response + */ + public function destroy(MailSender $mailSender) + { + $this->authorize('delete', $mailSender); + + if ($mailSender->is_default) { + return respondJson('You can\'t remove default mail sender.', 'You can\'t remove default mail sender.'); + } + + $mailSender->delete(); + + return response()->json([ + 'success' => true, + ]); + } +} diff --git a/app/Http/Controllers/V1/Admin/Settings/MailConfigurationController.php b/app/Http/Controllers/V1/Admin/Settings/MailConfigurationController.php index 29794d5c9..f07029dcc 100755 --- a/app/Http/Controllers/V1/Admin/Settings/MailConfigurationController.php +++ b/app/Http/Controllers/V1/Admin/Settings/MailConfigurationController.php @@ -3,80 +3,29 @@ namespace Crater\Http\Controllers\V1\Admin\Settings; use Crater\Http\Controllers\Controller; -use Crater\Http\Requests\MailEnvironmentRequest; +use Crater\Http\Requests\TestMailDriverRequest; use Crater\Mail\TestMail; -use Crater\Models\Setting; -use Crater\Space\EnvironmentManager; -use Illuminate\Http\JsonResponse; +use Crater\Models\MailSender; use Illuminate\Http\Request; use Mail; class MailConfigurationController extends Controller { - /** - * @var EnvironmentManager - */ - protected $environmentManager; - - /** - * @param EnvironmentManager $environmentManager - */ - public function __construct(EnvironmentManager $environmentManager) - { - $this->environmentManager = $environmentManager; - } - - /** - * - * @param MailEnvironmentRequest $request - * @return JsonResponse - */ - public function saveMailEnvironment(MailEnvironmentRequest $request) + public function TestMailDriver(TestMailDriverRequest $request) { $this->authorize('manage email config'); - $setting = Setting::getSetting('profile_complete'); - $results = $this->environmentManager->saveMailVariables($request); - - if ($setting !== 'COMPLETED') { - Setting::setSetting('profile_complete', 4); - } - - return response()->json($results); - } - - public function getMailEnvironment() - { - $this->authorize('manage email config'); - - $MailData = [ - 'mail_driver' => config('mail.driver'), - 'mail_host' => config('mail.host'), - 'mail_port' => config('mail.port'), - 'mail_username' => config('mail.username'), - 'mail_password' => config('mail.password'), - 'mail_encryption' => config('mail.encryption'), - 'from_name' => config('mail.from.name'), - 'from_mail' => config('mail.from.address'), - 'mail_mailgun_endpoint' => config('services.mailgun.endpoint'), - 'mail_mailgun_domain' => config('services.mailgun.domain'), - 'mail_mailgun_secret' => config('services.mailgun.secret'), - 'mail_ses_key' => config('services.ses.key'), - 'mail_ses_secret' => config('services.ses.secret'), - ]; + MailSender::setMailConfiguration($request->mail_sender_id); + Mail::to($request->to)->send(new TestMail($request->subject, $request->message)); - return response()->json($MailData); + return response()->json([ + 'success' => true, + ]); } - /** - * - * @return JsonResponse - */ - public function getMailDrivers() + public function getMailDrivers(Request $request) { - $this->authorize('manage email config'); - $drivers = [ 'smtp', 'mail', @@ -87,21 +36,4 @@ public function getMailDrivers() return response()->json($drivers); } - - public function testEmailConfig(Request $request) - { - $this->authorize('manage email config'); - - $this->validate($request, [ - 'to' => 'required|email', - 'subject' => 'required', - 'message' => 'required', - ]); - - Mail::to($request->to)->send(new TestMail($request->subject, $request->message)); - - return response()->json([ - 'success' => true, - ]); - } } diff --git a/app/Http/Controllers/V1/Customer/EstimatePdfController.php b/app/Http/Controllers/V1/Customer/EstimatePdfController.php index 9cba60ce7..dfa0d08c1 100644 --- a/app/Http/Controllers/V1/Customer/EstimatePdfController.php +++ b/app/Http/Controllers/V1/Customer/EstimatePdfController.php @@ -9,6 +9,7 @@ use Crater\Models\Customer; use Crater\Models\EmailLog; use Crater\Models\Estimate; +use Crater\Models\MailSender; use Illuminate\Http\Request; class EstimatePdfController extends Controller @@ -27,14 +28,16 @@ public function getPdf(EmailLog $emailLog, Request $request) ); if ($notifyEstimateViewed == 'YES') { - $data['estimate'] = Estimate::findOrFail($estimate->id)->toArray(); + $notificationEmail = CompanySetting::getSetting('notification_email', $estimate->company_id); + $mailSender = MailSender::where('company_id', $estimate->company_id)->where('is_default', true)->first(); + MailSender::setMailConfiguration($mailSender->id); + + $data['from_address'] = $mailSender->from_address; + $data['from_name'] = $mailSender->from_name; $data['user'] = Customer::find($estimate->customer_id)->toArray(); - $notificationEmail = CompanySetting::getSetting( - 'notification_email', - $estimate->company_id - ); + $data['estimate'] = Estimate::findOrFail($estimate->id)->toArray(); - \Mail::to($notificationEmail)->send(new EstimateViewedMail($data)); + send_mail(new EstimateViewedMail($data), $mailSender, $notificationEmail); } } diff --git a/app/Http/Controllers/V1/Customer/InvoicePdfController.php b/app/Http/Controllers/V1/Customer/InvoicePdfController.php index ad94403c3..ee63de955 100644 --- a/app/Http/Controllers/V1/Customer/InvoicePdfController.php +++ b/app/Http/Controllers/V1/Customer/InvoicePdfController.php @@ -9,6 +9,7 @@ use Crater\Models\Customer; use Crater\Models\EmailLog; use Crater\Models\Invoice; +use Crater\Models\MailSender; use Illuminate\Http\Request; class InvoicePdfController extends Controller @@ -28,14 +29,16 @@ public function getPdf(EmailLog $emailLog, Request $request) ); if ($notifyInvoiceViewed == 'YES') { + $notificationEmail = CompanySetting::getSetting('notification_email', $invoice->company_id); + $mailSender = MailSender::where('company_id', $invoice->company_id)->where('is_default', true)->first(); + MailSender::setMailConfiguration($mailSender->id); + + $data['from_address'] = $mailSender->from_address; + $data['from_name'] = $mailSender->from_name; $data['invoice'] = Invoice::findOrFail($invoice->id)->toArray(); $data['user'] = Customer::find($invoice->customer_id)->toArray(); - $notificationEmail = CompanySetting::getSetting( - 'notification_email', - $invoice->company_id - ); - \Mail::to($notificationEmail)->send(new InvoiceViewedMail($data)); + send_mail(new InvoiceViewedMail($data), $mailSender, $notificationEmail); } } diff --git a/app/Http/Middleware/ConfigMiddleware.php b/app/Http/Middleware/ConfigMiddleware.php index 2bcfc07fc..948b7d364 100644 --- a/app/Http/Middleware/ConfigMiddleware.php +++ b/app/Http/Middleware/ConfigMiddleware.php @@ -4,6 +4,7 @@ use Closure; use Crater\Models\FileDisk; +use Crater\Models\MailSender; class ConfigMiddleware { @@ -28,6 +29,12 @@ public function handle($request, Closure $next) } } + $default_mail_sender = MailSender::where('company_id', $request->header('company'))->where('is_default', true)->first(); + + if ($default_mail_sender) { + $default_mail_sender->setMailConfiguration($default_mail_sender->id); + } + return $next($request); } } diff --git a/app/Http/Requests/MailSenderRequest.php b/app/Http/Requests/MailSenderRequest.php new file mode 100644 index 000000000..7db202c83 --- /dev/null +++ b/app/Http/Requests/MailSenderRequest.php @@ -0,0 +1,85 @@ + [ + 'required', + Rule::unique('mail_senders') + ->where('company_id', $this->header('company')) + ], + 'driver' => [ + 'required', + ], + 'is_default' => [ + 'nullable' + ], + 'bcc' => [ + 'nullable' + ], + 'cc' => [ + 'nullable' + ], + 'from_address' => [ + 'nullable' + ], + 'from_name' => [ + 'nullable' + ], + 'settings' => [ + 'nullable' + ], + 'settings.*' => [ + 'nullable' + ] + ]; + + if ($this->isMethod('PUT')) { + $rules['name'] = [ + 'nullable', + Rule::unique('mail_senders') + ->ignore($this->route('mail_sender')->id) + ->where('company_id', $this->header('company')) + ]; + } + + return $rules; + } + + public function getMailSenderPayload() + { + $data = $this->validated(); + + if ($data['settings'] && $data['settings']['encryption'] == 'none') { + $data['settings']['encryption'] = ''; + } + + return collect($data) + ->merge([ + 'company_id' => $this->header('company'), + ]) + ->toArray(); + } +} diff --git a/app/Http/Requests/SendEstimatesRequest.php b/app/Http/Requests/SendEstimatesRequest.php index d7826486a..6ece4a5fc 100644 --- a/app/Http/Requests/SendEstimatesRequest.php +++ b/app/Http/Requests/SendEstimatesRequest.php @@ -30,7 +30,7 @@ public function rules() 'body' => [ 'required', ], - 'from' => [ + 'mail_sender_id' => [ 'required', ], 'to' => [ diff --git a/app/Http/Requests/SendInvoiceRequest.php b/app/Http/Requests/SendInvoiceRequest.php index 077e80479..c1dc5dc03 100644 --- a/app/Http/Requests/SendInvoiceRequest.php +++ b/app/Http/Requests/SendInvoiceRequest.php @@ -30,7 +30,7 @@ public function rules() 'subject' => [ 'required', ], - 'from' => [ + 'mail_sender_id' => [ 'required', ], 'to' => [ diff --git a/app/Http/Requests/SendPaymentRequest.php b/app/Http/Requests/SendPaymentRequest.php index 87745b94e..70fd46003 100644 --- a/app/Http/Requests/SendPaymentRequest.php +++ b/app/Http/Requests/SendPaymentRequest.php @@ -30,7 +30,7 @@ public function rules() 'body' => [ 'required', ], - 'from' => [ + 'mail_sender_id' => [ 'required', ], 'to' => [ diff --git a/app/Http/Requests/TestMailDriverRequest.php b/app/Http/Requests/TestMailDriverRequest.php new file mode 100644 index 000000000..b3bc9e2e3 --- /dev/null +++ b/app/Http/Requests/TestMailDriverRequest.php @@ -0,0 +1,39 @@ + [ + 'required', + 'email' + ], + 'subject' => [ + 'required' + ], + 'message' => [ + 'required' + ], + ]; + } +} diff --git a/app/Http/Resources/MailSenderResource.php b/app/Http/Resources/MailSenderResource.php new file mode 100644 index 000000000..e4b14504d --- /dev/null +++ b/app/Http/Resources/MailSenderResource.php @@ -0,0 +1,30 @@ + $this->id, + 'name' => $this->name, + 'driver' => $this->driver, + 'is_default' => $this->is_default, + 'bcc' => $this->bcc, + 'cc' => $this->cc, + 'from_address' => $this->from_address, + 'from_name' => $this->from_name, + 'company_id' => $this->company_id, + 'settings' => $this->settings + ]; + } +} diff --git a/app/Mail/EstimateViewedMail.php b/app/Mail/EstimateViewedMail.php index a29d8f6c7..61b4f1cde 100644 --- a/app/Mail/EstimateViewedMail.php +++ b/app/Mail/EstimateViewedMail.php @@ -30,7 +30,7 @@ public function __construct($data) */ public function build() { - return $this->from(config('mail.from.address'), config('mail.from.name')) + return $this->from($this->data['from_address'], $this->data['from_name']) ->markdown('emails.viewed.estimate', ['data', $this->data]); } } diff --git a/app/Mail/InvoiceViewedMail.php b/app/Mail/InvoiceViewedMail.php index 94e858b31..d63a01ff7 100644 --- a/app/Mail/InvoiceViewedMail.php +++ b/app/Mail/InvoiceViewedMail.php @@ -30,7 +30,7 @@ public function __construct($data) */ public function build() { - return $this->from(config('mail.from.address'), config('mail.from.name')) + return $this->from($this->data['from_address'], $this->data['from_name']) ->markdown('emails.viewed.invoice', ['data', $this->data]); } } diff --git a/app/Mail/SendEstimateMail.php b/app/Mail/SendEstimateMail.php index 70121e006..664f09bd8 100644 --- a/app/Mail/SendEstimateMail.php +++ b/app/Mail/SendEstimateMail.php @@ -34,7 +34,7 @@ public function __construct($data) public function build() { $log = EmailLog::create([ - 'from' => $this->data['from'], + 'from' => $this->data['from_address'], 'to' => $this->data['to'], 'subject' => $this->data['subject'], 'body' => $this->data['body'], @@ -47,9 +47,10 @@ public function build() $this->data['url'] = route('estimate', ['email_log' => $log->token]); - $mailContent = $this->from($this->data['from'], config('mail.from.name')) - ->subject($this->data['subject']) - ->markdown('emails.send.estimate', ['data', $this->data]); + $mailContent = $this->from($this->data['from_address'], $this->data['from_name']) + ->subject($this->data['subject']) + ->markdown("emails.send.estimate", ['data', $this->data]); + if ($this->data['attach']['data']) { $mailContent->attachData( diff --git a/app/Mail/SendInvoiceMail.php b/app/Mail/SendInvoiceMail.php index 81d76fa77..b48c35089 100644 --- a/app/Mail/SendInvoiceMail.php +++ b/app/Mail/SendInvoiceMail.php @@ -34,7 +34,7 @@ public function __construct($data) public function build() { $log = EmailLog::create([ - 'from' => $this->data['from'], + 'from' => $this->data['from_address'], 'to' => $this->data['to'], 'subject' => $this->data['subject'], 'body' => $this->data['body'], @@ -47,9 +47,9 @@ public function build() $this->data['url'] = route('invoice', ['email_log' => $log->token]); - $mailContent = $this->from($this->data['from'], config('mail.from.name')) + $mailContent = $this->from($this->data['from_address'], $this->data['from_name']) ->subject($this->data['subject']) - ->markdown('emails.send.invoice', ['data', $this->data]); + ->markdown("emails.send.invoice", ['data', $this->data]); if ($this->data['attach']['data']) { $mailContent->attachData( diff --git a/app/Mail/SendPaymentMail.php b/app/Mail/SendPaymentMail.php index 4d52547f1..81b504133 100644 --- a/app/Mail/SendPaymentMail.php +++ b/app/Mail/SendPaymentMail.php @@ -34,7 +34,7 @@ public function __construct($data) public function build() { $log = EmailLog::create([ - 'from' => $this->data['from'], + 'from' => $this->data['from_address'], 'to' => $this->data['to'], 'subject' => $this->data['subject'], 'body' => $this->data['body'], @@ -47,9 +47,9 @@ public function build() $this->data['url'] = route('payment', ['email_log' => $log->token]); - $mailContent = $this->from($this->data['from'], config('mail.from.name')) - ->subject($this->data['subject']) - ->markdown('emails.send.payment', ['data', $this->data]); + $mailContent = $this->from($this->data['from_address'], $this->data['from_name']) + ->subject($this->data['subject']) + ->markdown("emails.send.payment", ['data', $this->data]); if ($this->data['attach']['data']) { $mailContent->attachData( diff --git a/app/Models/Estimate.php b/app/Models/Estimate.php index 5b7c3b8fd..2e0356b46 100644 --- a/app/Models/Estimate.php +++ b/app/Models/Estimate.php @@ -5,10 +5,10 @@ use App; use Barryvdh\DomPDF\Facade as PDF; use Carbon\Carbon; -use Crater\Mail\SendEstimateMail; use Crater\Services\SerialNumberFormatter; use Crater\Traits\GeneratesPdfTrait; use Crater\Traits\HasCustomFieldsTrait; +use Crater\Traits\MailTrait; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Storage; @@ -20,6 +20,7 @@ class Estimate extends Model implements HasMedia { use HasFactory; + use MailTrait; use InteractsWithMedia; use GeneratesPdfTrait; use HasCustomFieldsTrait; @@ -363,7 +364,7 @@ public function send($data) $this->save(); } - \Mail::to($data['to'])->send(new SendEstimateMail($data)); + $this->setMail('estimate', $data); return [ 'success' => true, diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 39cd31659..4618bc4b1 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -9,6 +9,7 @@ use Crater\Services\SerialNumberFormatter; use Crater\Traits\GeneratesPdfTrait; use Crater\Traits\HasCustomFieldsTrait; +use Crater\Traits\MailTrait; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Storage; @@ -21,6 +22,7 @@ class Invoice extends Model implements HasMedia { use HasFactory; + use MailTrait; use InteractsWithMedia; use GeneratesPdfTrait; use HasCustomFieldsTrait; @@ -464,7 +466,7 @@ public function send($data) { $data = $this->sendInvoiceData($data); - \Mail::to($data['to'])->send(new SendInvoiceMail($data)); + $this->setMail('invoice', $data); if ($this->status == Invoice::STATUS_DRAFT) { $this->status = Invoice::STATUS_SENT; diff --git a/app/Models/MailSender.php b/app/Models/MailSender.php new file mode 100644 index 000000000..bc286013c --- /dev/null +++ b/app/Models/MailSender.php @@ -0,0 +1,111 @@ + 'array', + 'is_default' => 'boolean' + ]; + + public function company() + { + return $this->belongsTo(Company::class); + } + + public function scopeWhereOrder($query, $orderByField, $orderBy) + { + $query->orderBy($orderByField, $orderBy); + } + + public function scopeApplyFilters($query, array $filters) + { + $filters = collect($filters); + + if ($filters->get('orderByField') || $filters->get('orderBy')) { + $field = $filters->get('orderByField') ? $filters->get('orderByField') : 'name'; + $orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc'; + $query->whereOrder($field, $orderBy); + } + } + + public function scopePaginateData($query, $limit) + { + if ($limit == 'all') { + return $query->get(); + } + + return $query->paginate($limit); + } + + public function scopeWhereCompany($query) + { + $query->where('mail_senders.company_id', request()->header('company')); + } + + public static function createFromRequest(MailSenderRequest $request) + { + $senderMail = self::create($request->getMailSenderPayload()); + + if ($request->is_default) { + $senderMail->removeOtherDefaultMailSenders($request); + } + + return $senderMail; + } + + public function updateFromRequest(MailSenderRequest $request) + { + $data = $request->getMailSenderPayload(); + + $this->update($data); + + if ($request->is_default) { + $this->removeOtherDefaultMailSenders($request); + } + + return $this; + } + + public static function setMailConfiguration($id, $check = null) + { + $mailSender = MailSender::find($id); + + $settings = $mailSender->settings; + $settings['driver'] = $mailSender->driver; + $settings['from'] = [ + 'address' => $mailSender->from_address, + 'name' => $mailSender->from_name + ]; + $settings['sendmail'] = config('mail.sendmail'); + $settings['markdown'] = config('mail.markdown'); + $settings['log_channel'] = config('mail.log_channel'); + + Config::set('mail', $settings); + + if ($check) { + return $mailSender; + } + + return true; + } + + public function removeOtherDefaultMailSenders($request) { + MailSender::where('company_id', $request->header('company')) + ->where('is_default', true) + ->where('id', '<>', $this->id) + ->update(['is_default' => false]); + } +} diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 83a17441d..84b60c36d 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -5,10 +5,10 @@ use Barryvdh\DomPDF\Facade as PDF; use Carbon\Carbon; use Crater\Jobs\GeneratePaymentPdfJob; -use Crater\Mail\SendPaymentMail; use Crater\Services\SerialNumberFormatter; use Crater\Traits\GeneratesPdfTrait; use Crater\Traits\HasCustomFieldsTrait; +use Crater\Traits\MailTrait; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Spatie\MediaLibrary\HasMedia; @@ -18,6 +18,7 @@ class Payment extends Model implements HasMedia { use HasFactory; + use MailTrait; use InteractsWithMedia; use GeneratesPdfTrait; use HasCustomFieldsTrait; @@ -135,7 +136,7 @@ public function send($data) { $data = $this->sendPaymentData($data); - \Mail::to($data['to'])->send(new SendPaymentMail($data)); + $this->setMail('payment', $data); return [ 'success' => true, diff --git a/app/Policies/MailSenderPolicy.php b/app/Policies/MailSenderPolicy.php new file mode 100644 index 000000000..079afe756 --- /dev/null +++ b/app/Policies/MailSenderPolicy.php @@ -0,0 +1,123 @@ +hasCompany($mailSender->company_id)) { + return true; + } + + return false; + } + + /** + * Determine whether the user can create models. + * + * @param \Crater\Models\User $user + * @return \Illuminate\Auth\Access\Response|bool + */ + public function create(User $user) + { + if (BouncerFacade::can('create-mail-sender', MailSender::class)) { + return true; + } + + return false; + } + + /** + * Determine whether the user can update the model. + * + * @param \Crater\Models\User $user + * @param \Crater\Models\MailSender $mailSender + * @return \Illuminate\Auth\Access\Response|bool + */ + public function update(User $user, MailSender $mailSender) + { + if (BouncerFacade::can('edit-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) { + return true; + } + + return false; + } + + /** + * Determine whether the user can delete the model. + * + * @param \Crater\Models\User $user + * @param \Crater\Models\MailSender $mailSender + * @return \Illuminate\Auth\Access\Response|bool + */ + public function delete(User $user, MailSender $mailSender) + { + if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) { + return true; + } + + return false; + } + + /** + * Determine whether the user can restore the model. + * + * @param \Crater\Models\User $user + * @param \Crater\Models\MailSender $mailSender + * @return \Illuminate\Auth\Access\Response|bool + */ + public function restore(User $user, MailSender $mailSender) + { + if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) { + return true; + } + + return false; + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param \Crater\Models\User $user + * @param \Crater\Models\MailSender $mailSender + * @return \Illuminate\Auth\Access\Response|bool + */ + public function forceDelete(User $user, MailSender $mailSender) + { + if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) { + return true; + } + + return false; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 61cd43057..63a6d28a0 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -39,6 +39,7 @@ class AuthServiceProvider extends ServiceProvider \Crater\Models\CustomField::class => \Crater\Policies\CustomFieldPolicy::class, \Crater\Models\User::class => \Crater\Policies\UserPolicy::class, \Crater\Models\Item::class => \Crater\Policies\ItemPolicy::class, + \Crater\Models\MailSender::class => \Crater\Policies\MailSenderPolicy::class, \Silber\Bouncer\Database\Role::class => \Crater\Policies\RolePolicy::class, \Crater\Models\Unit::class => \Crater\Policies\UnitPolicy::class, \Crater\Models\RecurringInvoice::class => \Crater\Policies\RecurringInvoicePolicy::class, diff --git a/app/Space/EnvironmentManager.php b/app/Space/EnvironmentManager.php index 7ba57dcb8..a1600fecf 100755 --- a/app/Space/EnvironmentManager.php +++ b/app/Space/EnvironmentManager.php @@ -223,204 +223,6 @@ private function checkVersionRequirements(DatabaseEnvironmentRequest $request, $ return false; } - /** - * Save the mail content to the .env file. - * - * @param Request $request - * @return array - */ - public function saveMailVariables(MailEnvironmentRequest $request) - { - $mailData = $this->getMailData($request); - - try { - file_put_contents($this->envPath, str_replace( - $mailData['old_mail_data'], - $mailData['new_mail_data'], - file_get_contents($this->envPath) - )); - - if ($mailData['extra_old_mail_data']) { - file_put_contents($this->envPath, str_replace( - $mailData['extra_old_mail_data'], - $mailData['extra_mail_data'], - file_get_contents($this->envPath) - )); - } else { - file_put_contents( - $this->envPath, - "\n".$mailData['extra_mail_data'], - FILE_APPEND - ); - } - } catch (Exception $e) { - return [ - 'error' => 'mail_variables_save_error', - ]; - } - - return [ - 'success' => 'mail_variables_save_successfully', - ]; - } - - private function getMailData($request) - { - $mailFromCredential = ""; - $extraMailData = ""; - $extraOldMailData = ""; - $oldMailData = ""; - $newMailData = ""; - - if (env('MAIL_FROM_ADDRESS') !== null && env('MAIL_FROM_NAME') !== null) { - $mailFromCredential = - 'MAIL_FROM_ADDRESS='.config('mail.from.address')."\n". - 'MAIL_FROM_NAME="'.config('mail.from.name')."\"\n\n"; - } - - switch ($request->mail_driver) { - case 'smtp': - - $oldMailData = - 'MAIL_DRIVER='.config('mail.driver')."\n". - 'MAIL_HOST='.config('mail.host')."\n". - 'MAIL_PORT='.config('mail.port')."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n". - $mailFromCredential; - - $newMailData = - 'MAIL_DRIVER='.$request->mail_driver."\n". - 'MAIL_HOST='.$request->mail_host."\n". - 'MAIL_PORT='.$request->mail_port."\n". - 'MAIL_USERNAME='.$request->mail_username."\n". - 'MAIL_PASSWORD='.$request->mail_password."\n". - 'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n". - 'MAIL_FROM_ADDRESS='.$request->from_mail."\n". - 'MAIL_FROM_NAME="'.$request->from_name."\"\n\n"; - - break; - - case 'mailgun': - $oldMailData = - 'MAIL_DRIVER='.config('mail.driver')."\n". - 'MAIL_HOST='.config('mail.host')."\n". - 'MAIL_PORT='.config('mail.port')."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n". - $mailFromCredential; - - $newMailData = - 'MAIL_DRIVER='.$request->mail_driver."\n". - 'MAIL_HOST='.$request->mail_host."\n". - 'MAIL_PORT='.$request->mail_port."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n". - 'MAIL_FROM_ADDRESS='.$request->from_mail."\n". - 'MAIL_FROM_NAME="'.$request->from_name."\"\n\n"; - - $extraMailData = - 'MAILGUN_DOMAIN='.$request->mail_mailgun_domain."\n". - 'MAILGUN_SECRET='.$request->mail_mailgun_secret."\n". - 'MAILGUN_ENDPOINT='.$request->mail_mailgun_endpoint."\n"; - - if (env('MAILGUN_DOMAIN') !== null && env('MAILGUN_SECRET') !== null && env('MAILGUN_ENDPOINT') !== null) { - $extraOldMailData = - 'MAILGUN_DOMAIN='.config('services.mailgun.domain')."\n". - 'MAILGUN_SECRET='.config('services.mailgun.secret')."\n". - 'MAILGUN_ENDPOINT='.config('services.mailgun.endpoint')."\n"; - } - - break; - - case 'ses': - $oldMailData = - 'MAIL_DRIVER='.config('mail.driver')."\n". - 'MAIL_HOST='.config('mail.host')."\n". - 'MAIL_PORT='.config('mail.port')."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n". - $mailFromCredential; - - $newMailData = - 'MAIL_DRIVER='.$request->mail_driver."\n". - 'MAIL_HOST='.$request->mail_host."\n". - 'MAIL_PORT='.$request->mail_port."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n". - 'MAIL_FROM_ADDRESS='.$request->from_mail."\n". - 'MAIL_FROM_NAME="'.$request->from_name."\"\n\n"; - - $extraMailData = - 'SES_KEY='.$request->mail_ses_key."\n". - 'SES_SECRET='.$request->mail_ses_secret."\n"; - - if (env('SES_KEY') !== null && env('SES_SECRET') !== null) { - $extraOldMailData = - 'SES_KEY='.config('services.ses.key')."\n". - 'SES_SECRET='.config('services.ses.secret')."\n"; - } - - break; - - case 'mail': - $oldMailData = - 'MAIL_DRIVER='.config('mail.driver')."\n". - 'MAIL_HOST='.config('mail.host')."\n". - 'MAIL_PORT='.config('mail.port')."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n". - $mailFromCredential; - - $newMailData = - 'MAIL_DRIVER='.$request->mail_driver."\n". - 'MAIL_HOST='.config('mail.host')."\n". - 'MAIL_PORT='.config('mail.port')."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n". - 'MAIL_FROM_ADDRESS='.$request->from_mail."\n". - 'MAIL_FROM_NAME="'.$request->from_name."\"\n\n"; - - break; - - case 'sendmail': - $oldMailData = - 'MAIL_DRIVER='.config('mail.driver')."\n". - 'MAIL_HOST='.config('mail.host')."\n". - 'MAIL_PORT='.config('mail.port')."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n". - $mailFromCredential; - - $newMailData = - 'MAIL_DRIVER='.$request->mail_driver."\n". - 'MAIL_HOST='.config('mail.host')."\n". - 'MAIL_PORT='.config('mail.port')."\n". - 'MAIL_USERNAME='.config('mail.username')."\n". - 'MAIL_PASSWORD='.config('mail.password')."\n". - 'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n". - 'MAIL_FROM_ADDRESS='.$request->from_mail."\n". - 'MAIL_FROM_NAME="'.$request->from_name."\"\n\n"; - - break; - } - - return [ - 'old_mail_data' => $oldMailData, - 'new_mail_data' => $newMailData, - 'extra_mail_data' => $extraMailData, - 'extra_old_mail_data' => $extraOldMailData, - ]; - } - /** * Save the disk content to the .env file. * diff --git a/app/Space/helpers.php b/app/Space/helpers.php index 8e14fe0ad..877c25c14 100644 --- a/app/Space/helpers.php +++ b/app/Space/helpers.php @@ -5,6 +5,7 @@ use Crater\Models\CustomField; use Crater\Models\Setting; use Illuminate\Support\Str; +use Illuminate\Mail\Mailable; /** * Get company setting @@ -70,6 +71,42 @@ function set_active($path, $active = 'active') return call_user_func_array('Request::is', (array)$path) ? $active : ''; } +/** + * Send Mail + * + * @param Mailable $mailable + * @param object $mailSender + * @return string $to + */ +function send_mail(Mailable $mailable, object $mailSender = null, string $to) +{ + if ($mailSender->bcc && $mailSender->cc) { + \Mail::to($to) + ->bcc(explode(',', $mailSender->bcc)) + ->cc(explode(',', $mailSender->cc)) + ->send($mailable); + } + + if ($mailSender->bcc && $mailSender->cc == null) { + \Mail::to($to) + ->bcc(explode(',', $mailSender->bcc)) + ->send($mailable); + } + + if ($mailSender->bcc == null && $mailSender->cc) { + \Mail::to($to) + ->cc(explode(',', $mailSender->cc)) + ->send($mailable); + } + + if ($mailSender->bcc == null && $mailSender->cc == null) { + \Mail::to($to) + ->send($mailable); + } + + return true; +} + /** * @param $path * @return mixed diff --git a/app/Traits/MailTrait.php b/app/Traits/MailTrait.php new file mode 100644 index 000000000..e940bc20a --- /dev/null +++ b/app/Traits/MailTrait.php @@ -0,0 +1,40 @@ +from_address; + $data['from_name'] = $mailSender->from_name; + + switch ($model) { + case 'invoice': + send_mail(new SendInvoiceMail($data), $mailSender, $data['to']); + + break; + + case 'estimate': + send_mail(new SendEstimateMail($data), $mailSender, $data['to']); + + break; + + case 'payment': + send_mail(new SendPaymentMail($data), $mailSender, $data['to']); + + break; + } + + return true; + } +} diff --git a/config/abilities.php b/config/abilities.php index 278050ad9..dfbd1f88d 100644 --- a/config/abilities.php +++ b/config/abilities.php @@ -7,6 +7,7 @@ use Crater\Models\Expense; use Crater\Models\Invoice; use Crater\Models\Item; +use Crater\Models\MailSender; use Crater\Models\Note; use Crater\Models\Payment; use Crater\Models\RecurringInvoice; @@ -397,6 +398,41 @@ ] ], + // Mail Sender + [ + "name" => "view mail sender", + "ability" => "view-mail-sender", + "model" => MailSender::class, + 'owner_only' => false, + ], + [ + "name" => "create mail sender", + "ability" => "create-mail-sender", + "model" => MailSender::class, + 'owner_only' => false, + "depends_on" => [ + 'view-mail-sender', + ] + ], + [ + "name" => "edit mail sender", + "ability" => "edit-mail-sender", + "model" => MailSender::class, + 'owner_only' => false, + "depends_on" => [ + 'view-mail-sender', + ] + ], + [ + "name" => "delete mail sender", + "ability" => "delete-mail-sender", + "model" => MailSender::class, + 'owner_only' => false, + "depends_on" => [ + 'view-mail-sender', + ] + ], + // Settings [ "name" => "view company dashboard", diff --git a/config/crater.php b/config/crater.php index 4de981054..894ba767e 100644 --- a/config/crater.php +++ b/config/crater.php @@ -7,6 +7,7 @@ use Crater\Models\Expense; use Crater\Models\Invoice; use Crater\Models\Item; +use Crater\Models\MailSender; use Crater\Models\Note; use Crater\Models\Payment; use Crater\Models\RecurringInvoice; @@ -225,6 +226,17 @@ 'ability' => 'view-all-notes', 'model' => Note::class ], + [ + 'title' => 'settings.menu_title.mail_sender', + 'group' => '', + 'name' => 'Mail Sender', + 'link' => '/admin/settings/mail-sender', + 'icon' => 'MailIcon', + 'owner_only' => false, + 'ability' => 'view-mail-sender', + 'model' => MailSender::class + ], + [ 'title' => 'settings.menu_title.expense_category', 'group' => '', @@ -235,16 +247,6 @@ 'ability' => 'view-expense', 'model' => Expense::class ], - [ - 'title' => 'settings.mail.mail_config', - 'group' => '', - 'name' => 'Mail Configuration', - 'link' => '/admin/settings/mail-configuration', - 'icon' => 'MailIcon', - 'owner_only' => true, - 'ability' => '', - 'model' => '' - ], [ 'title' => 'settings.menu_title.file_disk', 'group' => '', @@ -275,6 +277,7 @@ 'ability' => '', 'model' => '' ], + ], /* diff --git a/database/migrations/2023_03_10_125202_create_mail_senders_table.php b/database/migrations/2023_03_10_125202_create_mail_senders_table.php new file mode 100644 index 000000000..8e1cf725a --- /dev/null +++ b/database/migrations/2023_03_10_125202_create_mail_senders_table.php @@ -0,0 +1,106 @@ +id(); + $table->string('name'); + $table->string('driver'); + $table->boolean('is_default')->default(false); + $table->string('bcc')->nullable(); + $table->string('cc')->nullable(); + $table->string('from_address')->nullable(); + $table->string('from_name')->nullable(); + $table->json('settings')->nullable(); + $table->integer('company_id')->unsigned()->nullable(); + $table->foreign('company_id')->references('id')->on('companies'); + $table->timestamps(); + }); + + $users = User::where('role', 'super admin')->get(); + + foreach ($users as $user) { + BouncerFacade::allow($user)->toManage(MailSender::class); + } + + $companies = Company::all(); + + $companies->map(function ($company) { + if (env('MAIL_DRIVER') == 'smtp') { + $settings = [ + 'MAIL_HOST' => env('MAIL_HOST'), + 'MAIL_PORT' => env('MAIL_PORT'), + 'MAIL_USERNAME' => env('MAIL_USERNAME'), + 'MAIL_PASSWORD' => env('MAIL_PASSWORD'), + 'MAIL_ENCRYPTION' => env('MAIL_ENCRYPTION') + ]; + $this->createSender($settings, $company->id); + } + + if (env('MAIL_DRIVER') == 'mail' || env('MAIL_DRIVER') == 'sendmail') { + $this->createSender(null, $company->id); + } + + if (env('MAIL_DRIVER') == 'mailgun') { + $settings = [ + 'MAILGUN_DOMAIN' => env('MAILGUN_DOMAIN'), + 'MAILGUN_SECRET' => env('MAILGUN_SECRET'), + 'MAILGUN_ENDPOINT' => env('MAILGUN_ENDPOINT'), + ]; + $this->createSender($settings, $company->id); + } + + if (env('MAIL_DRIVER') == 'ses') { + $settings = [ + 'MAIL_HOST' => env('MAIL_HOST'), + 'MAIL_PORT' => env('MAIL_PORT'), + 'MAIL_ENCRYPTION' => env('MAIL_ENCRYPTION'), + 'MAILGUN_DOMAIN' => env('MAILGUN_DOMAIN'), + 'SES_KEY' => env('SES_KEY'), + 'SES_SECRET' => env('SES_SECRET'), + ]; + $this->createSender($settings, $company->id); + } + }); + } + + public function createSender($settings, $company_id) + { + $data = [ + 'name' => env('MAIL_DRIVER'), + 'driver' => env('MAIL_DRIVER'), + 'is_default' => true, + 'from_address' => env('MAIL_FROM_ADDRESS'), + 'from_name' => env('MAIL_FROM_NAME'), + 'settings' => $settings ?? null, + 'company_id' => $company_id + ]; + + MailSender::create($data); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mail_senders'); + } +} diff --git a/resources/scripts/admin/admin-router.js b/resources/scripts/admin/admin-router.js index d11952e4b..15de1d40c 100644 --- a/resources/scripts/admin/admin-router.js +++ b/resources/scripts/admin/admin-router.js @@ -47,8 +47,6 @@ const ExpenseCategory = () => import('@/scripts/admin/views/settings/ExpenseCategorySetting.vue') const ExchangeRateSetting = () => import('@/scripts/admin/views/settings/ExchangeRateProviderSetting.vue') -const MailConfig = () => - import('@/scripts/admin/views/settings/MailConfigSetting.vue') const FileDisk = () => import('@/scripts/admin/views/settings/FileDiskSetting.vue') const Backup = () => import('@/scripts/admin/views/settings/BackupSetting.vue') @@ -56,6 +54,8 @@ const UpdateApp = () => import('@/scripts/admin/views/settings/UpdateAppSetting.vue') const RolesSettings = () => import('@/scripts/admin/views/settings/RolesSettings.vue') +const MailSender = () => + import('@/scripts/admin/views/settings/mail-sender/Index.vue') // Items const ItemsIndex = () => import('@/scripts/admin/views/items/Index.vue') @@ -302,13 +302,6 @@ export default [ meta: { ability: abilities.VIEW_EXPENSE }, component: ExpenseCategory, }, - - { - path: 'mail-configuration', - name: 'mailconfig', - meta: { isOwner: true }, - component: MailConfig, - }, { path: 'file-disk', name: 'file-disk', @@ -327,6 +320,13 @@ export default [ meta: { isOwner: true }, component: UpdateApp, }, + { + path: 'mail-sender', + name: 'mailsender', + meta: { ability: abilities.VIEW_MAIL_SENDER }, + component: MailSender, + }, + ], }, diff --git a/resources/scripts/admin/components/FeedbackAlert.vue b/resources/scripts/admin/components/FeedbackAlert.vue new file mode 100644 index 000000000..2c1b14cf5 --- /dev/null +++ b/resources/scripts/admin/components/FeedbackAlert.vue @@ -0,0 +1,123 @@ + + + diff --git a/resources/scripts/admin/components/dropdowns/MailSenderIndexDropdown.vue b/resources/scripts/admin/components/dropdowns/MailSenderIndexDropdown.vue new file mode 100644 index 000000000..8b1226a7a --- /dev/null +++ b/resources/scripts/admin/components/dropdowns/MailSenderIndexDropdown.vue @@ -0,0 +1,111 @@ + + + diff --git a/resources/scripts/admin/components/modal-components/CustomerModal.vue b/resources/scripts/admin/components/modal-components/CustomerModal.vue index da158bfc6..260a31462 100644 --- a/resources/scripts/admin/components/modal-components/CustomerModal.vue +++ b/resources/scripts/admin/components/modal-components/CustomerModal.vue @@ -453,7 +453,7 @@ diff --git a/resources/scripts/admin/components/modal-components/MailSenderTestModal.vue b/resources/scripts/admin/components/modal-components/MailSenderTestModal.vue new file mode 100644 index 000000000..06c4e39a4 --- /dev/null +++ b/resources/scripts/admin/components/modal-components/MailSenderTestModal.vue @@ -0,0 +1,208 @@ + + + diff --git a/resources/scripts/admin/components/modal-components/SendEstimateModal.vue b/resources/scripts/admin/components/modal-components/SendEstimateModal.vue index 795cdfebb..2076460b7 100644 --- a/resources/scripts/admin/components/modal-components/SendEstimateModal.vue +++ b/resources/scripts/admin/components/modal-components/SendEstimateModal.vue @@ -16,18 +16,28 @@
-
+ +
-
+ +
+ + + +
+
@@ -75,6 +124,7 @@ { }) const rules = { - from: { + mail_sender_id: { required: helpers.withMessage(t('validation.required'), required), - email: helpers.withMessage(t('validation.email_incorrect'), email), }, to: { required: helpers.withMessage(t('validation.required'), required), @@ -207,20 +262,26 @@ function cancelPreview() { } async function setInitialData() { - let admin = await companyStore.fetchBasicMailConfig() - estimateMailForm.id = modalStore.id - - if (admin.data) { - estimateMailForm.from = admin.data.from_mail - } - if (modalData.value) { estimateMailForm.to = modalData.value.customer.email } estimateMailForm.body = companyStore.selectedCompanySettings.estimate_mail_body + + isFetchingInitialData.value = true + let mailSenderData = await mailSenderStore.fetchMailSenders({ limit: 'all' }) + if (mailSenderData.data) { + mailSenders.value = mailSenderData.data.data + let defaultMailSender = mailSenderData.data.data.find( + (mailSender) => mailSender.is_default == true + ) + estimateMailForm.mail_sender_id = defaultMailSender + ? defaultMailSender.id + : null + isFetchingInitialData.value = false + } } async function submitForm() { @@ -274,4 +335,18 @@ function closeSendEstimateModal() { templateUrl.value = null }, 300) } + +function getTickImage() { + const imgUrl = new URL('/img/tick.png', import.meta.url) + return imgUrl +} + +const isMailSenderExist = computed(() => { + return mailSenders.value && mailSenders.value.length +}) + +function gotoMailSender() { + closeSendEstimateModal() + router.push('/admin/settings/mail-sender') +} diff --git a/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue b/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue index 63e7e6af4..70a37fee7 100644 --- a/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue +++ b/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue @@ -15,18 +15,28 @@
-
+ +
-
+ +
+ + + +
+
@@ -77,6 +126,7 @@ {{ $t('general.cancel') }} { }) const rules = { - from: { + mail_sender_id: { required: helpers.withMessage(t('validation.required'), required), - email: helpers.withMessage(t('validation.email_incorrect'), email), }, to: { required: helpers.withMessage(t('validation.required'), required), @@ -224,19 +279,25 @@ function cancelPreview() { } async function setInitialData() { - let admin = await companyStore.fetchBasicMailConfig() - invoiceMailForm.id = modalStore.id - - if (admin.data) { - invoiceMailForm.from = admin.data.from_mail - } - if (modalData.value) { invoiceMailForm.to = modalData.value.customer.email } invoiceMailForm.body = companyStore.selectedCompanySettings.invoice_mail_body + + isFetchingInitialData.value = true + let mailSenderData = await mailSenderStore.fetchMailSenders({ limit: 'all' }) + if (mailSenderData.data) { + mailSenders.value = mailSenderData.data.data + let defaultMailSender = mailSenderData.data.data.find( + (mailSender) => mailSender.is_default == true + ) + invoiceMailForm.mail_sender_id = defaultMailSender + ? defaultMailSender.id + : null + isFetchingInitialData.value = false + } } async function submitForm() { @@ -287,4 +348,18 @@ function closeSendInvoiceModal() { templateUrl.value = null }, 300) } + +function getTickImage() { + const imgUrl = new URL('/img/tick.png', import.meta.url) + return imgUrl +} + +const isMailSenderExist = computed(() => { + return mailSenders.value && mailSenders.value.length +}) + +function gotoMailSender() { + closeSendInvoiceModal() + router.push('/admin/settings/mail-sender') +} diff --git a/resources/scripts/admin/components/modal-components/SendPaymentModal.vue b/resources/scripts/admin/components/modal-components/SendPaymentModal.vue index 6c786312b..946bc54a6 100644 --- a/resources/scripts/admin/components/modal-components/SendPaymentModal.vue +++ b/resources/scripts/admin/components/modal-components/SendPaymentModal.vue @@ -15,18 +15,28 @@
-
+ +
-
+ +
+ + + +
+
@@ -77,6 +126,7 @@ {{ $t('general.cancel') }} { }) const rules = { - from: { + mail_sender_id: { required: helpers.withMessage(t('validation.required'), required), - email: helpers.withMessage(t('validation.email_incorrect'), email), }, to: { required: helpers.withMessage(t('validation.required'), required), @@ -221,18 +276,25 @@ function cancelPreview() { } async function setInitialData() { - let admin = await companyStore.fetchBasicMailConfig() paymentMailForm.id = modalStore.id - - if (admin.data) { - paymentMailForm.from = admin.data.from_mail - } - if (modalData.value) { paymentMailForm.to = modalData.value.customer.email } paymentMailForm.body = companyStore.selectedCompanySettings.payment_mail_body + + isFetchingInitialData.value = true + let mailSenderData = await mailSenderStore.fetchMailSenders({ limit: 'all' }) + if (mailSenderData.data) { + mailSenders.value = mailSenderData.data.data + let defaultMailSender = mailSenderData.data.data.find( + (mailSender) => mailSender.is_default == true + ) + paymentMailForm.mail_sender_id = defaultMailSender + ? defaultMailSender.id + : null + isFetchingInitialData.value = false + } } async function sendPaymentData() { @@ -280,4 +342,18 @@ function closeSendPaymentModal() { modalStore.resetModalData() }, 300) } + +function getTickImage() { + const imgUrl = new URL('/img/tick.png', import.meta.url) + return imgUrl +} + +const isMailSenderExist = computed(() => { + return mailSenders.value && mailSenders.value.length +}) + +function gotoMailSender() { + closeSendPaymentModal() + router.push('/admin/settings/mail-sender') +} diff --git a/resources/scripts/admin/stores/mail-driver.js b/resources/scripts/admin/stores/mail-driver.js deleted file mode 100644 index f6086efd2..000000000 --- a/resources/scripts/admin/stores/mail-driver.js +++ /dev/null @@ -1,146 +0,0 @@ -import axios from 'axios' -import { defineStore } from 'pinia' -import { useNotificationStore } from '@/scripts/stores/notification' -import { handleError } from '@/scripts/helpers/error-handling' - -export const useMailDriverStore = (useWindow = false) => { - const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore - const { global } = window.i18n - - return defineStoreFunc({ - id: 'mail-driver', - - state: () => ({ - mailConfigData: null, - mail_driver: 'smtp', - mail_drivers: [], - - basicMailConfig: { - mail_driver: '', - mail_host: '', - from_mail: '', - from_name: '', - }, - - mailgunConfig: { - mail_driver: '', - mail_mailgun_domain: '', - mail_mailgun_secret: '', - mail_mailgun_endpoint: '', - from_mail: '', - from_name: '', - }, - - sesConfig: { - mail_driver: '', - mail_host: '', - mail_port: null, - mail_ses_key: '', - mail_ses_secret: '', - mail_encryption: 'tls', - from_mail: '', - from_name: '', - }, - - smtpConfig: { - mail_driver: '', - mail_host: '', - mail_port: null, - mail_username: '', - mail_password: '', - mail_encryption: 'tls', - from_mail: '', - from_name: '', - }, - }), - - actions: { - fetchMailDrivers() { - return new Promise((resolve, reject) => { - axios - .get('/api/v1/mail/drivers') - .then((response) => { - if (response.data) { - this.mail_drivers = response.data - } - resolve(response) - }) - .catch((err) => { - handleError(err) - reject(err) - }) - }) - }, - - fetchMailConfig() { - return new Promise((resolve, reject) => { - axios - .get('/api/v1/mail/config') - .then((response) => { - if (response.data) { - this.mailConfigData = response.data - this.mail_driver = response.data.mail_driver - } - resolve(response) - }) - .catch((err) => { - handleError(err) - reject(err) - }) - }) - }, - - updateMailConfig(data) { - return new Promise((resolve, reject) => { - axios - .post('/api/v1/mail/config', data) - .then((response) => { - const notificationStore = useNotificationStore() - if (response.data.success) { - notificationStore.showNotification({ - type: 'success', - message: global.t('wizard.success.' + response.data.success), - }) - } else { - notificationStore.showNotification({ - type: 'error', - message: global.t('wizard.errors.' + response.data.error), - }) - } - resolve(response) - }) - .catch((err) => { - handleError(err) - reject(err) - }) - }) - }, - - sendTestMail(data) { - return new Promise((resolve, reject) => { - axios - .post('/api/v1/mail/test', data) - .then((response) => { - const notificationStore = useNotificationStore() - if (response.data.success) { - notificationStore.showNotification({ - type: 'success', - message: global.t('general.send_mail_successfully'), - }) - } else { - notificationStore.showNotification({ - type: 'error', - message: global.t('validation.something_went_wrong'), - }) - } - resolve(response) - }) - .catch((err) => { - handleError(err) - reject(err) - }) - }) - }, - }, - })() -} diff --git a/resources/scripts/admin/stores/mail-sender.js b/resources/scripts/admin/stores/mail-sender.js new file mode 100644 index 000000000..0105e7a87 --- /dev/null +++ b/resources/scripts/admin/stores/mail-sender.js @@ -0,0 +1,202 @@ +import axios from 'axios' +import { defineStore } from 'pinia' +import { useNotificationStore } from '@/scripts/stores/notification' +import { handleError } from '@/scripts/helpers/error-handling' +import mailSenderStub from '@/scripts/admin/stub/mail-sender.js' + +export const useMailSenderStore = (useWindow = false) => { + const pre_t = 'settings.mail_sender' + const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore + const { global } = window.i18n + + return defineStoreFunc({ + id: 'mailSender', + + state: () => ({ + mailSenders: [], + mail_drivers: [], // list of mail drivers + currentMailSender: { ...mailSenderStub.basicConfig }, + smtpConfig: { ...mailSenderStub.smtpConfig }, + mailgunConfig: { ...mailSenderStub.mailgunConfig }, + sesConfig: { ...mailSenderStub.sesConfig }, + mail_encryptions: ['none', 'tls', 'ssl', 'starttls'], + isDisable: false + }), + + getters: { + isEdit: (state) => (state.currentMailSender.id ? true : false), + }, + + actions: { + resetCurrentMailSender() { + this.currentMailSender = { ...mailSenderStub.basicConfig } + this.smtpConfig = { ...mailSenderStub.smtpConfig } + this.mailgunConfig = { ...mailSenderStub.mailgunConfig } + this.sesConfig = { ...mailSenderStub.sesConfig } + this.isDisable = false + }, + + fetchMailDrivers() { + return new Promise((resolve, reject) => { + axios + .get('/api/v1/mail-drivers') + .then((response) => { + if (response.data) { + this.mail_drivers = response.data + } + resolve(response) + }) + .catch((err) => { + handleError(err) + reject(err) + }) + }) + }, + + + fetchMailSenders(params) { + return new Promise((resolve, reject) => { + axios + .get(`/api/v1/mail-senders`, { params }) + .then((response) => { + this.mailSenders = response.data.data + resolve(response) + }) + .catch((err) => { + handleError(err) + reject(err) + }) + }) + }, + + fetchMailSender(id) { + return new Promise((resolve, reject) => { + axios + .get(`/api/v1/mail-senders/${id}`) + .then((response) => { + this.currentMailSender = response.data.data + this.isDisable = response.data.data.is_default + if (response.data.data.settings) { + var settings = response.data.data.settings + const encryptionNone = settings.encryption == '' || settings.encryption == undefined + switch (response.data.data.driver) { + case 'smtp': + this.smtpConfig = settings + encryptionNone ? this.smtpConfig.encryption = 'none' : '' + break + case 'mailgun': + this.mailgunConfig = settings + break + case 'ses': + this.sesConfig = settings + encryptionNone ? this.sesConfig.encryption = 'none' : '' + break + } + } + resolve(response) + }) + .catch((err) => { + handleError(err) + reject(err) + }) + }) + }, + + addMailSender(data) { + const notificationStore = useNotificationStore() + return new Promise((resolve, reject) => { + axios + .post('/api/v1/mail-senders', data) + .then((response) => { + this.mailSenders.push(response.data.data) + notificationStore.showNotification({ + type: 'success', + message: global.t(`${pre_t}.created_message`), + }) + resolve(response) + }) + .catch((err) => { + handleError(err) + reject(err) + }) + }) + }, + + updateMailSender(data) { + const notificationStore = useNotificationStore() + return new Promise((resolve, reject) => { + axios + .put(`/api/v1/mail-senders/${data.id}`, data) + .then((response) => { + if (response.data) { + let pos = this.mailSenders.findIndex( + (mailSender) => mailSender.id === response.data.data.id + ) + this.mailSenders[pos] = data + notificationStore.showNotification({ + type: 'success', + message: global.t(`${pre_t}.updated_message`), + }) + } + resolve(response) + }) + .catch((err) => { + handleError(err) + reject(err) + }) + }) + }, + + deleteMailSender(id) { + return new Promise((resolve, reject) => { + axios + .delete(`/api/v1/mail-senders/${id}`) + .then((response) => { + if (response.data.success) { + let index = this.mailSenders.findIndex( + (mailSender) => mailSender.id === id + ) + this.mailSenders.splice(index, 1) + const notificationStore = useNotificationStore() + notificationStore.showNotification({ + type: 'success', + message: global.t(`${pre_t}.deleted_message`), + }) + } + resolve(response) + }) + .catch((err) => { + handleError(err) + reject(err) + }) + }) + }, + + sendTestMail(data) { + return new Promise((resolve, reject) => { + axios + .post('/api/v1/mail-test', data) + .then((response) => { + const notificationStore = useNotificationStore() + if (response.data.success) { + notificationStore.showNotification({ + type: 'success', + message: global.t('general.send_mail_successfully'), + }) + } else { + notificationStore.showNotification({ + type: 'error', + message: global.t('validation.something_went_wrong'), + }) + } + resolve(response) + }) + .catch((err) => { + handleError(err) + reject(err) + }) + }) + }, + }, + })() +} diff --git a/resources/scripts/admin/stores/users.js b/resources/scripts/admin/stores/users.js index 30a5bfd41..8e337f70c 100644 --- a/resources/scripts/admin/stores/users.js +++ b/resources/scripts/admin/stores/users.js @@ -25,6 +25,7 @@ export const useUsersStore = (useWindow = false) => { password: null, phone: null, companies: [], + sender_id: null, }, }), diff --git a/resources/scripts/admin/stub/abilities.js b/resources/scripts/admin/stub/abilities.js index 024e9992e..f01b73690 100644 --- a/resources/scripts/admin/stub/abilities.js +++ b/resources/scripts/admin/stub/abilities.js @@ -64,6 +64,13 @@ export default { EDIT_ROLE: 'edit-role', VIEW_ROLE: 'view-role', + // Mail Sender + CREATE_MAIL_SENDER: 'view-mail-sender', + DELETE_MAIL_SENDER: 'delete-mail-sender', + EDIT_MAIL_SENDER: 'edit-mail-sender', + VIEW_MAIL_SENDER: 'view-mail-sender', + + // exchange rates VIEW_EXCHANGE_RATE: 'view-exchange-rate-provider', CREATE_EXCHANGE_RATE: 'create-exchange-rate-provider', diff --git a/resources/scripts/admin/stub/customer.js b/resources/scripts/admin/stub/customer.js index 55512add5..512c96417 100644 --- a/resources/scripts/admin/stub/customer.js +++ b/resources/scripts/admin/stub/customer.js @@ -15,5 +15,6 @@ export default function () { customFields: [], fields: [], enable_portal: false, + mail_sender_id: null, } } diff --git a/resources/scripts/admin/stub/mail-sender.js b/resources/scripts/admin/stub/mail-sender.js new file mode 100644 index 000000000..3e608a183 --- /dev/null +++ b/resources/scripts/admin/stub/mail-sender.js @@ -0,0 +1,31 @@ +export default { + basicConfig: { + name: '', + from_name: '', + from_address: '', + cc: '', + bcc: '', + is_default: false, + driver: 'smtp', // 'smtp', 'mail', 'sendmail', 'mailgun', 'ses' + settings: '', + }, + smtpConfig: { + host: '', + port: null, + username: '', + password: '', + encryption: 'tls', // 'tls', 'ssl', 'starttls' + }, + mailgunConfig: { + domain: '', + secret: '', + endpoint: '', + }, + sesConfig: { + host: '', + port: null, + encryption: 'tls', // 'tls', 'ssl', 'starttls' + ses_key: '', + ses_secret: '', + }, +} diff --git a/resources/scripts/admin/views/customers/Create.vue b/resources/scripts/admin/views/customers/Create.vue index a40a2a6b0..0dc20fff1 100644 --- a/resources/scripts/admin/views/customers/Create.vue +++ b/resources/scripts/admin/views/customers/Create.vue @@ -256,6 +256,7 @@ /> +
@@ -650,10 +651,7 @@ const rules = computed(() => { }, email: { - required: helpers.withMessage( - t('validation.required'), - requiredIf(customerStore.currentCustomer.enable_portal == true) - ), + required: helpers.withMessage(t('validation.required'), required), email: helpers.withMessage(t('validation.email_incorrect'), email), }, password: { diff --git a/resources/scripts/admin/views/installation/Step5EmailConfig.vue b/resources/scripts/admin/views/installation/Step5EmailConfig.vue index ad2813d47..413f3f7ab 100644 --- a/resources/scripts/admin/views/installation/Step5EmailConfig.vue +++ b/resources/scripts/admin/views/installation/Step5EmailConfig.vue @@ -3,75 +3,238 @@ :title="$t('wizard.mail.mail_config')" :description="$t('wizard.mail.mail_config_desc')" > - - - - - +
+
+ + + + + - diff --git a/resources/scripts/admin/views/settings/mail-driver/BasicMailDriver.vue b/resources/scripts/admin/views/settings/mail-driver/BasicMailDriver.vue deleted file mode 100644 index 031c9412a..000000000 --- a/resources/scripts/admin/views/settings/mail-driver/BasicMailDriver.vue +++ /dev/null @@ -1,158 +0,0 @@ - - - diff --git a/resources/scripts/admin/views/settings/mail-driver/MailgunMailDriver.vue b/resources/scripts/admin/views/settings/mail-driver/MailgunMailDriver.vue deleted file mode 100644 index fa2ebbcf4..000000000 --- a/resources/scripts/admin/views/settings/mail-driver/MailgunMailDriver.vue +++ /dev/null @@ -1,247 +0,0 @@ - - - diff --git a/resources/scripts/admin/views/settings/mail-driver/SesMailDriver.vue b/resources/scripts/admin/views/settings/mail-driver/SesMailDriver.vue deleted file mode 100644 index 5eae5a074..000000000 --- a/resources/scripts/admin/views/settings/mail-driver/SesMailDriver.vue +++ /dev/null @@ -1,294 +0,0 @@ - - - diff --git a/resources/scripts/admin/views/settings/mail-driver/SmtpMailDriver.vue b/resources/scripts/admin/views/settings/mail-driver/SmtpMailDriver.vue deleted file mode 100644 index dbb947f2d..000000000 --- a/resources/scripts/admin/views/settings/mail-driver/SmtpMailDriver.vue +++ /dev/null @@ -1,275 +0,0 @@ - - - diff --git a/resources/scripts/admin/views/settings/mail-sender/Index.vue b/resources/scripts/admin/views/settings/mail-sender/Index.vue new file mode 100644 index 000000000..d46cf160e --- /dev/null +++ b/resources/scripts/admin/views/settings/mail-sender/Index.vue @@ -0,0 +1,136 @@ + + + diff --git a/resources/scripts/admin/views/settings/mail-sender/MailgunDriver.vue b/resources/scripts/admin/views/settings/mail-sender/MailgunDriver.vue new file mode 100644 index 000000000..1889c9f04 --- /dev/null +++ b/resources/scripts/admin/views/settings/mail-sender/MailgunDriver.vue @@ -0,0 +1,104 @@ + + + diff --git a/resources/scripts/admin/views/settings/mail-sender/SesDriver.vue b/resources/scripts/admin/views/settings/mail-sender/SesDriver.vue new file mode 100644 index 000000000..454bdda67 --- /dev/null +++ b/resources/scripts/admin/views/settings/mail-sender/SesDriver.vue @@ -0,0 +1,143 @@ + + + diff --git a/resources/scripts/admin/views/settings/mail-sender/SmtpDriver.vue b/resources/scripts/admin/views/settings/mail-sender/SmtpDriver.vue new file mode 100644 index 000000000..36a387b03 --- /dev/null +++ b/resources/scripts/admin/views/settings/mail-sender/SmtpDriver.vue @@ -0,0 +1,120 @@ + + + diff --git a/resources/scripts/admin/views/users/Create.vue b/resources/scripts/admin/views/users/Create.vue index e909601ff..72b9bc65a 100644 --- a/resources/scripts/admin/views/users/Create.vue +++ b/resources/scripts/admin/views/users/Create.vue @@ -162,7 +162,7 @@