Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/files/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,6 @@
'OCA\\Files\\Sharing\\Permission\\NodeReadSharePermissionType' => $baseDir . '/../lib/Sharing/Permission/NodeReadSharePermissionType.php',
'OCA\\Files\\Sharing\\Permission\\NodeUpdateSharePermissionType' => $baseDir . '/../lib/Sharing/Permission/NodeUpdateSharePermissionType.php',
'OCA\\Files\\Sharing\\Property\\NodeGridViewSharePropertyType' => $baseDir . '/../lib/Sharing/Property/NodeGridViewSharePropertyType.php',
'OCA\\Files\\Sharing\\Source\\NodeShareSourceMetadata' => $baseDir . '/../lib/Sharing/Source/NodeShareSourceMetadata.php',
'OCA\\Files\\Sharing\\Source\\NodeShareSourceType' => $baseDir . '/../lib/Sharing/Source/NodeShareSourceType.php',
);
1 change: 1 addition & 0 deletions apps/files/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\Sharing\\Permission\\NodeReadSharePermissionType' => __DIR__ . '/..' . '/../lib/Sharing/Permission/NodeReadSharePermissionType.php',
'OCA\\Files\\Sharing\\Permission\\NodeUpdateSharePermissionType' => __DIR__ . '/..' . '/../lib/Sharing/Permission/NodeUpdateSharePermissionType.php',
'OCA\\Files\\Sharing\\Property\\NodeGridViewSharePropertyType' => __DIR__ . '/..' . '/../lib/Sharing/Property/NodeGridViewSharePropertyType.php',
'OCA\\Files\\Sharing\\Source\\NodeShareSourceMetadata' => __DIR__ . '/..' . '/../lib/Sharing/Source/NodeShareSourceMetadata.php',
'OCA\\Files\\Sharing\\Source\\NodeShareSourceType' => __DIR__ . '/..' . '/../lib/Sharing/Source/NodeShareSourceType.php',
);

Expand Down
36 changes: 36 additions & 0 deletions apps/files/lib/Sharing/Source/NodeShareSourceMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

declare(strict_types=1);

namespace OCA\Files\Sharing\Source;

use NCU\Sharing\Icon\ShareIconURL;
use NCU\Sharing\Source\IShareSourceMetadata;
use OCP\Files\Cache\ICacheEntry;
use OCP\IURLGenerator;

final readonly class NodeShareSourceMetadata implements IShareSourceMetadata {
public function __construct(
private IURLGenerator $urlGenerator,
private ICacheEntry $cacheEntry,
) {
}

#[\Override]
public function getDisplayName(): string {
$name = $this->cacheEntry->getName();
return $name !== '' ? $name: (string)$this->cacheEntry->getId();
}

#[\Override]
public function getIcon(): ShareIconURL {
$url = $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreviewByFileId', ['fileId' => $this->cacheEntry->getId(), 'x' => 64, 'y' => 64]);

return new ShareIconURL($url, $url);
}
}
27 changes: 17 additions & 10 deletions apps/files/lib/Sharing/Source/NodeShareSourceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@
namespace OCA\Files\Sharing\Source;

