Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions lib/Folder/FolderManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,53 @@ public function getFoldersForCircle(string $circleId): array {
return array_map($this->rowToFolder(...), $rows);
}

/**
* Resolve the folder mounted with the given file cache root. Trash and
* versions mounts have their own root ids and are not matched.
*
* @throws Exception
*/
public function getFolderIdByRootId(int $rootId): ?int {
$query = $this->connection->getQueryBuilder();
$query->select('folder_id')
->from('group_folders')
->where($query->expr()->eq('root_id', $query->createNamedParameter($rootId, IQueryBuilder::PARAM_INT)));

$folderId = $query->executeQuery()->fetchOne();

return is_numeric($folderId) ? (int)$folderId : null;
}

/**
* Circle single ids assigned to a folder, either as an applicable group or as
* a team space. Inverse of {@see FolderManager::getFoldersForCircle()}.
*
* @return list<string>
* @throws Exception
*/
public function getCircleIdsForFolder(int $folderId): array {
$query = $this->connection->getQueryBuilder();
$query->select('f.team_circle_id', 'g.circle_id')
->from('group_folders', 'f')
->leftJoin('f', 'group_folders_groups', 'g', $query->expr()->eq('f.folder_id', 'g.folder_id'))
->where($query->expr()->eq('f.folder_id', $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));

/** @var list<array{team_circle_id: ?string, circle_id: ?string}> $rows */
$rows = $query->executeQuery()->fetchAll();

// The left join repeats team_circle_id per applicable group row.
$circleIds = [];
foreach ($rows as $row) {
foreach ([$row['team_circle_id'], $row['circle_id']] as $circleId) {
if ($circleId !== null && $circleId !== '') {
$circleIds[$circleId] = true;
}
}
}

return array_map('strval', array_keys($circleIds));
}

/**
* @param list<string> $paths
* @return list<FolderDefinitionWithPermissions>
Expand Down
19 changes: 15 additions & 4 deletions lib/TeamSpace/TeamSpaceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@

use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Teams\ITeamFileResolver;
use OCP\Teams\ITeamFolderProvider;
use OCP\Teams\Team;
use OCP\Teams\TeamFolder;
use OCP\Teams\TeamResource;

class TeamSpaceProvider implements ITeamFolderProvider {
class TeamSpaceProvider implements ITeamFolderProvider, ITeamFileResolver {

Check failure on line 20 in lib/TeamSpace/TeamSpaceProvider.php

View workflow job for this annotation

GitHub Actions / static-phpstan-analysis

Class OCA\GroupFolders\TeamSpace\TeamSpaceProvider implements unknown interface OCP\Teams\ITeamFileResolver.
public function __construct(
private readonly TeamSpaceService $service,
private readonly IL10N $l10n,
Expand Down Expand Up @@ -97,11 +98,21 @@
return false;
}

#[\Override]
/**
* @return list<Team>
* Our resource ids are folder ids, not file ids.
*
* @return list<string>
*/
#[\Override]
public function getTeamsForResource(string $resourceId): array {
return [];
return $this->service->getCircleIdsForFolder((int)$resourceId);
}

/**
* @return list<string>
*/
#[\Override]

Check failure on line 114 in lib/TeamSpace/TeamSpaceProvider.php

View workflow job for this annotation

GitHub Actions / static-phpstan-analysis

Method OCA\GroupFolders\TeamSpace\TeamSpaceProvider::getTeamsForFile() has #[\Override] attribute but does not override any method.
public function getTeamsForFile(int $fileId): array {
return $this->service->getCircleIdsForFile($fileId);
}
}
34 changes: 34 additions & 0 deletions lib/TeamSpace/TeamSpaceService.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

use OCA\GroupFolders\Folder\FolderDefinition;
use OCA\GroupFolders\Folder\FolderManager;
use OCA\GroupFolders\Mount\MountProvider;
use OCP\Files\Config\IUserMountCache;
use OCP\Teams\Team;
use OCP\Teams\TeamFolder;
use Psr\Log\LoggerInterface;
Expand All @@ -34,6 +36,7 @@ class TeamSpaceService {
public function __construct(
private readonly FolderManager $folderManager,
private readonly LoggerInterface $logger,
private readonly IUserMountCache $userMountCache,
) {
}

Expand Down Expand Up @@ -215,6 +218,37 @@ public function getGroupFoldersForCircle(string $circleId): array {
);
}

