<?php
namespace App\Providers;
use App\Http\ViewComposers\GlobalLayoutComposer;
use App\Modules\CMS\Models\Setting;
use App\Modules\Pharmacy\Models\Prescription;
use App\Modules\Pharmacy\Policies\PrescriptionPolicy;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
if ($this->app->environment('local')) {
// Register Telescope in local env only
if (class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) {
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
}
}
}
public function boot(): void
{
// Resolve factories for modular models (App\Modules\*\Models\Foo → Database\Factories\FooFactory)
Factory::guessFactoryNamesUsing(function (string $modelName): string {
$baseName = class_basename($modelName);
return "Database\\Factories\\{$baseName}Factory";
});
// Strict Eloquent mode in non-production
Model::shouldBeStrict(! $this->app->isProduction());
// Force HTTPS in production
if ($this->app->isProduction()) {
URL::forceScheme('https');
}
// Super-admin bypass — grants all Gate abilities unconditionally.
// Referenced in RolePermissionSeeder; required for Filament CUD actions
// on models that have no explicit policy (e.g. TherapeuticCategory).
Gate::before(function ($user, string $ability) {
if ($user->hasRole('super_admin')) {
return true;
}
});
// ── Email settings (Admin → Settings → Email Settings) ─────────────────
// Applies DB-entered SMTP credentials on every boot, mirroring the
// Payment settings pattern (PaymentServiceProvider::applyDbPaymentSettings).
// Without this, the Email Settings page's SMTP fields only ever affected
// the "Send Test Email" button — real order/prescription emails kept
// going out via whatever was in .env regardless of what was saved here.
$this->applyDbEmailSettings();
// ── Global layout data ────────────────────────────────────────────────
// Most storefront views use <x-app-layout> which @include('layouts.app').
// @include does NOT trigger View Composers bound to a specific view name,
// so we register on '*' (all views) to guarantee the data is always present.
// The composer itself caches the DB queries per request so there is no
// N+1 performance cost.
View::composer('*', GlobalLayoutComposer::class);
// Policies
Gate::policy(Prescription::class, PrescriptionPolicy::class);
// Global password rules
Password::defaults(function () {
return $this->app->isProduction()
? Password::min(8)->mixedCase()->numbers()->symbols()->uncompromised()
: Password::min(8);
});
}
/**
* Admin-entered SMTP credentials (Email Settings page) live in the DB
* `settings` table under group "email". Overlay them onto the config
* repository here — DB wins over .env when a value is set. Falls back
* silently to .env-only behaviour when the settings table doesn't exist
* yet (fresh install, before migrations run) or nothing has been saved.
*/
private function applyDbEmailSettings(): void
{
try {
if (! Schema::hasTable('settings')) {
return;
}
$e = Setting::group('email');
} catch (\Throwable) {
return;
}
if (empty($e)) {
return;
}
$overrides = array_filter([
'mail.mailers.smtp.host' => $e['mail_host'] ?? null,
'mail.mailers.smtp.port' => $e['mail_port'] ?? null,
'mail.mailers.smtp.username' => $e['mail_username'] ?? null,
'mail.mailers.smtp.password' => $e['mail_password'] ?? null,
'mail.from.name' => $e['mail_from_name'] ?? null,
'mail.from.address' => $e['mail_from_email'] ?? null,
], fn ($v) => $v !== null && $v !== '');
// Handled separately: "none" must become a real `null` config value
// (Laravel's SMTP transport treats null as "no encryption"), which
// array_filter above would otherwise strip out.
if (isset($e['mail_encryption'])) {
$overrides['mail.mailers.smtp.encryption'] =
$e['mail_encryption'] === 'none' ? null : $e['mail_encryption'];
}
// Overlaying SMTP host/port/credentials alone does nothing unless
// `mail.default` actually points at the `smtp` mailer. .env ships
// MAIL_MAILER=log for local dev, and nothing else in the app ever
// changes it — so every outgoing email (verification links, order
// notifications, prescription approvals, and even the admin's own
// "Send Test Email" button) was silently being written to
// storage/logs/laravel.log instead of actually being sent, no
// matter how correctly SMTP was configured on the Email Settings
// page. Once an admin has entered a host here, they clearly intend
// SMTP to be used, so switch the default mailer over too.
if (! empty($e['mail_host'])) {
$overrides['mail.default'] = 'smtp';
}
if ($overrides) {
config($overrides);
}
}
}