*/ public function all(): array { if ($this->cache !== null) { return $this->cache; } $readFromFile = function () { $path = $this->getSettingsPath(); if (File::exists($path)) { try { return json_decode(File::get($path), true) ?: []; } catch (\Throwable) { // Fallback to empty if json is corrupt } } return []; }; $stored = app()->bound('cache') ? cache()->remember('filament-short-url:settings', 86400, $readFromFile) : $readFromFile(); // Merge stored settings with default values from config() $this->cache = array_merge([ 'route_prefix' => config('filament-short-url.route_prefix', 's'), 'redirect_status_code' => config('filament-short-url.redirect_status_code', 302), 'key_length' => config('filament-short-url.key_length', 6), 'geo_ip_enabled' => config('filament-short-url.geo_ip.enabled', true), 'geo_ip_driver' => config('filament-short-url.geo_ip.driver', 'headers'), 'geo_ip_cache_ttl' => config('filament-short-url.geo_ip.cache_ttl', 86400), 'geo_ip_timeout' => config('filament-short-url.geo_ip.timeout', 3), 'maxmind_database_path' => config('filament-short-url.geo_ip.maxmind.database_path', storage_path('geoip/GeoLite2-Country.mmdb')), 'geo_ip_stats_cache_ttl' => config('filament-short-url.geo_ip.stats_cache_ttl', 300), 'ga4_api_secret' => config('filament-short-url.ga4.api_secret'), 'ga4_firebase_app_id' => config('filament-short-url.ga4.firebase_app_id'), 'queue_connection' => config('filament-short-url.queue_connection', 'sync'), 'queue_name' => config('filament-short-url.queue_name', 'default'), 'cache_ttl' => config('filament-short-url.cache_ttl', 3600), 'counter_buffering_enabled' => config('filament-short-url.counter_buffering.enabled', false), 'trust_cdn_headers' => config('filament-short-url.trust_cdn_headers', false), 'pruning_enabled' => config('filament-short-url.pruning.enabled', true), 'pruning_retention_days' => config('filament-short-url.pruning.retention_days', 90), 'rate_limiting_enabled' => config('filament-short-url.rate_limiting.enabled', false), 'rate_limiting_max_attempts' => config('filament-short-url.rate_limiting.max_attempts', 60), 'rate_limiting_decay_seconds' => config('filament-short-url.rate_limiting.decay_seconds', 60), 'tracking_enabled' => config('filament-short-url.tracking.enabled', true), 'tracking_fields_ip_address' => config('filament-short-url.tracking.fields.ip_address', true), 'tracking_fields_browser' => config('filament-short-url.tracking.fields.browser', true), 'tracking_fields_browser_version' => config('filament-short-url.tracking.fields.browser_version', true), 'tracking_fields_operating_system' => config('filament-short-url.tracking.fields.operating_system', true), 'tracking_fields_operating_system_version' => config('filament-short-url.tracking.fields.operating_system_version', true), 'tracking_fields_referer_url' => config('filament-short-url.tracking.fields.referer_url', true), 'tracking_fields_device_type' => config('filament-short-url.tracking.fields.device_type', true), 'tracking_fields_browser_language' => config('filament-short-url.tracking.fields.browser_language', true), 'qr_size' => config('filament-short-url.qr_defaults.size', 300), 'qr_margin' => config('filament-short-url.qr_defaults.margin', 1), 'qr_dot_style' => config('filament-short-url.qr_defaults.dot_style', 'square'), 'qr_foreground_color' => config('filament-short-url.qr_defaults.foreground_color', '#000000'), 'qr_background_color' => config('filament-short-url.qr_defaults.background_color', '#ffffff'), 'qr_gradient_enabled' => config('filament-short-url.qr_defaults.gradient_enabled', false), 'qr_gradient_from' => config('filament-short-url.qr_defaults.gradient_from', '#4f46e5'), 'qr_gradient_to' => config('filament-short-url.qr_defaults.gradient_to', '#06b6d4'), 'qr_gradient_type' => config('filament-short-url.qr_defaults.gradient_type', 'linear'), 'global_webhook_url' => null, 'webhook_events' => ['visited'], 'global_webhook_enabled' => false, 'api_keys' => [], 'api_enabled' => false, 'site_name' => config('filament-short-url.site_name'), // Security v2.0 'vpn_detection_enabled' => config('filament-short-url.vpn_detection.enabled', false), 'vpn_detection_driver' => config('filament-short-url.vpn_detection.driver', 'ip-api'), 'vpnapi_key' => config('filament-short-url.vpn_detection.vpnapi_key'), 'vpn_block_action' => config('filament-short-url.vpn_detection.block_action', 'flag_only'), 'safe_browsing_enabled' => config('filament-short-url.safe_browsing.enabled', false), 'google_safe_browsing_api_key' => config('filament-short-url.safe_browsing.api_key'), ], $stored); return $this->cache; } public function get(string $key, mixed $default = null): mixed { return $this->all()[$key] ?? $default; } /** * Persist settings to JSON file. * * @param array $data */ public function set(array $data): void { $path = $this->getSettingsPath(); $dir = dirname($path); if (! File::isDirectory($dir)) { File::makeDirectory($dir, 0755, true); } $oldPrefix = $this->get('route_prefix'); // Keep only supported settings keys to prevent bloat $keys = [ 'route_prefix', 'redirect_status_code', 'key_length', 'geo_ip_enabled', 'geo_ip_driver', 'geo_ip_cache_ttl', 'geo_ip_timeout', 'maxmind_database_path', 'geo_ip_stats_cache_ttl', 'ga4_api_secret', 'ga4_firebase_app_id', 'queue_connection', 'queue_name', 'cache_ttl', 'counter_buffering_enabled', 'trust_cdn_headers', 'pruning_enabled', 'pruning_retention_days', 'rate_limiting_enabled', 'rate_limiting_max_attempts', 'rate_limiting_decay_seconds', 'tracking_enabled', 'tracking_fields_ip_address', 'tracking_fields_browser', 'tracking_fields_browser_version', 'tracking_fields_operating_system', 'tracking_fields_operating_system_version', 'tracking_fields_referer_url', 'tracking_fields_device_type', 'tracking_fields_browser_language', 'qr_size', 'qr_margin', 'qr_dot_style', 'qr_foreground_color', 'qr_background_color', 'qr_gradient_enabled', 'qr_gradient_from', 'qr_gradient_to', 'qr_gradient_type', 'global_webhook_url', 'webhook_events', 'global_webhook_enabled', 'api_keys', 'api_enabled', 'site_name', // Security v2.0 'vpn_detection_enabled', 'vpn_detection_driver', 'vpnapi_key', 'vpn_block_action', 'safe_browsing_enabled', 'google_safe_browsing_api_key', ]; $filtered = array_intersect_key($data, array_flip($keys)); // Format datatypes properly if (isset($filtered['redirect_status_code'])) { $filtered['redirect_status_code'] = (int) $filtered['redirect_status_code']; } if (isset($filtered['key_length'])) { $filtered['key_length'] = (int) $filtered['key_length']; } if (isset($filtered['geo_ip_enabled'])) { $filtered['geo_ip_enabled'] = (bool) $filtered['geo_ip_enabled']; } if (isset($filtered['geo_ip_cache_ttl'])) { $filtered['geo_ip_cache_ttl'] = (int) $filtered['geo_ip_cache_ttl']; } if (isset($filtered['geo_ip_timeout'])) { $filtered['geo_ip_timeout'] = (int) $filtered['geo_ip_timeout']; } if (isset($filtered['geo_ip_stats_cache_ttl'])) { $filtered['geo_ip_stats_cache_ttl'] = (int) $filtered['geo_ip_stats_cache_ttl']; } if (isset($filtered['cache_ttl'])) { $filtered['cache_ttl'] = (int) $filtered['cache_ttl']; } if (isset($filtered['counter_buffering_enabled'])) { $filtered['counter_buffering_enabled'] = (bool) $filtered['counter_buffering_enabled']; } if (isset($filtered['trust_cdn_headers'])) { $filtered['trust_cdn_headers'] = (bool) $filtered['trust_cdn_headers']; } if (isset($filtered['pruning_enabled'])) { $filtered['pruning_enabled'] = (bool) $filtered['pruning_enabled']; } if (isset($filtered['pruning_retention_days'])) { $filtered['pruning_retention_days'] = (int) $filtered['pruning_retention_days']; } if (isset($filtered['rate_limiting_enabled'])) { $filtered['rate_limiting_enabled'] = (bool) $filtered['rate_limiting_enabled']; } if (isset($filtered['rate_limiting_max_attempts'])) { $filtered['rate_limiting_max_attempts'] = (int) $filtered['rate_limiting_max_attempts']; } if (isset($filtered['rate_limiting_decay_seconds'])) { $filtered['rate_limiting_decay_seconds'] = (int) $filtered['rate_limiting_decay_seconds']; } // Tracking defaults if (isset($filtered['tracking_enabled'])) { $filtered['tracking_enabled'] = (bool) $filtered['tracking_enabled']; } if (isset($filtered['tracking_fields_ip_address'])) { $filtered['tracking_fields_ip_address'] = (bool) $filtered['tracking_fields_ip_address']; } if (isset($filtered['tracking_fields_browser'])) { $filtered['tracking_fields_browser'] = (bool) $filtered['tracking_fields_browser']; } if (isset($filtered['tracking_fields_browser_version'])) { $filtered['tracking_fields_browser_version'] = (bool) $filtered['tracking_fields_browser_version']; } if (isset($filtered['tracking_fields_operating_system'])) { $filtered['tracking_fields_operating_system'] = (bool) $filtered['tracking_fields_operating_system']; } if (isset($filtered['tracking_fields_operating_system_version'])) { $filtered['tracking_fields_operating_system_version'] = (bool) $filtered['tracking_fields_operating_system_version']; } if (isset($filtered['tracking_fields_referer_url'])) { $filtered['tracking_fields_referer_url'] = (bool) $filtered['tracking_fields_referer_url']; } if (isset($filtered['tracking_fields_device_type'])) { $filtered['tracking_fields_device_type'] = (bool) $filtered['tracking_fields_device_type']; } if (isset($filtered['tracking_fields_browser_language'])) { $filtered['tracking_fields_browser_language'] = (bool) $filtered['tracking_fields_browser_language']; } // QR defaults if (isset($filtered['qr_size'])) { $filtered['qr_size'] = (int) $filtered['qr_size']; } if (isset($filtered['qr_margin'])) { $filtered['qr_margin'] = (int) $filtered['qr_margin']; } if (isset($filtered['qr_gradient_enabled'])) { $filtered['qr_gradient_enabled'] = (bool) $filtered['qr_gradient_enabled']; } if (isset($filtered['global_webhook_enabled'])) { $filtered['global_webhook_enabled'] = (bool) $filtered['global_webhook_enabled']; } // Security v2.0 casts if (isset($filtered['vpn_detection_enabled'])) { $filtered['vpn_detection_enabled'] = (bool) $filtered['vpn_detection_enabled']; } if (isset($filtered['safe_browsing_enabled'])) { $filtered['safe_browsing_enabled'] = (bool) $filtered['safe_browsing_enabled']; } File::put($path, json_encode($filtered, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); cache()->forget('filament-short-url:settings'); $this->cache = null; // Apply immediately to current request config $this->applyConfigOverrides(); // Clear route cache if route prefix has changed $newPrefix = $filtered['route_prefix'] ?? null; if ($oldPrefix !== null && $newPrefix !== null && $oldPrefix !== $newPrefix) { try { if (app()->routesAreCached()) { Artisan::call('route:clear'); } } catch (\Throwable) { // Ignore route clear errors during boot/test } } } /** * Override standard Laravel config with user-configured settings. */ public function applyConfigOverrides(): void { $settings = $this->all(); config([ 'filament-short-url.route_prefix' => $settings['route_prefix'], 'filament-short-url.redirect_status_code' => $settings['redirect_status_code'], 'filament-short-url.key_length' => $settings['key_length'], 'filament-short-url.geo_ip.enabled' => $settings['geo_ip_enabled'], 'filament-short-url.geo_ip.driver' => $settings['geo_ip_driver'], 'filament-short-url.geo_ip.cache_ttl' => $settings['geo_ip_cache_ttl'], 'filament-short-url.geo_ip.timeout' => $settings['geo_ip_timeout'], 'filament-short-url.geo_ip.maxmind.database_path' => $settings['maxmind_database_path'], 'filament-short-url.geo_ip.stats_cache_ttl' => $settings['geo_ip_stats_cache_ttl'], 'filament-short-url.ga4.api_secret' => $settings['ga4_api_secret'], 'filament-short-url.ga4.firebase_app_id' => $settings['ga4_firebase_app_id'], 'filament-short-url.queue_connection' => $settings['queue_connection'], 'filament-short-url.queue_name' => $settings['queue_name'], 'filament-short-url.cache_ttl' => $settings['cache_ttl'], 'filament-short-url.counter_buffering.enabled' => $settings['counter_buffering_enabled'], 'filament-short-url.trust_cdn_headers' => $settings['trust_cdn_headers'], 'filament-short-url.pruning.enabled' => $settings['pruning_enabled'], 'filament-short-url.pruning.retention_days' => $settings['pruning_retention_days'], 'filament-short-url.rate_limiting.enabled' => $settings['rate_limiting_enabled'], 'filament-short-url.rate_limiting.max_attempts' => $settings['rate_limiting_max_attempts'], 'filament-short-url.rate_limiting.decay_seconds' => $settings['rate_limiting_decay_seconds'], 'filament-short-url.tracking.enabled' => $settings['tracking_enabled'], 'filament-short-url.tracking.fields.ip_address' => $settings['tracking_fields_ip_address'], 'filament-short-url.tracking.fields.browser' => $settings['tracking_fields_browser'], 'filament-short-url.tracking.fields.browser_version' => $settings['tracking_fields_browser_version'], 'filament-short-url.tracking.fields.operating_system' => $settings['tracking_fields_operating_system'], 'filament-short-url.tracking.fields.operating_system_version' => $settings['tracking_fields_operating_system_version'], 'filament-short-url.tracking.fields.referer_url' => $settings['tracking_fields_referer_url'], 'filament-short-url.tracking.fields.device_type' => $settings['tracking_fields_device_type'], 'filament-short-url.tracking.fields.browser_language' => $settings['tracking_fields_browser_language'], 'filament-short-url.qr_defaults.size' => $settings['qr_size'], 'filament-short-url.qr_defaults.margin' => $settings['qr_margin'], 'filament-short-url.qr_defaults.dot_style' => $settings['qr_dot_style'], 'filament-short-url.qr_defaults.foreground_color' => $settings['qr_foreground_color'], 'filament-short-url.qr_defaults.background_color' => $settings['qr_background_color'], 'filament-short-url.qr_defaults.gradient_enabled' => $settings['qr_gradient_enabled'], 'filament-short-url.qr_defaults.gradient_from' => $settings['qr_gradient_from'], 'filament-short-url.qr_defaults.gradient_to' => $settings['qr_gradient_to'], 'filament-short-url.qr_defaults.gradient_type' => $settings['qr_gradient_type'], 'filament-short-url.global_webhook_url' => $settings['global_webhook_url'] ?? null, 'filament-short-url.webhook_events' => $settings['webhook_events'] ?? ['visited'], 'filament-short-url.api_enabled' => (bool) ($settings['api_enabled'] ?? false), 'filament-short-url.site_name' => $settings['site_name'] ?? null, // Security v2.0 'filament-short-url.vpn_detection.enabled' => (bool) ($settings['vpn_detection_enabled'] ?? false), 'filament-short-url.vpn_detection.driver' => $settings['vpn_detection_driver'] ?? 'ip-api', 'filament-short-url.vpn_detection.vpnapi_key' => $settings['vpnapi_key'] ?? null, 'filament-short-url.vpn_detection.block_action' => $settings['vpn_block_action'] ?? 'flag_only', 'filament-short-url.vpn_detection.cache_ttl' => 86400, 'filament-short-url.vpn_detection.timeout' => 2, 'filament-short-url.safe_browsing.enabled' => (bool) ($settings['safe_browsing_enabled'] ?? false), 'filament-short-url.safe_browsing.api_key' => $settings['google_safe_browsing_api_key'] ?? null, ]); } }