aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing/lib/Controller/ShareAPIController.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_sharing/lib/Controller/ShareAPIController.php')
-rw-r--r--apps/files_sharing/lib/Controller/ShareAPIController.php268
1 files changed, 181 insertions, 87 deletions
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php
index 1fb62480049..095a8a75963 100644
--- a/apps/files_sharing/lib/Controller/ShareAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareAPIController.php
@@ -10,8 +10,12 @@ declare(strict_types=1);
namespace OCA\Files_Sharing\Controller;
use Exception;
+use OC\Core\AppInfo\ConfigLexicon;
+use OC\Files\FileInfo;
use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Circles\Api\v1\Circles;
+use OCA\Deck\Sharing\ShareAPIHelper;
+use OCA\Federation\TrustedServers;
use OCA\Files\Helper;
use OCA\Files_Sharing\Exceptions\SharingRightsException;
use OCA\Files_Sharing\External\Storage;
@@ -39,18 +43,21 @@ use OCP\Files\Mount\IShareOwnerlessMount;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\HintException;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IDateTimeZone;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IRequest;
+use OCP\ITagManager;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use OCP\Mail\IMailer;
use OCP\Server;
+use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\Exceptions\ShareTokenException;
use OCP\Share\IManager;
@@ -70,6 +77,7 @@ use Psr\Log\LoggerInterface;
class ShareAPIController extends OCSController {
private ?Node $lockedNode = null;
+ private array $trustedServerCache = [];
/**
* Share20OCS constructor.
@@ -84,6 +92,7 @@ class ShareAPIController extends OCSController {
private IURLGenerator $urlGenerator,
private IL10N $l,
private IConfig $config,
+ private IAppConfig $appConfig,
private IAppManager $appManager,
private ContainerInterface $serverContainer,
private IUserStatusManager $userStatusManager,
@@ -92,6 +101,8 @@ class ShareAPIController extends OCSController {
private LoggerInterface $logger,
private IProviderFactory $factory,
private IMailer $mailer,
+ private ITagManager $tagManager,
+ private ?TrustedServers $trustedServers,
private ?string $userId = null,
) {
parent::__construct($appName, $request);
@@ -194,6 +205,32 @@ class ShareAPIController extends OCSController {
$result['item_size'] = $node->getSize();
$result['item_mtime'] = $node->getMTime();
+ if ($this->trustedServers !== null && in_array($share->getShareType(), [IShare::TYPE_REMOTE, IShare::TYPE_REMOTE_GROUP], true)) {
+ $result['is_trusted_server'] = false;
+ $sharedWith = $share->getSharedWith();
+ $remoteIdentifier = is_string($sharedWith) ? strrchr($sharedWith, '@') : false;
+ if ($remoteIdentifier !== false) {
+ $remote = substr($remoteIdentifier, 1);
+
+ if (isset($this->trustedServerCache[$remote])) {
+ $result['is_trusted_server'] = $this->trustedServerCache[$remote];
+ } else {
+ try {
+ $isTrusted = $this->trustedServers->isTrustedServer($remote);
+ $this->trustedServerCache[$remote] = $isTrusted;
+ $result['is_trusted_server'] = $isTrusted;
+ } catch (\Exception $e) {
+ // Server not found or other issue, we consider it not trusted
+ $this->trustedServerCache[$remote] = false;
+ $this->logger->error(
+ 'Error checking if remote server is trusted (treating as untrusted): ' . $e->getMessage(),
+ ['exception' => $e]
+ );
+ }
+ }
+ }
+ }
+
$expiration = $share->getExpirationDate();
if ($expiration !== null) {
$expiration->setTimezone($this->dateTimeZone->getTimeZone());
@@ -280,7 +317,7 @@ class ShareAPIController extends OCSController {
/** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
$roomShare = $this->getRoomShareHelper()->formatShare($share);
$result = array_merge($result, $roomShare);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_DECK) {
$result['share_with'] = $share->getSharedWith();
@@ -290,7 +327,7 @@ class ShareAPIController extends OCSController {
/** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
$deckShare = $this->getDeckShareHelper()->formatShare($share);
$result = array_merge($result, $deckShare);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
}
} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
$result['share_with'] = $share->getSharedWith();
@@ -300,7 +337,7 @@ class ShareAPIController extends OCSController {
/** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
$scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
$result = array_merge($result, $scienceMeshShare);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
}
}
@@ -327,7 +364,7 @@ class ShareAPIController extends OCSController {
private function getDisplayNameFromAddressBook(string $query, string $property): string {
// FIXME: If we inject the contacts manager it gets initialized before any address books are registered
try {
- $result = \OC::$server->getContactsManager()->search($query, [$property], [
+ $result = Server::get(\OCP\Contacts\IManager::class)->search($query, [$property], [
'limit' => 1,
'enumeration' => false,
'strict_search' => true,
@@ -407,7 +444,7 @@ class ShareAPIController extends OCSController {
private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
// check if gss is enabled and available
if (count($userIds) === 0
- || !$this->appManager->isInstalled('globalsiteselector')
+ || !$this->appManager->isEnabledForAnyone('globalsiteselector')
|| !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
return [];
}
@@ -470,7 +507,7 @@ class ShareAPIController extends OCSController {
$share = $this->formatShare($share);
if ($include_tags) {
- $share = Helper::populateTags([$share], \OC::$server->getTagManager());
+ $share = $this->populateTags([$share]);
} else {
$share = [$share];
}
@@ -556,6 +593,7 @@ class ShareAPIController extends OCSController {
* 200: Share created
*/
#[NoAdminRequired]
+ #[UserRateLimit(limit: 20, period: 600)]
public function createShare(
?string $path = null,
?int $permissions = null,
@@ -592,7 +630,7 @@ class ShareAPIController extends OCSController {
// combine all permissions to determine if the user can share this file
$nodes = $userFolder->getById($node->getId());
foreach ($nodes as $nodeById) {
- /** @var \OC\Files\FileInfo $fileInfo */
+ /** @var FileInfo $fileInfo */
$fileInfo = $node->getFileInfo();
$fileInfo['permissions'] |= $nodeById->getPermissions();
}
@@ -637,7 +675,9 @@ class ShareAPIController extends OCSController {
$share = $this->setShareAttributes($share, $attributes);
}
- // Expire date
+ // Expire date checks
+ // Normally, null means no expiration date but we still set the default for backwards compatibility
+ // If the client sends an empty string, we set noExpirationDate to true
if ($expireDate !== null) {
if ($expireDate !== '') {
try {
@@ -654,7 +694,6 @@ class ShareAPIController extends OCSController {
}
$share->setSharedBy($this->userId);
- $this->checkInheritedAttributes($share);
// Handle mail send
if (is_null($sendMail)) {
@@ -752,7 +791,7 @@ class ShareAPIController extends OCSController {
$share->setSharedWith($shareWith);
$share->setPermissions($permissions);
} elseif ($shareType === IShare::TYPE_CIRCLE) {
- if (!\OC::$server->getAppManager()->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
+ if (!Server::get(IAppManager::class)->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
throw new OCSNotFoundException($this->l->t('You cannot share to a Team if the app is not enabled'));
}
@@ -767,19 +806,19 @@ class ShareAPIController extends OCSController {
} elseif ($shareType === IShare::TYPE_ROOM) {
try {
$this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
}
} elseif ($shareType === IShare::TYPE_DECK) {
try {
$this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
}
} elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
try {
$this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
}
} else {
@@ -787,6 +826,7 @@ class ShareAPIController extends OCSController {
}
$share->setShareType($shareType);
+ $this->checkInheritedAttributes($share);
if ($note !== '') {
$share->setNote($note);
@@ -797,6 +837,9 @@ class ShareAPIController extends OCSController {
} catch (HintException $e) {
$code = $e->getCode() === 0 ? 403 : $e->getCode();
throw new OCSException($e->getHint(), $code);
+ } catch (GenericShareException|\InvalidArgumentException $e) {
+ $this->logger->error($e->getMessage(), ['exception' => $e]);
+ throw new OCSForbiddenException($e->getMessage(), $e);
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
throw new OCSForbiddenException('Failed to create share.', $e);
@@ -839,7 +882,7 @@ class ShareAPIController extends OCSController {
}
if ($includeTags) {
- $formatted = Helper::populateTags($formatted, \OC::$server->getTagManager());
+ $formatted = $this->populateTags($formatted);
}
return $formatted;
@@ -958,9 +1001,9 @@ class ShareAPIController extends OCSController {
: Constants::PERMISSION_READ;
}
- // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
- && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ && $this->shareManager->outgoingServer2ServerSharesAllowed()
+ && $this->appConfig->getValueBool('core', ConfigLexicon::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES)) {
$permissions |= Constants::PERMISSION_SHARE;
}
@@ -1092,8 +1135,7 @@ class ShareAPIController extends OCSController {
$formatted = $this->fixMissingDisplayName($formatted);
if ($includeTags) {
- $formatted =
- Helper::populateTags($formatted, \OC::$server->getTagManager());
+ $formatted = $this->populateTags($formatted);
}
return $formatted;
@@ -1250,17 +1292,17 @@ class ShareAPIController extends OCSController {
}
if (
- $permissions === null &&
- $password === null &&
- $sendPasswordByTalk === null &&
- $publicUpload === null &&
- $expireDate === null &&
- $note === null &&
- $label === null &&
- $hideDownload === null &&
- $attributes === null &&
- $sendMail === null &&
- $token === null
+ $permissions === null
+ && $password === null
+ && $sendPasswordByTalk === null
+ && $publicUpload === null
+ && $expireDate === null
+ && $note === null
+ && $label === null
+ && $hideDownload === null
+ && $attributes === null
+ && $sendMail === null
+ && $token === null
) {
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
}
@@ -1272,7 +1314,6 @@ class ShareAPIController extends OCSController {
if ($attributes !== null) {
$share = $this->setShareAttributes($share, $attributes);
}
- $this->checkInheritedAttributes($share);
// Handle mail send
if ($sendMail === 'true' || $sendMail === 'false') {
@@ -1286,16 +1327,11 @@ class ShareAPIController extends OCSController {
|| $share->getShareType() === IShare::TYPE_EMAIL) {
// Update hide download state
- $attributes = $share->getAttributes() ?? $share->newAttributes();
if ($hideDownload === 'true') {
$share->setHideDownload(true);
- $attributes->setAttribute('permissions', 'download', false);
} elseif ($hideDownload === 'false') {
$share->setHideDownload(false);
- $attributes->setAttribute('permissions', 'download', true);
}
- $share->setAttributes($attributes);
-
// If either manual permissions are specified or publicUpload
// then we need to also update the permissions of the share
@@ -1359,6 +1395,7 @@ class ShareAPIController extends OCSController {
}
try {
+ $this->checkInheritedAttributes($share);
$share = $this->shareManager->updateShare($share);
} catch (HintException $e) {
$code = $e->getCode() === 0 ? 403 : $e->getCode();
@@ -1522,7 +1559,7 @@ class ShareAPIController extends OCSController {
if ($share->getShareType() === IShare::TYPE_ROOM) {
try {
return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
return false;
}
}
@@ -1530,7 +1567,7 @@ class ShareAPIController extends OCSController {
if ($share->getShareType() === IShare::TYPE_DECK) {
try {
return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
return false;
}
}
@@ -1538,7 +1575,7 @@ class ShareAPIController extends OCSController {
if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
try {
return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
return false;
}
}
@@ -1560,8 +1597,8 @@ class ShareAPIController extends OCSController {
// The owner of the file and the creator of the share
// can always edit the share
- if ($share->getShareOwner() === $this->userId ||
- $share->getSharedBy() === $this->userId
+ if ($share->getShareOwner() === $this->userId
+ || $share->getSharedBy() === $this->userId
) {
return true;
}
@@ -1593,16 +1630,16 @@ class ShareAPIController extends OCSController {
// if the user is the recipient, i can unshare
// the share with self
- if ($share->getShareType() === IShare::TYPE_USER &&
- $share->getSharedWith() === $this->userId
+ if ($share->getShareType() === IShare::TYPE_USER
+ && $share->getSharedWith() === $this->userId
) {
return true;
}
// The owner of the file and the creator of the share
// can always delete the share
- if ($share->getShareOwner() === $this->userId ||
- $share->getSharedBy() === $this->userId
+ if ($share->getShareOwner() === $this->userId
+ || $share->getSharedBy() === $this->userId
) {
return true;
}
@@ -1629,16 +1666,16 @@ class ShareAPIController extends OCSController {
* @suppress PhanUndeclaredClassMethod
*/
protected function canDeleteShareFromSelf(IShare $share): bool {
- if ($share->getShareType() !== IShare::TYPE_GROUP &&
- $share->getShareType() !== IShare::TYPE_ROOM &&
- $share->getShareType() !== IShare::TYPE_DECK &&
- $share->getShareType() !== IShare::TYPE_SCIENCEMESH
+ if ($share->getShareType() !== IShare::TYPE_GROUP
+ && $share->getShareType() !== IShare::TYPE_ROOM
+ && $share->getShareType() !== IShare::TYPE_DECK
+ && $share->getShareType() !== IShare::TYPE_SCIENCEMESH
) {
return false;
}
- if ($share->getShareOwner() === $this->userId ||
- $share->getSharedBy() === $this->userId
+ if ($share->getShareOwner() === $this->userId
+ || $share->getSharedBy() === $this->userId
) {
// Delete the whole share, not just for self
return false;
@@ -1656,7 +1693,7 @@ class ShareAPIController extends OCSController {
if ($share->getShareType() === IShare::TYPE_ROOM) {
try {
return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
return false;
}
}
@@ -1664,7 +1701,7 @@ class ShareAPIController extends OCSController {
if ($share->getShareType() === IShare::TYPE_DECK) {
try {
return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
return false;
}
}
@@ -1672,7 +1709,7 @@ class ShareAPIController extends OCSController {
if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
try {
return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
return false;
}
}
@@ -1798,10 +1835,10 @@ class ShareAPIController extends OCSController {
* Returns the helper of ShareAPIController for room shares.
*
* If the Talk application is not enabled or the helper is not available
- * a QueryException is thrown instead.
+ * a ContainerExceptionInterface is thrown instead.
*
* @return \OCA\Talk\Share\Helper\ShareAPIController
- * @throws QueryException
+ * @throws ContainerExceptionInterface
*/
private function getRoomShareHelper() {
if (!$this->appManager->isEnabledForUser('spreed')) {
@@ -1815,10 +1852,10 @@ class ShareAPIController extends OCSController {
* Returns the helper of ShareAPIHelper for deck shares.
*
* If the Deck application is not enabled or the helper is not available
- * a QueryException is thrown instead.
+ * a ContainerExceptionInterface is thrown instead.
*
- * @return \OCA\Deck\Sharing\ShareAPIHelper
- * @throws QueryException
+ * @return ShareAPIHelper
+ * @throws ContainerExceptionInterface
*/
private function getDeckShareHelper() {
if (!$this->appManager->isEnabledForUser('deck')) {
@@ -1832,10 +1869,10 @@ class ShareAPIController extends OCSController {
* Returns the helper of ShareAPIHelper for sciencemesh shares.
*
* If the sciencemesh application is not enabled or the helper is not available
- * a QueryException is thrown instead.
+ * a ContainerExceptionInterface is thrown instead.
*
- * @return \OCA\Deck\Sharing\ShareAPIHelper
- * @throws QueryException
+ * @return ShareAPIHelper
+ * @throws ContainerExceptionInterface
*/
private function getSciencemeshShareHelper() {
if (!$this->appManager->isEnabledForUser('sciencemesh')) {
@@ -1871,8 +1908,8 @@ class ShareAPIController extends OCSController {
continue;
}
- $providerShares =
- $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
+ $providerShares
+ = $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
$shares = array_merge($shares, $providerShares);
}
@@ -1968,7 +2005,7 @@ class ShareAPIController extends OCSController {
return true;
}
- if ($share->getShareType() === IShare::TYPE_CIRCLE && \OC::$server->getAppManager()->isEnabledForUser('circles')
+ if ($share->getShareType() === IShare::TYPE_CIRCLE && Server::get(IAppManager::class)->isEnabledForUser('circles')
&& class_exists('\OCA\Circles\Api\v1\Circles')) {
$hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
@@ -1984,7 +2021,7 @@ class ShareAPIController extends OCSController {
return true;
}
return false;
- } catch (QueryException $e) {
+ } catch (ContainerExceptionInterface $e) {
return false;
}
}
@@ -2083,30 +2120,50 @@ class ShareAPIController extends OCSController {
if (!$share->getSharedBy()) {
return; // Probably in a test
}
+
+ $canDownload = false;
+ $hideDownload = true;
+
$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
- $node = $userFolder->getFirstNodeById($share->getNodeId());
- if (!$node) {
- return;
- }
- if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
- $storage = $node->getStorage();
- if ($storage instanceof Wrapper) {
- $storage = $storage->getInstanceOfStorage(SharedStorage::class);
- if ($storage === null) {
- throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
- }
- } else {
- throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
+ $nodes = $userFolder->getById($share->getNodeId());
+ foreach ($nodes as $node) {
+ // Owner always can download it - so allow it and break
+ if ($node->getOwner()?->getUID() === $share->getSharedBy()) {
+ $canDownload = true;
+ $hideDownload = false;
+ break;
}
- /** @var SharedStorage $storage */
- $inheritedAttributes = $storage->getShare()->getAttributes();
- if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
- $share->setHideDownload(true);
- $attributes = $share->getAttributes();
- if ($attributes) {
- $attributes->setAttribute('permissions', 'download', false);
- $share->setAttributes($attributes);
+
+ if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
+ $storage = $node->getStorage();
+ if ($storage instanceof Wrapper) {
+ $storage = $storage->getInstanceOfStorage(SharedStorage::class);
+ if ($storage === null) {
+ throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
+ }
+ } else {
+ throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
}
+
+ /** @var SharedStorage $storage */
+ $originalShare = $storage->getShare();
+ $inheritedAttributes = $originalShare->getAttributes();
+ // hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner)
+ $hideDownload = $hideDownload && $originalShare->getHideDownload();
+ // allow download if already allowed by previous share or when the current share allows downloading
+ $canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false;
+ } elseif ($node->getStorage()->instanceOfStorage(Storage::class)) {
+ $canDownload = true; // in case of federation storage, we can expect the download to be activated by default
+ }
+ }
+
+ if ($hideDownload || !$canDownload) {
+ $share->setHideDownload(true);
+
+ if (!$canDownload) {
+ $attributes = $share->getAttributes() ?? $share->newAttributes();
+ $attributes->setAttribute('permissions', 'download', false);
+ $share->setAttributes($attributes);
}
}
}
@@ -2125,7 +2182,7 @@ class ShareAPIController extends OCSController {
* 200: The email notification was sent successfully
*/
#[NoAdminRequired]
- #[UserRateLimit(limit: 5, period: 120)]
+ #[UserRateLimit(limit: 10, period: 600)]
public function sendShareEmail(string $id, $password = ''): DataResponse {
try {
$share = $this->getShareById($id);
@@ -2198,4 +2255,41 @@ class ShareAPIController extends OCSController {
throw new OCSException($this->l->t('Failed to generate a unique token'));
}
}
+
+ /**
+ * Populate the result set with file tags
+ *
+ * @psalm-template T of array{tags?: list<string>, file_source: int, ...array<string, mixed>}
+ * @param list<T> $fileList
+ * @return list<T> file list populated with tags
+ */
+ private function populateTags(array $fileList): array {
+ $tagger = $this->tagManager->load('files');
+ $tags = $tagger->getTagsForObjects(array_map(static fn (array $fileData) => $fileData['file_source'], $fileList));
+
+ if (!is_array($tags)) {
+ throw new \UnexpectedValueException('$tags must be an array');
+ }
+
+ // Set empty tag array
+ foreach ($fileList as &$fileData) {
+ $fileData['tags'] = [];
+ }
+ unset($fileData);
+
+ if (!empty($tags)) {
+ foreach ($tags as $fileId => $fileTags) {
+ foreach ($fileList as &$fileData) {
+ if ($fileId !== $fileData['file_source']) {
+ continue;
+ }
+
+ $fileData['tags'] = $fileTags;
+ }
+ unset($fileData);
+ }
+ }
+
+ return $fileList;
+ }
}