From ff7533770ed0f64e2e1b715055bb2b7d787f3a8b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 7 Jul 2026 10:53:06 +0200 Subject: [PATCH 01/15] 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 1f7d9f22152..bce2aa64c33 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2588,6 +2588,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( @@ -2603,7 +2604,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 7731f10e6b5..d5fefefad41 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -621,7 +621,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); @@ -655,6 +655,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 18bc6366ac6ada4dc7fd23cc5640b211bcf09697 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 9 Jul 2026 10:11:45 +0200 Subject: [PATCH 02/15] 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 c1ce47af600..2f44cbe83d7 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 3e21ce58a41dd7fd8de5fad6bf2ae7e2916032c3 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 9 Jul 2026 10:22:02 +0200 Subject: [PATCH 03/15] 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 6a0611f88e4..c29b433a77e 100644 --- a/lib/SetupCheck/SIPConfiguration.php +++ b/lib/SetupCheck/SIPConfiguration.php @@ -48,9 +48,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 01b2be18834055463baa66c6504afbf6c8e0ce60 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/15] 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 141ff6a61adba3f090f60b84ff4884d393c91412 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/15] 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 bce2aa64c33..d15ba914894 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2582,8 +2582,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 bca74945a2a6ed6fd6ba51f254251173f7f3b36b 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/15] 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 d15ba914894..4c528120ff5 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2602,8 +2602,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( @@ -2625,7 +2623,7 @@ public function directDialIn(string $phoneNumber, string $caller): DataResponse '', true, displayName: $caller, - phoneNumber: $inBoundPhoneNumber, + phoneNumber: $caller, ); return new DataResponse($this->formatRoom($room, $participant)); } From fd91067dca93044a64024bd6d536fe8a693779fe 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/15] 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 632fd6602a9..4da3893545c 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 9914d61e400..9ae68fae4f9 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 2f44cbe83d7..655551ce1b7 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 432e1ae2904..0c134993622 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 Given user "participant2" exists 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 d82d5f9fd39..4fb631b8b86 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -36,6 +36,7 @@ + From ad88d378beca5e3f9f6d6fe3952355dc08f4c0b9 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/15] 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 655551ce1b7..6b5dcfd5a3a 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 70bf136391c3f0a1cfc194b611bac5f0b33be5c7 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/15] 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 6419784c5f220b7f370e39da52c8912bc0588674 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/15] 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 3b5df5713ee..2ae3c405128 100644 --- a/lib/Controller/CallController.php +++ b/lib/Controller/CallController.php @@ -454,14 +454,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 e251c96f722ea5b50be135b664d07022580cdc7f 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/15] 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 --- docs/settings.md | 2 +- src/components/AdminSettings/SIPBridge.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index 6292b39f8b5..2ac4f70fcfb 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -83,7 +83,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 | diff --git a/src/components/AdminSettings/SIPBridge.vue b/src/components/AdminSettings/SIPBridge.vue index 0bb3621b044..de6ee533181 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 b545300901d31871ed7961de9f53c408cec93151 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/15] 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 c29b433a77e..896729d77f2 100644 --- a/lib/SetupCheck/SIPConfiguration.php +++ b/lib/SetupCheck/SIPConfiguration.php @@ -10,7 +10,6 @@ use OCA\Talk\Config; use OCP\AppFramework\Services\IAppConfig; -use OCP\IDBConnection; use OCP\IL10N; use OCP\SetupCheck\ISetupCheck; use OCP\SetupCheck\SetupResult; @@ -19,7 +18,6 @@ class SIPConfiguration implements ISetupCheck { public function __construct( private readonly IAppConfig $appConfig, private readonly Config $talkConfig, - private readonly IDBConnection $connection, private readonly IL10N $l, ) { } @@ -44,32 +42,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() === '' && $this->appConfig->getAppValueInt('feature_hints_hidden') < 34) { From b50062e25fa85b1382111884d6fb038993bed4db 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/15] 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 2416bb531e79d0c70648582f83df8e3d6113ff3a 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/15] 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 | 4 ++++ lib/Command/PhoneNumber/RemovePhoneNumber.php | 10 +++++++++- .../integration/features/command/phone-number.feature | 6 +++++- tests/psalm-baseline.xml | 1 + 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/Command/PhoneNumber/FindPhoneNumber.php b/lib/Command/PhoneNumber/FindPhoneNumber.php index 1922a7f79dc..05571179ee9 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; @@ -19,6 +20,7 @@ class FindPhoneNumber extends Base { public function __construct( private readonly PhoneNumberMapper $mapper, + private readonly PhoneNumberValidation $phoneNumberValidation, ) { parent::__construct(); } @@ -48,6 +50,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 0ae6132ac5d..77a77d4fdb8 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; @@ -18,6 +19,7 @@ class RemovePhoneNumber extends Base { public function __construct( private readonly PhoneNumberMapper $mapper, + private readonly PhoneNumberValidation $phoneNumberValidation, ) { parent::__construct(); } @@ -36,7 +38,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 0c134993622..a73d24fd8b1 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 4fb631b8b86..5a96c5e913b 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -61,6 +61,7 @@ + From 9a18f5d9d36536ce248d1434e3b2b601f1fe0943 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/15] 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,