/**
* @return list<string>
*/
public function getCircleIdsForFolder(int $folderId): array {
return $this->folderManager->getCircleIdsForFolder($folderId);
}

/**
* Circle single ids of the group folder a file lives in. Resolved through the
* mount cache, so files nested anywhere inside the folder are covered.
*
* @return list<string>
*/
public function getCircleIdsForFile(int $fileId): array {
$circleIds = [];
foreach ($this->userMountCache->getMountsForFileId($fileId) as $mount) {
if ($mount->getMountProvider() !== MountProvider::class) {
continue;
}

$folderId = $this->folderManager->getFolderIdByRootId($mount->getRootId());
if ($folderId === null) {
continue;
}

$circleIds = array_merge($circleIds, $this->getCircleIdsForFolder($folderId));
}

return array_values(array_unique($circleIds));
}

/**
* Whether the given circle (looked up by single id) owns a team space.
*/
Expand Down
50 changes: 50 additions & 0 deletions tests/Folder/FolderManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -849,4 +849,54 @@ public function testGuardsLiftedAfterUnlink(): void {
// No exception thrown — the folder is a regular team folder again.
$this->addToAssertionCount(1);
}

public function testGetCircleIdsForFolderCoversBothWaysACircleIsLinked(): void {
$this->config->method('getSystemValueInt')->willReturn(FileInfo::SPACE_UNLIMITED);
$applied = $this->manager->createFolder('Design');
$teamSpace = $this->manager->createFolder('Engineering');
$this->addCircleApplicable($applied, 'circle-applied');
$this->manager->setTeamCircleId($teamSpace, 'circle-owner');

$this->assertSame(['circle-applied'], $this->manager->getCircleIdsForFolder($applied));
$this->assertSame(['circle-owner'], $this->manager->getCircleIdsForFolder($teamSpace));
}

public function testGetCircleIdsForFolderIgnoresGroupsAndUnknownFolders(): void {
$this->config->method('getSystemValueInt')->willReturn(FileInfo::SPACE_UNLIMITED);
$folderId = $this->manager->createFolder('Design');
$this->manager->addApplicableGroup($folderId, 'g1');

$this->assertSame([], $this->manager->getCircleIdsForFolder($folderId));
$this->assertSame([], $this->manager->getCircleIdsForFolder(0));
}

public function testGetCircleIdsForFolderReturnsEachCircleOnce(): void {
$this->config->method('getSystemValueInt')->willReturn(FileInfo::SPACE_UNLIMITED);
$folderId = $this->manager->createFolder('Design');
$this->addCircleApplicable($folderId, 'circle-1');
$this->manager->setTeamCircleId($folderId, 'circle-1');

$this->assertSame(['circle-1'], $this->manager->getCircleIdsForFolder($folderId));
}

public function testGetFolderIdByRootIdMatchesOnlyTheFolderMount(): void {
$this->config->method('getSystemValueInt')->willReturn(FileInfo::SPACE_UNLIMITED);
$folderId = $this->manager->createFolder('Design');
$rootId = $this->manager->getAllFolders()[$folderId]->rootId;

$this->assertSame($folderId, $this->manager->getFolderIdByRootId($rootId));
$this->assertNull($this->manager->getFolderIdByRootId($rootId + 10000));
}

private function addCircleApplicable(int $folderId, string $circleId): void {
$query = Server::get(IDBConnection::class)->getQueryBuilder();
$query->insert('group_folders_groups')
->values([
'folder_id' => $query->createNamedParameter($folderId, IQueryBuilder::PARAM_INT),
'group_id' => $query->createNamedParameter(''),
'circle_id' => $query->createNamedParameter($circleId),
'permissions' => $query->createNamedParameter(31, IQueryBuilder::PARAM_INT),
])
->executeStatement();
}
}
18 changes: 18 additions & 0 deletions tests/TeamSpace/TeamSpaceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,22 @@ public function testIsSharedWithTeamChecksAllFoldersAssignedToTeam(): void {
$this->assertTrue($this->provider->isSharedWithTeam('team-1', '43'));
$this->assertFalse($this->provider->isSharedWithTeam('team-1', '44'));
}