use Exception;
use NCU\Sharing\Icon\ShareIconURL;
use NCU\Sharing\ISharingManager;
use NCU\Sharing\ShareAccessContext;
use NCU\Sharing\Source\IShareSourceMetadata;
use NCU\Sharing\Source\IShareSourceType;
use NCU\Sharing\Source\ShareSource;
use OCA\Files\AppInfo\Application;
use OCA\Files_Trashbin\Events\MoveToTrashEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Cache\IFileAccess;
use OCP\Files\Events\Node\NodeDeletedEvent;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
Expand All @@ -39,6 +41,7 @@ public function __construct(
private IRootFolder $rootFolder,
private IURLGenerator $urlGenerator,
private ISharingManager $manager,
private IFileAccess $fileAccess,
) {
$eventDispatcher->addServiceListener(NodeDeletedEvent::class, self::class);
$eventDispatcher->addServiceListener(MoveToTrashEvent::class, self::class);
Expand All @@ -55,20 +58,24 @@ public function validateSource(string $source): bool {
}

#[\Override]
public function getSourceDisplayName(string $source): ?string {
$displayName = $this->rootFolder->getFirstNodeById((int)$source)?->getName();
if ($displayName === '') {
return null;
public function getSourceMetadata(string $source): ?IShareSourceMetadata {
$cacheEntry = $this->fileAccess->getByFileId((int)$source);
if ($cacheEntry instanceof ICacheEntry) {
return new NodeShareSourceMetadata($this->urlGenerator, $cacheEntry);
}

return $displayName;
return null;
}

#[\Override]
public function getSourceIcon(string $source): ShareIconURL {
$url = $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreviewByFileId', ['fileId' => $source, 'x' => 64, 'y' => 64]);

return new ShareIconURL($url, $url);
public function getSourcesMetadata(array $sources): array {
$sources = array_map(intval(...), $sources);
$cacheEntries = $this->fileAccess->getByFileIds($sources);
// we actually have an `array<int, IShareSourceMetadata>` instead of an `array<non-empty-string, IShareSourceMetadata>` here,
// but since numeric string array keys are automatically casted to ints anyway they are functionally equivalent
/** @var array<non-empty-string, IShareSourceMetadata> $metadata */
$metadata = array_map(fn (ICacheEntry $cacheEntry): NodeShareSourceMetadata => new NodeShareSourceMetadata($this->urlGenerator, $cacheEntry), $cacheEntries);
return $metadata;
}

#[\Override]
Expand Down
30 changes: 19 additions & 11 deletions apps/files/tests/Sharing/Source/NodeShareSourceTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,29 @@

declare(strict_types=1);

use NCU\Sharing\Icon\ShareIconURL;
use NCU\Sharing\ISharingManager;
use NCU\Sharing\ISharingRegistry;
use NCU\Sharing\ShareAccessContext;
use NCU\Sharing\Source\ShareSource;
use OC\Files\Filesystem;
use OC\User\Database;
use OCA\Files\Sharing\Source\NodeShareSourceType;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\IFileAccess;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IDBConnection;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Server;
use PHPUnit\Framework\Attributes\Group;
use Test\TestCase;
use Test\Traits\UserTrait;

#[Group(name: 'DB')]
final class NodeShareSourceTypeTest extends TestCase {
use UserTrait;

private IDBConnection $dbConnection;

private ISharingManager $manager;
Expand All @@ -45,18 +48,20 @@ public function setUp(): void {

$this->manager = Server::get(ISharingManager::class);

$userManager = Server::get(IUserManager::class);
$userManager->clearBackends();
$userManager->registerBackend(new Database());

$user1 = $userManager->createUser('user1', 'password');
$this->assertNotFalse($user1);
$user1 = $this->createUser('user1', 'password');
$this->user1 = $user1;

$userFolder = Server::get(IRootFolder::class)->getUserFolder($this->user1->getUID());
$this->node = $userFolder->newFile('foo.txt', 'bar');

$this->sourceType = new NodeShareSourceType(Server::get(IEventDispatcher::class), $this->dbConnection, Server::get(IRootFolder::class), Server::get(IURLGenerator::class), $this->manager);
$this->sourceType = new NodeShareSourceType(
Server::get(IEventDispatcher::class),
$this->dbConnection,
Server::get(IRootFolder::class),
Server::get(IURLGenerator::class),
$this->manager,
Server::get(IFileAccess::class),
);
}

#[\Override]
Expand All @@ -74,13 +79,16 @@ public function testValidateSource(): void {
}

public function testGetSourceDisplayName(): void {
$this->assertEquals('foo.txt', $this->sourceType->getSourceDisplayName((string)$this->node->getId()));
$this->assertEquals('foo.txt', $this->sourceType->getSourceMetadata((string)$this->node->getId())?->getDisplayName());
}

public function testGetSourceIcon(): void {
$source = (string)$this->node->getId();

$icon = $this->sourceType->getSourceIcon($source);
$icon = $this->sourceType->getSourceMetadata($source)?->getIcon();
if (!$icon instanceof ShareIconURL) {
$this->fail('Unexpected share icon for ' . $source);
}

foreach ([$icon->light, $icon->dark] as $url) {
$this->assertStringStartsWith('http://localhost/index.php/core/preview?', $url);
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@
'NCU\\Sharing\\ShareAccessContext' => $baseDir . '/lib/unstable/Sharing/ShareAccessContext.php',
'NCU\\Sharing\\ShareState' => $baseDir . '/lib/unstable/Sharing/ShareState.php',
'NCU\\Sharing\\ShareUser' => $baseDir . '/lib/unstable/Sharing/ShareUser.php',
'NCU\\Sharing\\Source\\IShareSourceMetadata' => $baseDir . '/lib/unstable/Sharing/Source/IShareSourceMetadata.php',
'NCU\\Sharing\\Source\\IShareSourceType' => $baseDir . '/lib/unstable/Sharing/Source/IShareSourceType.php',
'NCU\\Sharing\\Source\\ShareSource' => $baseDir . '/lib/unstable/Sharing/Source/ShareSource.php',
'NCU\\Sharing\\Source\\ShareSourceMetadata' => $baseDir . '/lib/unstable/Sharing/Source/ShareSourceMetadata.php',
'NCU\\WorkflowEngine\\Events\\RegisterRuntimeOperationsEvent' => $baseDir . '/lib/unstable/WorkflowEngine/Events/RegisterRuntimeOperationsEvent.php',
'NCU\\WorkflowEngine\\RuntimeOperation' => $baseDir . '/lib/unstable/WorkflowEngine/RuntimeOperation.php',
'NCU\\WorkflowEngine\\RuntimeScope' => $baseDir . '/lib/unstable/WorkflowEngine/RuntimeScope.php',
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'NCU\\Sharing\\ShareAccessContext' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/ShareAccessContext.php',
'NCU\\Sharing\\ShareState' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/ShareState.php',
'NCU\\Sharing\\ShareUser' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/ShareUser.php',
'NCU\\Sharing\\Source\\IShareSourceMetadata' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Source/IShareSourceMetadata.php',
'NCU\\Sharing\\Source\\IShareSourceType' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Source/IShareSourceType.php',
'NCU\\Sharing\\Source\\ShareSource' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Source/ShareSource.php',
'NCU\\Sharing\\Source\\ShareSourceMetadata' => __DIR__ . '/../../..' . '/lib/unstable/Sharing/Source/ShareSourceMetadata.php',
'NCU\\WorkflowEngine\\Events\\RegisterRuntimeOperationsEvent' => __DIR__ . '/../../..' . '/lib/unstable/WorkflowEngine/Events/RegisterRuntimeOperationsEvent.php',
'NCU\\WorkflowEngine\\RuntimeOperation' => __DIR__ . '/../../..' . '/lib/unstable/WorkflowEngine/RuntimeOperation.php',
'NCU\\WorkflowEngine\\RuntimeScope' => __DIR__ . '/../../..' . '/lib/unstable/WorkflowEngine/RuntimeScope.php',
Expand Down
35 changes: 30 additions & 5 deletions lib/private/Sharing/SharingBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use NCU\Sharing\ShareAccessContext;
use NCU\Sharing\ShareState;
use NCU\Sharing\ShareUser;
use NCU\Sharing\Source\IShareSourceMetadata;
use NCU\Sharing\Source\IShareSourceType;
use NCU\Sharing\Source\ShareSource;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -697,9 +698,14 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID,
$chunks = array_chunk(array_keys($shares), 1000);

$registrySourceTypes = $this->registry->getSourceTypes();
/** @var array<int, array<class-string<IShareSourceType>, bool>> $shareSourceTypeClasses */
/** @var array<non-empty-string, array<class-string<IShareSourceType>, bool>> $shareSourceTypeClasses */
$shareSourceTypeClasses = [];
foreach ($chunks as $chunk) {
/** @var array<class-string<IShareSourceType>, non-empty-string[]> $shareSourceValues */
$shareSourceValues = [];
/** @var array<class-string<IShareSourceType>, array<non-empty-string, IShareSourceMetadata>> $shareSourceMetas */
$shareSourceMetas = [];

$qb = $this->connection->getQueryBuilder();
$qb
->select(
Expand All @@ -711,21 +717,38 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID,
->where($qb->expr()->in('ss.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY)));

$result = $qb->executeQuery();
foreach ($result->fetchAll() as $row) {
/** @var class-string<IShareSourceType> $typeClass */
/** @var array{source_class: class-string<IShareSourceType>, source_value: non-empty-string, share_id: int}[] $rows */
$rows = $result->fetchAll();

foreach ($rows as $row) {
$typeClass = $row['source_class'];
$value = $row['source_value'];

$shareSourceValues[$typeClass] ??= [];
$shareSourceValues[$typeClass][] = $value;
}

foreach ($shareSourceValues as $typeClass => $values) {
if (($sourceType = ($this->registry->getSourceTypes()[$typeClass] ?? null)) === null) {
throw new RuntimeException('The source type is not registered: ' . $typeClass);
}

$shareSourceMetas[$typeClass] = $sourceType->getSourcesMetadata($values);
}

foreach ($rows as $row) {
$typeClass = $row['source_class'];
if (!isset($registrySourceTypes[$typeClass])) {
// Skip sources that are currently not compatible, but don't remove them.
continue;
}

/** @var non-empty-string $value */
$value = $row['source_value'];
/** @var non-empty-string $id */
$id = (string)$row['share_id'];
$shares[$id]['sources'][] = new ShareSource(
$typeClass,
$value,
$shareSourceMetas[$typeClass][$value] ?? null,
);

$shareSourceTypeClasses[$id] ??= [];
Expand Down Expand Up @@ -907,6 +930,7 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID,
/** @var array<int, array<class-string<ISharePermissionType>, bool>> $shareCompatiblePermissionTypeClasses */
$shareCompatiblePermissionTypeClasses = [];
foreach (array_keys($shares) as $id) {
$id = (string)$id;
$shareCompatiblePermissionTypeClasses[$id] = [];
foreach ($registryGenericPermissionTypeClasses as $permissionTypeClass) {
$shareCompatiblePermissionTypeClasses[$id][$permissionTypeClass] = true;
Expand Down Expand Up @@ -982,6 +1006,7 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID,
}

foreach (array_keys($shares) as $id) {
$id = (string)$id;
foreach (array_keys($registryPropertyTypes) as $propertyTypeClass) {
$share = $shares[$id];
if (
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Sharing/SharingManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ private function validateInteraction(ShareAccessContext $accessContext, Share $s
$action = new ShareAction(null, array_values(array_map(static fn (SharePermission $permission): string => $permission->class, $share->getEnabledPermissions())));

$usersToCheck = [];
if ($share->owner->instance === null && ($ownerUser = $this->userManager->get($share->owner->userId)) !== null) {
if ($share->owner->instance === null && ($ownerUser = $this->userManager->get($share->owner->userId)) instanceof IUser) {
$usersToCheck[] = $ownerUser;
}

Expand Down
35 changes: 35 additions & 0 deletions lib/unstable/Sharing/Source/IShareSourceMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

declare(strict_types=1);

namespace NCU\Sharing\Source;

use NCU\Sharing\Icon\ShareIconSVG;
use NCU\Sharing\Icon\ShareIconURL;
use OCP\AppFramework\Attribute\Implementable;

/**
* @experimental 35.0.0
*/
#[Implementable(since: '35.0.0')]
interface IShareSourceMetadata {
/**
* Get a user friendly display name for thew source
*
* @return non-empty-string
* @experimental 35.0.0
*/
public function getDisplayName(): string;

/**
* Get the icon for for the source
*
* @experimental 35.0.0
*/
public function getIcon(): null|ShareIconSVG|ShareIconURL;
}
10 changes: 4 additions & 6 deletions lib/unstable/Sharing/Source/IShareSourceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

namespace NCU\Sharing\Source;

use NCU\Sharing\Icon\ShareIconSVG;
use NCU\Sharing\Icon\ShareIconURL;
use OCP\AppFramework\Attribute\Implementable;
use OCP\Interaction\InteractionResource;
use OCP\L10N\IFactory;
Expand Down Expand Up @@ -40,16 +38,16 @@ public function validateSource(string $source): bool;

/**
* @param non-empty-string $source
* @return ?non-empty-string
* @experimental 35.0.0
*/
public function getSourceDisplayName(string $source): ?string;
public function getSourceMetadata(string $source): ?IShareSourceMetadata;

/**
* @param non-empty-string $source
* @param non-empty-string[] $sources
* @return array<non-empty-string, IShareSourceMetadata>
* @experimental 35.0.0
*/
public function getSourceIcon(string $source): null|ShareIconSVG|ShareIconURL;
public function getSourcesMetadata(array $sources): array;

/**
* @param non-empty-string $userId
Expand Down
Loading
Loading