mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-23 21:24:04 +00:00
Refactor install wizard and mail configuration
This commit is contained in:
453
app/Services/MailConfigurationService.php
Normal file
453
app/Services/MailConfigurationService.php
Normal file
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\CompanySetting;
|
||||
use App\Models\Setting;
|
||||
use Aws\Sdk;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
|
||||
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
|
||||
|
||||
class MailConfigurationService
|
||||
{
|
||||
public const DEFAULT_DRIVER = 'smtp';
|
||||
|
||||
private const GLOBAL_SCOPE = 'global';
|
||||
|
||||
private const COMPANY_SCOPE = 'company';
|
||||
|
||||
private const DRIVER_ORDER = [
|
||||
'smtp',
|
||||
'mail',
|
||||
'sendmail',
|
||||
'ses',
|
||||
'mailgun',
|
||||
'postmark',
|
||||
];
|
||||
|
||||
private const DRIVER_FIELDS = [
|
||||
'smtp' => [
|
||||
'mail_host',
|
||||
'mail_port',
|
||||
'mail_username',
|
||||
'mail_password',
|
||||
'mail_encryption',
|
||||
'mail_scheme',
|
||||
'mail_url',
|
||||
'mail_timeout',
|
||||
'mail_local_domain',
|
||||
],
|
||||
'mail' => [],
|
||||
'sendmail' => [
|
||||
'mail_sendmail_path',
|
||||
],
|
||||
'ses' => [
|
||||
'mail_ses_key',
|
||||
'mail_ses_secret',
|
||||
'mail_ses_region',
|
||||
],
|
||||
'mailgun' => [
|
||||
'mail_mailgun_domain',
|
||||
'mail_mailgun_secret',
|
||||
'mail_mailgun_endpoint',
|
||||
'mail_mailgun_scheme',
|
||||
],
|
||||
'postmark' => [
|
||||
'mail_postmark_token',
|
||||
'mail_postmark_message_stream_id',
|
||||
],
|
||||
];
|
||||
|
||||
private const BASE_FIELDS = [
|
||||
'mail_driver',
|
||||
'from_name',
|
||||
'from_mail',
|
||||
];
|
||||
|
||||
public function getAvailableDrivers(): array
|
||||
{
|
||||
return array_values(array_filter(self::DRIVER_ORDER, fn (string $driver) => $this->isDriverAvailable($driver)));
|
||||
}
|
||||
|
||||
public function getGlobalConfig(): array
|
||||
{
|
||||
return $this->buildConfigPayload(
|
||||
Setting::getSettings($this->getGlobalSettingKeys())->all(),
|
||||
self::GLOBAL_SCOPE
|
||||
);
|
||||
}
|
||||
|
||||
public function getCompanyConfig(int|string $companyId): array
|
||||
{
|
||||
$settings = CompanySetting::getSettings($this->getCompanySettingKeys(), $companyId)->all();
|
||||
|
||||
return array_merge(
|
||||
[
|
||||
'use_custom_mail_config' => $settings['use_custom_mail_config'] ?? 'NO',
|
||||
],
|
||||
$this->buildConfigPayload($settings, self::COMPANY_SCOPE)
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefaultConfig(): array
|
||||
{
|
||||
return [
|
||||
'from_name' => $this->getDefaultValue('from_name'),
|
||||
'from_mail' => $this->getDefaultValue('from_mail'),
|
||||
];
|
||||
}
|
||||
|
||||
public function saveGlobalConfig(array $payload): void
|
||||
{
|
||||
Setting::setSettings($this->prepareSettingsForStorage($payload, self::GLOBAL_SCOPE));
|
||||
}
|
||||
|
||||
public function saveCompanyConfig(int|string $companyId, array $payload): void
|
||||
{
|
||||
if (($payload['use_custom_mail_config'] ?? 'YES') !== 'YES') {
|
||||
CompanySetting::setSettings([
|
||||
'use_custom_mail_config' => 'NO',
|
||||
], $companyId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CompanySetting::setSettings(
|
||||
$this->prepareSettingsForStorage($payload, self::COMPANY_SCOPE) + [
|
||||
'use_custom_mail_config' => 'YES',
|
||||
],
|
||||
$companyId
|
||||
);
|
||||
}
|
||||
|
||||
public function applyGlobalConfig(): void
|
||||
{
|
||||
$settings = Setting::getSettings($this->getGlobalSettingKeys())->all();
|
||||
|
||||
$this->applyStoredSettings($settings, self::GLOBAL_SCOPE);
|
||||
}
|
||||
|
||||
public function applyCompanyConfig(int|string $companyId): void
|
||||
{
|
||||
$settings = CompanySetting::getSettings($this->getCompanySettingKeys(), $companyId)->all();
|
||||
|
||||
if (($settings['use_custom_mail_config'] ?? 'NO') !== 'YES') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->applyStoredSettings($settings, self::COMPANY_SCOPE);
|
||||
}
|
||||
|
||||
public function validationRules(?string $driver, bool $allowDisabledCustomConfig = false): array
|
||||
{
|
||||
$availableDrivers = $this->getAvailableDrivers();
|
||||
$driver = $this->normalizeRequestedDriver($driver, $availableDrivers);
|
||||
|
||||
$rules = [
|
||||
'mail_driver' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in($availableDrivers),
|
||||
],
|
||||
'from_name' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
'from_mail' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
],
|
||||
];
|
||||
|
||||
if ($allowDisabledCustomConfig) {
|
||||
$rules['use_custom_mail_config'] = [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in(['YES', 'NO']),
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge($rules, match ($driver) {
|
||||
'smtp' => [
|
||||
'mail_host' => ['required', 'string'],
|
||||
'mail_port' => ['required', 'integer'],
|
||||
'mail_username' => ['nullable', 'string'],
|
||||
'mail_password' => ['nullable', 'string'],
|
||||
'mail_encryption' => ['nullable', 'string', Rule::in(['none', 'tls', 'ssl'])],
|
||||
'mail_scheme' => ['nullable', 'string', Rule::in(['smtp', 'smtps'])],
|
||||
'mail_url' => ['nullable', 'string'],
|
||||
'mail_timeout' => ['nullable', 'integer'],
|
||||
'mail_local_domain' => ['nullable', 'string'],
|
||||
],
|
||||
'sendmail' => [
|
||||
'mail_sendmail_path' => ['nullable', 'string'],
|
||||
],
|
||||
'ses' => [
|
||||
'mail_ses_key' => ['required', 'string'],
|
||||
'mail_ses_secret' => ['required', 'string'],
|
||||
'mail_ses_region' => ['nullable', 'string'],
|
||||
],
|
||||
'mailgun' => [
|
||||
'mail_mailgun_domain' => ['required', 'string'],
|
||||
'mail_mailgun_secret' => ['required', 'string'],
|
||||
'mail_mailgun_endpoint' => ['required', 'string'],
|
||||
'mail_mailgun_scheme' => ['nullable', 'string', Rule::in(['https', 'api'])],
|
||||
],
|
||||
'postmark' => [
|
||||
'mail_postmark_token' => ['required', 'string'],
|
||||
'mail_postmark_message_stream_id' => ['nullable', 'string'],
|
||||
],
|
||||
default => [],
|
||||
});
|
||||
}
|
||||
|
||||
public function getGlobalSettingKeys(): array
|
||||
{
|
||||
return $this->buildSettingKeys(self::GLOBAL_SCOPE, true);
|
||||
}
|
||||
|
||||
public function getCompanySettingKeys(): array
|
||||
{
|
||||
return array_merge(
|
||||
$this->buildSettingKeys(self::COMPANY_SCOPE, true),
|
||||
['use_custom_mail_config']
|
||||
);
|
||||
}
|
||||
|
||||
private function buildSettingKeys(string $scope, bool $includeAllDrivers): array
|
||||
{
|
||||
$fields = self::BASE_FIELDS;
|
||||
|
||||
if ($includeAllDrivers) {
|
||||
foreach (self::DRIVER_FIELDS as $driverFields) {
|
||||
$fields = array_merge($fields, $driverFields);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_map(
|
||||
fn (string $field) => $this->storedKey($scope, $field),
|
||||
$fields
|
||||
)));
|
||||
}
|
||||
|
||||
private function buildConfigPayload(array $settings, string $scope): array
|
||||
{
|
||||
$driver = $this->normalizeStoredDriver(
|
||||
$this->resolveStoredValue($settings, $scope, 'mail_driver')
|
||||
);
|
||||
|
||||
$payload = [
|
||||
'mail_driver' => $driver,
|
||||
'from_name' => $this->resolveStoredValue($settings, $scope, 'from_name'),
|
||||
'from_mail' => $this->resolveStoredValue($settings, $scope, 'from_mail'),
|
||||
];
|
||||
|
||||
foreach (self::DRIVER_FIELDS[$driver] as $field) {
|
||||
$payload[$field] = $this->resolveStoredValue($settings, $scope, $field);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function prepareSettingsForStorage(array $payload, string $scope): array
|
||||
{
|
||||
$driver = $this->normalizeRequestedDriver($payload['mail_driver'] ?? null, $this->getAvailableDrivers());
|
||||
|
||||
$settings = [
|
||||
$this->storedKey($scope, 'mail_driver') => $driver,
|
||||
$this->storedKey($scope, 'from_name') => $payload['from_name'] ?? $this->getDefaultValue('from_name'),
|
||||
$this->storedKey($scope, 'from_mail') => $payload['from_mail'] ?? $this->getDefaultValue('from_mail'),
|
||||
];
|
||||
|
||||
foreach (self::DRIVER_FIELDS[$driver] as $field) {
|
||||
$settings[$this->storedKey($scope, $field)] = $this->normalizeStoredValue(
|
||||
$field,
|
||||
$payload[$field] ?? $this->getDefaultValue($field)
|
||||
);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function applyStoredSettings(array $settings, string $scope): void
|
||||
{
|
||||
$driver = $settings[$this->storedKey($scope, 'mail_driver')] ?? null;
|
||||
|
||||
if (! $driver || ! in_array($driver, self::DRIVER_ORDER, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Config::set('mail.default', $driver);
|
||||
|
||||
match ($driver) {
|
||||
'smtp' => $this->applySmtpSettings($settings, $scope),
|
||||
'sendmail' => $this->applySendmailSettings($settings, $scope),
|
||||
'ses' => $this->applySesSettings($settings, $scope),
|
||||
'mailgun' => $this->applyMailgunSettings($settings, $scope),
|
||||
'postmark' => $this->applyPostmarkSettings($settings, $scope),
|
||||
default => null,
|
||||
};
|
||||
|
||||
Config::set('mail.from.address', $this->resolveStoredValue($settings, $scope, 'from_mail'));
|
||||
Config::set('mail.from.name', $this->resolveStoredValue($settings, $scope, 'from_name'));
|
||||
|
||||
Mail::purge($driver);
|
||||
}
|
||||
|
||||
private function applySmtpSettings(array $settings, string $scope): void
|
||||
{
|
||||
Config::set('mail.mailers.smtp.host', $this->resolveStoredValue($settings, $scope, 'mail_host'));
|
||||
Config::set('mail.mailers.smtp.port', $this->resolveStoredValue($settings, $scope, 'mail_port'));
|
||||
Config::set('mail.mailers.smtp.username', $this->resolveStoredValue($settings, $scope, 'mail_username'));
|
||||
Config::set('mail.mailers.smtp.password', $this->resolveStoredValue($settings, $scope, 'mail_password'));
|
||||
Config::set('mail.mailers.smtp.encryption', $this->resolveStoredValue($settings, $scope, 'mail_encryption'));
|
||||
Config::set('mail.mailers.smtp.scheme', $this->nullIfBlank($this->resolveStoredValue($settings, $scope, 'mail_scheme')));
|
||||
Config::set('mail.mailers.smtp.url', $this->nullIfBlank($this->resolveStoredValue($settings, $scope, 'mail_url')));
|
||||
Config::set('mail.mailers.smtp.timeout', $this->nullIfBlank($this->resolveStoredValue($settings, $scope, 'mail_timeout')));
|
||||
Config::set('mail.mailers.smtp.local_domain', $this->nullIfBlank($this->resolveStoredValue($settings, $scope, 'mail_local_domain')));
|
||||
}
|
||||
|
||||
private function applySendmailSettings(array $settings, string $scope): void
|
||||
{
|
||||
Config::set('mail.mailers.sendmail.path', $this->resolveStoredValue($settings, $scope, 'mail_sendmail_path'));
|
||||
}
|
||||
|
||||
private function applySesSettings(array $settings, string $scope): void
|
||||
{
|
||||
Config::set('services.ses.key', $this->resolveStoredValue($settings, $scope, 'mail_ses_key'));
|
||||
Config::set('services.ses.secret', $this->resolveStoredValue($settings, $scope, 'mail_ses_secret'));
|
||||
Config::set('services.ses.region', $this->resolveStoredValue($settings, $scope, 'mail_ses_region'));
|
||||
}
|
||||
|
||||
private function applyMailgunSettings(array $settings, string $scope): void
|
||||
{
|
||||
$domain = $this->resolveStoredValue($settings, $scope, 'mail_mailgun_domain');
|
||||
$secret = $this->resolveStoredValue($settings, $scope, 'mail_mailgun_secret');
|
||||
$endpoint = $this->resolveStoredValue($settings, $scope, 'mail_mailgun_endpoint');
|
||||
$scheme = $this->resolveStoredValue($settings, $scope, 'mail_mailgun_scheme');
|
||||
|
||||
Config::set('mail.mailers.mailgun.domain', $domain);
|
||||
Config::set('mail.mailers.mailgun.secret', $secret);
|
||||
Config::set('mail.mailers.mailgun.endpoint', $endpoint);
|
||||
Config::set('mail.mailers.mailgun.scheme', $scheme);
|
||||
|
||||
Config::set('services.mailgun.domain', $domain);
|
||||
Config::set('services.mailgun.secret', $secret);
|
||||
Config::set('services.mailgun.endpoint', $endpoint);
|
||||
Config::set('services.mailgun.scheme', $scheme);
|
||||
}
|
||||
|
||||
private function applyPostmarkSettings(array $settings, string $scope): void
|
||||
{
|
||||
$token = $this->resolveStoredValue($settings, $scope, 'mail_postmark_token');
|
||||
$messageStreamId = $this->nullIfBlank($this->resolveStoredValue($settings, $scope, 'mail_postmark_message_stream_id'));
|
||||
|
||||
Config::set('services.postmark.token', $token);
|
||||
Config::set('mail.mailers.postmark.token', $token);
|
||||
Config::set('mail.mailers.postmark.message_stream_id', $messageStreamId);
|
||||
}
|
||||
|
||||
private function resolveStoredValue(array $settings, string $scope, string $field): mixed
|
||||
{
|
||||
$key = $this->storedKey($scope, $field);
|
||||
|
||||
if (array_key_exists($key, $settings)) {
|
||||
return $settings[$key];
|
||||
}
|
||||
|
||||
return $this->getDefaultValue($field);
|
||||
}
|
||||
|
||||
private function storedKey(string $scope, string $field): string
|
||||
{
|
||||
return $scope === self::COMPANY_SCOPE ? "company_{$field}" : $field;
|
||||
}
|
||||
|
||||
private function getDefaultValue(string $field): mixed
|
||||
{
|
||||
return match ($field) {
|
||||
'mail_driver' => $this->normalizeStoredDriver(config('mail.default')),
|
||||
'from_name' => config('mail.from.name'),
|
||||
'from_mail' => config('mail.from.address'),
|
||||
'mail_host' => config('mail.mailers.smtp.host', '127.0.0.1'),
|
||||
'mail_port' => config('mail.mailers.smtp.port', 587),
|
||||
'mail_username', 'mail_password', 'mail_scheme', 'mail_url', 'mail_timeout', 'mail_local_domain' => '',
|
||||
'mail_encryption' => config('mail.mailers.smtp.encryption', 'none'),
|
||||
'mail_sendmail_path' => config('mail.mailers.sendmail.path', '/usr/sbin/sendmail -bs -i'),
|
||||
'mail_ses_key' => config('services.ses.key', ''),
|
||||
'mail_ses_secret' => config('services.ses.secret', ''),
|
||||
'mail_ses_region' => config('services.ses.region', 'us-east-1'),
|
||||
'mail_mailgun_domain' => config('services.mailgun.domain', ''),
|
||||
'mail_mailgun_secret' => config('services.mailgun.secret', ''),
|
||||
'mail_mailgun_endpoint' => config('services.mailgun.endpoint', 'api.mailgun.net'),
|
||||
'mail_mailgun_scheme' => config('mail.mailers.mailgun.scheme', config('services.mailgun.scheme', 'https')),
|
||||
'mail_postmark_token' => config('services.postmark.token', ''),
|
||||
'mail_postmark_message_stream_id' => config('mail.mailers.postmark.message_stream_id', ''),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
private function normalizeRequestedDriver(?string $driver, array $availableDrivers): string
|
||||
{
|
||||
if ($driver && in_array($driver, $availableDrivers, true)) {
|
||||
return $driver;
|
||||
}
|
||||
|
||||
return $availableDrivers[0] ?? self::DEFAULT_DRIVER;
|
||||
}
|
||||
|
||||
private function normalizeStoredDriver(?string $driver): string
|
||||
{
|
||||
$availableDrivers = $this->getAvailableDrivers();
|
||||
|
||||
if ($driver && in_array($driver, $availableDrivers, true)) {
|
||||
return $driver;
|
||||
}
|
||||
|
||||
return $availableDrivers[0] ?? self::DEFAULT_DRIVER;
|
||||
}
|
||||
|
||||
private function normalizeStoredValue(string $field, mixed $value): mixed
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
}
|
||||
|
||||
return match ($field) {
|
||||
'mail_port', 'mail_timeout' => $value === '' ? null : $value,
|
||||
'mail_scheme',
|
||||
'mail_url',
|
||||
'mail_local_domain',
|
||||
'mail_postmark_message_stream_id' => $value === '' ? '' : $value,
|
||||
'mail_mailgun_endpoint' => $value === '' ? 'api.mailgun.net' : $value,
|
||||
'mail_mailgun_scheme' => $value === '' ? 'https' : $value,
|
||||
'mail_sendmail_path' => $value === '' ? '/usr/sbin/sendmail -bs -i' : $value,
|
||||
'mail_ses_region' => $value === '' ? 'us-east-1' : $value,
|
||||
'mail_encryption' => $value === '' ? 'none' : $value,
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
|
||||
private function nullIfBlank(mixed $value): mixed
|
||||
{
|
||||
return $value === '' ? null : $value;
|
||||
}
|
||||
|
||||
private function isDriverAvailable(string $driver): bool
|
||||
{
|
||||
return match ($driver) {
|
||||
'smtp', 'mail', 'sendmail' => true,
|
||||
'ses' => class_exists(Sdk::class),
|
||||
'mailgun' => class_exists(MailgunTransportFactory::class)
|
||||
&& class_exists(HttpClient::class),
|
||||
'postmark' => class_exists(PostmarkTransportFactory::class)
|
||||
&& class_exists(HttpClient::class),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user