Files
servicedesk/src/app/Providers/AppServiceProvider.php
Kacper ab90abcaa3 v1.1.3
- Triggers (Admin > Wyzwalacze): event-driven rules that fire immediately on
  a ticket lifecycle event (created/updated/status/priority/assignee/team/
  category changed, new reply), with AND-conditions and ordered actions
  (set status/priority/team/assignee, send e-mail). Ships its own dedicated,
  freely add/edit/delete-able e-mail templates, kept separate from the fixed
  system templates.
- Ticket watching: operators can star/"Obserwuj" any ticket to follow it
  regardless of assignment/team.
- Real-time notification bell (private per-user broadcast channel, 30s
  fallback poll) with an opt-in in-tab browser push notification.
- Per-user notification preferences (/settings/notifications): scope
  (mine/unassigned/watched/all) and e-mail toggle per event category.
- Admin > Integracje: new tab for LDAP/AD + BookStack config, split out of
  Konfiguracja.
- Operator queue: Podkategoria/Zespół/Utworzono columns (off by default).
- Obserwuj button moved next to the auto-refresh countdown; trigger
  condition builder shows subcategory/zgłaszający as name dropdowns instead
  of raw IDs; /settings/notifications got a back link, full-width push
  card, and a bordered table container; admin panel tab and operator queue
  view now persist across a plain page refresh.
- Docs: README/ARCHITECTURE/wiki updated for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 23:43:01 +02:00

200 lines
7.1 KiB
PHP

<?php
namespace App\Providers;
use App\Events\NotificationCreated;
use App\Models\ApiClient;
use App\Models\User;
use App\Notifications\TicketNotification;
use App\Support\Settings;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\Request;
use Illuminate\Notifications\Events\NotificationSent;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use LdapRecord\Connection;
use LdapRecord\Container;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->applyLdapSettingsOverride();
$this->applyMailSettingsOverride();
$this->applySessionSettingsOverride();
$this->applyTimezoneSettingsOverride();
$this->configureApiRateLimiting();
$this->broadcastBellNotifications();
// 'user' backs the polymorphic notifiable_type column on the
// database-notifications table (in-app notification bell).
Relation::enforceMorphMap(['api_client' => ApiClient::class, 'user' => User::class]);
}
/**
* A single choke point for realtime bell delivery — hooks Laravel's own
* post-send event instead of threading a broadcast dispatch into every
* TicketService call site that creates a "database" notification
* (client leg, staff fan-out, and eventually the Trigger engine's
* send_notification action). $event->response is the DatabaseChannel's
* return value: the DatabaseNotification row that was just created,
* whose id is the same one the bell already reads.
*/
protected function broadcastBellNotifications(): void
{
Event::listen(NotificationSent::class, function (NotificationSent $event) {
if ($event->channel !== 'database' || ! $event->notification instanceof TicketNotification) {
return;
}
$data = $event->notification->toDatabase($event->notifiable);
NotificationCreated::dispatch($event->notifiable->id, $event->response->id, $data['message'], $data['url']);
});
}
/**
* API keys get a generous per-key budget; unauthenticated requests (which
* only ever hit the guard before rejecting with 401) get a much smaller
* per-IP one so a single misconfigured client can't exhaust it for everyone.
*/
protected function configureApiRateLimiting(): void
{
RateLimiter::for('api', function (Request $request) {
$clientId = $request->user()?->id;
return $clientId
? Limit::perMinute(120)->by('api-client:'.$clientId)
: Limit::perMinute(30)->by('ip:'.$request->ip());
});
}
/**
* Avoid touching the DB during artisan commands that run before the
* `settings` table exists (e.g. `migrate` itself), or before it can be
* queried at all — shared by every settings-driven config override below.
*/
protected function settingsTableUsable(): bool
{
if ($this->app->runningInConsole() && ! $this->app->runningUnitTests()) {
return false;
}
try {
return Schema::hasTable('settings');
} catch (\Throwable) {
return false;
}
}
/**
* Let the Admin -> Konfiguracja -> LDAP/AD screen override the .env-based
* LDAP connection at runtime, so changes take effect without a redeploy.
*/
protected function applyLdapSettingsOverride(): void
{
if (! $this->settingsTableUsable()) {
return;
}
$host = Settings::get('ldap_host');
if (! $host) {
return;
}
$config = Config::get('ldap.connections.default', []);
$config['hosts'] = [$host];
$config['port'] = (int) Settings::get('ldap_port', $config['port'] ?? 389);
$config['base_dn'] = Settings::get('ldap_base_dn', $config['base_dn'] ?? '');
$config['username'] = Settings::get('ldap_bind_dn', $config['username'] ?? '');
$config['password'] = Settings::get('ldap_bind_password') ?? $config['password'] ?? '';
$config['use_tls'] = Settings::bool('ldap_use_ssl');
Config::set('ldap.connections.default', $config);
Container::addConnection(new Connection($config), 'default');
}
/**
* Let the Admin -> Konfiguracja -> E-mail (SMTP) screen override the
* .env-based mail transport/sender at runtime. Sender address/name and
* footer apply regardless; the SMTP transport itself only when the admin
* has explicitly enabled it (otherwise the .env `MAIL_MAILER` stands).
*/
protected function applyMailSettingsOverride(): void
{
if (! $this->settingsTableUsable()) {
return;
}
if (Settings::bool('mail_smtp_enabled') && Settings::get('mail_smtp_host')) {
Config::set('mail.default', 'smtp');
Config::set('mail.mailers.smtp.host', Settings::get('mail_smtp_host'));
Config::set('mail.mailers.smtp.port', (int) Settings::get('mail_smtp_port', '587'));
Config::set('mail.mailers.smtp.username', Settings::get('mail_smtp_username') ?: null);
Config::set('mail.mailers.smtp.password', Settings::get('mail_smtp_password'));
Config::set('mail.mailers.smtp.scheme', match (Settings::get('mail_smtp_encryption')) {
'ssl' => 'smtps',
'tls' => 'smtp',
default => null,
});
}
if ($address = Settings::get('mail_from_address')) {
Config::set('mail.from.address', $address);
Config::set('mail.from.name', Settings::get('mail_from_name') ?: Settings::get('company_name'));
}
}
/**
* Let the Admin -> Konfiguracja -> "Czas wygaśnięcia sesji" field override
* the .env-based session lifetime. Only affects sessions created/renewed
* after the change — already-active sessions keep their existing expiry.
*/
protected function applySessionSettingsOverride(): void
{
if (! $this->settingsTableUsable()) {
return;
}
$minutes = Settings::get('session_lifetime_minutes');
if ($minutes) {
Config::set('session.lifetime', (int) $minutes);
}
}
/**
* Let the Admin -> Konfiguracja -> "Strefa czasowa" field override the
* .env-based app timezone at runtime, so every date shown or stored
* (ticket timestamps, SLA deadlines, "x minutes ago" labels, ...)
* reflects the admin's chosen zone instead of the container's UTC default.
*/
protected function applyTimezoneSettingsOverride(): void
{
if (! $this->settingsTableUsable()) {
return;
}
$timezone = Settings::timezone();
Config::set('app.timezone', $timezone);
date_default_timezone_set($timezone);
}
}