- 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>
200 lines
7.1 KiB
PHP
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);
|
|
}
|
|
}
|