From 8ec4760645b21bfeca4b58572b321881ffcd2635 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 27 Apr 2023 22:24:16 +0200 Subject: [PATCH 1/7] PoC: SystemTags endpoint to return tags used by a user with meta data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Target case is photos app: when visiting the tags category, all systemtags of the whole cloud are retrieved. In subequent steps the next tag is requested until the browser view is filled with tag tiles (i.e. previews are requested just as well). With this approach, we incorpoate the dav search and look for user related tags that are used by them, and already returns the statistics (number of files tagged with the respective tag) as well as a file id for the purpose to load the preview. This defaults to the file with the highest id. Call: curl -s -u 'user:password' \ 'https://my.nc.srv/remote.php/dav/systemtags-current' \ -X PROPFIND -H 'Accept: text/plain' \ -H 'Accept-Language: en-US,en;q=0.5' -H 'Depth: 1' \ -H 'Content-Type: text/plain;charset=UTF-8' \ --data @/home/doe/request-systemtag-props.xml With request-systemtag-props.xml: Example output: … /master/remote.php/dav/systemtags/84 84 Computer true true true 42 924022 HTTP/1.1 200 OK /remote.php/dav/systemtags/97 97 Bear true true true 1 923422 HTTP/1.1 200 OK … Signed-off-by: Arthur Schiwon --- .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + apps/dav/lib/RootCollection.php | 7 ++ apps/dav/lib/SystemTag/SystemTagNode.php | 19 ++++++ apps/dav/lib/SystemTag/SystemTagPlugin.php | 24 +++++++ .../SystemTag/SystemTagsInUseCollection.php | 67 +++++++++++++++++++ lib/private/Files/Cache/CacheQueryBuilder.php | 22 +++++- lib/private/Files/Cache/QuerySearchHelper.php | 58 ++++++++++------ lib/private/Files/Node/Folder.php | 64 +++++++++++------- 9 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 apps/dav/lib/SystemTag/SystemTagsInUseCollection.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index d3290c4e79260..a29e7e72327cb 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -301,6 +301,7 @@ 'OCA\\DAV\\SystemTag\\SystemTagNode' => $baseDir . '/../lib/SystemTag/SystemTagNode.php', 'OCA\\DAV\\SystemTag\\SystemTagPlugin' => $baseDir . '/../lib/SystemTag/SystemTagPlugin.php', 'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => $baseDir . '/../lib/SystemTag/SystemTagsByIdCollection.php', + 'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => $baseDir . '/../lib/SystemTag/SystemTagsInUseCollection.php', 'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php', 'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php', 'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => $baseDir . '/../lib/SystemTag/SystemTagsRelationsCollection.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 4d425f70f3b84..1a7e239acd1cf 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -316,6 +316,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\SystemTag\\SystemTagNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagNode.php', 'OCA\\DAV\\SystemTag\\SystemTagPlugin' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagPlugin.php', 'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsByIdCollection.php', + 'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsInUseCollection.php', 'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php', 'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php', 'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsRelationsCollection.php', diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 29ab65d46a923..88517ee83f7d6 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -48,6 +48,7 @@ use OCP\App\IAppManager; use OCP\AppFramework\Utility\ITimeFactory; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\IRootFolder; use OCP\IConfig; use Psr\Log\LoggerInterface; use Sabre\DAV\SimpleCollection; @@ -65,6 +66,7 @@ public function __construct() { $dispatcher = \OC::$server->get(IEventDispatcher::class); $config = \OC::$server->get(IConfig::class); $proxyMapper = \OC::$server->query(ProxyMapper::class); + $rootFolder = \OCP\Server::get(IRootFolder::class); $userPrincipalBackend = new Principal( $userManager, @@ -132,6 +134,10 @@ public function __construct() { $groupManager, \OC::$server->getEventDispatcher() ); + $systemTagInUseCollection = new SystemTag\SystemTagsInUseCollection( + $userSession, + $rootFolder + ); $commentsCollection = new Comments\RootCollection( \OC::$server->getCommentsManager(), $userManager, @@ -180,6 +186,7 @@ public function __construct() { $systemAddressBookRoot]), $systemTagCollection, $systemTagRelationsCollection, + $systemTagInUseCollection, $commentsCollection, $uploadCollection, $avatarCollection, diff --git a/apps/dav/lib/SystemTag/SystemTagNode.php b/apps/dav/lib/SystemTag/SystemTagNode.php index a31deb59a93a8..597aff8420376 100644 --- a/apps/dav/lib/SystemTag/SystemTagNode.php +++ b/apps/dav/lib/SystemTag/SystemTagNode.php @@ -64,6 +64,9 @@ class SystemTagNode implements \Sabre\DAV\INode { */ protected $isAdmin; + protected int $numberOfFiles = -1; + protected int $referenceFileId = -1; + /** * Sets up the node, expects a full path name * @@ -172,4 +175,20 @@ public function delete() { throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e); } } + + public function getNumberOfFiles(): int { + return $this->numberOfFiles; + } + + public function setNumberOfFiles(int $numberOfFiles): void { + $this->numberOfFiles = $numberOfFiles; + } + + public function getReferenceFileId(): int { + return $this->referenceFileId; + } + + public function setReferenceFileId(int $referenceFileId): void { + $this->referenceFileId = $referenceFileId; + } } diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index b6bd7d3b7cdb9..115f126aef088 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -56,6 +56,8 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { public const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable'; public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups'; public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign'; + public const NUM_FILES_PROPERTYNAME = '{http://nextcloud.org/ns}files-assigned'; + public const FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid'; /** * @var \Sabre\DAV\Server $server @@ -224,6 +226,11 @@ public function handleGetProperties( return; } + // child nodes from systemtags-current should point to normal tag endpoint + if (preg_match('/^systemtags-current\/[0-9]+/', $propFind->getPath())) { + $propFind->setPath(str_replace('systemtags-current/', 'systemtags/', $propFind->getPath())); + } + $propFind->handle(self::ID_PROPERTYNAME, function () use ($node) { return $node->getSystemTag()->getId(); }); @@ -258,6 +265,16 @@ public function handleGetProperties( } return implode('|', $groups); }); + + if ($node instanceof SystemTagNode) { + $propFind->handle(self::NUM_FILES_PROPERTYNAME, function () use ($node): int { + return $node->getNumberOfFiles(); + }); + + $propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node): int { + return $node->getReferenceFileId(); + }); + } } /** @@ -279,6 +296,8 @@ public function handleUpdateProperties($path, PropPatch $propPatch) { self::USERVISIBLE_PROPERTYNAME, self::USERASSIGNABLE_PROPERTYNAME, self::GROUPS_PROPERTYNAME, + self::NUM_FILES_PROPERTYNAME, + self::FILEID_PROPERTYNAME, ], function ($props) use ($node) { $tag = $node->getSystemTag(); $name = $tag->getName(); @@ -315,6 +334,11 @@ public function handleUpdateProperties($path, PropPatch $propPatch) { $this->tagManager->setTagGroups($tag, $groupIds); } + if (isset($props[self::NUM_FILES_PROPERTYNAME]) || isset($props[self::FILEID_PROPERTYNAME])) { + // read-only properties + throw new Forbidden(); + } + if ($updateTag) { $node->update($name, $userVisible, $userAssignable); } diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php new file mode 100644 index 0000000000000..938b14e1f651e --- /dev/null +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -0,0 +1,67 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\SystemTag; + +use OC\SystemTag\SystemTag; +use OCP\Files\IRootFolder; +use OCP\IUserSession; +use OCP\SystemTag\ISystemTagManager; +use Sabre\DAV\Exception\Forbidden; + +class SystemTagsInUseCollection extends \Sabre\DAV\SimpleCollection { + protected IUserSession $userSession; + protected IRootFolder $rootFolder; + + public function __construct(IUserSession $userSession, IRootFolder $rootFolder) { + $this->userSession = $userSession; + $this->rootFolder = $rootFolder; + $this->name = 'systemtags-current'; + } + + public function setName($name): void { + throw new Forbidden('Permission denied to rename this collection'); + } + + public function getChildren() { + $user = $this->userSession->getUser(); + if ($user === null) { + throw new Forbidden('Permission denied to read this collection'); + } + + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $result = $userFolder->getSystemTags('image'); + $children = []; + foreach ($result as $tagData) { + $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']); + $node = new SystemTagNode($tag, $user, false, \OCP\Server::get(ISystemTagManager::class)); + $node->setNumberOfFiles($tagData['number_files']); + $node->setReferenceFileId($tagData['ref_file_id']); + $children[] = $node; + } + return $children; + } +} diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index 496a8361d7748..c5563750c4d59 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -41,8 +41,28 @@ public function __construct(IDBConnection $connection, SystemConfig $systemConfi parent::__construct($connection, $systemConfig, $logger); } + public function selectTagUsage(): self { + $this + ->select('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable') + ->selectAlias($this->createFunction('COUNT(filecache.fileid)'), 'number_files') + ->selectAlias($this->createFunction('MAX(filecache.fileid)'), 'ref_file_id') + ->from('filecache', 'filecache') + ->leftJoin('filecache', 'systemtag_object_mapping', 'systemtagmap', $this->expr()->andX( + $this->expr()->eq('filecache.fileid', $this->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)), + $this->expr()->eq('systemtagmap.objecttype', $this->createNamedParameter('files')) + )) + ->leftJoin('systemtagmap', 'systemtag', 'systemtag', $this->expr()->andX( + $this->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'), + $this->expr()->eq('systemtag.visibility', $this->createNamedParameter(true)) + )) + ->where($this->expr()->like('systemtag.name', $this->createNamedParameter('_%'))) + ->groupBy('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable'); + + return $this; + } + public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) { - $name = $alias ? $alias : 'filecache'; + $name = $alias ?: 'filecache'; $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size') ->from('filecache', $name); diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index 3529ede9746d7..fdba8a1dd7442 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -75,6 +75,41 @@ protected function getQueryBuilder() { ); } + protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void { + $storageFilters = array_values(array_map(function (ICache $cache) { + return $cache->getQueryFilterForStorage(); + }, $caches)); + $storageFilter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters); + $filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]); + $this->queryOptimizer->processOperator($filter); + + $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter); + if ($searchExpr) { + $query->andWhere($searchExpr); + } + + $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder()); + + if ($searchQuery->getLimit()) { + $query->setMaxResults($searchQuery->getLimit()); + } + if ($searchQuery->getOffset()) { + $query->setFirstResult($searchQuery->getOffset()); + } + } + + public function findUsedTagsInCaches(ISearchQuery $searchQuery, array $caches): array { + $query = $this->getQueryBuilder(); + $query->selectTagUsage(); + + $this->applySearchConstraints($query, $searchQuery, $caches); + + $result = $query->execute(); + $tags = $result->fetchAll(); + $result->closeCursor(); + return $tags; + } + /** * Perform a file system search in multiple caches * @@ -128,26 +163,7 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array )); } - $storageFilters = array_values(array_map(function (ICache $cache) { - return $cache->getQueryFilterForStorage(); - }, $caches)); - $storageFilter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters); - $filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]); - $this->queryOptimizer->processOperator($filter); - - $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($builder, $filter); - if ($searchExpr) { - $query->andWhere($searchExpr); - } - - $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder()); - - if ($searchQuery->getLimit()) { - $query->setMaxResults($searchQuery->getLimit()); - } - if ($searchQuery->getOffset()) { - $query->setFirstResult($searchQuery->getOffset()); - } + $this->applySearchConstraints($query, $searchQuery, $caches); $result = $query->execute(); $files = $result->fetchAll(); @@ -159,7 +175,7 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array $result->closeCursor(); // loop through all caches for each result to see if the result matches that storage - // results are grouped by the same array keys as the caches argument to allow the caller to distringuish the source of the results + // results are grouped by the same array keys as the caches argument to allow the caller to distinguish the source of the results $results = array_fill_keys(array_keys($caches), []); foreach ($rawEntries as $rawEntry) { foreach ($caches as $cacheKey => $cache) { diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index bf9ae3c148d5c..e8d507b8a8f3b 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -203,7 +203,7 @@ public function newFile($path, $content = null) { throw new NotPermittedException('No create permission for path'); } - private function queryFromOperator(ISearchOperator $operator, string $uid = null): ISearchQuery { + private function queryFromOperator(ISearchOperator $operator, string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery { if ($uid === null) { $user = null; } else { @@ -211,36 +211,17 @@ private function queryFromOperator(ISearchOperator $operator, string $uid = null $userManager = \OC::$server->query(IUserManager::class); $user = $userManager->get($uid); } - return new SearchQuery($operator, 0, 0, [], $user); + return new SearchQuery($operator, $limit, $offset, [], $user); } /** - * search for files with the name matching $query - * - * @param string|ISearchQuery $query - * @return \OC\Files\Node\Node[] + * @psalm-return list{0: array, 1: array} */ - public function search($query) { - if (is_string($query)) { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%')); - } - - // search is handled by a single query covering all caches that this folder contains - // this is done by collect - - $limitToHome = $query->limitToHome(); - if ($limitToHome && count(explode('/', $this->path)) !== 3) { - throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); - } - + protected function getCachesAndMountpointsForSearch(bool $limitToHome = false): array { $rootLength = strlen($this->path); $mount = $this->root->getMount($this->path); $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($this->path); - - // collect all caches for this folder, indexed by their mountpoint relative to this folder - // and save the mount which is needed later to construct the FileInfo objects - if ($internalPath !== '') { // a temporary CacheJail is used to handle filtering down the results to within this folder $caches = ['' => new CacheJail($storage->getCache(''), $internalPath)]; @@ -261,12 +242,36 @@ public function search($query) { } } + return [$caches, $mountByMountPoint]; + } + + /** + * search for files with the name matching $query + * + * @param string|ISearchQuery $query + * @return \OC\Files\Node\Node[] + */ + public function search($query) { + if (is_string($query)) { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%')); + } + + // search is handled by a single query covering all caches that this folder contains + // this is done by collect + + $limitToHome = $query->limitToHome(); + if ($limitToHome && count(explode('/', $this->path)) !== 3) { + throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); + } + + [$caches, $mountByMountPoint] = $this->getCachesAndMountpointsForSearch($limitToHome); + /** @var QuerySearchHelper $searchHelper */ $searchHelper = \OC::$server->get(QuerySearchHelper::class); $resultsPerCache = $searchHelper->searchInCaches($query, $caches); // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all - $files = array_merge(...array_map(function (array $results, $relativeMountPoint) use ($mountByMountPoint) { + $files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) { $mount = $mountByMountPoint[$relativeMountPoint]; return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) { return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result); @@ -331,6 +336,17 @@ public function searchByTag($tag, $userId) { return $this->search($query); } + /** + * @return Node[] + */ + public function getSystemTags(string $mediaType, int $limit = 0, int $offset = 0): array { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); + [$caches, ] = $this->getCachesAndMountpointsForSearch(); + /** @var QuerySearchHelper $searchHelper */ + $searchHelper = \OCP\Server::get(QuerySearchHelper::class); + return $searchHelper->findUsedTagsInCaches($query, $caches); + } + /** * @param int $id * @return \OC\Files\Node\Node[] From de008e1109241526a740c3d082511ed58dd2f9a7 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 4 May 2023 11:57:07 +0200 Subject: [PATCH 2/7] feat: specify media type via url path: systemtags-current/$mediaType - only the media part of the mime type can be search, but not the full mime type. It can be added, should it become necessary. - thus fixes previously hardcoded selector for image/ types - also fixes a return type hint - adds a return type hint Signed-off-by: Arthur Schiwon --- .../lib/SystemTag/SystemTagsInUseCollection.php | 17 +++++++++++++++-- lib/private/Files/Cache/QuerySearchHelper.php | 4 ++++ lib/private/Files/Node/Folder.php | 11 +++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index 938b14e1f651e..94a86352a9612 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -31,21 +31,34 @@ use OCP\IUserSession; use OCP\SystemTag\ISystemTagManager; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; class SystemTagsInUseCollection extends \Sabre\DAV\SimpleCollection { protected IUserSession $userSession; protected IRootFolder $rootFolder; + protected string $mediaType; - public function __construct(IUserSession $userSession, IRootFolder $rootFolder) { + public function __construct(IUserSession $userSession, IRootFolder $rootFolder, string $mediaType = '') { $this->userSession = $userSession; $this->rootFolder = $rootFolder; + $this->mediaType = $mediaType; $this->name = 'systemtags-current'; + if ($this->mediaType != '') { + $this->name .= '/' . $this->mediaType; + } } public function setName($name): void { throw new Forbidden('Permission denied to rename this collection'); } + public function getChild($name) { + if ($this->mediaType !== '') { + throw new NotFound('Invalid media type'); + } + return new self($this->userSession, $this->rootFolder, $name); + } + public function getChildren() { $user = $this->userSession->getUser(); if ($user === null) { @@ -53,7 +66,7 @@ public function getChildren() { } $userFolder = $this->rootFolder->getUserFolder($user->getUID()); - $result = $userFolder->getSystemTags('image'); + $result = $userFolder->getSystemTags($this->mediaType); $children = []; foreach ($result as $tagData) { $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']); diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index fdba8a1dd7442..19ce5a7b23823 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -98,6 +98,10 @@ protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery } } + + /** + * @return array + */ public function findUsedTagsInCaches(ISearchQuery $searchQuery, array $caches): array { $query = $this->getQueryBuilder(); $query->selectTagUsage(); diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index e8d507b8a8f3b..06b902dd64b9b 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -337,10 +337,17 @@ public function searchByTag($tag, $userId) { } /** - * @return Node[] + * + * @return array */ public function getSystemTags(string $mediaType, int $limit = 0, int $offset = 0): array { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); + // Currently query has to have exactly one search condition. If no media type is provided, + // we fall back to the presence of a systemtag. + if (empty($mediaType)) { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), null, $limit, $offset); + } else { + $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); + } [$caches, ] = $this->getCachesAndMountpointsForSearch(); /** @var QuerySearchHelper $searchHelper */ $searchHelper = \OCP\Server::get(QuerySearchHelper::class); From 2c9377c66aa72fbd6e030890be12f35c496c7be3 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 4 May 2023 21:17:55 +0200 Subject: [PATCH 3/7] fix: improve naming of new systemtags endpoint to systemtags-assigned Signed-off-by: Arthur Schiwon --- apps/dav/lib/SystemTag/SystemTagPlugin.php | 6 +++--- apps/dav/lib/SystemTag/SystemTagsInUseCollection.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index 115f126aef088..4f1d6d4823902 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -226,9 +226,9 @@ public function handleGetProperties( return; } - // child nodes from systemtags-current should point to normal tag endpoint - if (preg_match('/^systemtags-current\/[0-9]+/', $propFind->getPath())) { - $propFind->setPath(str_replace('systemtags-current/', 'systemtags/', $propFind->getPath())); + // child nodes from systemtags-assigned should point to normal tag endpoint + if (preg_match('/^systemtags-assigned\/[0-9]+/', $propFind->getPath())) { + $propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath())); } $propFind->handle(self::ID_PROPERTYNAME, function () use ($node) { diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index 94a86352a9612..790ebee792f26 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -42,7 +42,7 @@ public function __construct(IUserSession $userSession, IRootFolder $rootFolder, $this->userSession = $userSession; $this->rootFolder = $rootFolder; $this->mediaType = $mediaType; - $this->name = 'systemtags-current'; + $this->name = 'systemtags-assigned'; if ($this->mediaType != '') { $this->name .= '/' . $this->mediaType; } From f42cae2bdb55c18686c4fc58ff13db89845508f0 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 4 May 2023 21:58:08 +0200 Subject: [PATCH 4/7] chore: polish SystemTagsInUseCollection - DI SystemTagManager - add some comments and doc - catch NoUserException - add return type hints Signed-off-by: Arthur Schiwon --- apps/dav/lib/RootCollection.php | 5 +-- .../SystemTag/SystemTagsInUseCollection.php | 40 +++++++++++++++---- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 88517ee83f7d6..a1d32fdb72ebd 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -134,10 +134,7 @@ public function __construct() { $groupManager, \OC::$server->getEventDispatcher() ); - $systemTagInUseCollection = new SystemTag\SystemTagsInUseCollection( - $userSession, - $rootFolder - ); + $systemTagInUseCollection = \OCP\Server::get(SystemTag\SystemTagsInUseCollection::class); $commentsCollection = new Comments\RootCollection( \OC::$server->getCommentsManager(), $userManager, diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index 790ebee792f26..aa45e7d8f0717 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -27,20 +27,31 @@ namespace OCA\DAV\SystemTag; use OC\SystemTag\SystemTag; +use OC\User\NoUserException; use OCP\Files\IRootFolder; +use OCP\Files\NotPermittedException; use OCP\IUserSession; use OCP\SystemTag\ISystemTagManager; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\SimpleCollection; -class SystemTagsInUseCollection extends \Sabre\DAV\SimpleCollection { +class SystemTagsInUseCollection extends SimpleCollection { protected IUserSession $userSession; protected IRootFolder $rootFolder; protected string $mediaType; + private ISystemTagManager $systemTagManager; - public function __construct(IUserSession $userSession, IRootFolder $rootFolder, string $mediaType = '') { + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct( + IUserSession $userSession, + IRootFolder $rootFolder, + ISystemTagManager $systemTagManager, + string $mediaType = '' + ) { $this->userSession = $userSession; $this->rootFolder = $rootFolder; + $this->systemTagManager = $systemTagManager; $this->mediaType = $mediaType; $this->name = 'systemtags-assigned'; if ($this->mediaType != '') { @@ -52,25 +63,38 @@ public function setName($name): void { throw new Forbidden('Permission denied to rename this collection'); } - public function getChild($name) { + public function getChild($name): self { if ($this->mediaType !== '') { throw new NotFound('Invalid media type'); } - return new self($this->userSession, $this->rootFolder, $name); + return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $name); } - public function getChildren() { + /** + * @return SystemTagNode[] + * @throws NotPermittedException + * @throws Forbidden + */ + public function getChildren(): array { $user = $this->userSession->getUser(); - if ($user === null) { + $userFolder = null; + try { + if ($user) { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + } + } catch (NoUserException) { + // will throw a Sabre exception in the next step. + } + if ($user === null || $userFolder === null) { throw new Forbidden('Permission denied to read this collection'); } - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); $result = $userFolder->getSystemTags($this->mediaType); $children = []; foreach ($result as $tagData) { $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']); - $node = new SystemTagNode($tag, $user, false, \OCP\Server::get(ISystemTagManager::class)); + // read only, so we can submit the isAdmin parameter as false generally + $node = new SystemTagNode($tag, $user, false, $this->systemTagManager); $node->setNumberOfFiles($tagData['number_files']); $node->setReferenceFileId($tagData['ref_file_id']); $children[] = $node; From 1e5de8aff49363410851ecd5fd389464e531d621 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 5 May 2023 21:28:09 +0200 Subject: [PATCH 5/7] refactor: remove SystemTag logic from Folder into QuerySearchHelper - adds OC\SystemTag\SystemTagsInFilesDetector where the search logic is moved to Signed-off-by: Arthur Schiwon --- .../SystemTag/SystemTagsInUseCollection.php | 10 ++- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Files/Cache/QuerySearchHelper.php | 39 +++++++++++ lib/private/Files/Node/Folder.php | 53 +-------------- .../SystemTag/SystemTagsInFilesDetector.php | 68 +++++++++++++++++++ 6 files changed, 117 insertions(+), 55 deletions(-) create mode 100644 lib/private/SystemTag/SystemTagsInFilesDetector.php diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index aa45e7d8f0717..b57e685e7e71b 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -27,6 +27,7 @@ namespace OCA\DAV\SystemTag; use OC\SystemTag\SystemTag; +use OC\SystemTag\SystemTagsInFilesDetector; use OC\User\NoUserException; use OCP\Files\IRootFolder; use OCP\Files\NotPermittedException; @@ -40,19 +41,22 @@ class SystemTagsInUseCollection extends SimpleCollection { protected IUserSession $userSession; protected IRootFolder $rootFolder; protected string $mediaType; - private ISystemTagManager $systemTagManager; + protected ISystemTagManager $systemTagManager; + protected SystemTagsInFilesDetector $systemTagsInFilesDetector; /** @noinspection PhpMissingParentConstructorInspection */ public function __construct( IUserSession $userSession, IRootFolder $rootFolder, ISystemTagManager $systemTagManager, + SystemTagsInFilesDetector $systemTagsInFilesDetector, string $mediaType = '' ) { $this->userSession = $userSession; $this->rootFolder = $rootFolder; $this->systemTagManager = $systemTagManager; $this->mediaType = $mediaType; + $this->systemTagsInFilesDetector = $systemTagsInFilesDetector; $this->name = 'systemtags-assigned'; if ($this->mediaType != '') { $this->name .= '/' . $this->mediaType; @@ -67,7 +71,7 @@ public function getChild($name): self { if ($this->mediaType !== '') { throw new NotFound('Invalid media type'); } - return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $name); + return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->systemTagsInFilesDetector, $name); } /** @@ -89,7 +93,7 @@ public function getChildren(): array { throw new Forbidden('Permission denied to read this collection'); } - $result = $userFolder->getSystemTags($this->mediaType); + $result = $this->systemTagsInFilesDetector->detectAssignedSystemTagsIn($userFolder, $this->mediaType); $children = []; foreach ($result as $tagData) { $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index b19ba18d38d74..bd2e96a177f8a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1568,6 +1568,7 @@ 'OC\\SystemTag\\SystemTag' => $baseDir . '/lib/private/SystemTag/SystemTag.php', 'OC\\SystemTag\\SystemTagManager' => $baseDir . '/lib/private/SystemTag/SystemTagManager.php', 'OC\\SystemTag\\SystemTagObjectMapper' => $baseDir . '/lib/private/SystemTag/SystemTagObjectMapper.php', + 'OC\\SystemTag\\SystemTagsInFilesDetector' => $baseDir . '/lib/private/SystemTag/SystemTagsInFilesDetector.php', 'OC\\TagManager' => $baseDir . '/lib/private/TagManager.php', 'OC\\Tagging\\Tag' => $baseDir . '/lib/private/Tagging/Tag.php', 'OC\\Tagging\\TagMapper' => $baseDir . '/lib/private/Tagging/TagMapper.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 0babc7697e975..ffddd7723527d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1601,6 +1601,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\SystemTag\\SystemTag' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTag.php', 'OC\\SystemTag\\SystemTagManager' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagManager.php', 'OC\\SystemTag\\SystemTagObjectMapper' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagObjectMapper.php', + 'OC\\SystemTag\\SystemTagsInFilesDetector' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagsInFilesDetector.php', 'OC\\TagManager' => __DIR__ . '/../../..' . '/lib/private/TagManager.php', 'OC\\Tagging\\Tag' => __DIR__ . '/../../..' . '/lib/private/Tagging/Tag.php', 'OC\\Tagging\\TagMapper' => __DIR__ . '/../../..' . '/lib/private/Tagging/TagMapper.php', diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index 19ce5a7b23823..8271cbf5ba473 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -25,13 +25,18 @@ */ namespace OC\Files\Cache; +use OC\Files\Cache\Wrapper\CacheJail; +use OC\Files\Node\Root; use OC\Files\Search\QueryOptimizer\QueryOptimizer; use OC\Files\Search\SearchBinaryOperator; use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; +use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountPoint; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchQuery; use OCP\IDBConnection; @@ -191,4 +196,38 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array } return $results; } + + /** + * @return array{array, array} + */ + public function getCachesAndMountPointsForSearch(Root $root, string $path, bool $limitToHome = false): array { + $rootLength = strlen($path); + $mount = $root->getMount($path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($path); + + if ($internalPath !== '') { + // a temporary CacheJail is used to handle filtering down the results to within this folder + $caches = ['' => new CacheJail($storage->getCache(''), $internalPath)]; + } else { + $caches = ['' => $storage->getCache('')]; + } + $mountByMountPoint = ['' => $mount]; + + if (!$limitToHome) { + /** @var IMountPoint[] $mounts */ + $mounts = $root->getMountsIn($path); + foreach ($mounts as $mount) { + $storage = $mount->getStorage(); + if ($storage) { + $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/'); + $caches[$relativeMountPoint] = $storage->getCache(''); + $mountByMountPoint[$relativeMountPoint] = $mount; + } + } + } + + return [$caches, $mountByMountPoint]; + } + } diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 06b902dd64b9b..e6bae29807dd6 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -32,7 +32,6 @@ use OC\Files\Cache\QuerySearchHelper; use OC\Files\Search\SearchBinaryOperator; -use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; @@ -214,37 +213,6 @@ private function queryFromOperator(ISearchOperator $operator, string $uid = null return new SearchQuery($operator, $limit, $offset, [], $user); } - /** - * @psalm-return list{0: array, 1: array} - */ - protected function getCachesAndMountpointsForSearch(bool $limitToHome = false): array { - $rootLength = strlen($this->path); - $mount = $this->root->getMount($this->path); - $storage = $mount->getStorage(); - $internalPath = $mount->getInternalPath($this->path); - if ($internalPath !== '') { - // a temporary CacheJail is used to handle filtering down the results to within this folder - $caches = ['' => new CacheJail($storage->getCache(''), $internalPath)]; - } else { - $caches = ['' => $storage->getCache('')]; - } - $mountByMountPoint = ['' => $mount]; - - if (!$limitToHome) { - $mounts = $this->root->getMountsIn($this->path); - foreach ($mounts as $mount) { - $storage = $mount->getStorage(); - if ($storage) { - $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/'); - $caches[$relativeMountPoint] = $storage->getCache(''); - $mountByMountPoint[$relativeMountPoint] = $mount; - } - } - } - - return [$caches, $mountByMountPoint]; - } - /** * search for files with the name matching $query * @@ -264,10 +232,9 @@ public function search($query) { throw new \InvalidArgumentException('searching by owner is only allows on the users home folder'); } - [$caches, $mountByMountPoint] = $this->getCachesAndMountpointsForSearch($limitToHome); - /** @var QuerySearchHelper $searchHelper */ $searchHelper = \OC::$server->get(QuerySearchHelper::class); + [$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome); $resultsPerCache = $searchHelper->searchInCaches($query, $caches); // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all @@ -336,24 +303,6 @@ public function searchByTag($tag, $userId) { return $this->search($query); } - /** - * - * @return array - */ - public function getSystemTags(string $mediaType, int $limit = 0, int $offset = 0): array { - // Currently query has to have exactly one search condition. If no media type is provided, - // we fall back to the presence of a systemtag. - if (empty($mediaType)) { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), null, $limit, $offset); - } else { - $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mediaType . '/%'), null, $limit, $offset); - } - [$caches, ] = $this->getCachesAndMountpointsForSearch(); - /** @var QuerySearchHelper $searchHelper */ - $searchHelper = \OCP\Server::get(QuerySearchHelper::class); - return $searchHelper->findUsedTagsInCaches($query, $caches); - } - /** * @param int $id * @return \OC\Files\Node\Node[] diff --git a/lib/private/SystemTag/SystemTagsInFilesDetector.php b/lib/private/SystemTag/SystemTagsInFilesDetector.php new file mode 100644 index 0000000000000..0fcd7e051f6e4 --- /dev/null +++ b/lib/private/SystemTag/SystemTagsInFilesDetector.php @@ -0,0 +1,68 @@ + + * + * @author Arthur Schiwon + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\SystemTag; + +use OC\Files\Cache\QuerySearchHelper; +use OC\Files\Node\Root; +use OC\Files\Search\SearchComparison; +use OC\Files\Search\SearchQuery; +use OCP\Files\Folder; +use OCP\Files\Search\ISearchComparison; + +class SystemTagsInFilesDetector { + public function __construct(protected QuerySearchHelper $searchHelper) { + } + + public function detectAssignedSystemTagsIn( + Folder $folder, + string $filteredMediaType = '', + int $limit = 0, + int $offset = 0 + ): array { + // Currently query has to have exactly one search condition. If no media type is provided, + // we fall back to the presence of a system tag. + if (empty($filteredMediaType)) { + $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), $limit, $offset, []); + } else { + $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%'), $limit, $offset, []); + } + [$caches, ] = $this->searchHelper->getCachesAndMountPointsForSearch( + $this->getRootFolder($folder), + $folder->getPath(), + ); + return $this->searchHelper->findUsedTagsInCaches($query, $caches); + } + + protected function getRootFolder(?Folder $folder): Root { + if ($folder instanceof Root) { + return $folder; + } elseif ($folder === null) { + throw new \LogicException('Could not climb up to root folder'); + } + return $this->getRootFolder($folder->getParent()); + } +} From 0612ad250e408a7def3518618126595b62fbd211 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 10 May 2023 18:40:44 +0200 Subject: [PATCH 6/7] refactor: remove where specification from SELECT getter - search constraints are now fully in control of SystemTagsInFilesDetector::detectAssignedSystemTagsIn(), avoids duplication of a WHERE statement Signed-off-by: Arthur Schiwon --- lib/private/Files/Cache/CacheQueryBuilder.php | 1 - lib/private/Files/Cache/QuerySearchHelper.php | 2 -- lib/private/SystemTag/SystemTagsInFilesDetector.php | 12 ++++++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index c5563750c4d59..34d2177b84e9e 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -55,7 +55,6 @@ public function selectTagUsage(): self { $this->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'), $this->expr()->eq('systemtag.visibility', $this->createNamedParameter(true)) )) - ->where($this->expr()->like('systemtag.name', $this->createNamedParameter('_%'))) ->groupBy('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable'); return $this; diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index 8271cbf5ba473..695fbcd0717d7 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -35,7 +35,6 @@ use OCP\Files\Cache\ICacheEntry; use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; -use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountPoint; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchQuery; @@ -229,5 +228,4 @@ public function getCachesAndMountPointsForSearch(Root $root, string $path, bool return [$caches, $mountByMountPoint]; } - } diff --git a/lib/private/SystemTag/SystemTagsInFilesDetector.php b/lib/private/SystemTag/SystemTagsInFilesDetector.php index 0fcd7e051f6e4..c9f26c58c0251 100644 --- a/lib/private/SystemTag/SystemTagsInFilesDetector.php +++ b/lib/private/SystemTag/SystemTagsInFilesDetector.php @@ -28,9 +28,11 @@ use OC\Files\Cache\QuerySearchHelper; use OC\Files\Node\Root; +use OC\Files\Search\SearchBinaryOperator; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchQuery; use OCP\Files\Folder; +use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchComparison; class SystemTagsInFilesDetector { @@ -43,13 +45,15 @@ public function detectAssignedSystemTagsIn( int $limit = 0, int $offset = 0 ): array { + $operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'); // Currently query has to have exactly one search condition. If no media type is provided, // we fall back to the presence of a system tag. - if (empty($filteredMediaType)) { - $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%'), $limit, $offset, []); - } else { - $query = new SearchQuery(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%'), $limit, $offset, []); + if ($filteredMediaType !== '') { + $mimeOperator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%'); + $operator = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$operator, $mimeOperator]); } + + $query = new SearchQuery($operator, $limit, $offset, []); [$caches, ] = $this->searchHelper->getCachesAndMountPointsForSearch( $this->getRootFolder($folder), $folder->getPath(), From 236854688345c33230bc25e1ebb1c5c81aeedfe5 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 16 May 2023 13:12:10 +0200 Subject: [PATCH 7/7] fix: PHP 7.4 compat Signed-off-by: Arthur Schiwon --- apps/dav/lib/SystemTag/SystemTagsInUseCollection.php | 2 +- lib/private/SystemTag/SystemTagsInFilesDetector.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index b57e685e7e71b..4f1d6c58566c1 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -86,7 +86,7 @@ public function getChildren(): array { if ($user) { $userFolder = $this->rootFolder->getUserFolder($user->getUID()); } - } catch (NoUserException) { + } catch (NoUserException $e) { // will throw a Sabre exception in the next step. } if ($user === null || $userFolder === null) { diff --git a/lib/private/SystemTag/SystemTagsInFilesDetector.php b/lib/private/SystemTag/SystemTagsInFilesDetector.php index c9f26c58c0251..a214bd77e7bb2 100644 --- a/lib/private/SystemTag/SystemTagsInFilesDetector.php +++ b/lib/private/SystemTag/SystemTagsInFilesDetector.php @@ -36,7 +36,10 @@ use OCP\Files\Search\ISearchComparison; class SystemTagsInFilesDetector { - public function __construct(protected QuerySearchHelper $searchHelper) { + protected QuerySearchHelper $searchHelper; + + public function __construct(QuerySearchHelper $searchHelper) { + $this->searchHelper = $searchHelper; } public function detectAssignedSystemTagsIn(