public function testGetTeamsForResourceReturnsCirclesAssignedToFolder(): void {
$this->service->expects($this->once())
->method('getCircleIdsForFolder')
->with(43)
->willReturn(['team-1', 'team-2']);

$this->assertSame(['team-1', 'team-2'], $this->provider->getTeamsForResource('43'));
}

public function testGetTeamsForFileResolvesThroughTheContainingFolder(): void {
$this->service->expects($this->once())
->method('getCircleIdsForFile')
->with(1688)
->willReturn(['team-1']);

$this->assertSame(['team-1'], $this->provider->getTeamsForFile(1688));
}
}
55 changes: 55 additions & 0 deletions tests/TeamSpace/TeamSpaceServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,29 @@

use OCA\GroupFolders\Folder\FolderDefinition;
use OCA\GroupFolders\Folder\FolderManager;
use OCA\GroupFolders\Mount\MountProvider;
use OCA\GroupFolders\TeamSpace\TeamSpaceService;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
use OCP\Teams\Team;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;

class TeamSpaceServiceTest extends TestCase {
private FolderManager&MockObject $folderManager;
private IUserMountCache&MockObject $userMountCache;
private TeamSpaceService $service;

#[\Override]
protected function setUp(): void {
parent::setUp();
$this->folderManager = $this->createMock(FolderManager::class);
$this->userMountCache = $this->createMock(IUserMountCache::class);
$this->service = new TeamSpaceService(
$this->folderManager,
$this->createMock(LoggerInterface::class),
$this->userMountCache,
);
}

Expand Down Expand Up @@ -84,4 +90,53 @@ public function testPickBaseNameUsesDisplayName(): void {
public function testSanitizeMountPointStripsControlCharsAndSeparators(): void {
$this->assertSame('Engineering', $this->service->sanitizeMountPoint("Engi\nnee/rin\\g"));
}

public function testGetCircleIdsForFileResolvesThroughTheContainingFolder(): void {
$this->userMountCache->method('getMountsForFileId')
->with(1688)
->willReturn([$this->mount(MountProvider::class, 1686)]);
$this->folderManager->method('getFolderIdByRootId')->with(1686)->willReturn(3);
$this->folderManager->method('getCircleIdsForFolder')->with(3)->willReturn(['team-1']);

$this->assertSame(['team-1'], $this->service->getCircleIdsForFile(1688));
}

public function testGetCircleIdsForFileIgnoresMountsFromOtherProviders(): void {
$this->userMountCache->method('getMountsForFileId')
->willReturn([$this->mount('OCA\\Files_External\\Config\\ConfigAdapter', 99)]);
$this->folderManager->expects($this->never())->method('getFolderIdByRootId');

$this->assertSame([], $this->service->getCircleIdsForFile(1688));
}

public function testGetCircleIdsForFileIgnoresMountsWithoutAFolder(): void {
$this->userMountCache->method('getMountsForFileId')
->willReturn([$this->mount(MountProvider::class, 1686)]);
$this->folderManager->method('getFolderIdByRootId')->willReturn(null);
$this->folderManager->expects($this->never())->method('getCircleIdsForFolder');

$this->assertSame([], $this->service->getCircleIdsForFile(1688));
}

public function testGetCircleIdsForFileReturnsEachTeamOnce(): void {
$this->userMountCache->method('getMountsForFileId')->willReturn([
$this->mount(MountProvider::class, 1686),
$this->mount(MountProvider::class, 1786),
]);
$this->folderManager->method('getFolderIdByRootId')->willReturnMap([[1686, 3], [1786, 4]]);
$this->folderManager->method('getCircleIdsForFolder')->willReturnMap([
[3, ['team-1', 'team-2']],
[4, ['team-2']],
]);

$this->assertSame(['team-1', 'team-2'], $this->service->getCircleIdsForFile(1688));
}

private function mount(string $mountProvider, int $rootId): ICachedMountInfo&MockObject {
$mount = $this->createMock(ICachedMountInfo::class);
$mount->method('getMountProvider')->willReturn($mountProvider);
$mount->method('getRootId')->willReturn($rootId);

return $mount;
}
}
Loading