*/ public array $passwordCharacterSets = []; public bool $passwordAvoidAmbiguous = true; public int $passphraseWords = 6; public string $passphraseSeparator = 'hyphen'; public string $siteTitle = ''; public string $siteDescription = ''; public $siteLogo; /** Whether the "Remove the logo?" dialog is open. */ public bool $confirmingLogoRemoval = false; /** Whether the "Remove the system password?" dialog is open. */ public bool $confirmingPasswordRemoval = false; public function mount(): void { $this->colorProfile = Scheme::profile() ?? ''; $this->defaultExpiration = Setting::get('default_expiration', '') ?? ''; $this->maxFileSize = min( (int) Setting::get('max_file_size', 100 * 1024 * 1024) / (1024 * 1024), self::phpMaxUploadMb(), ); $this->maxStorageQuota = (int) Setting::get('max_storage_quota', 20 * 1024 * 1024 * 1024) / (1024 * 1024 * 1024); $this->maxFilesPerShare = (int) Setting::get('max_files_per_share', 50); $this->maxSizePerShare = (int) Setting::get('max_size_per_share', 2 * 1024 * 1024 * 1024) / (1024 * 1024 * 1024); $this->allowNeverExpire = (bool) Setting::get('allow_never_expire', false); $this->siteTitle = Setting::get('site_title', '') ?? ''; $this->siteDescription = Setting::get('site_description', '') ?? ''; $passwordOptions = app(PasswordGeneratorService::class)->options(); $this->passwordGeneratorMode = $passwordOptions['mode']; $this->passwordGeneratorType = $passwordOptions['type']; $this->passwordLength = $passwordOptions['length']; $this->passwordCharacterSets = $passwordOptions['characterSets']; $this->passwordAvoidAmbiguous = $passwordOptions['avoidAmbiguous']; $this->passphraseWords = $passwordOptions['words']; $this->passphraseSeparator = $passwordOptions['separator']; } public static function phpMaxUploadMb(): int { $parse = function (string $value): int { $value = trim($value); $last = strtolower($value[strlen($value) - 1]); $num = (int) $value; return match ($last) { 'g' => $num * 1024, 'm' => $num, 'k' => max(1, (int) ($num / 1024)), default => max(1, (int) ($num / (1024 * 1024))), }; }; $upload = $parse(ini_get('upload_max_filesize') ?: '2M'); $post = $parse(ini_get('post_max_size') ?: '8M'); return min($upload, $post); } public function saveSettings(): void { $phpMaxMb = self::phpMaxUploadMb(); $validated = $this->validate([ 'colorProfile' => ['required', 'string', Rule::in(array_keys(Scheme::profiles()))], 'maxFileSize' => ['required', 'integer', 'min:1', 'max:'.$phpMaxMb], 'maxStorageQuota' => ['required', 'integer', 'min:1'], 'maxFilesPerShare' => ['required', 'integer', 'min:1'], 'maxSizePerShare' => ['required', 'integer', 'min:1'], 'siteTitle' => ['nullable', 'string', 'max:255'], 'siteDescription' => ['nullable', 'string', 'max:1000'], 'siteLogo' => ['nullable', 'file', 'mimes:png,jpg,jpeg,gif,webp', 'max:2048'], ...$this->passwordGeneratorRules(), ], [ 'maxFileSize.max' => __('Cannot exceed the PHP limit of :max MB. Increase upload_max_filesize and post_max_size in your PHP configuration.', ['max' => $phpMaxMb]), ...$this->passwordGeneratorMessages(), ]); if ($this->systemPassword) { Setting::set('system_password', Hash::make($this->systemPassword)); } Setting::set('color_profile', $this->colorProfile); Setting::set('default_expiration', $this->defaultExpiration ?: null); Setting::set('max_file_size', $this->maxFileSize * 1024 * 1024); Setting::set('max_storage_quota', $this->maxStorageQuota * 1024 * 1024 * 1024); Setting::set('max_files_per_share', $this->maxFilesPerShare); Setting::set('max_size_per_share', $this->maxSizePerShare * 1024 * 1024 * 1024); Setting::set('allow_never_expire', $this->allowNeverExpire ? '1' : null); Setting::set('site_title', $this->siteTitle ?: null); Setting::set('site_description', $this->siteDescription ?: null); $this->savePasswordGeneratorSettings($validated); if ($this->siteLogo && is_object($this->siteLogo)) { $existingLogo = Setting::get('site_logo'); if ($existingLogo) { Storage::disk('public')->delete($existingLogo); } $path = $this->siteLogo->store('branding', 'public'); Setting::set('site_logo', $path); $this->siteLogo = null; } $this->systemPassword = ''; $this->success(__('Settings saved successfully.')); } /** * The generator's rules. A field the chosen mode or type hides is excluded, so it never blocks * saving and keeps the value saved before. * * @return array> */ protected function passwordGeneratorRules(): array { $characters = ['exclude_if:passwordGeneratorMode,off', 'exclude_unless:passwordGeneratorType,characters']; $passphrase = ['exclude_if:passwordGeneratorMode,off', 'exclude_unless:passwordGeneratorType,passphrase']; return [ 'passwordGeneratorMode' => ['required', 'string', Rule::in(PasswordGeneratorService::MODES)], 'passwordGeneratorType' => ['exclude_if:passwordGeneratorMode,off', 'required', 'string', Rule::in(PasswordGeneratorService::TYPES)], 'passwordLength' => [...$characters, 'required', 'integer', 'min:'.PasswordGeneratorService::MIN_LENGTH, 'max:'.PasswordGeneratorService::MAX_LENGTH], 'passwordCharacterSets' => [...$characters, 'required', 'array'], 'passwordCharacterSets.*' => [...$characters, 'string', Rule::in(array_keys(PasswordGeneratorService::CHARACTER_SETS))], 'passwordAvoidAmbiguous' => [...$characters, 'boolean'], 'passphraseWords' => [...$passphrase, 'required', 'integer', 'min:'.PasswordGeneratorService::MIN_WORDS, 'max:'.PasswordGeneratorService::MAX_WORDS], 'passphraseSeparator' => [...$passphrase, 'required', 'string', Rule::in(array_keys(PasswordGeneratorService::SEPARATORS))], ]; } /** * @return array */ protected function passwordGeneratorMessages(): array { return [ 'passwordCharacterSets.required' => __('Choose at least one kind of character.'), ]; } /** * Store the generator settings that passed validation; excluded ones keep their saved value. * * @param array $validated */ protected function savePasswordGeneratorSettings(array $validated): void { Setting::set('password_generator_mode', $validated['passwordGeneratorMode']); if (array_key_exists('passwordGeneratorType', $validated)) { Setting::set('password_generator_type', $validated['passwordGeneratorType']); } if (array_key_exists('passwordLength', $validated)) { Setting::set('password_generator_length', $validated['passwordLength']); Setting::set('password_generator_character_sets', implode(',', $validated['passwordCharacterSets'])); Setting::set('password_generator_avoid_ambiguous', $validated['passwordAvoidAmbiguous'] ? '1' : '0'); } if (array_key_exists('passphraseWords', $validated)) { Setting::set('password_generator_words', $validated['passphraseWords']); Setting::set('password_generator_separator', $validated['passphraseSeparator']); } } /** * The form's generator options while they are valid, for the example; `null` otherwise. * * @return array{type: string, length: int, characterSets: list, avoidAmbiguous: bool, words: int, separator: string}|null */ protected function passwordPreviewOptions(): ?array { $values = $this->only(['passwordGeneratorMode', 'passwordGeneratorType', 'passwordLength', 'passwordCharacterSets', 'passwordAvoidAmbiguous', 'passphraseWords', 'passphraseSeparator']); if ($this->passwordGeneratorMode === 'off' || Validator::make($values, $this->passwordGeneratorRules())->fails()) { return null; } return [ 'type' => $this->passwordGeneratorType, 'length' => $this->passwordLength, 'characterSets' => array_values($this->passwordCharacterSets), 'avoidAmbiguous' => $this->passwordAvoidAmbiguous, 'words' => $this->passphraseWords, 'separator' => $this->passphraseSeparator, ]; } public function removeLogo(): void { $existingLogo = Setting::get('site_logo'); if ($existingLogo) { Storage::disk('public')->delete($existingLogo); Setting::set('site_logo', null); } $this->confirmingLogoRemoval = false; $this->success(__('Logo removed.')); } public function clearSystemPassword(): void { Setting::set('system_password', null); $this->confirmingPasswordRemoval = false; $this->success(__('System password cleared.')); } public function render(): mixed { $passwordGenerator = app(PasswordGeneratorService::class); $passwordPreviewOptions = $this->passwordPreviewOptions(); return view('livewire.admin.admin-settings', [ 'hasSystemPassword' => (bool) Setting::get('system_password'), 'currentLogo' => Setting::get('site_logo'), 'phpMaxUploadMb' => self::phpMaxUploadMb(), 'passwordExample' => $passwordPreviewOptions ? $passwordGenerator->generate($passwordPreviewOptions) : null, 'passwordEntropy' => $passwordPreviewOptions ? $passwordGenerator->entropyBits($passwordPreviewOptions) : null, ]); } }