From 527da719635df29bdcd81e165431108792cb7e7e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 7 Jul 2026 10:53:06 +0200 Subject: [PATCH 01/17] fix(sip): Handle direct incoming calls as phone participants Signed-off-by: Joas Schilling --- lib/Controller/RoomController.php | 10 +++++++++- lib/Service/ParticipantService.php | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 187b5657e41..ee7fd5f05a0 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2369,6 +2369,7 @@ public function directDialIn(string $phoneNumber, string $caller): DataResponse $caller = trim($caller); // TODO: Use later and get name from addressbook? $cleanedCaller = $this->phoneNumberUtil->convertToStandardFormat($caller); + $inBoundPhoneNumber = $this->phoneNumberUtil->convertToStandardFormat($caller); $user = $this->userManager->get($entity->getActorId()); try { $room = $this->roomService->createConversation( @@ -2384,7 +2385,14 @@ public function directDialIn(string $phoneNumber, string $caller): DataResponse return new DataResponse(null, Http::STATUS_INTERNAL_SERVER_ERROR); } - $participant = $this->participantService->joinRoomAsNewGuest($this->roomService, $room, '', true, displayName: $caller); + $participant = $this->participantService->joinRoomAsNewGuest( + $this->roomService, + $room, + '', + true, + displayName: $caller, + phoneNumber: $inBoundPhoneNumber, + ); return new DataResponse($this->formatRoom($room, $participant)); } diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index 9a880fecf8c..3e40b74b0ef 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -483,7 +483,7 @@ public function joinRoomAsFederatedUser(Room $room, string $actorType, string $a * @throws InvalidPasswordException * @throws UnauthorizedException */ - public function joinRoomAsNewGuest(RoomService $roomService, Room $room, string $password, bool $passedPasswordProtection = false, ?Participant $previousParticipant = null, ?string $displayName = null): Participant { + public function joinRoomAsNewGuest(RoomService $roomService, Room $room, string $password, bool $passedPasswordProtection = false, ?Participant $previousParticipant = null, ?string $displayName = null, ?string $phoneNumber = null): Participant { $event = new BeforeGuestJoinedRoomEvent($room, $password, $passedPasswordProtection); $this->dispatcher->dispatchTyped($event); @@ -516,6 +516,10 @@ public function joinRoomAsNewGuest(RoomService $roomService, Room $room, string if ($displayName !== null && $displayName !== '') { $attendee->setDisplayName($displayName); } + if ($phoneNumber !== null && $phoneNumber !== '') { + $attendee->setPhoneNumber($phoneNumber); + $attendee->setActorType(Attendee::ACTOR_PHONES); + } $this->attendeeMapper->insert($attendee); From 8a9f5108b2465a6451890b3778399038b356d59c Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 9 Jul 2026 10:11:45 +0200 Subject: [PATCH 02/17] fix(sip): Allow adding phone numbers with leading + Signed-off-by: Joas Schilling --- lib/Service/PhoneNumberValidation.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/Service/PhoneNumberValidation.php b/lib/Service/PhoneNumberValidation.php index 69fb017cb5f..e119ec3afc7 100644 --- a/lib/Service/PhoneNumberValidation.php +++ b/lib/Service/PhoneNumberValidation.php @@ -23,9 +23,7 @@ public function __construct( * Validate input as a phone number * * - Local number: allow - * - International number - * a. If valid, strip + and allow - * b. If invalid, throw + * - International number: If invalid, throw * @throws \InvalidArgumentException When the number is invalid */ public function validateNumber(string $phoneNumber): string { @@ -46,10 +44,6 @@ public function validateNumber(string $phoneNumber): string { throw new \InvalidArgumentException(); } - if (str_starts_with($standardPhoneNumber, '+')) { - return substr($standardPhoneNumber, 1); - } - - throw new \InvalidArgumentException(); + return $standardPhoneNumber; } } From f7a452cd50f07eb6869e69068fd43bd3e7c34c4b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 9 Jul 2026 10:22:02 +0200 Subject: [PATCH 03/17] fix(sip): Disable setupcheck complaining about leading + Signed-off-by: Joas Schilling --- lib/SetupCheck/SIPConfiguration.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/SetupCheck/SIPConfiguration.php b/lib/SetupCheck/SIPConfiguration.php index 5eed31f6b3e..8acfa9c3540 100644 --- a/lib/SetupCheck/SIPConfiguration.php +++ b/lib/SetupCheck/SIPConfiguration.php @@ -46,9 +46,11 @@ public function run(): SetupResult { $query->select('phone_number') ->from('talk_phone_numbers') ->where($query->expr()->like('phone_number', $query->createNamedParameter( - $this->connection->escapeLikeParameter('+') . '%' - ))) - ->orWhere($query->expr()->like('phone_number', $query->createNamedParameter( + // TODO: Temporary disabled while we migrate to leading `+` for international numbers + // TODO: Either to be deleted later or should complain for numbers without it + // $this->connection->escapeLikeParameter('+') . '%' + // ))) + // ->orWhere($query->expr()->like('phone_number', $query->createNamedParameter( $this->connection->escapeLikeParameter('0') . '%' ))); From 8aa50fbfc5ba6014256953f508581fa1bb26ded9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 18 Sep 2026 18:04:48 +0200 Subject: [PATCH 04/17] fix(sip): Migrate phone numbers to include prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- .../Version24000Date20260918142246.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 lib/Migration/Version24000Date20260918142246.php diff --git a/lib/Migration/Version24000Date20260918142246.php b/lib/Migration/Version24000Date20260918142246.php new file mode 100644 index 00000000000..632ee39ed4f --- /dev/null +++ b/lib/Migration/Version24000Date20260918142246.php @@ -0,0 +1,55 @@ +appConfig->getAppValueString('sip_bridge_dialout_prefix', '+'); + + if ($dialoutPrefix === '') { + return; + } + + $update = $this->connection->getQueryBuilder(); + $update->update('talk_phone_numbers') + ->set('phone_number', $update->func()->concat( + $update->createNamedParameter($dialoutPrefix, IQueryBuilder::PARAM_STR), + 'phone_number', + )) + ->where($update->expr()->gte( + $update->func()->charLength('phone_number'), + $update->createNamedParameter(6, IQueryBuilder::PARAM_INT)) + ); + + $update->executeStatement(); + } +} From 8590729d232a715ce44a34f766fb0c3ffb7e26a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 18 Sep 2026 18:05:15 +0200 Subject: [PATCH 05/17] fix(sip): Add fallback for old sip bridges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- lib/Controller/RoomController.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index ee7fd5f05a0..d93ff9ea2f6 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2363,8 +2363,23 @@ public function directDialIn(string $phoneNumber, string $caller): DataResponse try { $entity = $this->phoneService->getAccountToCallForPhoneNumber($phoneNumber); } catch (DoesNotExistException) { - $this->logger->info('No account found for direct dial-in with number: ' . $phoneNumber); - return new DataResponse(null, Http::STATUS_NOT_FOUND); + $dialoutPrefix = $this->appConfig->getAppValueString('sip_bridge_dialout_prefix', '+'); + + if ($dialoutPrefix === '') { + $this->logger->info('No account found for direct dial-in with number: ' . $phoneNumber); + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + + try { + // Try to find the number with added dialout prefix as a fallback for old sip-bridges + // TODO: Log as error in 26, remove fallback and dialout prefix with 27 + $entity = $this->phoneService->getAccountToCallForPhoneNumber($dialoutPrefix . $phoneNumber); + } catch (DoesNotExistException) { + $this->logger->info('No account found for direct dial-in with number: ' . $phoneNumber); + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + + $this->logger->warning('Found account for direct dial-in only by adding the dial-out prefix. Please adjust the mapping for number: ' . $phoneNumber); } $caller = trim($caller); From 86a959eca10e0a8824a71760f638f6e435dcd7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 18 Sep 2026 18:05:30 +0200 Subject: [PATCH 06/17] fix(sip): Use incoming caller without conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- lib/Controller/RoomController.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index d93ff9ea2f6..667738fd7cf 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2383,8 +2383,6 @@ public function directDialIn(string $phoneNumber, string $caller): DataResponse } $caller = trim($caller); - // TODO: Use later and get name from addressbook? $cleanedCaller = $this->phoneNumberUtil->convertToStandardFormat($caller); - $inBoundPhoneNumber = $this->phoneNumberUtil->convertToStandardFormat($caller); $user = $this->userManager->get($entity->getActorId()); try { $room = $this->roomService->createConversation( @@ -2406,7 +2404,7 @@ public function directDialIn(string $phoneNumber, string $caller): DataResponse '', true, displayName: $caller, - phoneNumber: $inBoundPhoneNumber, + phoneNumber: $caller, ); return new DataResponse($this->formatRoom($room, $participant)); } From b5a37316aaaa6245dc20b04c4e41b90cd33cad4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 21 Sep 2026 12:14:04 +0200 Subject: [PATCH 07/17] fix(sip): Assign phone numbers as provided MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- lib/Command/PhoneNumber/AddPhoneNumber.php | 9 ++++- .../PhoneNumber/ImportPhoneNumbers.php | 4 +- lib/Service/PhoneNumberValidation.php | 12 ++++-- .../features/command/phone-number.feature | 40 +++++++++---------- tests/psalm-baseline.xml | 1 + 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/lib/Command/PhoneNumber/AddPhoneNumber.php b/lib/Command/PhoneNumber/AddPhoneNumber.php index 8785684ecb3..02ed6bf03a9 100644 --- a/lib/Command/PhoneNumber/AddPhoneNumber.php +++ b/lib/Command/PhoneNumber/AddPhoneNumber.php @@ -66,8 +66,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $userId = $user->getUID(); + $phoneNumber = $this->phoneNumberValidation->cleanNumber($phoneNumber); + + if ($phoneNumber === '') { + $output->writeln('Phone number is invalid.'); + return self::FAILURE; + } + try { - $phoneNumber = $this->phoneNumberValidation->validateNumber($phoneNumber); + $this->phoneNumberValidation->validateNumber($phoneNumber); } catch (\InvalidArgumentException) { $output->writeln('Not a valid phone number ' . $phoneNumber . '. The format is invalid.'); return self::FAILURE; diff --git a/lib/Command/PhoneNumber/ImportPhoneNumbers.php b/lib/Command/PhoneNumber/ImportPhoneNumbers.php index 32c46978369..650a404cc23 100644 --- a/lib/Command/PhoneNumber/ImportPhoneNumbers.php +++ b/lib/Command/PhoneNumber/ImportPhoneNumbers.php @@ -72,8 +72,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } + $row[0] = $this->phoneNumberValidation->cleanNumber($row[0]); + try { - $row[0] = $this->phoneNumberValidation->validateNumber($row[0]); + $this->phoneNumberValidation->validateNumber($row[0]); } catch (\InvalidArgumentException) { $output->writeln('Not a valid phone number ' . $row[0] . '. The format is invalid.'); return self::FAILURE; diff --git a/lib/Service/PhoneNumberValidation.php b/lib/Service/PhoneNumberValidation.php index e119ec3afc7..69bf5af9f3a 100644 --- a/lib/Service/PhoneNumberValidation.php +++ b/lib/Service/PhoneNumberValidation.php @@ -26,8 +26,7 @@ public function __construct( * - International number: If invalid, throw * @throws \InvalidArgumentException When the number is invalid */ - public function validateNumber(string $phoneNumber): string { - + public function validateNumber(string $phoneNumber) { if ( // Not an internation number !str_starts_with($phoneNumber, '0') @@ -43,7 +42,14 @@ public function validateNumber(string $phoneNumber): string { if ($standardPhoneNumber === null) { throw new \InvalidArgumentException(); } + } - return $standardPhoneNumber; + /** + * Removes any character that is not a number or "+" + * + * @param string $phoneNumber Phonenumber to clean + */ + public function cleanNumber(string $phoneNumber): string { + return preg_replace('/[^0-9+]/', '', $phoneNumber) ?? ''; } } diff --git a/tests/integration/features/command/phone-number.feature b/tests/integration/features/command/phone-number.feature index af7c52fdd8f..9515a8895ed 100644 --- a/tests/integration/features/command/phone-number.feature +++ b/tests/integration/features/command/phone-number.feature @@ -6,7 +6,7 @@ Feature: command/phone-number Scenario: Configuring a phone number Given invoking occ with "talk:phone-number:add abc participant1" Then the command failed with exit code 1 - And the command output contains the text "Not a valid phone number abc. The format is invalid." + And the command output contains the text "Phone number is invalid." Given invoking occ with "talk:phone-number:add +49-160-123-12-12 participant3" Then the command failed with exit code 1 @@ -14,19 +14,19 @@ Feature: command/phone-number Given invoking occ with "talk:phone-number:add +49-160-123-12-12 participant1" Then the command failed with exit code 0 - And the command output contains the text "Phone number 491601231212 is now assigned to participant1" + And the command output contains the text "Phone number +491601231212 is now assigned to participant1" - Given invoking occ with "talk:phone-number:find --phone 491601231212" + Given invoking occ with "talk:phone-number:find --phone +491601231212" Then the command failed with exit code 0 - And the command output contains the text "Phone number 491601231212 is assigned to participant1" + And the command output contains the text "Phone number +491601231212 is assigned to participant1" - Given invoking occ with "talk:phone-number:find --phone 49160123121234" + Given invoking occ with "talk:phone-number:find --phone +49160123121234" Then the command failed with exit code 1 - And the command output contains the text "Phone number 49160123121234 could not be found" + And the command output contains the text "Phone number +49160123121234 could not be found" Given invoking occ with "talk:phone-number:find --user participant1" Then the command failed with exit code 0 - And the command output contains the text "participant1 has phone number 491601231212 assigned" + And the command output contains the text "participant1 has phone number +491601231212 assigned" Given invoking occ with "talk:phone-number:find --user participant2" Then the command failed with exit code 1 @@ -34,13 +34,13 @@ Feature: command/phone-number Given invoking occ with "talk:phone-number:add +49-160-123-1213 participant1" Then the command failed with exit code 0 - And the command output contains the text "Phone number 491601231213 is now assigned to participant1" + And the command output contains the text "Phone number +491601231213 is now assigned to participant1" Given invoking occ with "talk:phone-number:find --user participant1" Then the command failed with exit code 0 And the command output contains the text "participant1 has the following phone numbers assigned:" - And the command output contains the text "- 491601231212" - And the command output contains the text "- 491601231213" + And the command output contains the text "- +491601231212" + And the command output contains the text "- +491601231213" Given invoking occ with "talk:phone-number:add +49-160-123-1212 participant2" Then the command failed with exit code 1 @@ -48,7 +48,7 @@ Feature: command/phone-number Given invoking occ with "talk:phone-number:add --force '+49-160-123-12-12' participant2" Then the command failed with exit code 0 - And the command output contains the text "Phone number 491601231212 is now assigned to participant2" + And the command output contains the text "Phone number +491601231212 is now assigned to participant2" And the command output contains the text "Was assigned to participant1" Given invoking occ with "talk:phone-number:add 23 participant2" @@ -58,14 +58,14 @@ Feature: command/phone-number Given invoking occ with "talk:phone-number:find --user participant2" Then the command failed with exit code 0 And the command output contains the text "participant2 has the following phone numbers assigned:" - And the command output contains the text "- 491601231212" + And the command output contains the text "- +491601231212" And the command output contains the text "- 23" Given invoking occ with "talk:phone-number:find --user participant1" Then the command failed with exit code 0 - And the command output contains the text "participant1 has phone number 491601231213 assigned" + And the command output contains the text "participant1 has phone number +491601231213 assigned" - Given invoking occ with "talk:phone-number:remove 491601231213" + Given invoking occ with "talk:phone-number:remove +491601231213" Then the command failed with exit code 0 Given invoking occ with "talk:phone-number:find --user participant1" @@ -89,19 +89,19 @@ Feature: command/phone-number # Valid German number Given invoking occ with "talk:phone-number:add 004971112347 participant1" Then the command failed with exit code 0 - And the command output contains the text "Phone number 4971112347 is now assigned to participant1" + And the command output contains the text "Phone number 004971112347 is now assigned to participant1" Given invoking occ with "talk:phone-number:add +4971112346 participant1" Then the command failed with exit code 0 - And the command output contains the text "Phone number 4971112346 is now assigned to participant1" + And the command output contains the text "Phone number +4971112346 is now assigned to participant1" - Given invoking occ with "talk:phone-number:add 4971112345 participant1" + Given invoking occ with "talk:phone-number:add +4971112345 participant1" Then the command failed with exit code 0 - And the command output contains the text "Phone number 4971112345 is now assigned to participant1" + And the command output contains the text "Phone number +4971112345 is now assigned to participant1" Given invoking occ with "talk:phone-number:add 071112348 participant1" Then the command failed with exit code 0 - And the command output contains the text "Phone number 4971112348 is now assigned to participant1" + And the command output contains the text "Phone number 071112348 is now assigned to participant1" # 01122 is not a valid prefix in Germany Given invoking occ with "talk:phone-number:add 011223344 participant1" @@ -126,4 +126,4 @@ Feature: command/phone-number # Valid US number Given invoking occ with "talk:phone-number:add 00112345678901 participant1" Then the command failed with exit code 0 - And the command output contains the text "Phone number 12345678901 is now assigned to participant1" + And the command output contains the text "Phone number 00112345678901 is now assigned to participant1" diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index 6bee6e2c255..4eea1d07c9c 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -23,6 +23,7 @@ + From 56f32b80c0581fb17357a8551c4f1a430aafd650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 21:38:52 +0200 Subject: [PATCH 08/17] fix: validateNumber has no return type anymore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- lib/Service/PhoneNumberValidation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Service/PhoneNumberValidation.php b/lib/Service/PhoneNumberValidation.php index 69bf5af9f3a..d8748b00e44 100644 --- a/lib/Service/PhoneNumberValidation.php +++ b/lib/Service/PhoneNumberValidation.php @@ -26,14 +26,14 @@ public function __construct( * - International number: If invalid, throw * @throws \InvalidArgumentException When the number is invalid */ - public function validateNumber(string $phoneNumber) { + public function validateNumber(string $phoneNumber): void { if ( // Not an internation number !str_starts_with($phoneNumber, '0') // And matches a local number or dial-through && preg_match('/^[0-9]{1,20}$/', $phoneNumber) ) { - return $phoneNumber; + return; } $defaultRegion = $this->config->getSystemValueString('default_phone_region') ?: null; From 163028075501ee05fc89cdb7285996325672bf4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 21:51:59 +0200 Subject: [PATCH 09/17] fix: Prevent unique constraint violation on old numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Marcel Müller --- lib/Migration/Version24000Date20260918142246.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/Migration/Version24000Date20260918142246.php b/lib/Migration/Version24000Date20260918142246.php index 632ee39ed4f..c1fbc6c7b77 100644 --- a/lib/Migration/Version24000Date20260918142246.php +++ b/lib/Migration/Version24000Date20260918142246.php @@ -48,7 +48,14 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ->where($update->expr()->gte( $update->func()->charLength('phone_number'), $update->createNamedParameter(6, IQueryBuilder::PARAM_INT)) - ); + ) + // Numbers added before the leading + was stripped already have it + ->andWhere($update->expr()->notLike('phone_number', $update->createNamedParameter( + $this->connection->escapeLikeParameter('+') . '%' + ))) + ->andWhere($update->expr()->notLike('phone_number', $update->createNamedParameter( + $this->connection->escapeLikeParameter($dialoutPrefix) . '%' + ))); $update->executeStatement(); } From 8b00f59b613c6ad80565f96ccb189aec9267ae3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 22:07:38 +0200 Subject: [PATCH 10/17] fix: Don't use dialout prefix on caller number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Marcel Müller --- lib/Controller/CallController.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/Controller/CallController.php b/lib/Controller/CallController.php index 4105746b07b..e472cd29098 100644 --- a/lib/Controller/CallController.php +++ b/lib/Controller/CallController.php @@ -437,14 +437,12 @@ public function sipDialOut(int $attendeeId): DataResponse { } // No elseif, so we have the fallback to sip_bridge_dialout_number when the caller is no user or doesn't have a number - if ($callerNumber !== false && $this->appConfig->getAppValueString('sip_bridge_dialout_prefix', '+') !== '') { + if ($callerNumber !== false) { $attendee = $this->participant->getAttendee(); if ($attendee->getActorType() === Attendee::ACTOR_USERS) { $numbers = $this->phoneNumberMapper->findByUser($attendee->getActorId()); if (!empty($numbers)) { - $number = array_shift($numbers); - $callerNumber = $this->appConfig->getAppValueString('sip_bridge_dialout_prefix', '+'); - $callerNumber .= $number->getPhoneNumber(); + $callerNumber = array_shift($numbers)->getPhoneNumber(); } } } From 240bda4af6dfd18ee0202d86888a7146b42f4071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 22:29:45 +0200 Subject: [PATCH 11/17] chore: Document deprecation of sip dialout prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Marcel Müller --- src/components/AdminSettings/SIPBridge.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AdminSettings/SIPBridge.vue b/src/components/AdminSettings/SIPBridge.vue index c914624de89..f50e95f3f61 100644 --- a/src/components/AdminSettings/SIPBridge.vue +++ b/src/components/AdminSettings/SIPBridge.vue @@ -63,7 +63,7 @@ :disabled="loading" labelOutside />

