*/ class BeforePreferenceSetEventListener implements IEventListener { public function __construct( protected IRootFolder $rootFolder, protected ParticipantService $participantService, protected LoggerInterface $logger, ) { } #[\Override] public function handle(Event $event): void { if (!($event instanceof BeforePreferenceSetEvent)) { // Unrelated return; } if ($event->getAppId() !== Application::APP_ID) { return; } $event->setValid($this->validatePreference( $event->getUserId(), $event->getConfigKey(), $event->getConfigValue(), )); } /** * @internal Make private/protected once SettingsController route was removed */ public function validatePreference(string $userId, string $key, string|int|null $value): bool { if ($key === UserPreference::ATTACHMENT_FOLDER) { return $this->validateAttachmentFolder($userId, $value); } // "boolean" yes/no if ($key === UserPreference::CALLS_START_WITHOUT_MEDIA || $key === UserPreference::PLAY_SOUNDS || $key === UserPreference::BLUR_VIRTUAL_BACKGROUND) { return $value === 'yes' || $value === 'no'; } // "privacy" 0/1 if ($key === UserPreference::TYPING_PRIVACY || $key === UserPreference::READ_STATUS_PRIVACY) { $valid = is_numeric($value) && ((int)$value === Participant::PRIVACY_PRIVATE || (int)$value === Participant::PRIVACY_PUBLIC); if ($valid && $key === UserPreference::READ_STATUS_PRIVACY) { $this->participantService->updateReadPrivacyForActor(Attendee::ACTOR_USERS, $userId, (int)$value); } return $valid; } // "list-style" 'two-lines' / 'compact' if ($key === UserPreference::CONVERSATIONS_LIST_STYLE) { return $value === UserPreference::CONVERSATION_LIST_STYLE_TWO_LINES || $value === UserPreference::CONVERSATION_LIST_STYLE_COMPACT; } if ($key === UserPreference::CHAT_STYLE) { return $value === UserPreference::CHAT_STYLE_SPLIT || $value === UserPreference::CHAT_STYLE_UNIFIED; } if ($key === UserPreference::LIVE_TRANSCRIPTION_TARGET_LANGUAGE_ID) { // Accept any value, as it will be used for both local and federated // instances and therefore the valid values might change depending // on the instance. return true; } return false; } protected function validateAttachmentFolder(string $userId, string $value): bool { try { $userFolder = $this->rootFolder->getUserFolder($userId); $node = $userFolder->get($value); if (!$node instanceof Folder) { throw new NotPermittedException('Node is not a directory'); } if ($node->isShared()) { throw new NotPermittedException('Folder is shared'); } return !$node->getStorage()->instanceOfStorage(SharedStorage::class); } catch (NotFoundException) { $userFolder->newFolder($value); return true; } catch (NotPermittedException) { } catch (\Exception $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); } return false; } }