diff --git a/.github/workflows/occ-command-documentation.yml b/.github/workflows/occ-command-documentation.yml index fac29170166..b332b928ee6 100644 --- a/.github/workflows/occ-command-documentation.yml +++ b/.github/workflows/occ-command-documentation.yml @@ -90,4 +90,4 @@ jobs: run: | ./occ talk:developer:update-docs cd apps/${{ env.APP_NAME }} - bash -c "[[ ! \"`git status --porcelain `\" ]] || (cat docs/occ.md && echo 'Documentation outdated. Run the command talk:developer:update-docs locally, commit the occ.md file and push the changes.' && exit 1)" + bash -c "[[ ! \"`git status --porcelain `\" ]] || (git diff && echo 'Documentation outdated. Run the command talk:developer:update-docs locally, commit the occ.md and settings.md files and push the changes.' && exit 1)" diff --git a/docs/settings.md b/docs/settings.md index 0cd1afe45c6..9ba1ad65d14 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -68,84 +68,92 @@ Legend: * 🖌️ - UI option in the admin settings available * 💻 - Dedicated OCC command available -| Key | Internal type | Default | Hash | Option | Valid values | -|--------------------------------------|------------------------------------------------------------------|-------------|------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `allowed_groups` | string[] | `[]` | Yes | 🖌️ | List of group ids that are allowed to use Talk | -| `sip_bridge_groups` | string[] | `[]` | Yes | 🖌️ | List of group ids that are allowed to enable SIP dial-in in a conversation | -| `start_conversations` | string[] | `[]` | Yes | 🖌️ | List of group ids that are allowed to create conversations | -| `start_calls_groups` | string[] | `[]` | Yes | 🖌️ | List of group ids that are allowed to start calls (everyone when empty). This also applies to moderators; guests and federated users can then never start calls. For federated conversations the host server decides. Others can still chat and join ongoing calls | -| `federation_allowed_groups` | string[] | `[]` | Yes | 🖌️ | 🏗️ *Work in progress:* List of group ids that are allowed to invite federated users into their conversations (everyone when empty) | -| `hosted_signaling_server_account` | array | `{}` | No | 🖌️ | Account information of the hosted signaling server | -| `matrix_allowed_groups` | string[] | `[]` | Yes | 🖌️ | List of group ids that are allowed to link a Matrix account (everyone when empty) | -| `stun_servers` | string[] | `[]` | Yes | 🖌💻️ | List of STUN servers, should be configured via the web interface or the OCC commands | -| `turn_servers` | array[] | `[]` | Yes | 🖌️💻 | List of TURN servers, should be configured via the web interface or the OCC commands | -| `recording_servers` | array[] | `[]` | Yes | 🖌️ | List of recording servers, should be configured via the web interface | -| `signaling_servers` | array[] | `[]` | Yes | 🖌️💻 | List of signaling servers, should be configured via the web interface or the OCC commands | -| `sip_bridge_dialin_info` | string | | Yes | 🖌️ | Additional information added in the SIP dial-in invitation mail and sidebar | -| `sip_bridge_dialout_anonymous` | bool | `false` | No | | When set to true, all out-going SIP calls will be anonymous calls (CLIR) | -| `sip_bridge_dialout_number` | string | | No | | Fallback phone number that is used as caller for out-going calls when the user has no phone-number configured (has to be E164 `+49123456789` formatted) | -| `sip_bridge_dialout_prefix` | string | `+` | No | | Deprecated: Only used for direct dial-in from older SIP bridges, which send phone numbers without this prefix | -| `sip_bridge_shared_secret` | string | | Yes | 🖌️ | Shared secret allowing the SIP bridge to authenticate on the Nextcloud server | -| `signaling_ticket_secret` | string | | Yes | | Secret used to secure the signaling tickets for guests (255 character random string) | -| `signaling_token_alg` | string
`ES256`, `ES384`, `RS256`, `RS384`, `RS512` or `EdDSA` | `ES256` | Yes | | Algorithm for the signaling tickets | -| `signaling_token_privkey` | string | `''` | No | | Private key for the signaling ticket creation by the server | -| `signaling_token_pubkey` | string | `''` | Yes | | Public key for the signaling ticket creation by the server | -| `hosted_signaling_server_nonce` | string | | No | | Temporary nonce while configuring the hosted signaling server | -| `hosted_signaling_server_account_id` | string | | No | | Account identifier of the hosted signaling server | -| `matterbridge_binary` | string | | No | | Path to the matterbridge binary file | -| `bridge_bot_password` | string | | No | | Automatically generated password of the matterbridge bot user profile | -| `default_attachment_folder` | string | `/Talk` | No | | Specify default attachment folder location | -| `samples_directory` | string | | No | | Specify a readable directory that contains other sample conversation data | -| `start_calls` | int | `0` | Yes | 🖌️ | Who can start a call, see [constants list](constants.md#start-call) | -| `max_call_duration` | int | `0` | No | | Maximum duration of a call in seconds, 0 for unlimited. Federated calls will be terminated based on the setting of the host server. Calls are ended via a background job, so system cron should be used and calls will last a bit longer (until the next execution of the cron). | -| `max_gif_size` | int | `3145728` | No | | Maximum file size for clients to render gifs previews with animation | -| `session_ping_limit` | int | `200` | No | | Number of sessions the HPB can ping in a single request | -| `token_entropy` | int | `8` | No | | Length of conversation tokens, can be increased to make tokens harder to guess but reduces readability and dial-in comfort | -| `default_group_notification` | int | `1` | No | 🖌️ | Default notification level for group conversations [constants list](constants.md#participant-notification-levels) (Default changed from 2 (mentions) to 1 (always) in Nextcloud 33 for new installations) | -| `default_permissions` | int | `246` | Yes | | Default permissions for non-moderators (see [constants list](constants.md#attendee-permissions) for bit flags) | -| `recording_consent` | int | `0` | Yes | 🖌️ | Whether users have to agree on being recorded before they can join the call (see [constants](constants.md#recording-consent-required)) | -| `grid_videos_limit` | int | `19` | No | | Maximum number of videos to show (additional to the own video) | -| `sip_dialin_default` | int | `0` | No | | Default value of SIP dial-in when creating new conversations | -| `retention_event_rooms` | int | `28` | No | | Retention period of event conversations in days (`0` means no-retention) | -| `retention_phone_rooms` | int | `7` | No | | Retention period of phone dial-in and dial-out conversations in days (`0` means no-retention) | -| `retention_instant_meetings` | int | `1` | No | | Retention period of instant meetings in days (`0` means no-retention) | -| `retention_classified_rooms` | int | `604800` | No | | Retention period of classified conversations in seconds after a call happened (`0` means no-retention) | -| `experiments_users` | int | `0` | Yes | | Bit flag of experiments that should be enabled for logged-in users on this server (see [Experiments](#experiments) below) | -| `experiments_guests` | int | `0` | Yes | | Bit flag of experiments that should be enabled for guests on this server (see [Experiments](#experiments) below) | -| `summary_threshold` | int | `100` | No | | Amount of unread messages a user needs before they see the option to summarize with AI | -| `feature_hints_hidden` | int | `0` | Yes | 🖌️ | Feature hints that should be hidden. Set to `999999` to hide all. | -| `certificate_expiration_days` | int | `10` | No | | Minimum days a certificate needs to be valid for, before an expiration notification will be shown. (default 10, minimum 0 and maximum 365) | -| `grid_videos_limit_enforced` | bool | `false` | No | | Whether the number of grid videos should be enforced | -| `changelog` | bool | `true` | No | | Whether the changelog conversation is updated with new features on major releases | -| `hide_signaling_warning` | bool | `false` | No | 🖌️ | Flag that allows to suppress the warning that an HPB should be configured | -| `conversations_list_style` | string
`two-lines` or `compact` | `two-lines` | No | | Default conversation list style when not overwritten by the user | -| `chat_style` | string
`split` or `unified` | `split` | No | | Default chat style when not overwritten by the user | -| `calls_start_without_media` | bool | `false` | Yes | | Whether participants start with enabled or disabled audio and video by default | -| `breakout_rooms` | bool | `true` | Yes | | Whether or not breakout rooms are allowed (Will only prevent creating new breakout rooms. Existing conversations are not modified.) | -| `call_recording` | bool | `true` | Yes | | Enable call recording | -| `call_recording_summary` | bool | `true` | No | 🖌️ | Whether call recordings should automatically be summarized when a transcription and summary provider is enabled. | -| `call_recording_transcription` | bool | `false` | Yes | | Whether call recordings should automatically be transcribed when a transcription provider is enabled. | -| `call_recording_summary_prompt` | string | `''` | No | | Instructions used by LLM to generate Talk call recording summaries | -| `sip_bridge_dialout` | bool | `false` | Yes | | SIP dial-out is allowed when a SIP bridge is configured | -| `federation_enabled` | bool | `false` | Yes | | 🏗️ *Work in progress:* Whether or not federation with this instance is allowed | -| `federation_incoming_enabled` | bool | `true` | Yes | | 🏗️ *Work in progress:* Whether users of this instance can be invited to federated conversations | -| `federation_outgoing_enabled` | bool | `true` | Yes | | 🏗️ *Work in progress:* Whether users of this instance can invite federated users into conversations | -| `federation_only_trusted_servers` | bool | `false` | Yes | | 🏗️ *Work in progress:* Whether federation should be limited to the list of "Trusted servers" | -| `federation_allowed_groups` | array | `[]` | Yes | 🖌️ | List of local group ids that are allowed to use federated features | -| `conversations_files` | bool | `true` | No | 🖌️ | Whether the files app integration is enabled allowing to start conversations in the right sidebar | -| `conversations_files_public_shares` | bool | `true` | No | 🖌️ | Whether the public share integration is enabled allowing to start conversations in the right sidebar on the public share page (Requires `conversations_files` also to be enabled) | -| `delete_one_to_one_conversations` | bool | `false` | No | ️ | Whether one-to-one conversations can be left by either participant or should be deleted when one participant leaves | -| `enable_matterbridge` | bool | `false` | No | 🖌️ | Whether the Matterbridge integration is enabled and can be configured | -| `force_passwords` | bool | `false` | No | ️ | Whether public chats are forced to use a password | -| `create_samples` | bool | `true` | No | ️ | Create sample conversations (the content can be overwritten by providing files in a provided `samples_directory` app config) | -| `call_end_to_end_encryption` | bool | `false` | No | 🖌️ | Whether clients should end-to-end encrypt streams in calls (Only supported with High-performance backend) | -| `inactivity_lock_after_days` | int | `0` | No | | A duration (in days) after which rooms are locked. Calculated from the last activity in the room. | -| `inactivity_enable_lobby` | bool | `false` | No | | Additionally enable the lobby for inactive rooms so they can only be read by moderators. | -| `backgrounds_branded_for_guests` | bool | `false` | No | | Whether guests are allowed to use the virtual backgrounds provided via `themes/talk-backgrounds/` | -| `backgrounds_default_for_users` | bool | `true` | No | | Whether users are allowed to use the default virtual backgrounds provided by the releases | -| `backgrounds_upload_users` | bool | `true` | No | | Whether users are allowed to upload custom virtual backgrounds and choose from their Nextcloud Files | -| `guests_play_sounds` | bool | `true` | No | | Whether guests hear the join and leave sounds by default | -| `matrix_enabled` | bool | `false` | Yes | 🖌️ | Whether users can link a Matrix account and use their Matrix rooms as conversations | +| Key | Type | Default | Hash | Option | Description | +|---|---|---|---|---|---| +| `allowed_groups` | `list` | `[]` | Yes | 🖌️ | List of group ids that are allowed to use Talk (everyone when empty) | +| `backgrounds_branded_for_guests` | bool | `false` | No | | Whether guests are allowed to use the virtual backgrounds provided via `themes/talk-backgrounds/` | +| `backgrounds_default_for_users` | bool | `true` | No | | Whether users are allowed to use the default virtual backgrounds provided by the releases | +| `backgrounds_upload_users` | bool | `true` | No | | Whether users are allowed to upload custom virtual backgrounds and choose from their Nextcloud Files | +| `breakout_rooms` | bool | `true` | Yes | | Whether or not breakout rooms are allowed (Will only prevent creating new breakout rooms. Existing conversations are not modified.) | +| `bridge_bot_password` | string | `''` | No | | Automatically generated password of the matterbridge bot user profile | +| `call_end_to_end_encryption` | bool | `false` | No | 🖌️ | Whether clients should end-to-end encrypt streams in calls (Only supported with High-performance backend) | +| `call_recording` | bool | `true` | Yes | | Enable call recording | +| `call_recording_summary` | bool | `true` | Yes | 🖌️ | Whether call recordings should automatically be summarized when a transcription and a summary provider is enabled. | +| `call_recording_summary_prompt` | string | See `lib/ConfigLexicon.php` | No | | Instructions used by LLM to generate Talk call recording summaries | +| `call_recording_transcription` | bool | `false` | Yes | 🖌️ | Whether call recordings should automatically be transcribed when a transcription provider is enabled. | +| `calls_start_without_media` | bool | `false` | No | | Whether participants start with enabled or disabled audio and video by default | +| `certificate_expiration_days` | int | `10` | No | | Minimum days a certificate needs to be valid for, before an expiration notification will be shown. (default 10, minimum 0 and maximum 365) | +| `changelog` | bool | `true` | No | | Whether the changelog conversation is updated with new features on major releases | +| `chat_style` | string
`split` or `unified` | `split` | No | | Default chat style when not overwritten by the user | +| `conversation_subfolders` | bool | `true` | No | | Whether files shared in a conversation are stored in a dedicated subfolder of the conversation inside the attachment folder | +| `conversations_files` | bool | `true` | No | 🖌️ | Whether the files app integration is enabled allowing to start conversations in the right sidebar | +| `conversations_files_public_shares` | bool | `true` | No | 🖌️ | Whether the public share integration is enabled allowing to start conversations in the right sidebar on the public share page (Requires `conversations_files` also to be enabled) | +| `conversations_list_style` | string
`two-lines` or `compact` | `two-lines` | No | | Default conversation list style when not overwritten by the user | +| `create_samples` | bool | `true` | No | | Create sample conversations (the content can be overwritten by providing files in a provided `samples_directory` app config) | +| `default_attachment_folder` | string | `/Talk` | No | | Specify default attachment folder location | +| `default_group_notification` | int | `1` | No | 🖌️ | Default notification level for group conversations, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#participant-notification-levels
(Default changed from 2 (mentions) to 1 (always) in Nextcloud 33 for new installations) | +| `default_permissions` | int | `246` | Yes | | Default permissions for non-moderators
(see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#attendee-permissions for bit flags) | +| `delete_one_to_one_conversations` | bool | `false` | No | | Whether one-to-one conversations can be left by either participant or should be deleted when one participant leaves | +| `enable_matterbridge` | bool | `false` | Yes | 🖌️ | Whether the Matterbridge integration is enabled and can be configured | +| `experiments_guests` | int | `0` | Yes | | Bit flag of experiments that should be enabled for guests on this server
See https://github.com/nextcloud/spreed/blob/main/docs/settings.md#experiments | +| `experiments_users` | int | `0` | Yes | | Bit flag of experiments that should be enabled for logged-in users on this server
See https://github.com/nextcloud/spreed/blob/main/docs/settings.md#experiments | +| `external_call_service` | string | `''` | No | | URL of the external service endpoint. `{meetingId}` is replaced with the conversation's `objectId` when Talk makes the request | +| `external_call_service_auth_password` | string | `''` | No | | HTTP Basic Auth password used when Talk calls the external service | +| `external_call_service_auth_user` | string | `''` | No | | HTTP Basic Auth username used when Talk calls the external service | +| `external_call_service_frame_origins` | `list` | `[]` | No | | JSON array of scheme+host(+port) origins that may be loaded in the iframe.
Added to `Content-Security-Policy: frame-src` and the `Permissions-Policy` for camera/microphone | +| `external_call_service_iframe_field` | string | `''` | No | | JSON field name in the external service response that contains the iframe URL | +| `external_call_service_shared_secret` | string | `''` | No | | Shared secret used for two purposes:
as the HTTP Basic Auth password when Talk calls the external service, and as the bearer token when the external service calls Talk.
Minimum 64 characters, `a-zA-Z0-9` recommended | +| `feature_hints_hidden` | int | `0` | Yes | | Feature hints that should be hidden. Set to `999999` to hide all. | +| `federation_allowed_groups` | `list` | `[]` | Yes | 🖌️ | List of local group ids that are allowed to use federated features (everyone when empty) | +| `federation_enabled` | bool | `false` | Yes | 🖌️ | Whether or not federation with this instance is allowed | +| `federation_incoming_enabled` | bool | `true` | Yes | 🖌️ | Whether users of this instance can be invited to federated conversations | +| `federation_only_trusted_servers` | bool | `false` | Yes | 🖌️ | Whether federation should be limited to the list of "Trusted servers" | +| `federation_outgoing_enabled` | bool | `true` | Yes | 🖌️ | Whether users of this instance can invite federated users into conversations | +| `force_passwords` | bool | `false` | No | | Whether public chats are forced to use a password | +| `grid_videos_limit` | int | `19` | No | | Maximum number of videos to show (additional to the own video) | +| `grid_videos_limit_enforced` | bool | `false` | No | | Whether the number of grid videos should be enforced | +| `guests_play_sounds` | bool | `true` | No | | Whether guests hear the join and leave sounds by default | +| `has_feature_changed_users` | bool | `false` | No | | Set automatically when the High-performance backend supports the `changed-users` feature, so only the affected users are sent in room updates | +| `hide_signaling_warning` | bool | `false` | No | 🖌️ | Flag that allows to suppress the warning that an HPB should be configured | +| `hosted_signaling_server_account` | `array` | `[]` | No | 🖌️ | Account information of the hosted signaling server | +| `hosted_signaling_server_account_id` | string | `''` | No | | Account identifier of the hosted signaling server | +| `hosted_signaling_server_nonce` | string | `''` | No | | Temporary nonce while configuring the hosted signaling server | +| `inactivity_enable_lobby` | bool | `false` | No | | Additionally enable the lobby for inactive rooms so they can only be read by moderators. | +| `inactivity_lock_after_days` | int | `0` | No | | A duration (in days) after which rooms are locked. Calculated from the last activity in the room. | +| `matrix_allowed_groups` | `list` | `[]` | No | 🖌️ | List of group ids that are allowed to link a Matrix account (everyone when empty) | +| `matrix_enabled` | bool | `false` | No | 🖌️ | Whether users can link a Matrix account and use their Matrix rooms as conversations | +| `matterbridge_binary` | string | `''` | No | | Path to the matterbridge binary file | +| `max_call_duration` | int | `0` | No | | Maximum duration of a call in seconds, 0 for unlimited. Federated calls will be terminated based on the setting of the host server.
Calls are ended via a background job, so system cron should be used and calls will last a bit longer
(until the next execution of cron). | +| `max_gif_size` | int | `3145728` | No | | Maximum file size for clients to render GIF previews with animation | +| `recording_consent` | int | `0` | Yes | 🖌️ | Whether users have to agree on being recorded before they can join the call (see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#recording-consent-required) | +| `recording_servers` | `array{servers: list, secret: string}` | `[]` | Yes | 🖌️ | List of recording servers, should be configured via the web interface | +| `retention_classified_rooms` | int | `604800` | No | | Retention period of classified conversations in seconds after a call happened (`0` means no-retention) | +| `retention_event_rooms` | int | `28` | No | | Retention period of event conversations in days (`0` means no-retention) | +| `retention_instant_meetings` | int | `1` | No | | Retention period of instant meetings in days (`0` means no-retention) | +| `retention_phone_rooms` | int | `7` | No | | Retention period of phone dial-in and dial-out conversations in days (`0` means no-retention) | +| `samples_directory` | string | `''` | No | | Specify a readable directory that contains other sample conversation data | +| `session_ping_limit` | int | `200` | No | | Number of sessions the HPB can ping in a single request | +| `signaling_servers` | `array{servers: list, secret: string}` | `{"servers":[],"secret":""}` | Yes | 🖌️💻 | List of signaling servers, should be configured via the web interface or the OCC commands | +| `signaling_servers_secret` | string | `''` | No | 💻 | Shared secret between Nextcloud and the signaling servers, should be configured via the web interface or the OCC commands | +| `signaling_ticket_secret` | string | `''` | Yes | | Secret used to secure the signaling tickets for guests (255 character random string) | +| `signaling_token_alg` | string
`ES256`, `ES384`, `RS256`, `RS384`, `RS512` or `EdDSA` | `ES256` | Yes | | Algorithm for the signaling tickets | +| `signaling_token_privkey` | string | `''` | No | | Private key for the signaling ticket creation by the server | +| `signaling_token_pubkey` | string | `''` | Yes | 💻 | Public key for the signaling ticket creation by the server | +| `sip_bridge_dialin_info` | string | `''` | Yes | 🖌️ | Additional information added in the SIP dial-in invitation mail and sidebar | +| `sip_bridge_dialout` | bool | `false` | No | 🖌️ | SIP dial-out is allowed when a SIP bridge is configured | +| `sip_bridge_dialout_anonymous` | bool | `false` | No | 🖌️ | When set to true, all out-going SIP calls will be anonymous calls (CLIR) | +| `sip_bridge_dialout_number` | string | `''` | No | 🖌️ | Fallback phone number that is used as caller for out-going calls when the user has no phone-number configured (has to be E164 `+49123456789` formatted) | +| `sip_bridge_dialout_prefix` | string | `+` | No | 🖌️ | Deprecated: Only used for direct dial-in from older SIP bridges, which send phone numbers without this prefix | +| `sip_bridge_groups` | `list` | `[]` | Yes | 🖌️ | List of group ids that are allowed to enable SIP dial-in in a conversation (everyone when empty) | +| `sip_bridge_shared_secret` | string | `''` | Yes | 🖌️ | Shared secret allowing the SIP bridge to authenticate on the Nextcloud server | +| `sip_dialin_default` | int | `0` | No | | Default value of SIP dial-in when creating new conversations | +| `start_calls` | int | `0` | Yes | 🖌️ | Who can start a call, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#start-call | +| `start_calls_groups` | `list` | `[]` | Yes | 🖌️ | List of group ids that are allowed to start calls (everyone when empty). This also applies to moderators; guests and federated users can then never start calls.
For federated conversations the host server decides. Others can still chat and join ongoing calls | +| `start_conversations` | `list` | `[]` | Yes | 🖌️ | List of group ids that are allowed to create conversations (everyone when empty) | +| `stun_servers` | `list` | `["stun.nextcloud.com:443"]` | Yes | 🖌️💻 | List of STUN servers for WebRTC connections | +| `summary_threshold` | int | `100` | No | | Amount of unread messages a user needs before they see the option to summarize with AI | +| `token_entropy` | int | `8` | No | | Length of conversation tokens, can be increased to make tokens harder to guess but reduces readability and dial-in comfort | +| `turn_servers` | `list` | `[]` | Yes | 🖌️💻 | List of TURN servers for WebRTC connections | ## Experiments diff --git a/lib/Command/Developer/UpdateDocs.php b/lib/Command/Developer/UpdateDocs.php index 3df1d08230b..bd912a08e91 100644 --- a/lib/Command/Developer/UpdateDocs.php +++ b/lib/Command/Developer/UpdateDocs.php @@ -9,7 +9,12 @@ namespace OCA\Talk\Command\Developer; use OC\Core\Command\Base; +use OCA\Talk\Config\AppConfigEntry; +use OCA\Talk\ConfigLexicon; use OCP\App\IAppManager; +use OCP\Config\Lexicon\Entry; +use OCP\Config\Lexicon\Preset; +use OCP\Config\ValueType; use OCP\Console\Attribute\AsCommand; use OCP\IConfig; use OCP\Server; @@ -23,6 +28,7 @@ class UpdateDocs extends Base { public function __construct( private readonly IConfig $config, private readonly IAppManager $appManager, + private readonly ConfigLexicon $configLexicon, ) { parent::__construct(); } @@ -35,7 +41,7 @@ public function isEnabled(): bool { protected function configure(): void { $this ->setName('talk:developer:update-docs') - ->setDescription('Update documentation of commands') + ->setDescription('Update documentation of commands and app configs') ; } @@ -55,9 +61,73 @@ protected function execute(InputInterface $input, OutputInterface $output): int $handle = fopen(__DIR__ . '/../../../docs/occ.md', 'w'); fwrite($handle, $documentation); fclose($handle); + + $settingsFile = __DIR__ . '/../../../docs/settings.md'; + file_put_contents($settingsFile, $this->replaceAppConfigTable((string)file_get_contents($settingsFile))); return 0; } + public function replaceAppConfigTable(string $settings): string { + $section = strpos($settings, '## App configuration'); + $start = strpos($settings, "\n| Key ", $section ?: 0); + $end = strpos($settings, "\n## ", $start ?: 0); + if ($section === false || $start === false || $end === false) { + throw new \RuntimeException('Could not find the app configuration table in docs/settings.md'); + } + + return substr($settings, 0, $start + 1) . $this->getAppConfigTable() . substr($settings, $end); + } + + protected function getAppConfigTable(): string { + $table = "| Key | Type | Default | Hash | Option | Description |\n"; + $table .= "|---|---|---|---|---|---|\n"; + $configs = $this->configLexicon->getTalkAppConfigs(); + usort($configs, static fn (AppConfigEntry $a, AppConfigEntry $b): int => strcmp($a->entry->getKey(), $b->entry->getKey())); + foreach ($configs as $config) { + $table .= $this->getAppConfigRow($config) . "\n"; + } + return $table; + } + + public function getAppConfigRow(AppConfigEntry $config): string { + $entry = $config->entry; + return '| `' . $entry->getKey() . '` | ' + . $this->formatType($config) . ' | ' + . $this->formatDefault($entry) . ' | ' + . ($config->hash ? 'Yes' : 'No') . ' | ' + . ($config->adminSetting ? '🖌️' : '') . ($config->occCommands !== [] ? '💻' : '') . ' | ' + . ($entry->isDeprecated() ? 'Deprecated: ' : '') . str_replace([PHP_EOL, '|'], ['
', '\|'], $entry->getDefinition()) . ' |'; + } + + protected function formatType(AppConfigEntry $config): string { + if ($config->arrayType !== null) { + return '`' . str_replace('|', '\|', $config->arrayType) . '`'; + } + + $type = strtolower($config->entry->getValueType()->name); + if ($config->enumType === null) { + return $type; + } + + $values = array_map(static fn (string $value): string => '`' . $value . '`', explode('|', $config->enumType)); + $last = array_pop($values); + return $type . '
' . ($values === [] ? $last : implode(', ', $values) . ' or ' . $last); + } + + protected function formatDefault(Entry $entry): string { + $default = $entry->getDefault(Preset::NONE); + if ($default === null) { + return ''; + } + if (str_contains($default, "\n")) { + return 'See `lib/ConfigLexicon.php`'; + } + if ($entry->getValueType() === ValueType::BOOL) { + return \in_array($default, ['1', 'yes', 'true', 'on'], true) ? '`true`' : '`false`'; + } + return '`' . ($default === '' ? "''" : str_replace('|', '\|', $default)) . '`'; + } + protected function getCommand(string $namespace): Command { $asCommand = (new \ReflectionClass($namespace))->getAttributes(AsCommand::class); if ($asCommand !== []) { diff --git a/lib/Config/AppConfigEntry.php b/lib/Config/AppConfigEntry.php new file mode 100644 index 00000000000..2282e45389c --- /dev/null +++ b/lib/Config/AppConfigEntry.php @@ -0,0 +1,35 @@ + $occCommands Dedicated occ commands to set the value + * @param ?string $arrayType Psalm style type of the content for {@see ValueType::ARRAY} entries, e.g. `list` + * @param ?string $enumType Allowed values of {@see ValueType::STRING} and {@see ValueType::INT} entries separated by `|`, e.g. `split|unified` + */ + public function __construct( + public readonly Entry $entry, + public readonly bool $hash = false, + public readonly bool $adminSetting = false, + public readonly array $occCommands = [], + public readonly ?string $arrayType = null, + public readonly ?string $enumType = null, + ) { + } +} diff --git a/lib/ConfigLexicon.php b/lib/ConfigLexicon.php index d6ba2c6644a..3c750b27f1f 100644 --- a/lib/ConfigLexicon.php +++ b/lib/ConfigLexicon.php @@ -8,6 +8,14 @@ namespace OCA\Talk; +use OCA\Talk\Command\Signaling\Add as SignalingAdd; +use OCA\Talk\Command\Signaling\Delete as SignalingDelete; +use OCA\Talk\Command\Signaling\VerifyKeys as SignalingVerifyKeys; +use OCA\Talk\Command\Stun\Add as StunAdd; +use OCA\Talk\Command\Stun\Delete as StunDelete; +use OCA\Talk\Command\Turn\Add as TurnAdd; +use OCA\Talk\Command\Turn\Delete as TurnDelete; +use OCA\Talk\Config\AppConfigEntry; use OCA\Talk\Service\RecordingService; use OCA\Talk\Settings\UserPreference; use OCA\Talk\Signaling\Manager as SignalingManager; @@ -143,111 +151,281 @@ public function getStrictness(): Strictness { return Strictness::IGNORE; } - #[\Override] - public function getAppConfigs(): array { + /** + * @return list + */ + public function getTalkAppConfigs(): array { return [ - new Entry(UserPreference::CONVERSATIONS_LIST_STYLE, ValueType::STRING, UserPreference::CONVERSATION_LIST_STYLE_TWO_LINES), - new Entry(UserPreference::CHAT_STYLE, ValueType::STRING, UserPreference::CHAT_STYLE_SPLIT), - new Entry(SignalingManager::HAS_FEATURE_CHANGED_USERS, ValueType::BOOL, false), - new Entry(Config::RETENTION_CLASSIFIED_ROOMS, ValueType::INT, 604800, definition: 'Retention period of classified conversations in seconds after a call happened (`0` means no-retention)'), - new Entry(Config::RETENTION_EVENT_ROOMS, ValueType::INT, 28, definition: 'Retention period of event conversations in days (`0` means no-retention)'), - new Entry(Config::RETENTION_PHONE_ROOMS, ValueType::INT, 7, definition: 'Retention period of phone dial-in and dial-out conversations in days (`0` means no-retention'), - new Entry(Config::RETENTION_INSTANT_MEETINGS, ValueType::INT, 1, definition: 'Retention period of instant meetings in days (`0` means no-retention)'), - new Entry(Config::STUN_SERVERS, ValueType::ARRAY, [Config::DEFAULT_STUN_SERVER], definition: 'List of STUN servers for WebRTC connections', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::TURN_SERVERS, ValueType::ARRAY, [], definition: 'List of TURN servers for WebRTC connections', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::ALLOWED_GROUPS_TALK, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to use Talk'), - new Entry(Config::ALLOWED_GROUPS_SIP, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to enable SIP dial-in in a conversation'), - new Entry(Config::ALLOWED_GROUPS_CONVERSATIONS, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to create conversation'), - new Entry(Config::ALLOWED_GROUPS_FEDERATION, ValueType::ARRAY, [], definition: 'List of local group ids that are allowed to use federated features'), - new Entry(Config::FEDERATION_ENABLED, ValueType::BOOL, false, definition: 'Whether or not federation with this instance is allowed'), - new Entry(Config::FEDERATION_INCOMING_ENABLED, ValueType::BOOL, true, definition: 'Whether users of this instance can be invited to federated conversations'), - new Entry(Config::FEDERATION_OUTGOING_ENABLED, ValueType::BOOL, true, definition: 'Whether users of this instance can invite federated users into conversations'), - new Entry(Config::FEDERATION_ONLY_TRUSTED_SERVERS, ValueType::BOOL, false, definition: 'Whether federation should be limited to the list of "Trusted servers"'), - new Entry(Config::ALLOWED_START_CALLS, ValueType::INT, 0, definition: 'Who can start a call,' . PHP_EOL . 'see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#start-call'), - new Entry(Config::ALLOWED_START_CALLS_GROUPS, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to start calls (everyone when empty). This also applies to moderators; guests and federated users can then never start calls.' . PHP_EOL . 'For federated conversations the host server decides. Others can still chat and join ongoing calls'), - new Entry(Config::BREAKOUT_ROOMS_ENABLED, ValueType::BOOL, true, definition: 'Whether or not breakout rooms are allowed (Will only prevent creating new breakout rooms. Existing conversations are not modified.'), - new Entry(Config::CONVERSATIONS_FILES, ValueType::BOOL, true, definition: 'Whether the files app integration is enabled allowing tostart conversations in the right sidebar'), - new Entry(Config::CONVERSATIONS_FILES_PUBLIC_SHARES, ValueType::BOOL, true, definition: 'Whether the public share integration is enabled allowing to start conversations in the right sidebar on the public share page (Requires `conversations_files` also to be enabled'), - new Entry(Config::DEFAULT_ROOM_PERMISSIONS, ValueType::INT, 246, definition: 'Default permissions for non-moderators' . PHP_EOL . '(see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#attendee-permissions for bit flags)'), - new Entry(Config::CONVERSATION_SUBFOLDERS, ValueType::BOOL, true, definition: ''), - new Entry(Config::DEFAULT_ATTACHMENT_FOLDER, ValueType::STRING, '/Talk', definition: 'Specify default attachment folder location'), - new Entry(Config::GRID_VIDEOS_LIMIT, ValueType::INT, 19 /* 5*4 - self */, definition: 'Maximum number of videos to show (additional to the own video)'), - new Entry(Config::GRID_VIDEOS_LIMIT_ENFORCED, ValueType::BOOL, false, definition: 'Whether the number of grid videos should be enforced'), - new Entry(Config::GUESTS_PLAY_SOUNDS, ValueType::BOOL, true, definition: 'Whether guests hear the join and leave sounds by default'), - new Entry(Config::GROUP_CHATS_FORCE_PASSWORDS_ENABLED, ValueType::BOOL, false, definition: 'Whether public chats are forced to use a password'), - new Entry(Config::EXTERNAL_CALL_SERVICE, ValueType::STRING, '', definition: 'URL of the external service endpoint. `{meetingId}` is replaced with the conversation\'s `objectId` when Talk makes the request'), - new Entry(Config::EXTERNAL_CALL_SERVICE_SHARED_SECRET, ValueType::STRING, '', definition: 'Shared secret used for two purposes:' . PHP_EOL . 'as the HTTP Basic Auth password when Talk calls the external service, and as the bearer token when the external service calls Talk.' . PHP_EOL . 'Minimum 64 characters, `a-zA-Z0-9` recommended'), - new Entry(Config::EXTERNAL_CALL_SERVICE_AUTH_USER, ValueType::STRING, '', definition: 'HTTP Basic Auth username used when Talk calls the external service'), - new Entry(Config::EXTERNAL_CALL_SERVICE_AUTH_PASSWORD, ValueType::STRING, '', definition: 'HTTP Basic Auth password used when Talk calls the external service'), - new Entry(Config::EXTERNAL_CALL_SERVICE_FRAME_ORIGINS, ValueType::ARRAY, [], definition: 'JSON array of scheme+host(+port) origins that may be loaded in the iframe.' . PHP_EOL . 'Added to `Content-Security-Policy: frame-src` and the `Permissions-Policy` for camera/microphone'), - new Entry(Config::EXTERNAL_CALL_SERVICE_IFRAME_FIELD, ValueType::STRING, '', definition: 'JSON field name in the external service response that contains the iframe URL'), - new Entry(Config::CALLS_START_WITHOUT_MEDIA, ValueType::BOOL, false, definition: 'Whether participants start with enabled or disabled audio and video by default'), - new Entry(Config::INACTIVITY_LOCK_AFTER_DAYS, ValueType::INT, 0, definition: 'A duration (in days) after which rooms are locked. Calculated from the last activity in the room.'), - new Entry(Config::INACTIVITY_ENABLE_LOBBY, ValueType::BOOL, false, definition: 'Additionally enable the lobby for inactive rooms so they can only be read by moderators.'), - new Entry(Config::EXPERIMENTS_USERS, ValueType::INT, 0, definition: 'Bit flag of experiments that should be enabled for logged-in users on this server' . PHP_EOL . 'See https://github.com/nextcloud/spreed/blob/main/docs/settings.md#experiments'), - new Entry(Config::EXPERIMENTS_GUESTS, ValueType::INT, 0, definition: 'Bit flag of experiments that should be enabled for guests on this server' . PHP_EOL . 'See https://github.com/nextcloud/spreed/blob/main/docs/settings.md#experiments'), - new Entry(Config::CALL_END_TO_END_ENCRYPTION, ValueType::BOOL, false, definition: 'Whether clients should end-to-end encrypt streams in calls (Only supported with High-performance backend'), - new Entry(Config::CALL_RECORDING, ValueType::BOOL, true, definition: 'Enable call recording'), - new Entry(Config::CALL_RECORDING_SUMMARY, ValueType::BOOL, true, definition: 'Whether call recordings should automatically be summarized when a transcription summary provider is enabled.'), - new Entry(Config::CALL_RECORDING_SUMMARY_PROMPT, ValueType::STRING, self::DEFAULT_CALL_RECORDING_SUMMARY_PROMPT, definition: 'Instructions used by LLM to generate Talk call recording summaries'), - new Entry(Config::CALL_RECORDING_TRANSCRIPTION, ValueType::BOOL, false, definition: 'Whether call recordings should automatically be transcribed when a transcription provider is enabled.'), - new Entry(Config::RECORDING_CONSENT, ValueType::INT, RecordingService::CONSENT_REQUIRED_NO, definition: 'Whether users have to agree on being recorded before they can join the call (see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#recording-consent-required)'), - new Entry(Config::RECORDING_SERVERS, ValueType::ARRAY, [], definition: 'List of recording servers, should be configured via the web interface', flags: IAPPConfig::FLAG_SENSITIVE), - new Entry(Config::FORCE_PASSWORDS, ValueType::BOOL, false, definition: 'Whether public chats are forced to use a password'), - new Entry(Config::BACKGROUNDS_BRANDED_FOR_GUESTS, ValueType::BOOL, false, definition: 'Whether guests are allowed to use the virtual backgrounds provided via `themes/talk-backgrounds/`'), - new Entry(Config::BACKGROUNDS_DEFAULT_FOR_USERS, ValueType::BOOL, definition: 'Whether users are allowed to use the default virutal backgrounds provided by the releases'), - new Entry(Config::BACKGROUNDS_UPLOAD_USERS, ValueType::BOOL, definition: 'Whether users are allowed to upload custom virtual backgrounds and choose from their Nextcloud Files'), - new Entry(Config::CREATE_SAMPLES, ValueType::BOOL, true, definition: 'Create sample conversations (the content can be overwritten by providing files in a provided `samples_directory` app config)'), - new Entry(Config::SAMPLES_DIRECTORY, ValueType::STRING, '', definition: 'Specify a readable directory that contains other sample conversation data'), - new Entry(Config::MATTERBRIDGE_ENABLED, ValueType::BOOL, false, definition: 'Whether the Matterbridge integration is enabled and can be configured'), - new Entry(Config::MATRIX_ENABLED, ValueType::BOOL, false, definition: 'Whether users can link a Matrix account and use their Matrix rooms as conversations'), - new Entry(Config::MATRIX_ALLOWED_GROUPS, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to link a Matrix account (empty means everyone)'), - new Entry(Config::DELETE_ONE_TO_ONE_CONVERSATIONS, ValueType::BOOL, false, definition: 'Whether one-to-one conversations can be left by either participant or should be deleted when one participant leaves'), - new Entry(Config::MAX_GIF_SIZE, ValueType::INT, 3145728, definition: 'Maximum file size for clients to render gifs previews with animation', rename: 'max-gif-size'), - new Entry(Config::CERTIFICATE_EXPIRATION_DAYS, ValueType::INT, 10, definition: 'Minimum days a certificate needs to be valid for, before an expiration notification will be shown. (default 10, minimum 0 and maximum 365)'), - new Entry(Config::TOKEN_ENTROPY, ValueType::INT, 8, definition: 'Length of conversation tokens, can be increased to make tokens harder to guess but reduces readability and dial-in comfort'), - new Entry(Config::SUMMARY_THRESHOLD, ValueType::INT, 100, definition: 'Amount of unread messages a user needs before they see the option to summarize with AI'), - new Entry(Config::ALLOWED_START_CALLS, ValueType::INT, Room::START_CALL_EVERYONE, definition: 'Who can start a call, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#start-call '), - new Entry(Config::SAMPLES_DIRECTORY, ValueType::STRING, '', definition: 'Specify a readable directory that contains other sample conversation data'), - new Entry(Config::RETENTION_EVENT_ROOMS, ValueType::INT, 28, definition: 'Retention period of event conversations in days (`0` means no-retention)'), - new Entry(Config::RETENTION_PHONE_ROOMS, ValueType::INT, 7, definition: 'Retention period of phone dial-in and dial-out conversations in days (`0` means no-retention)'), - new Entry(Config::RETENTION_INSTANT_MEETINGS, ValueType::INT, 1, definition: 'Retention period of instant meetings in days (`0` means no-retention'), - new Entry(Config::MAX_CALL_DURATION, ValueType::INT, 0, definition: 'Maximum duration of a call in seconds, 0 for unlimited. Federated calls will be terminated based on the setting of the host server.' . PHP_EOL . 'Calls are ended via a background job, so system cron should be used and calls will last a bit longer' . PHP_EOL . '(until the next execution of cron).'), - new Entry(Config::FEATURE_HINTS_HIDDEN, ValueType::INT, 0, definition: 'Feature hints that should be hidden. Set to `999999` to hide all.'), - new Entry(Config::MATTERBRIDGE_BINARY, ValueType::STRING, '', definition: 'Path to the matterbridge binary file'), - new Entry(Config::MATTERBRIDGE_BOT_PASSWORD, ValueType::STRING, '', definition: 'Automatically generated password of the matterbridge bot user profile', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::DEFAULT_GROUP_NOTIFICATION, ValueType::INT, Participant::NOTIFY_ALWAYS, definition: 'Default notification level for group conversations, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#participant-notification-levels' . PHP_EOL . '(Default changed from 2 (mentions) to 1 (always) in Nextcloud 33 for new installations'), - new Entry(Config::CHAT_STYLE, ValueType::STRING, UserPreference::CHAT_STYLE_SPLIT, definition: 'Default chat style when not overwritten by the user'), - new Entry(Config::CONVERSATIONS_LIST_STYLE, ValueType::STRING, UserPreference::CONVERSATION_LIST_STYLE_TWO_LINES, definition: 'Default conversation list style when not overwritten by the user'), - new Entry(Config::HIDE_SIGNALING_WARNING, ValueType::BOOL, false, definition: 'Flag that allows to suppress the warning that an HPB should be configured'), - new Entry(Config::CHANGELOG, ValueType::BOOL, true, definition: 'Whether the changelog conversation is updated with new features on major releases'), - new Entry(Config::SESSION_PING_LIMIT, ValueType::INT, 200, definition: 'Number of sessions the HPB can ping in a single request', rename: 'session-ping-limit'), - new Entry(Config::HOSTED_SIGNALING_SERVER_ACCOUNT, ValueType::ARRAY, [], definition: 'Account information of the hosted signaling server', flags: IAppConfig::FLAG_SENSITIVE, rename: 'hosted-signaling-server-account'), - new Entry(Config::HOSTED_SIGNALING_SERVER_NONCE, ValueType::STRING, '', definition: 'Temporary nonce while configuring the hosted signaling server', flags: IAppConfig::FLAG_SENSITIVE, rename: 'hosted-signaling-server-nonce'), - new Entry(Config::HOSTED_SIGNALING_SERVER_ACCOUNT_ID, ValueType::STRING, '', definition: 'Account identifier of the hosted signaling server', flags: IAppConfig::FLAG_SENSITIVE, rename: 'hosted-signaling-server-account-id'), - new Entry(Config::SIP_BRIDGE_DIALIN_INFO, ValueType::STRING, '', definition: 'Additional information added in the SIP dial-in invitation mail and sidebar', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::SIP_DIALIN_DEFAULT, ValueType::INT, 0, definition: 'Default value of SIP dial-in when creating new conversations'), - new Entry(Config::SIP_BRIDGE_DIALOUT_ANONYMOUS, ValueType::BOOL, false, definition: 'When set to true, all out-going SIP calls will be anonymous calls (CLIR)'), - new Entry(Config::SIP_BRIDGE_DIALOUT_NUMBER, ValueType::STRING, '', definition: 'Fallback phone number that is used as caller for out-going calls when the user has no phone-number configured (has to be E164 `+49123456789` formatted'), - new Entry(Config::SIP_BRIDGE_DIALOUT_PREFIX, ValueType::STRING, '+', definition: 'Deprecated: Only used for direct dial-in from older SIP bridges, which send phone numbers without this prefix', deprecated: true), - new Entry(Config::SIP_BRIDGE_SHARED_SECRET, ValueType::STRING, '', definition: 'Shared secret allowing the SIP bridge to authenticate on the Nextcloud server', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::SIP_BRIDGE_DIALOUT, ValueType::BOOL, false, definition: 'SIP dial-out is allowed when a SIP bridge is configured', rename: 'sip_dialout'), - new Entry(Config::SIGNALING_TICKET_SECRET, ValueType::STRING, '', definition: 'Secret used to secure the signaling tickets for guests (255 character random string)', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::SIGNALING_TOKEN_ALG, ValueType::STRING, 'ES256', definition: 'Algorithm for the signaling tickets'), - new Entry(Config::SIGNALING_TOKEN_PRIVKEY, ValueType::STRING, '', definition: 'Private key for the signaling ticket creation by the server', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::SIGNALING_TOKEN_PUBKEY, ValueType::STRING, '', definition: 'Public key for the signaling ticket creation by the server'), - new Entry(Config::SIGNALING_SERVERS, ValueType::ARRAY, ['servers' => [],'secret' => ''], definition: 'List of signaling servers, should be configured via the web interface or the OCC commands', flags: IAppConfig::FLAG_SENSITIVE), - new Entry(Config::SIGNALING_SERVERS_SECRET, ValueType::STRING, '', flags: IAppConfig::FLAG_SENSITIVE), + new AppConfigEntry(new Entry(SignalingManager::HAS_FEATURE_CHANGED_USERS, ValueType::BOOL, false, definition: 'Set automatically when the High-performance backend supports the `changed-users` feature, so only the affected users are sent in room updates')), + new AppConfigEntry(new Entry(Config::RETENTION_CLASSIFIED_ROOMS, ValueType::INT, 604800, definition: 'Retention period of classified conversations in seconds after a call happened (`0` means no-retention)')), + new AppConfigEntry( + new Entry(Config::STUN_SERVERS, ValueType::ARRAY, [Config::DEFAULT_STUN_SERVER], definition: 'List of STUN servers for WebRTC connections', flags: IAppConfig::FLAG_SENSITIVE), + hash: true, + adminSetting: true, + occCommands: [StunAdd::class, StunDelete::class], + arrayType: 'list', + ), + new AppConfigEntry( + new Entry(Config::TURN_SERVERS, ValueType::ARRAY, [], definition: 'List of TURN servers for WebRTC connections', flags: IAppConfig::FLAG_SENSITIVE), + hash: true, + adminSetting: true, + occCommands: [TurnAdd::class, TurnDelete::class], + arrayType: 'list', + ), + new AppConfigEntry( + new Entry(Config::ALLOWED_GROUPS_TALK, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to use Talk (everyone when empty)'), + hash: true, + adminSetting: true, + arrayType: 'list', + ), + new AppConfigEntry( + new Entry(Config::ALLOWED_GROUPS_SIP, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to enable SIP dial-in in a conversation (everyone when empty)'), + hash: true, + adminSetting: true, + arrayType: 'list', + ), + new AppConfigEntry( + new Entry(Config::ALLOWED_GROUPS_CONVERSATIONS, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to create conversations (everyone when empty)'), + hash: true, + adminSetting: true, + arrayType: 'list', + ), + new AppConfigEntry( + new Entry(Config::ALLOWED_GROUPS_FEDERATION, ValueType::ARRAY, [], definition: 'List of local group ids that are allowed to use federated features (everyone when empty)'), + hash: true, + adminSetting: true, + arrayType: 'list', + ), + new AppConfigEntry( + new Entry(Config::FEDERATION_ENABLED, ValueType::BOOL, false, definition: 'Whether or not federation with this instance is allowed'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::FEDERATION_INCOMING_ENABLED, ValueType::BOOL, true, definition: 'Whether users of this instance can be invited to federated conversations'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::FEDERATION_OUTGOING_ENABLED, ValueType::BOOL, true, definition: 'Whether users of this instance can invite federated users into conversations'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::FEDERATION_ONLY_TRUSTED_SERVERS, ValueType::BOOL, false, definition: 'Whether federation should be limited to the list of "Trusted servers"'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::ALLOWED_START_CALLS_GROUPS, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to start calls (everyone when empty). This also applies to moderators; guests and federated users can then never start calls.' . PHP_EOL . 'For federated conversations the host server decides. Others can still chat and join ongoing calls'), + hash: true, + adminSetting: true, + arrayType: 'list', + ), + new AppConfigEntry( + new Entry(Config::BREAKOUT_ROOMS_ENABLED, ValueType::BOOL, true, definition: 'Whether or not breakout rooms are allowed (Will only prevent creating new breakout rooms. Existing conversations are not modified.)'), + hash: true, + ), + new AppConfigEntry( + new Entry(Config::CONVERSATIONS_FILES, ValueType::BOOL, true, definition: 'Whether the files app integration is enabled allowing to start conversations in the right sidebar'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::CONVERSATIONS_FILES_PUBLIC_SHARES, ValueType::BOOL, true, definition: 'Whether the public share integration is enabled allowing to start conversations in the right sidebar on the public share page (Requires `conversations_files` also to be enabled)'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::DEFAULT_ROOM_PERMISSIONS, ValueType::INT, 246, definition: 'Default permissions for non-moderators' . PHP_EOL . '(see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#attendee-permissions for bit flags)'), + hash: true, + ), + new AppConfigEntry(new Entry(Config::CONVERSATION_SUBFOLDERS, ValueType::BOOL, true, definition: 'Whether files shared in a conversation are stored in a dedicated subfolder of the conversation inside the attachment folder')), + new AppConfigEntry(new Entry(Config::DEFAULT_ATTACHMENT_FOLDER, ValueType::STRING, '/Talk', definition: 'Specify default attachment folder location')), + new AppConfigEntry(new Entry(Config::GRID_VIDEOS_LIMIT, ValueType::INT, 19 /* 5*4 - self */, definition: 'Maximum number of videos to show (additional to the own video)')), + new AppConfigEntry(new Entry(Config::GRID_VIDEOS_LIMIT_ENFORCED, ValueType::BOOL, false, definition: 'Whether the number of grid videos should be enforced')), + new AppConfigEntry(new Entry(Config::GUESTS_PLAY_SOUNDS, ValueType::BOOL, true, definition: 'Whether guests hear the join and leave sounds by default')), + new AppConfigEntry(new Entry(Config::EXTERNAL_CALL_SERVICE, ValueType::STRING, '', definition: 'URL of the external service endpoint. `{meetingId}` is replaced with the conversation\'s `objectId` when Talk makes the request')), + new AppConfigEntry(new Entry(Config::EXTERNAL_CALL_SERVICE_SHARED_SECRET, ValueType::STRING, '', definition: 'Shared secret used for two purposes:' . PHP_EOL . 'as the HTTP Basic Auth password when Talk calls the external service, and as the bearer token when the external service calls Talk.' . PHP_EOL . 'Minimum 64 characters, `a-zA-Z0-9` recommended')), + new AppConfigEntry(new Entry(Config::EXTERNAL_CALL_SERVICE_AUTH_USER, ValueType::STRING, '', definition: 'HTTP Basic Auth username used when Talk calls the external service')), + new AppConfigEntry(new Entry(Config::EXTERNAL_CALL_SERVICE_AUTH_PASSWORD, ValueType::STRING, '', definition: 'HTTP Basic Auth password used when Talk calls the external service')), + new AppConfigEntry( + new Entry(Config::EXTERNAL_CALL_SERVICE_FRAME_ORIGINS, ValueType::ARRAY, [], definition: 'JSON array of scheme+host(+port) origins that may be loaded in the iframe.' . PHP_EOL . 'Added to `Content-Security-Policy: frame-src` and the `Permissions-Policy` for camera/microphone'), + arrayType: 'list', + ), + new AppConfigEntry(new Entry(Config::EXTERNAL_CALL_SERVICE_IFRAME_FIELD, ValueType::STRING, '', definition: 'JSON field name in the external service response that contains the iframe URL')), + new AppConfigEntry(new Entry(Config::CALLS_START_WITHOUT_MEDIA, ValueType::BOOL, false, definition: 'Whether participants start with enabled or disabled audio and video by default')), + new AppConfigEntry(new Entry(Config::INACTIVITY_LOCK_AFTER_DAYS, ValueType::INT, 0, definition: 'A duration (in days) after which rooms are locked. Calculated from the last activity in the room.')), + new AppConfigEntry(new Entry(Config::INACTIVITY_ENABLE_LOBBY, ValueType::BOOL, false, definition: 'Additionally enable the lobby for inactive rooms so they can only be read by moderators.')), + new AppConfigEntry( + new Entry(Config::EXPERIMENTS_USERS, ValueType::INT, 0, definition: 'Bit flag of experiments that should be enabled for logged-in users on this server' . PHP_EOL . 'See https://github.com/nextcloud/spreed/blob/main/docs/settings.md#experiments'), + hash: true, + ), + new AppConfigEntry( + new Entry(Config::EXPERIMENTS_GUESTS, ValueType::INT, 0, definition: 'Bit flag of experiments that should be enabled for guests on this server' . PHP_EOL . 'See https://github.com/nextcloud/spreed/blob/main/docs/settings.md#experiments'), + hash: true, + ), + new AppConfigEntry( + new Entry(Config::CALL_END_TO_END_ENCRYPTION, ValueType::BOOL, false, definition: 'Whether clients should end-to-end encrypt streams in calls (Only supported with High-performance backend)'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::CALL_RECORDING, ValueType::BOOL, true, definition: 'Enable call recording'), + hash: true, + ), + new AppConfigEntry( + new Entry(Config::CALL_RECORDING_SUMMARY, ValueType::BOOL, true, definition: 'Whether call recordings should automatically be summarized when a transcription and a summary provider is enabled.'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry(new Entry(Config::CALL_RECORDING_SUMMARY_PROMPT, ValueType::STRING, self::DEFAULT_CALL_RECORDING_SUMMARY_PROMPT, definition: 'Instructions used by LLM to generate Talk call recording summaries')), + new AppConfigEntry( + new Entry(Config::CALL_RECORDING_TRANSCRIPTION, ValueType::BOOL, false, definition: 'Whether call recordings should automatically be transcribed when a transcription provider is enabled.'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::RECORDING_CONSENT, ValueType::INT, RecordingService::CONSENT_REQUIRED_NO, definition: 'Whether users have to agree on being recorded before they can join the call (see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#recording-consent-required)'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::RECORDING_SERVERS, ValueType::ARRAY, [], definition: 'List of recording servers, should be configured via the web interface', flags: IAppConfig::FLAG_SENSITIVE), + hash: true, + adminSetting: true, + arrayType: 'array{servers: list, secret: string}', + ), + new AppConfigEntry(new Entry(Config::FORCE_PASSWORDS, ValueType::BOOL, false, definition: 'Whether public chats are forced to use a password')), + new AppConfigEntry(new Entry(Config::BACKGROUNDS_BRANDED_FOR_GUESTS, ValueType::BOOL, false, definition: 'Whether guests are allowed to use the virtual backgrounds provided via `themes/talk-backgrounds/`')), + new AppConfigEntry(new Entry(Config::BACKGROUNDS_DEFAULT_FOR_USERS, ValueType::BOOL, true, definition: 'Whether users are allowed to use the default virtual backgrounds provided by the releases')), + new AppConfigEntry(new Entry(Config::BACKGROUNDS_UPLOAD_USERS, ValueType::BOOL, true, definition: 'Whether users are allowed to upload custom virtual backgrounds and choose from their Nextcloud Files')), + new AppConfigEntry(new Entry(Config::CREATE_SAMPLES, ValueType::BOOL, true, definition: 'Create sample conversations (the content can be overwritten by providing files in a provided `samples_directory` app config)')), + new AppConfigEntry( + new Entry(Config::MATTERBRIDGE_ENABLED, ValueType::BOOL, false, definition: 'Whether the Matterbridge integration is enabled and can be configured'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::MATRIX_ENABLED, ValueType::BOOL, false, definition: 'Whether users can link a Matrix account and use their Matrix rooms as conversations'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::MATRIX_ALLOWED_GROUPS, ValueType::ARRAY, [], definition: 'List of group ids that are allowed to link a Matrix account (everyone when empty)'), + adminSetting: true, + arrayType: 'list', + ), + new AppConfigEntry(new Entry(Config::DELETE_ONE_TO_ONE_CONVERSATIONS, ValueType::BOOL, false, definition: 'Whether one-to-one conversations can be left by either participant or should be deleted when one participant leaves')), + new AppConfigEntry(new Entry(Config::MAX_GIF_SIZE, ValueType::INT, 3145728, definition: 'Maximum file size for clients to render GIF previews with animation', rename: 'max-gif-size')), + new AppConfigEntry(new Entry(Config::CERTIFICATE_EXPIRATION_DAYS, ValueType::INT, 10, definition: 'Minimum days a certificate needs to be valid for, before an expiration notification will be shown. (default 10, minimum 0 and maximum 365)')), + new AppConfigEntry(new Entry(Config::TOKEN_ENTROPY, ValueType::INT, 8, definition: 'Length of conversation tokens, can be increased to make tokens harder to guess but reduces readability and dial-in comfort')), + new AppConfigEntry(new Entry(Config::SUMMARY_THRESHOLD, ValueType::INT, 100, definition: 'Amount of unread messages a user needs before they see the option to summarize with AI')), + new AppConfigEntry( + new Entry(Config::ALLOWED_START_CALLS, ValueType::INT, Room::START_CALL_EVERYONE, definition: 'Who can start a call, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#start-call'), + hash: true, + adminSetting: true, + ), + new AppConfigEntry(new Entry(Config::SAMPLES_DIRECTORY, ValueType::STRING, '', definition: 'Specify a readable directory that contains other sample conversation data')), + new AppConfigEntry(new Entry(Config::RETENTION_EVENT_ROOMS, ValueType::INT, 28, definition: 'Retention period of event conversations in days (`0` means no-retention)')), + new AppConfigEntry(new Entry(Config::RETENTION_PHONE_ROOMS, ValueType::INT, 7, definition: 'Retention period of phone dial-in and dial-out conversations in days (`0` means no-retention)')), + new AppConfigEntry(new Entry(Config::RETENTION_INSTANT_MEETINGS, ValueType::INT, 1, definition: 'Retention period of instant meetings in days (`0` means no-retention)')), + new AppConfigEntry(new Entry(Config::MAX_CALL_DURATION, ValueType::INT, 0, definition: 'Maximum duration of a call in seconds, 0 for unlimited. Federated calls will be terminated based on the setting of the host server.' . PHP_EOL . 'Calls are ended via a background job, so system cron should be used and calls will last a bit longer' . PHP_EOL . '(until the next execution of cron).')), + new AppConfigEntry( + new Entry(Config::FEATURE_HINTS_HIDDEN, ValueType::INT, 0, definition: 'Feature hints that should be hidden. Set to `999999` to hide all.'), + hash: true, + ), + new AppConfigEntry(new Entry(Config::MATTERBRIDGE_BINARY, ValueType::STRING, '', definition: 'Path to the matterbridge binary file')), + new AppConfigEntry(new Entry(Config::MATTERBRIDGE_BOT_PASSWORD, ValueType::STRING, '', definition: 'Automatically generated password of the matterbridge bot user profile', flags: IAppConfig::FLAG_SENSITIVE)), + new AppConfigEntry( + new Entry(Config::DEFAULT_GROUP_NOTIFICATION, ValueType::INT, Participant::NOTIFY_ALWAYS, definition: 'Default notification level for group conversations, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#participant-notification-levels' . PHP_EOL . '(Default changed from 2 (mentions) to 1 (always) in Nextcloud 33 for new installations)'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::CHAT_STYLE, ValueType::STRING, UserPreference::CHAT_STYLE_SPLIT, definition: 'Default chat style when not overwritten by the user'), + enumType: UserPreference::CHAT_STYLE_SPLIT . '|' . UserPreference::CHAT_STYLE_UNIFIED, + ), + new AppConfigEntry( + new Entry(Config::CONVERSATIONS_LIST_STYLE, ValueType::STRING, UserPreference::CONVERSATION_LIST_STYLE_TWO_LINES, definition: 'Default conversation list style when not overwritten by the user'), + enumType: UserPreference::CONVERSATION_LIST_STYLE_TWO_LINES . '|' . UserPreference::CONVERSATION_LIST_STYLE_COMPACT, + ), + new AppConfigEntry( + new Entry(Config::HIDE_SIGNALING_WARNING, ValueType::BOOL, false, definition: 'Flag that allows to suppress the warning that an HPB should be configured'), + adminSetting: true, + ), + new AppConfigEntry(new Entry(Config::CHANGELOG, ValueType::BOOL, true, definition: 'Whether the changelog conversation is updated with new features on major releases')), + new AppConfigEntry(new Entry(Config::SESSION_PING_LIMIT, ValueType::INT, 200, definition: 'Number of sessions the HPB can ping in a single request', rename: 'session-ping-limit')), + new AppConfigEntry( + new Entry(Config::HOSTED_SIGNALING_SERVER_ACCOUNT, ValueType::ARRAY, [], definition: 'Account information of the hosted signaling server', flags: IAppConfig::FLAG_SENSITIVE, rename: 'hosted-signaling-server-account'), + adminSetting: true, + arrayType: 'array', + ), + new AppConfigEntry(new Entry(Config::HOSTED_SIGNALING_SERVER_NONCE, ValueType::STRING, '', definition: 'Temporary nonce while configuring the hosted signaling server', flags: IAppConfig::FLAG_SENSITIVE, rename: 'hosted-signaling-server-nonce')), + new AppConfigEntry(new Entry(Config::HOSTED_SIGNALING_SERVER_ACCOUNT_ID, ValueType::STRING, '', definition: 'Account identifier of the hosted signaling server', flags: IAppConfig::FLAG_SENSITIVE, rename: 'hosted-signaling-server-account-id')), + new AppConfigEntry( + new Entry(Config::SIP_BRIDGE_DIALIN_INFO, ValueType::STRING, '', definition: 'Additional information added in the SIP dial-in invitation mail and sidebar', flags: IAppConfig::FLAG_SENSITIVE), + hash: true, + adminSetting: true, + ), + new AppConfigEntry(new Entry(Config::SIP_DIALIN_DEFAULT, ValueType::INT, 0, definition: 'Default value of SIP dial-in when creating new conversations')), + new AppConfigEntry( + new Entry(Config::SIP_BRIDGE_DIALOUT_ANONYMOUS, ValueType::BOOL, false, definition: 'When set to true, all out-going SIP calls will be anonymous calls (CLIR)'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::SIP_BRIDGE_DIALOUT_NUMBER, ValueType::STRING, '', definition: 'Fallback phone number that is used as caller for out-going calls when the user has no phone-number configured (has to be E164 `+49123456789` formatted)'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::SIP_BRIDGE_DIALOUT_PREFIX, ValueType::STRING, '+', definition: 'Only used for direct dial-in from older SIP bridges, which send phone numbers without this prefix', deprecated: true), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::SIP_BRIDGE_SHARED_SECRET, ValueType::STRING, '', definition: 'Shared secret allowing the SIP bridge to authenticate on the Nextcloud server', flags: IAppConfig::FLAG_SENSITIVE), + hash: true, + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::SIP_BRIDGE_DIALOUT, ValueType::BOOL, false, definition: 'SIP dial-out is allowed when a SIP bridge is configured', rename: 'sip_dialout'), + adminSetting: true, + ), + new AppConfigEntry( + new Entry(Config::SIGNALING_TICKET_SECRET, ValueType::STRING, '', definition: 'Secret used to secure the signaling tickets for guests (255 character random string)', flags: IAppConfig::FLAG_SENSITIVE), + hash: true, + ), + new AppConfigEntry( + new Entry(Config::SIGNALING_TOKEN_ALG, ValueType::STRING, 'ES256', definition: 'Algorithm for the signaling tickets'), + hash: true, + enumType: 'ES256|ES384|RS256|RS384|RS512|EdDSA', + ), + new AppConfigEntry(new Entry(Config::SIGNALING_TOKEN_PRIVKEY, ValueType::STRING, '', definition: 'Private key for the signaling ticket creation by the server', flags: IAppConfig::FLAG_SENSITIVE)), + new AppConfigEntry( + new Entry(Config::SIGNALING_TOKEN_PUBKEY, ValueType::STRING, '', definition: 'Public key for the signaling ticket creation by the server'), + hash: true, + occCommands: [SignalingVerifyKeys::class], + ), + new AppConfigEntry( + new Entry(Config::SIGNALING_SERVERS, ValueType::ARRAY, ['servers' => [],'secret' => ''], definition: 'List of signaling servers, should be configured via the web interface or the OCC commands', flags: IAppConfig::FLAG_SENSITIVE), + hash: true, + adminSetting: true, + occCommands: [SignalingAdd::class, SignalingDelete::class], + arrayType: 'array{servers: list, secret: string}', + ), + new AppConfigEntry( + new Entry(Config::SIGNALING_SERVERS_SECRET, ValueType::STRING, '', definition: 'Shared secret between Nextcloud and the signaling servers, should be configured via the web interface or the OCC commands', flags: IAppConfig::FLAG_SENSITIVE), + occCommands: [SignalingAdd::class, SignalingDelete::class], + ), ]; } + #[\Override] + public function getAppConfigs(): array { + return array_map(static fn (AppConfigEntry $config): Entry => $config->entry, $this->getTalkAppConfigs()); + } + #[\Override] public function getUserConfigs(): array { return [ - new Entry(UserPreference::PLAY_SOUNDS, ValueType::BOOL, true), - new Entry(UserPreference::CHAT_STYLE, ValueType::STRING, UserPreference::CHAT_STYLE_SPLIT), - new Entry(UserPreference::CONVERSATIONS_UNARCHIVE, ValueType::STRING, UserPreference::CONVERSATIONS_UNARCHIVE_NEVER), - new Entry(UserPreference::CONVERSATIONS_TAGS_SHOW_UNREAD, ValueType::STRING, UserPreference::CONVERSATIONS_TAGS_SHOW_UNREAD_ALWAYS), + new Entry(UserPreference::PLAY_SOUNDS, ValueType::BOOL, true, definition: 'Whether sounds are played when participants join or leave a call'), + new Entry(UserPreference::CHAT_STYLE, ValueType::STRING, UserPreference::CHAT_STYLE_SPLIT, definition: 'Chat style of the user, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#chat-style'), + new Entry(UserPreference::CONVERSATIONS_UNARCHIVE, ValueType::STRING, UserPreference::CONVERSATIONS_UNARCHIVE_NEVER, definition: 'When archived conversations are unarchived automatically, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#conversations-unarchive-mode'), + new Entry(UserPreference::CONVERSATIONS_TAGS_SHOW_UNREAD, ValueType::STRING, UserPreference::CONVERSATIONS_TAGS_SHOW_UNREAD_ALWAYS, definition: 'Which unread conversations are shown in collapsed tag sections, see https://github.com/nextcloud/spreed/blob/main/docs/constants.md#conversation-tags-show-unread-mode'), ]; } } diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index adc6da24f72..8d6492b29d4 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -14,6 +14,7 @@ use OCA\Talk\Authenticator; use OCA\Talk\Capabilities; use OCA\Talk\Config; +use OCA\Talk\ConfigLexicon; use OCA\Talk\Events\AAttendeeRemovedEvent; use OCA\Talk\Events\BeforeRoomsFetchEvent; use OCA\Talk\Events\RoomExtendedEvent; @@ -160,6 +161,7 @@ public function __construct( private readonly AvatarService $avatarService, private readonly Preloader $sharePreloader, private readonly IConfig $config, + private readonly ConfigLexicon $configLexicon, private readonly IAppConfig $appConfig, private readonly Config $talkConfig, private readonly ICloudIdManager $cloudIdManager, @@ -190,37 +192,17 @@ protected function getTalkHashHeader(): array { $values = [ $this->config->getSystemValueString('version'), $this->appConfig->getAppValueString('installed_version'), - json_encode($this->appConfig->getAppValueArray(Config::STUN_SERVERS)), - json_encode($this->appConfig->getAppValueArray(Config::TURN_SERVERS)), - json_encode($this->appConfig->getAppValueArray(Config::SIGNALING_SERVERS)), - $this->appConfig->getAppValueString(Config::SIGNALING_TICKET_SECRET), - $this->appConfig->getAppValueString(Config::SIGNALING_TOKEN_ALG), - $this->appConfig->getAppValueString(Config::SIGNALING_TOKEN_PUBKEY), - $this->appConfig->getAppValueBool(Config::CALL_RECORDING), - json_encode($this->appConfig->getAppValueArray(Config::RECORDING_SERVERS)), - json_encode($this->appConfig->getAppValueArray(Config::ALLOWED_GROUPS_TALK)), - $this->appConfig->getAppValueInt(Config::ALLOWED_START_CALLS), - json_encode($this->appConfig->getAppValueArray(Config::ALLOWED_START_CALLS_GROUPS)), - json_encode($this->appConfig->getAppValueArray(Config::ALLOWED_GROUPS_CONVERSATIONS)), - $this->appConfig->getAppValueInt(Config::DEFAULT_ROOM_PERMISSIONS), - $this->appConfig->getAppValueBool(Config::BREAKOUT_ROOMS_ENABLED), - json_encode($this->appConfig->getAppValueArray(Config::ALLOWED_GROUPS_SIP)), - $this->appConfig->getAppValueBool(Config::MATTERBRIDGE_ENABLED), - $this->appConfig->getAppValueBool(Config::FEDERATION_ENABLED), - $this->appConfig->getAppValueInt(Config::RECORDING_CONSENT), - $this->appConfig->getAppValueString(Config::SIP_BRIDGE_DIALIN_INFO), - $this->appConfig->getAppValueString(Config::SIP_BRIDGE_SHARED_SECRET), - $this->appConfig->getAppValueBool(Config::CALL_RECORDING_TRANSCRIPTION), - $this->appConfig->getAppValueBool(Config::CALL_RECORDING_SUMMARY), $this->config->getAppValue('theming', 'cachebuster', '1'), $this->config->getUserValue($this->userId, 'theming', 'userCacheBuster', '0'), - $this->appConfig->getAppValueInt(Config::FEATURE_HINTS_HIDDEN), - json_encode($this->appConfig->getAppValueArray(Config::ALLOWED_GROUPS_FEDERATION)), - $this->appConfig->getAppValueBool(Config::FEDERATION_INCOMING_ENABLED), - $this->appConfig->getAppValueBool(Config::FEDERATION_OUTGOING_ENABLED), - $this->appConfig->getAppValueBool(Config::FEDERATION_ONLY_TRUSTED_SERVERS), ]; + foreach ($this->configLexicon->getTalkAppConfigs() as $config) { + $key = $config->entry->getKey(); + if ($config->hash && $key !== Config::EXPERIMENTS_USERS && $key !== Config::EXPERIMENTS_GUESTS) { + $values[] = $this->config->getAppValue('spreed', $key); + } + } + if ($this->userId !== null) { $values[] = $this->appConfig->getAppValueInt(Config::EXPERIMENTS_USERS); $values[] = $this->config->getUserValue($this->userId, 'spreed', UserPreference::ATTACHMENT_FOLDER); diff --git a/tests/php/Command/Developer/UpdateDocsTest.php b/tests/php/Command/Developer/UpdateDocsTest.php new file mode 100644 index 00000000000..b762cdc8eb6 --- /dev/null +++ b/tests/php/Command/Developer/UpdateDocsTest.php @@ -0,0 +1,208 @@ +command = new UpdateDocs( + $this->createMock(IConfig::class), + $this->createMock(IAppManager::class), + new ConfigLexicon(), + ); + } + + public function testAppConfigKeysAreUnique(): void { + $keys = array_map(static fn (AppConfigEntry $config): string => $config->entry->getKey(), (new ConfigLexicon())->getTalkAppConfigs()); + $this->assertSame(array_unique($keys), $keys); + } + + public function testArrayTypeIsSetForArrayEntries(): void { + foreach ((new ConfigLexicon())->getTalkAppConfigs() as $config) { + $this->assertSame($config->entry->getValueType() === ValueType::ARRAY, $config->arrayType !== null, $config->entry->getKey()); + } + } + + public function testArrayTypesAreValid(): void { + foreach ((new ConfigLexicon())->getTalkAppConfigs() as $config) { + if ($config->arrayType !== null) { + $this->assertNull($this->validateArrayType($config->arrayType), $config->entry->getKey()); + } + } + } + + public static function dataValidateArrayType(): array { + return [ + ['list', null], + ['array', null], + ['array{servers: list, secret: string}', null], + ['list\' at offset 11 on line 1'], + ['lsit', 'Unknown type "lsit"'], + ['array{secret: strng}', 'Unknown type "strng"'], + ]; + } + + #[DataProvider('dataValidateArrayType')] + public function testValidateArrayType(string $type, ?string $expected): void { + $this->assertSame($expected, $this->validateArrayType($type)); + } + + /** + * Parses the psalm style type and returns an error message when it is invalid or uses unknown types + */ + private function validateArrayType(string $type): ?string { + $config = new ParserConfig([]); + $lexer = new Lexer($config); + $tokens = new TokenIterator($lexer->tokenize($type)); + try { + $node = (new TypeParser($config, new ConstExprParser($config)))->parse($tokens); + $tokens->consumeTokenType(Lexer::TOKEN_END); + } catch (ParserException $e) { + return $e->getMessage(); + } + + $visitor = new class extends AbstractNodeVisitor { + public array $names = []; + private array $shapeKeys = []; + + #[\Override] + public function enterNode(Node $node) { + if ($node instanceof ArrayShapeItemNode && $node->keyName instanceof IdentifierTypeNode) { + $this->shapeKeys[spl_object_id($node->keyName)] = true; + } elseif ($node instanceof IdentifierTypeNode && !isset($this->shapeKeys[spl_object_id($node)])) { + $this->names[] = $node->name; + } + return null; + } + }; + (new NodeTraverser([$visitor]))->traverse([$node]); + + foreach ($visitor->names as $name) { + if (!\in_array($name, self::KNOWN_TYPES, true)) { + return 'Unknown type "' . $name . '"'; + } + } + return null; + } + + public function testEnumTypesAreValid(): void { + foreach ((new ConfigLexicon())->getTalkAppConfigs() as $config) { + if ($config->enumType === null) { + continue; + } + + $key = $config->entry->getKey(); + $valueType = $config->entry->getValueType(); + $this->assertContains($valueType, [ValueType::STRING, ValueType::INT], $key); + + $values = explode('|', $config->enumType); + $this->assertNotContains('', $values, $key); + $this->assertSame(array_values(array_unique($values)), $values, $key); + if ($valueType === ValueType::INT) { + foreach ($values as $value) { + $this->assertMatchesRegularExpression('/^-?\d+$/', $value, $key); + } + } + $this->assertContains($config->entry->getDefault(Preset::NONE), $values, $key); + } + } + + public function testOccCommandsAreRegistered(): void { + $info = file_get_contents(__DIR__ . '/../../../../appinfo/info.xml'); + foreach ((new ConfigLexicon())->getTalkAppConfigs() as $config) { + foreach ($config->occCommands as $command) { + $this->assertStringContainsString('' . $command . '', $info); + } + } + } + + public static function dataGetAppConfigRow(): array { + return [ + 'bool with hash and UI' => [ + new AppConfigEntry(new Entry(Config::CALL_RECORDING_SUMMARY, ValueType::BOOL, true, definition: 'Summarize'), hash: true, adminSetting: true), + '| `call_recording_summary` | bool | `true` | Yes | 🖌️ | Summarize |', + ], + 'array with UI and OCC' => [ + new AppConfigEntry(new Entry(Config::STUN_SERVERS, ValueType::ARRAY, ['a|b'], definition: "Line 1\nLine 2"), hash: true, adminSetting: true, occCommands: [StunAdd::class]), + '| `stun_servers` | array | `["a\|b"]` | Yes | 🖌️💻 | Line 1
Line 2 |', + ], + 'array with array type' => [ + new AppConfigEntry(new Entry(Config::ALLOWED_GROUPS_TALK, ValueType::ARRAY, [], definition: 'Groups'), arrayType: 'list'), + '| `allowed_groups` | `list` | `[]` | No | | Groups |', + ], + 'multi-line string default' => [ + new AppConfigEntry(new Entry(Config::CALL_RECORDING_SUMMARY_PROMPT, ValueType::STRING, "a\nb", definition: 'Prompt')), + '| `call_recording_summary_prompt` | string | See `lib/ConfigLexicon.php` | No | | Prompt |', + ], + 'string enum' => [ + new AppConfigEntry(new Entry(Config::CHAT_STYLE, ValueType::STRING, 'split', definition: 'Style'), enumType: 'split|unified'), + '| `chat_style` | string
`split` or `unified` | `split` | No | | Style |', + ], + 'int enum with more values' => [ + new AppConfigEntry(new Entry(Config::ALLOWED_START_CALLS, ValueType::INT, 0, definition: 'Start'), enumType: '0|1|2|3'), + '| `start_calls` | int
`0`, `1`, `2` or `3` | `0` | No | | Start |', + ], + 'single value enum' => [ + new AppConfigEntry(new Entry(Config::SIGNALING_TOKEN_ALG, ValueType::STRING, 'ES256', definition: 'Alg'), enumType: 'ES256'), + '| `signaling_token_alg` | string
`ES256` | `ES256` | No | | Alg |', + ], + 'deprecated' => [ + new AppConfigEntry(new Entry(Config::SIP_BRIDGE_DIALOUT_PREFIX, ValueType::STRING, '+', definition: 'Old', deprecated: true)), + '| `sip_bridge_dialout_prefix` | string | `+` | No | | Deprecated: Old |', + ], + 'empty string default' => [ + new AppConfigEntry(new Entry(Config::SAMPLES_DIRECTORY, ValueType::STRING, '', definition: 'Samples')), + "| `samples_directory` | string | `''` | No | | Samples |", + ], + ]; + } + + #[DataProvider('dataGetAppConfigRow')] + public function testGetAppConfigRow(AppConfigEntry $config, string $expected): void { + $this->assertSame($expected, $this->command->getAppConfigRow($config)); + } + + public function testReplaceAppConfigTable(): void { + $settings = "# Settings\n\n## App configuration\n\nLegend\n\n| Key | Old |\n|---|---|\n| `old` | row |\n\n## Experiments\n\nKeep\n"; + $result = $this->command->replaceAppConfigTable($settings); + + $this->assertStringStartsWith("# Settings\n\n## App configuration\n\nLegend\n\n| Key | Type | Default | Hash | Option | Description |\n", $result); + $this->assertStringEndsWith("|\n\n## Experiments\n\nKeep\n", $result); + $this->assertStringNotContainsString('`old`', $result); + $this->assertStringContainsString('| `stun_servers` | `list` |', $result); + } +} diff --git a/vendor-bin/phpunit/composer.json b/vendor-bin/phpunit/composer.json index 4b12a187233..680a00a0889 100644 --- a/vendor-bin/phpunit/composer.json +++ b/vendor-bin/phpunit/composer.json @@ -6,6 +6,7 @@ "sort-packages": true }, "require-dev": { + "phpstan/phpdoc-parser": "^2.3", "phpunit/phpunit": "^11.5" } } diff --git a/vendor-bin/phpunit/composer.lock b/vendor-bin/phpunit/composer.lock index 5a11b170c78..a72b0581c0f 100644 --- a/vendor-bin/phpunit/composer.lock +++ b/vendor-bin/phpunit/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "43780a3c77cdd143acf00e34f8ac4b1c", + "content-hash": "a4ced24c5dd02c0c278a886a7304e149", "packages": [], "packages-dev": [ { @@ -242,6 +242,53 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.11", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "c9fb3e1560c6a33eca59c9a2fcb4d9925445a0a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c9fb3e1560c6a33eca59c9a2fcb4d9925445a0a4", + "reference": "c9fb3e1560c6a33eca59c9a2fcb4d9925445a0a4", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.11" + }, + "time": "2026-10-28T10:46:50+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "11.0.12",