- {{ t('spreed', 'Prefix to configured user number for outgoing calls (default is `+`)') }} + {{ t('spreed', 'Deprecated: Only used for direct dial-in from older SIP bridges, which send phone numbers without this prefix (default is `+`)') }}

From 804e84e0304491d223f0495840b7d35678dd0305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 22:42:22 +0200 Subject: [PATCH 12/17] chore: Remove obsolete sip number setupcheck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Marcel Müller --- lib/SetupCheck/SIPConfiguration.php | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/lib/SetupCheck/SIPConfiguration.php b/lib/SetupCheck/SIPConfiguration.php index 8acfa9c3540..9817c32af83 100644 --- a/lib/SetupCheck/SIPConfiguration.php +++ b/lib/SetupCheck/SIPConfiguration.php @@ -9,7 +9,6 @@ namespace OCA\Talk\SetupCheck; use OCA\Talk\Config; -use OCP\IDBConnection; use OCP\IL10N; use OCP\SetupCheck\ISetupCheck; use OCP\SetupCheck\SetupResult; @@ -17,7 +16,6 @@ class SIPConfiguration implements ISetupCheck { public function __construct( protected readonly Config $talkConfig, - protected readonly IDBConnection $connection, protected readonly IL10N $l, ) { } @@ -42,32 +40,6 @@ public function run(): SetupResult { return SetupResult::success($this->l->t('Using the SIP functionality requires a High-performance backend.')); } - $query = $this->connection->getQueryBuilder(); - $query->select('phone_number') - ->from('talk_phone_numbers') - ->where($query->expr()->like('phone_number', $query->createNamedParameter( - // TODO: Temporary disabled while we migrate to leading `+` for international numbers - // TODO: Either to be deleted later or should complain for numbers without it - // $this->connection->escapeLikeParameter('+') . '%' - // ))) - // ->orWhere($query->expr()->like('phone_number', $query->createNamedParameter( - $this->connection->escapeLikeParameter('0') . '%' - ))); - - $result = $query->executeQuery(); - $invalidNumbers = $result->fetchFirstColumn(); - $result->closeCursor(); - - if (!empty($invalidNumbers)) { - $message = $this->l->t("Assigned Talk phone numbers must not start with + or 0. Please remove or update the following numbers:\n{list}"); - $message = str_replace( - '{list}', - implode("\n", $invalidNumbers), - $message - ); - return SetupResult::error($message, 'https://portal.nextcloud.com/article/Nextcloud-Talk/Nextcloud-Talk-Phone/Direct-Dial-in#content-provisioning'); - } - if ($this->talkConfig->getSIPSharedSecret() === '' && $this->talkConfig->getDialInInfo() === '') { return SetupResult::info($this->l->t('No SIP backend configured')); } From cd99ef170033b192f43f78993ea80ebe224ca045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 22:49:01 +0200 Subject: [PATCH 13/17] fix: Adjust migration to catch unique constraint violation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Marcel Müller --- .../Version24000Date20260918142246.php | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/lib/Migration/Version24000Date20260918142246.php b/lib/Migration/Version24000Date20260918142246.php index c1fbc6c7b77..a29681e7f25 100644 --- a/lib/Migration/Version24000Date20260918142246.php +++ b/lib/Migration/Version24000Date20260918142246.php @@ -11,6 +11,7 @@ use Closure; use OCP\AppFramework\Services\IAppConfig; +use OCP\DB\Exception; use OCP\DB\ISchemaWrapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -39,24 +40,40 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array return; } + $query = $this->connection->getQueryBuilder(); + $query->select('id', 'phone_number') + ->from('talk_phone_numbers'); + + $phoneNumbers = []; + $result = $query->executeQuery(); + while ($row = $result->fetchAssociative()) { + $phoneNumbers[(int)$row['id']] = (string)$row['phone_number']; + } + $result->closeCursor(); + $update = $this->connection->getQueryBuilder(); $update->update('talk_phone_numbers') - ->set('phone_number', $update->func()->concat( - $update->createNamedParameter($dialoutPrefix, IQueryBuilder::PARAM_STR), - 'phone_number', - )) - ->where($update->expr()->gte( - $update->func()->charLength('phone_number'), - $update->createNamedParameter(6, IQueryBuilder::PARAM_INT)) - ) - // Numbers added before the leading + was stripped already have it - ->andWhere($update->expr()->notLike('phone_number', $update->createNamedParameter( - $this->connection->escapeLikeParameter('+') . '%' - ))) - ->andWhere($update->expr()->notLike('phone_number', $update->createNamedParameter( - $this->connection->escapeLikeParameter($dialoutPrefix) . '%' - ))); - - $update->executeStatement(); + ->set('phone_number', $update->createParameter('phone_number')) + ->where($update->expr()->eq('id', $update->createParameter('id'))); + + foreach ($phoneNumbers as $id => $phoneNumber) { + if (strlen($phoneNumber) < 6 + // Numbers added before the leading + was stripped already have it + || str_starts_with($phoneNumber, '+') + || str_starts_with($phoneNumber, $dialoutPrefix)) { + continue; + } + + $update->setParameter('phone_number', $dialoutPrefix . $phoneNumber) + ->setParameter('id', $id, IQueryBuilder::PARAM_INT); + try { + $update->executeStatement(); + } catch (Exception $e) { + if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + throw $e; + } + $output->warning('Phone number ' . $phoneNumber . ' was not migrated, because ' . $dialoutPrefix . $phoneNumber . ' is already assigned'); + } + } } } From 6a19f5e4c8aa18df463afe7c12fd0f35666c5a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 22:53:24 +0200 Subject: [PATCH 14/17] fix: Also clean phone numbers on find and remove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Marcel Müller --- lib/Command/PhoneNumber/FindPhoneNumber.php | 3 +++ lib/Command/PhoneNumber/RemovePhoneNumber.php | 9 ++++++++- tests/integration/features/command/phone-number.feature | 6 +++++- tests/psalm-baseline.xml | 1 + 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/Command/PhoneNumber/FindPhoneNumber.php b/lib/Command/PhoneNumber/FindPhoneNumber.php index 1e1b509d4ad..0434dbd57dc 100644 --- a/lib/Command/PhoneNumber/FindPhoneNumber.php +++ b/lib/Command/PhoneNumber/FindPhoneNumber.php @@ -10,6 +10,7 @@ use OC\Core\Command\Base; use OCA\Talk\Model\PhoneNumberMapper; +use OCA\Talk\Service\PhoneNumberValidation; use OCP\AppFramework\Db\DoesNotExistException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -48,6 +49,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $userId = (string)$input->getOption('user'); if ($phoneNumber !== '') { + $phoneNumber = $this->phoneNumberValidation->cleanNumber($phoneNumber); + try { $entry = $this->mapper->findByPhoneNumber($phoneNumber); } catch (DoesNotExistException) { diff --git a/lib/Command/PhoneNumber/RemovePhoneNumber.php b/lib/Command/PhoneNumber/RemovePhoneNumber.php index a5484c4da8d..487929defec 100644 --- a/lib/Command/PhoneNumber/RemovePhoneNumber.php +++ b/lib/Command/PhoneNumber/RemovePhoneNumber.php @@ -10,6 +10,7 @@ use OC\Core\Command\Base; use OCA\Talk\Model\PhoneNumberMapper; +use OCA\Talk\Service\PhoneNumberValidation; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -36,7 +37,13 @@ protected function configure(): void { } protected function execute(InputInterface $input, OutputInterface $output): int { - $phoneNumber = $input->getArgument('phone'); + $phoneNumber = $this->phoneNumberValidation->cleanNumber($input->getArgument('phone')); + + if ($phoneNumber === '') { + $output->writeln('Phone number is invalid.'); + return self::FAILURE; + } + $this->mapper->deleteByPhoneNumber($phoneNumber); return self::SUCCESS; } diff --git a/tests/integration/features/command/phone-number.feature b/tests/integration/features/command/phone-number.feature index 9515a8895ed..dd2a2004f12 100644 --- a/tests/integration/features/command/phone-number.feature +++ b/tests/integration/features/command/phone-number.feature @@ -20,6 +20,10 @@ Feature: command/phone-number Then the command failed with exit code 0 And the command output contains the text "Phone number +491601231212 is assigned to participant1" + Given invoking occ with "talk:phone-number:find --phone +49-160-123-12-12" + Then the command failed with exit code 0 + And the command output contains the text "Phone number +491601231212 is assigned to participant1" + Given invoking occ with "talk:phone-number:find --phone +49160123121234" Then the command failed with exit code 1 And the command output contains the text "Phone number +49160123121234 could not be found" @@ -65,7 +69,7 @@ Feature: command/phone-number Then the command failed with exit code 0 And the command output contains the text "participant1 has phone number +491601231213 assigned" - Given invoking occ with "talk:phone-number:remove +491601231213" + Given invoking occ with "talk:phone-number:remove +49-160-123-1213" Then the command failed with exit code 0 Given invoking occ with "talk:phone-number:find --user participant1" diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index 4eea1d07c9c..0892d46cec8 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -48,6 +48,7 @@ + From 1557aad87176cfa0e77aa30005a5203717849b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 14 Oct 2026 23:19:37 +0200 Subject: [PATCH 15/17] chore: Move migration to talk 23 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- ...ate20260918142246.php => Version23001Date20260918142246.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename lib/Migration/{Version24000Date20260918142246.php => Version23001Date20260918142246.php} (97%) diff --git a/lib/Migration/Version24000Date20260918142246.php b/lib/Migration/Version23001Date20260918142246.php similarity index 97% rename from lib/Migration/Version24000Date20260918142246.php rename to lib/Migration/Version23001Date20260918142246.php index a29681e7f25..6cbca6118d0 100644 --- a/lib/Migration/Version24000Date20260918142246.php +++ b/lib/Migration/Version23001Date20260918142246.php @@ -19,7 +19,7 @@ use OCP\Migration\SimpleMigrationStep; use Override; -class Version24000Date20260918142246 extends SimpleMigrationStep { +class Version23001Date20260918142246 extends SimpleMigrationStep { public function __construct( private readonly IDBConnection $connection, From dfee8bb60a65835586705c9a21440d6db877dc3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 15 Oct 2026 12:31:43 +0200 Subject: [PATCH 16/17] fix: Add missing DI in phone number commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- lib/Command/PhoneNumber/FindPhoneNumber.php | 1 + lib/Command/PhoneNumber/RemovePhoneNumber.php | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/Command/PhoneNumber/FindPhoneNumber.php b/lib/Command/PhoneNumber/FindPhoneNumber.php index 0434dbd57dc..d836a98c9d9 100644 --- a/lib/Command/PhoneNumber/FindPhoneNumber.php +++ b/lib/Command/PhoneNumber/FindPhoneNumber.php @@ -20,6 +20,7 @@ class FindPhoneNumber extends Base { public function __construct( private PhoneNumberMapper $mapper, + private PhoneNumberValidation $phoneNumberValidation, ) { parent::__construct(); } diff --git a/lib/Command/PhoneNumber/RemovePhoneNumber.php b/lib/Command/PhoneNumber/RemovePhoneNumber.php index 487929defec..c12ab4ab290 100644 --- a/lib/Command/PhoneNumber/RemovePhoneNumber.php +++ b/lib/Command/PhoneNumber/RemovePhoneNumber.php @@ -19,6 +19,7 @@ class RemovePhoneNumber extends Base { public function __construct( private PhoneNumberMapper $mapper, + private PhoneNumberValidation $phoneNumberValidation, ) { parent::__construct(); } From 4c5fb0ac0c3e9618a72ddb2a92933ccbf18f6800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Thu, 15 Oct 2026 12:32:05 +0200 Subject: [PATCH 17/17] chore: Deprecate dialout prefix in settings.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- docs/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index 4ac6c720bc5..3b065c468e5 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -81,7 +81,7 @@ Legend: | `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 | | Prefix that is added before the first configured phone-number of a user to make it a valid E164 number to be used as a caller for out-going calls | +| `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 |