From fe646bf25d10516ce318942c0659850c2d4e1239 Mon Sep 17 00:00:00 2001 From: Peter Ringelmann Date: Thu, 20 Aug 2026 17:56:10 +0200 Subject: [PATCH] feat(teams): add ITeamFileResourceProvider for cross-provider file lookups Signed-off-by: Peter Ringelmann --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Teams/TeamManager.php | 26 +++++++- .../Teams/ITeamFileResourceProvider.php | 43 +++++++++++++ tests/lib/Teams/TeamManagerTest.php | 60 +++++++++++++++++++ 5 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 lib/public/Teams/ITeamFileResourceProvider.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index be06cdd45b860..173b7d7812853 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1056,6 +1056,7 @@ 'OCP\\TaskProcessing\\TaskTypes\\TextToTextSummary' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTopics' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTranslate' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextTranslate.php', + 'OCP\\Teams\\ITeamFileResourceProvider' => $baseDir . '/lib/public/Teams/ITeamFileResourceProvider.php', 'OCP\\Teams\\ITeamFolderProvider' => $baseDir . '/lib/public/Teams/ITeamFolderProvider.php', 'OCP\\Teams\\ITeamManager' => $baseDir . '/lib/public/Teams/ITeamManager.php', 'OCP\\Teams\\ITeamResourceProvider' => $baseDir . '/lib/public/Teams/ITeamResourceProvider.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 73277b5502fe1..fad4d008bdcf8 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1097,6 +1097,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\TaskProcessing\\TaskTypes\\TextToTextSummary' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTopics' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php', 'OCP\\TaskProcessing\\TaskTypes\\TextToTextTranslate' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextTranslate.php', + 'OCP\\Teams\\ITeamFileResourceProvider' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamFileResourceProvider.php', 'OCP\\Teams\\ITeamFolderProvider' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamFolderProvider.php', 'OCP\\Teams\\ITeamManager' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamManager.php', 'OCP\\Teams\\ITeamResourceProvider' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamResourceProvider.php', diff --git a/lib/private/Teams/TeamManager.php b/lib/private/Teams/TeamManager.php index d17aefe36223b..587202364eebd 100644 --- a/lib/private/Teams/TeamManager.php +++ b/lib/private/Teams/TeamManager.php @@ -15,6 +15,7 @@ use OCA\Circles\Model\Probes\CircleProbe; use OCP\IURLGenerator; use OCP\Server; +use OCP\Teams\ITeamFileResourceProvider; use OCP\Teams\ITeamFolderProvider; use OCP\Teams\ITeamManager; use OCP\Teams\ITeamResourceProvider; @@ -130,8 +131,31 @@ public function getTeamsForResource(string $providerId, string $resourceId, stri return []; } + return array_map($this->circleToTeam(...), $this->getTeams($this->collectTeamIds($providerId, $resourceId), $userId)); + } + + /** + * When the addressed provider identifies its resources by file id, ask every + * other file provider too: a team folder is mounted rather than shared, so a + * share lookup alone never finds it. + * + * @return list + */ + private function collectTeamIds(string $providerId, string $resourceId): array { $provider = $this->getProvider($providerId); - return array_map($this->circleToTeam(...), $this->getTeams($provider->getTeamsForResource($resourceId), $userId)); + $teamIds = $provider->getTeamsForResource($resourceId); + + if ($provider instanceof ITeamFileResourceProvider) { + foreach ($this->getProviders() as $candidate) { + if ($candidate === $provider || !$candidate instanceof ITeamFileResourceProvider) { + continue; + } + + $teamIds = array_merge($teamIds, $candidate->getTeamsForFile((int)$resourceId)); + } + } + + return array_values(array_unique($teamIds)); } private function getTeamInternal(string $teamId, string $userId, ?CircleProbe $probe = null): ?Circle { diff --git a/lib/public/Teams/ITeamFileResourceProvider.php b/lib/public/Teams/ITeamFileResourceProvider.php new file mode 100644 index 0000000000000..41a3b09996706 --- /dev/null +++ b/lib/public/Teams/ITeamFileResourceProvider.php @@ -0,0 +1,43 @@ + Team ids, empty when the file is unrelated to this provider. + * @since 35.0.0 + */ + public function getTeamsForFile(int $fileId): array; +} diff --git a/tests/lib/Teams/TeamManagerTest.php b/tests/lib/Teams/TeamManagerTest.php index b5b1a42aca124..90f3cc51d093f 100644 --- a/tests/lib/Teams/TeamManagerTest.php +++ b/tests/lib/Teams/TeamManagerTest.php @@ -12,6 +12,7 @@ use OC\AppFramework\Bootstrap\Coordinator; use OC\Teams\TeamManager; use OCP\IURLGenerator; +use OCP\Teams\ITeamFileResourceProvider; use OCP\Teams\ITeamFolderProvider; use OCP\Teams\ITeamResourceProvider; use Test\TestCase; @@ -43,6 +44,65 @@ public function testGetTeamFolderProviderReturnsRegisteredFolderProvider(): void $this->assertSame($folderProvider, $teamManager->getTeamFolderProvider()); } + public function testCollectTeamIdsOnlyAsksTheAddressedProvider(): void { + $teamManager = $this->createTeamManager(true); + $addressed = $this->createMock(ITeamResourceProvider::class); + $addressed->expects($this->once()) + ->method('getTeamsForResource') + ->with('42') + ->willReturn(['team-1']); + + $fileProvider = $this->createMock(ITeamFileResourceProvider::class); + $fileProvider->expects($this->never())->method('getTeamsForFile'); + + $this->setProviders($teamManager, ['deck' => $addressed, 'other' => $fileProvider]); + + $this->assertSame(['team-1'], $this->collectTeamIds($teamManager, 'deck', '42')); + } + + public function testCollectTeamIdsAsksEveryOtherFileProvider(): void { + $teamManager = $this->createTeamManager(true); + $addressed = $this->createMock(ITeamFileResourceProvider::class); + $addressed->method('getTeamsForResource')->with('42')->willReturn(['team-1']); + $addressed->expects($this->never())->method('getTeamsForFile'); + + $mountProvider = $this->createMock(ITeamFileResourceProvider::class); + $mountProvider->expects($this->once()) + ->method('getTeamsForFile') + ->with(42) + ->willReturn(['team-2']); + + $unrelated = $this->createMock(ITeamResourceProvider::class); + $unrelated->expects($this->never())->method('getTeamsForResource'); + + $this->setProviders($teamManager, [ + 'files' => $addressed, + 'groupfolders' => $mountProvider, + 'talk' => $unrelated, + ]); + + $this->assertSame(['team-1', 'team-2'], $this->collectTeamIds($teamManager, 'files', '42')); + } + + public function testCollectTeamIdsReturnsEachTeamOnce(): void { + $teamManager = $this->createTeamManager(true); + $addressed = $this->createMock(ITeamFileResourceProvider::class); + $addressed->method('getTeamsForResource')->willReturn(['team-1']); + + $mountProvider = $this->createMock(ITeamFileResourceProvider::class); + $mountProvider->method('getTeamsForFile')->willReturn(['team-1', 'team-2']); + + $this->setProviders($teamManager, ['files' => $addressed, 'groupfolders' => $mountProvider]); + + $this->assertSame(['team-1', 'team-2'], $this->collectTeamIds($teamManager, 'files', '42')); + } + + private function collectTeamIds(TeamManager $teamManager, string $providerId, string $resourceId): array { + $method = new \ReflectionMethod(TeamManager::class, 'collectTeamIds'); + + return $method->invoke($teamManager, $providerId, $resourceId); + } + private function createTeamManager(bool $hasTeamSupport = false): TeamManager { return new class($this->createMock(Coordinator::class), $this->createMock(IURLGenerator::class), null, $hasTeamSupport, ) extends TeamManager { public function __construct(