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",