mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
Merge pull request #37969 from nextcloud/poc/noid/systemtags-perf-tag-endpoint
This commit is contained in:
commit
eddb64f8c3
10 changed files with 535 additions and 244 deletions
|
|
@ -30,6 +30,7 @@ namespace OCA\DAV\Connector\Sabre;
|
|||
use OC\Files\View;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\Node as INode;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\ITagManager;
|
||||
use OCP\IUserSession;
|
||||
|
|
@ -45,9 +46,9 @@ use Sabre\DAV\Xml\Element\Response;
|
|||
use Sabre\DAV\Xml\Response\MultiStatus;
|
||||
|
||||
class FilesReportPlugin extends ServerPlugin {
|
||||
|
||||
// namespace
|
||||
public const NS_OWNCLOUD = 'http://owncloud.org/ns';
|
||||
public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
|
||||
public const REPORT_NAME = '{http://owncloud.org/ns}filter-files';
|
||||
public const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
|
||||
public const CIRCLE_PROPERTYNAME = '{http://owncloud.org/ns}circle';
|
||||
|
|
@ -186,6 +187,7 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
}
|
||||
|
||||
$ns = '{' . $this::NS_OWNCLOUD . '}';
|
||||
$ncns = '{' . $this::NS_NEXTCLOUD . '}';
|
||||
$requestedProps = [];
|
||||
$filterRules = [];
|
||||
|
||||
|
|
@ -199,6 +201,14 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
foreach ($reportProps['value'] as $propVal) {
|
||||
$requestedProps[] = $propVal['name'];
|
||||
}
|
||||
} elseif ($name === '{DAV:}limit') {
|
||||
foreach ($reportProps['value'] as $propVal) {
|
||||
if ($propVal['name'] === '{DAV:}nresults') {
|
||||
$limit = (int)$propVal['value'];
|
||||
} elseif ($propVal['name'] === $ncns . 'firstresult') {
|
||||
$offset = (int)$propVal['value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,13 +219,32 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
|
||||
// gather all file ids matching filter
|
||||
try {
|
||||
$resultFileIds = $this->processFilterRules($filterRules);
|
||||
$resultFileIds = $this->processFilterRulesForFileIDs($filterRules);
|
||||
// no logic in circles and favorites for paging, we always have all results, and slice later on
|
||||
$resultFileIds = array_slice($resultFileIds, $offset ?? 0, $limit ?? null);
|
||||
// fetching nodes has paging on DB level – therefore we cannot mix and slice the results, similar
|
||||
// to user backends. I.e. the final result may return more results than requested.
|
||||
$resultNodes = $this->processFilterRulesForFileNodes($filterRules, $limit ?? null, $offset ?? null);
|
||||
} catch (TagNotFoundException $e) {
|
||||
throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($resultNodes as $entry) {
|
||||
if ($entry) {
|
||||
$results[] = $this->wrapNode($entry);
|
||||
}
|
||||
}
|
||||
|
||||
// find sabre nodes by file id, restricted to the root node path
|
||||
$results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
|
||||
$additionalNodes = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
|
||||
if ($additionalNodes && $results) {
|
||||
$results = array_uintersect($results, $additionalNodes, function (Node $a, Node $b): int {
|
||||
return $a->getId() - $b->getId();
|
||||
});
|
||||
} elseif (!$results && $additionalNodes) {
|
||||
$results = $additionalNodes;
|
||||
}
|
||||
|
||||
$filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath());
|
||||
$responses = $this->prepareResponses($filesUri, $requestedProps, $results);
|
||||
|
|
@ -261,19 +290,13 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
*
|
||||
* @param array $filterRules
|
||||
* @return array array of unique file id results
|
||||
*
|
||||
* @throws TagNotFoundException whenever a tag was not found
|
||||
*/
|
||||
protected function processFilterRules($filterRules) {
|
||||
protected function processFilterRulesForFileIDs(array $filterRules): array {
|
||||
$ns = '{' . $this::NS_OWNCLOUD . '}';
|
||||
$resultFileIds = null;
|
||||
$systemTagIds = [];
|
||||
$resultFileIds = [];
|
||||
$circlesIds = [];
|
||||
$favoriteFilter = null;
|
||||
foreach ($filterRules as $filterRule) {
|
||||
if ($filterRule['name'] === $ns . 'systemtag') {
|
||||
$systemTagIds[] = $filterRule['value'];
|
||||
}
|
||||
if ($filterRule['name'] === self::CIRCLE_PROPERTYNAME) {
|
||||
$circlesIds[] = $filterRule['value'];
|
||||
}
|
||||
|
|
@ -289,15 +312,6 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
if (!empty($systemTagIds)) {
|
||||
$fileIds = $this->getSystemTagFileIds($systemTagIds);
|
||||
if (empty($resultFileIds)) {
|
||||
$resultFileIds = $fileIds;
|
||||
} else {
|
||||
$resultFileIds = array_intersect($fileIds, $resultFileIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($circlesIds)) {
|
||||
$fileIds = $this->getCirclesFileIds($circlesIds);
|
||||
if (empty($resultFileIds)) {
|
||||
|
|
@ -310,47 +324,48 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
return $resultFileIds;
|
||||
}
|
||||
|
||||
private function getSystemTagFileIds($systemTagIds) {
|
||||
$resultFileIds = null;
|
||||
protected function processFilterRulesForFileNodes(array $filterRules, ?int $limit, ?int $offset): array {
|
||||
$systemTagIds = [];
|
||||
foreach ($filterRules as $filterRule) {
|
||||
if ($filterRule['name'] === self::SYSTEMTAG_PROPERTYNAME) {
|
||||
$systemTagIds[] = $filterRule['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$nodes = [];
|
||||
|
||||
// type check to ensure searchBySystemTag is available, it is not
|
||||
// exposed in API yet
|
||||
if (!empty($systemTagIds)) {
|
||||
$tags = $this->tagManager->getTagsByIds($systemTagIds, $this->userSession->getUser());
|
||||
|
||||
// For we run DB queries per tag and require intersection, we cannot apply limit and offset for DB queries on multi tag search.
|
||||
$oneTagSearch = count($tags) === 1;
|
||||
$dbLimit = $oneTagSearch ? $limit ?? 0 : 0;
|
||||
$dbOffset = $oneTagSearch ? $offset ?? 0 : 0;
|
||||
|
||||
// check user permissions, if applicable
|
||||
if (!$this->isAdmin()) {
|
||||
// check visibility/permission
|
||||
$tags = $this->tagManager->getTagsByIds($systemTagIds);
|
||||
$unknownTagIds = [];
|
||||
foreach ($tags as $tag) {
|
||||
if (!$tag->isUserVisible()) {
|
||||
$unknownTagIds[] = $tag->getId();
|
||||
$tagName = $tag->getName();
|
||||
$tmpNodes = $this->userFolder->searchBySystemTag($tagName, $this->userSession->getUser()->getUID(), $dbLimit, $dbOffset);
|
||||
if (count($nodes) === 0) {
|
||||
$nodes = $tmpNodes;
|
||||
} else {
|
||||
$nodes = array_uintersect($nodes, $tmpNodes, function (INode $a, INode $b): int {
|
||||
return $a->getId() - $b->getId();
|
||||
});
|
||||
}
|
||||
if ($nodes === []) {
|
||||
// there cannot be a common match when nodes are empty early.
|
||||
return $nodes;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($unknownTagIds)) {
|
||||
throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
|
||||
if (!$oneTagSearch && ($limit !== null || $offset !== null)) {
|
||||
$nodes = array_slice($nodes, $offset, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
// fetch all file ids and intersect them
|
||||
foreach ($systemTagIds as $systemTagId) {
|
||||
$fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
|
||||
|
||||
if (empty($fileIds)) {
|
||||
// This tag has no files, nothing can ever show up
|
||||
return [];
|
||||
}
|
||||
|
||||
// first run ?
|
||||
if ($resultFileIds === null) {
|
||||
$resultFileIds = $fileIds;
|
||||
} else {
|
||||
$resultFileIds = array_intersect($resultFileIds, $fileIds);
|
||||
}
|
||||
|
||||
if (empty($resultFileIds)) {
|
||||
// Empty intersection, nothing can show up anymore
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return $resultFileIds;
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -406,7 +421,10 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
* @param array $fileIds file ids
|
||||
* @return Node[] array of Sabre nodes
|
||||
*/
|
||||
public function findNodesByFileIds($rootNode, $fileIds) {
|
||||
public function findNodesByFileIds(Node $rootNode, array $fileIds): array {
|
||||
if (empty($fileIds)) {
|
||||
return [];
|
||||
}
|
||||
$folder = $this->userFolder;
|
||||
if (trim($rootNode->getPath(), '/') !== '') {
|
||||
$folder = $folder->get($rootNode->getPath());
|
||||
|
|
@ -417,17 +435,21 @@ class FilesReportPlugin extends ServerPlugin {
|
|||
$entry = $folder->getById($fileId);
|
||||
if ($entry) {
|
||||
$entry = current($entry);
|
||||
if ($entry instanceof \OCP\Files\File) {
|
||||
$results[] = new File($this->fileView, $entry);
|
||||
} elseif ($entry instanceof \OCP\Files\Folder) {
|
||||
$results[] = new Directory($this->fileView, $entry);
|
||||
}
|
||||
$results[] = $this->wrapNode($entry);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function wrapNode(\OCP\Files\File|\OCP\Files\Folder $node): File|Directory {
|
||||
if ($node instanceof \OCP\Files\File) {
|
||||
return new File($this->fileView, $node);
|
||||
} else {
|
||||
return new Directory($this->fileView, $node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the currently logged in user is an administrator
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -44,45 +44,49 @@ use OCP\IUserSession;
|
|||
use OCP\SystemTag\ISystemTag;
|
||||
use OCP\SystemTag\ISystemTagManager;
|
||||
use OCP\SystemTag\ISystemTagObjectMapper;
|
||||
use OCP\SystemTag\TagNotFoundException;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\DAV\INode;
|
||||
use Sabre\DAV\Tree;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
class FilesReportPluginTest extends \Test\TestCase {
|
||||
/** @var \Sabre\DAV\Server|\PHPUnit\Framework\MockObject\MockObject */
|
||||
/** @var \Sabre\DAV\Server|MockObject */
|
||||
private $server;
|
||||
|
||||
/** @var \Sabre\DAV\Tree|\PHPUnit\Framework\MockObject\MockObject */
|
||||
/** @var \Sabre\DAV\Tree|MockObject */
|
||||
private $tree;
|
||||
|
||||
/** @var ISystemTagObjectMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
/** @var ISystemTagObjectMapper|MockObject */
|
||||
private $tagMapper;
|
||||
|
||||
/** @var ISystemTagManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
/** @var ISystemTagManager|MockObject */
|
||||
private $tagManager;
|
||||
|
||||
/** @var ITags|\PHPUnit\Framework\MockObject\MockObject */
|
||||
/** @var ITags|MockObject */
|
||||
private $privateTags;
|
||||
|
||||
private ITagManager|MockObject $privateTagManager;
|
||||
|
||||
/** @var \OCP\IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/** @var FilesReportPluginImplementation */
|
||||
private $plugin;
|
||||
|
||||
/** @var View|\PHPUnit\Framework\MockObject\MockObject **/
|
||||
/** @var View|MockObject **/
|
||||
private $view;
|
||||
|
||||
/** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject **/
|
||||
/** @var IGroupManager|MockObject **/
|
||||
private $groupManager;
|
||||
|
||||
/** @var Folder|\PHPUnit\Framework\MockObject\MockObject **/
|
||||
/** @var Folder|MockObject **/
|
||||
private $userFolder;
|
||||
|
||||
/** @var IPreview|\PHPUnit\Framework\MockObject\MockObject * */
|
||||
/** @var IPreview|MockObject * */
|
||||
private $previewManager;
|
||||
|
||||
/** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject * */
|
||||
/** @var IAppManager|MockObject * */
|
||||
private $appManager;
|
||||
|
||||
protected function setUp(): void {
|
||||
|
|
@ -124,8 +128,8 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
$this->tagMapper = $this->createMock(ISystemTagObjectMapper::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->privateTags = $this->createMock(ITags::class);
|
||||
$privateTagManager = $this->createMock(ITagManager::class);
|
||||
$privateTagManager->expects($this->any())
|
||||
$this->privateTagManager = $this->createMock(ITagManager::class);
|
||||
$this->privateTagManager->expects($this->any())
|
||||
->method('load')
|
||||
->with('files')
|
||||
->willReturn($this->privateTags);
|
||||
|
|
@ -145,7 +149,7 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
$this->view,
|
||||
$this->tagManager,
|
||||
$this->tagMapper,
|
||||
$privateTagManager,
|
||||
$this->privateTagManager,
|
||||
$this->userSession,
|
||||
$this->groupManager,
|
||||
$this->userFolder,
|
||||
|
|
@ -217,17 +221,6 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(2))
|
||||
->method('getObjectIdsForTags')
|
||||
->withConsecutive(
|
||||
['123', 'files'],
|
||||
['456', 'files'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
['111', '222'],
|
||||
['111', '222', '333'],
|
||||
);
|
||||
|
||||
$reportTargetNode = $this->getMockBuilder(Directory::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
|
@ -255,20 +248,40 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->with('/' . $path)
|
||||
->willReturn($reportTargetNode);
|
||||
|
||||
$filesNode1 = $this->getMockBuilder(Folder::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$filesNode2 = $this->getMockBuilder(File::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$filesNode2->method('getSize')
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag456 = $this->createMock(ISystemTag::class);
|
||||
$tag456->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
$tag456->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag123, $tag456]);
|
||||
|
||||
$this->userFolder->expects($this->exactly(2))
|
||||
->method('getById')
|
||||
->method('searchBySystemTag')
|
||||
->withConsecutive(
|
||||
['111'],
|
||||
['222'],
|
||||
['OneTwoThree'],
|
||||
['FourFiveSix'],
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[$filesNode1],
|
||||
|
|
@ -317,7 +330,7 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
[$filesNode2],
|
||||
);
|
||||
|
||||
/** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit\Framework\MockObject\MockObject $reportTargetNode */
|
||||
/** @var \OCA\DAV\Connector\Sabre\Directory|MockObject $reportTargetNode */
|
||||
$result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
|
|
@ -370,7 +383,7 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
[$filesNode2],
|
||||
);
|
||||
|
||||
/** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit\Framework\MockObject\MockObject $reportTargetNode */
|
||||
/** @var \OCA\DAV\Connector\Sabre\Directory|MockObject $reportTargetNode */
|
||||
$result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
|
|
@ -454,20 +467,38 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(1))
|
||||
->method('getObjectIdsForTags')
|
||||
->withConsecutive(
|
||||
['123', 'files']
|
||||
)
|
||||
->willReturnMap([
|
||||
['123', 'files', 0, '', ['111', '222']],
|
||||
]);
|
||||
|
||||
$rules = [
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
|
||||
];
|
||||
|
||||
$this->assertEquals(['111', '222'], $this->invokePrivate($this->plugin, 'processFilterRules', [$rules]));
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123'])
|
||||
->willReturn([$tag123]);
|
||||
|
||||
$this->userFolder->expects($this->once())
|
||||
->method('searchBySystemTag')
|
||||
->with('OneTwoThree')
|
||||
->willReturn([$filesNode1, $filesNode2]);
|
||||
|
||||
$this->assertEquals([$filesNode1, $filesNode2], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, 0, 0]));
|
||||
}
|
||||
|
||||
public function testProcessFilterRulesAndCondition(): void {
|
||||
|
|
@ -475,23 +506,65 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(2))
|
||||
->method('getObjectIdsForTags')
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(111);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(222);
|
||||
$filesNode3 = $this->createMock(File::class);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(14);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(333);
|
||||
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag456 = $this->createMock(ISystemTag::class);
|
||||
$tag456->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
$tag456->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag123, $tag456]);
|
||||
|
||||
$this->userFolder->expects($this->exactly(2))
|
||||
->method('searchBySystemTag')
|
||||
->withConsecutive(
|
||||
['123', 'files'],
|
||||
['456', 'files']
|
||||
['OneTwoThree'],
|
||||
['FourFiveSix'],
|
||||
)
|
||||
->willReturnMap([
|
||||
['123', 'files', 0, '', ['111', '222']],
|
||||
['456', 'files', 0, '', ['222', '333']],
|
||||
]);
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[$filesNode1, $filesNode2],
|
||||
[$filesNode2, $filesNode3],
|
||||
);
|
||||
|
||||
$rules = [
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
|
||||
];
|
||||
|
||||
$this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules])));
|
||||
$this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
|
||||
}
|
||||
|
||||
public function testProcessFilterRulesAndConditionWithOneEmptyResult(): void {
|
||||
|
|
@ -499,23 +572,58 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(2))
|
||||
->method('getObjectIdsForTags')
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(111);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(222);
|
||||
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag456 = $this->createMock(ISystemTag::class);
|
||||
$tag456->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
$tag456->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag123, $tag456]);
|
||||
|
||||
$this->userFolder->expects($this->exactly(2))
|
||||
->method('searchBySystemTag')
|
||||
->withConsecutive(
|
||||
['123', 'files'],
|
||||
['456', 'files']
|
||||
['OneTwoThree'],
|
||||
['FourFiveSix'],
|
||||
)
|
||||
->willReturnMap([
|
||||
['123', 'files', 0, '', ['111', '222']],
|
||||
['456', 'files', 0, '', []],
|
||||
]);
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[$filesNode1, $filesNode2],
|
||||
[],
|
||||
);
|
||||
|
||||
$rules = [
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
|
||||
];
|
||||
|
||||
$this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules])));
|
||||
$this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]));
|
||||
}
|
||||
|
||||
public function testProcessFilterRulesAndConditionWithFirstEmptyResult(): void {
|
||||
|
|
@ -523,23 +631,55 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(1))
|
||||
->method('getObjectIdsForTags')
|
||||
->withConsecutive(
|
||||
['123', 'files'],
|
||||
['456', 'files']
|
||||
)
|
||||
->willReturnMap([
|
||||
['123', 'files', 0, '', []],
|
||||
['456', 'files', 0, '', ['111', '222']],
|
||||
]);
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(111);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(222);
|
||||
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag456 = $this->createMock(ISystemTag::class);
|
||||
$tag456->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
$tag456->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag123, $tag456]);
|
||||
|
||||
$this->userFolder->expects($this->once())
|
||||
->method('searchBySystemTag')
|
||||
->with('OneTwoThree')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[],
|
||||
[$filesNode1, $filesNode2],
|
||||
);
|
||||
|
||||
$rules = [
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
|
||||
];
|
||||
|
||||
$this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules])));
|
||||
$this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]));
|
||||
}
|
||||
|
||||
public function testProcessFilterRulesAndConditionWithEmptyMidResult(): void {
|
||||
|
|
@ -547,18 +687,63 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(2))
|
||||
->method('getObjectIdsForTags')
|
||||
->withConsecutive(
|
||||
['123', 'files'],
|
||||
['456', 'files'],
|
||||
['789', 'files']
|
||||
)
|
||||
->willReturnMap([
|
||||
['123', 'files', 0, '', ['111', '222']],
|
||||
['456', 'files', 0, '', ['333']],
|
||||
['789', 'files', 0, '', ['111', '222']],
|
||||
]);
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(111);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(222);
|
||||
$filesNode3 = $this->createMock(Folder::class);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(13);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(333);
|
||||
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag456 = $this->createMock(ISystemTag::class);
|
||||
$tag456->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
$tag456->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag789 = $this->createMock(ISystemTag::class);
|
||||
$tag789->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('SevenEightNein');
|
||||
$tag789->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456', '789'])
|
||||
->willReturn([$tag123, $tag456, $tag789]);
|
||||
|
||||
$this->userFolder->expects($this->exactly(2))
|
||||
->method('searchBySystemTag')
|
||||
->withConsecutive(['OneTwoThree'], ['FourFiveSix'], ['SevenEightNein'])
|
||||
->willReturnOnConsecutiveCalls(
|
||||
[$filesNode1, $filesNode2],
|
||||
[$filesNode3],
|
||||
[$filesNode1, $filesNode2],
|
||||
);
|
||||
|
||||
$rules = [
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
|
||||
|
|
@ -566,7 +751,7 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '789'],
|
||||
];
|
||||
|
||||
$this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules])));
|
||||
$this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
|
||||
}
|
||||
|
||||
public function testProcessFilterRulesInvisibleTagAsAdmin(): void {
|
||||
|
|
@ -574,39 +759,54 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(true);
|
||||
|
||||
$tag1 = $this->getMockBuilder(ISystemTag::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$tag1->expects($this->any())
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('123');
|
||||
$tag1->expects($this->any())
|
||||
->willReturn(111);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(222);
|
||||
$filesNode3 = $this->createMock(Folder::class);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(13);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(333);
|
||||
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$tag2 = $this->getMockBuilder(ISystemTag::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$tag2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('123');
|
||||
$tag2->expects($this->any())
|
||||
$tag456 = $this->createMock(ISystemTag::class);
|
||||
$tag456->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
$tag456->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(false);
|
||||
|
||||
// no need to fetch tags to check permissions
|
||||
$this->tagManager->expects($this->never())
|
||||
->method('getTagsByIds');
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag123, $tag456]);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(2))
|
||||
->method('getObjectIdsForTags')
|
||||
->withConsecutive(
|
||||
['123'],
|
||||
['456'],
|
||||
)
|
||||
$this->userFolder->expects($this->exactly(2))
|
||||
->method('searchBySystemTag')
|
||||
->withConsecutive(['OneTwoThree'], ['FourFiveSix'])
|
||||
->willReturnOnConsecutiveCalls(
|
||||
['111', '222'],
|
||||
['222', '333'],
|
||||
[$filesNode1, $filesNode2],
|
||||
[$filesNode2, $filesNode3],
|
||||
);
|
||||
|
||||
$rules = [
|
||||
|
|
@ -614,48 +814,46 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
|
||||
];
|
||||
|
||||
$this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules])));
|
||||
$this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
|
||||
}
|
||||
|
||||
|
||||
public function testProcessFilterRulesInvisibleTagAsUser(): void {
|
||||
$this->expectException(\OCP\SystemTag\TagNotFoundException::class);
|
||||
$this->expectException(TagNotFoundException::class);
|
||||
|
||||
$this->groupManager->expects($this->any())
|
||||
->method('isAdmin')
|
||||
->willReturn(false);
|
||||
|
||||
$tag1 = $this->getMockBuilder(ISystemTag::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$tag1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('123');
|
||||
$tag1->expects($this->any())
|
||||
$tag123 = $this->createMock(ISystemTag::class);
|
||||
$tag123->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
$tag123->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
|
||||
$tag2 = $this->getMockBuilder(ISystemTag::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$tag2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('123');
|
||||
$tag2->expects($this->any())
|
||||
$tag456 = $this->createMock(ISystemTag::class);
|
||||
$tag456->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
$tag456->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(false); // invisible
|
||||
->willReturn(false);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag1, $tag2]);
|
||||
->willThrowException(new TagNotFoundException());
|
||||
|
||||
$this->userFolder->expects($this->never())
|
||||
->method('searchBySystemTag');
|
||||
|
||||
$rules = [
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
|
||||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
|
||||
];
|
||||
|
||||
$this->invokePrivate($this->plugin, 'processFilterRules', [$rules]);
|
||||
$this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]);
|
||||
}
|
||||
|
||||
public function testProcessFilterRulesVisibleTagAsUser(): void {
|
||||
|
|
@ -663,40 +861,67 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('isAdmin')
|
||||
->willReturn(false);
|
||||
|
||||
$tag1 = $this->getMockBuilder(ISystemTag::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$tag1 = $this->createMock(ISystemTag::class);
|
||||
$tag1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('123');
|
||||
$tag1->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag1->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('OneTwoThree');
|
||||
|
||||
$tag2 = $this->getMockBuilder(ISystemTag::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$tag2 = $this->createMock(ISystemTag::class);
|
||||
$tag2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('123');
|
||||
$tag2->expects($this->any())
|
||||
->method('isUserVisible')
|
||||
->willReturn(true);
|
||||
$tag2->expects($this->any())
|
||||
->method('getName')
|
||||
->willReturn('FourFiveSix');
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag1, $tag2]);
|
||||
|
||||
$this->tagMapper->expects($this->exactly(2))
|
||||
->method('getObjectIdsForTags')
|
||||
->withConsecutive(
|
||||
['123'],
|
||||
['456'],
|
||||
)
|
||||
$filesNode1 = $this->createMock(File::class);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(111);
|
||||
$filesNode1->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(12);
|
||||
$filesNode2 = $this->createMock(Folder::class);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(222);
|
||||
$filesNode2->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(10);
|
||||
$filesNode3 = $this->createMock(Folder::class);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(333);
|
||||
$filesNode3->expects($this->any())
|
||||
->method('getSize')
|
||||
->willReturn(33);
|
||||
|
||||
$this->tagManager->expects($this->once())
|
||||
->method('getTagsByIds')
|
||||
->with(['123', '456'])
|
||||
->willReturn([$tag1, $tag2]);
|
||||
|
||||
// main assertion: only user visible tags are being passed through.
|
||||
$this->userFolder->expects($this->exactly(2))
|
||||
->method('searchBySystemTag')
|
||||
->withConsecutive(['OneTwoThree'], ['FourFiveSix'])
|
||||
->willReturnOnConsecutiveCalls(
|
||||
['111', '222'],
|
||||
['222', '333'],
|
||||
[$filesNode1, $filesNode2],
|
||||
[$filesNode2, $filesNode3],
|
||||
);
|
||||
|
||||
$rules = [
|
||||
|
|
@ -704,7 +929,7 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
|
||||
];
|
||||
|
||||
$this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules])));
|
||||
$this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
|
||||
}
|
||||
|
||||
public function testProcessFavoriteFilter(): void {
|
||||
|
|
@ -716,7 +941,7 @@ class FilesReportPluginTest extends \Test\TestCase {
|
|||
->method('getFavorites')
|
||||
->willReturn(['456', '789']);
|
||||
|
||||
$this->assertEquals(['456', '789'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules])));
|
||||
$this->assertEquals(['456', '789'], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileIDs', [$rules])));
|
||||
}
|
||||
|
||||
public function filesBaseUriProvider() {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ use OCP\Files\Mount\IMountPoint;
|
|||
use OCP\Files\Search\ISearchBinaryOperator;
|
||||
use OCP\Files\Search\ISearchQuery;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUser;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class QuerySearchHelper {
|
||||
|
|
@ -52,6 +54,7 @@ class QuerySearchHelper {
|
|||
private $searchBuilder;
|
||||
/** @var QueryOptimizer */
|
||||
private $queryOptimizer;
|
||||
private IGroupManager $groupManager;
|
||||
|
||||
public function __construct(
|
||||
IMimeTypeLoader $mimetypeLoader,
|
||||
|
|
@ -59,7 +62,8 @@ class QuerySearchHelper {
|
|||
SystemConfig $systemConfig,
|
||||
LoggerInterface $logger,
|
||||
SearchBuilder $searchBuilder,
|
||||
QueryOptimizer $queryOptimizer
|
||||
QueryOptimizer $queryOptimizer,
|
||||
IGroupManager $groupManager,
|
||||
) {
|
||||
$this->mimetypeLoader = $mimetypeLoader;
|
||||
$this->connection = $connection;
|
||||
|
|
@ -67,6 +71,7 @@ class QuerySearchHelper {
|
|||
$this->logger = $logger;
|
||||
$this->searchBuilder = $searchBuilder;
|
||||
$this->queryOptimizer = $queryOptimizer;
|
||||
$this->groupManager = $groupManager;
|
||||
}
|
||||
|
||||
protected function getQueryBuilder() {
|
||||
|
|
@ -116,6 +121,29 @@ class QuerySearchHelper {
|
|||
return $tags;
|
||||
}
|
||||
|
||||
protected function equipQueryForSystemTags(CacheQueryBuilder $query, IUser $user): void {
|
||||
$query->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $query->expr()->andX(
|
||||
$query->expr()->eq('file.fileid', $query->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
|
||||
$query->expr()->eq('systemtagmap.objecttype', $query->createNamedParameter('files'))
|
||||
));
|
||||
$on = $query->expr()->andX($query->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'));
|
||||
if (!$this->groupManager->isAdmin($user->getUID())) {
|
||||
$on->add($query->expr()->eq('systemtag.visibility', $query->createNamedParameter(true)));
|
||||
}
|
||||
$query->leftJoin('systemtagmap', 'systemtag', 'systemtag', $on);
|
||||
}
|
||||
|
||||
protected function equipQueryForDavTags(CacheQueryBuilder $query, IUser $user): void {
|
||||
$query
|
||||
->leftJoin('file', 'vcategory_to_object', 'tagmap', $query->expr()->eq('file.fileid', 'tagmap.objid'))
|
||||
->leftJoin('tagmap', 'vcategory', 'tag', $query->expr()->andX(
|
||||
$query->expr()->eq('tagmap.type', 'tag.type'),
|
||||
$query->expr()->eq('tagmap.categoryid', 'tag.id'),
|
||||
$query->expr()->eq('tag.type', $query->createNamedParameter('files')),
|
||||
$query->expr()->eq('tag.uid', $query->createNamedParameter($user->getUID()))
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a file system search in multiple caches
|
||||
*
|
||||
|
|
@ -146,27 +174,12 @@ class QuerySearchHelper {
|
|||
|
||||
$query = $builder->selectFileCache('file', false);
|
||||
|
||||
if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) {
|
||||
$user = $searchQuery->getUser();
|
||||
if ($user === null) {
|
||||
throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query");
|
||||
}
|
||||
$query
|
||||
->leftJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
|
||||
->leftJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
|
||||
$builder->expr()->eq('tagmap.type', 'tag.type'),
|
||||
$builder->expr()->eq('tagmap.categoryid', 'tag.id'),
|
||||
$builder->expr()->eq('tag.type', $builder->createNamedParameter('files')),
|
||||
$builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID()))
|
||||
))
|
||||
->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $builder->expr()->andX(
|
||||
$builder->expr()->eq('file.fileid', $builder->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
|
||||
$builder->expr()->eq('systemtagmap.objecttype', $builder->createNamedParameter('files'))
|
||||
))
|
||||
->leftJoin('systemtagmap', 'systemtag', 'systemtag', $builder->expr()->andX(
|
||||
$builder->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'),
|
||||
$builder->expr()->eq('systemtag.visibility', $builder->createNamedParameter(true))
|
||||
));
|
||||
$requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());
|
||||
if (in_array('systemtag', $requestedFields)) {
|
||||
$this->equipQueryForSystemTags($query, $this->requireUser($searchQuery));
|
||||
}
|
||||
if (in_array('tagname', $requestedFields) || in_array('favorite', $requestedFields)) {
|
||||
$this->equipQueryForDavTags($query, $this->requireUser($searchQuery));
|
||||
}
|
||||
|
||||
$this->applySearchConstraints($query, $searchQuery, $caches);
|
||||
|
|
@ -194,6 +207,14 @@ class QuerySearchHelper {
|
|||
return $results;
|
||||
}
|
||||
|
||||
protected function requireUser(ISearchQuery $searchQuery): IUser {
|
||||
$user = $searchQuery->getUser();
|
||||
if ($user === null) {
|
||||
throw new \InvalidArgumentException("This search operation requires the user to be set in the query");
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list{0?: array<array-key, ICache>, 1?: array<array-key, IMountPoint>}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -69,20 +69,17 @@ class SearchBuilder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether or not the tag tables should be joined to complete the search
|
||||
*
|
||||
* @param ISearchOperator $operator
|
||||
* @return boolean
|
||||
* @return string[]
|
||||
*/
|
||||
public function shouldJoinTags(ISearchOperator $operator) {
|
||||
public function extractRequestedFields(ISearchOperator $operator): array {
|
||||
if ($operator instanceof ISearchBinaryOperator) {
|
||||
return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
|
||||
return $shouldJoin || $this->shouldJoinTags($operator);
|
||||
}, false);
|
||||
return array_reduce($operator->getArguments(), function (array $fields, ISearchOperator $operator) {
|
||||
return array_unique(array_merge($fields, $this->extractRequestedFields($operator)));
|
||||
}, []);
|
||||
} elseif ($operator instanceof ISearchComparison) {
|
||||
return $operator->getField() === 'tagname' || $operator->getField() === 'favorite' || $operator->getField() === 'systemtag';
|
||||
return [$operator->getField()];
|
||||
}
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -300,6 +300,11 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
return $this->search($query);
|
||||
}
|
||||
|
||||
public function searchBySystemTag(string $tagName, string $userId, int $limit = 0, int $offset = 0): array {
|
||||
$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'systemtag', $tagName), $userId, $limit, $offset);
|
||||
return $this->search($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return \OC\Files\Node\Node[]
|
||||
|
|
|
|||
|
|
@ -449,6 +449,10 @@ class LazyFolder implements Folder {
|
|||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
public function searchBySystemTag(string $tagName, string $userId, int $limit = 0, int $offset = 0) {
|
||||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class SystemTagManager implements ISystemTagManager {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTagsByIds($tagIds): array {
|
||||
public function getTagsByIds($tagIds, ?IUser $user = null): array {
|
||||
if (!\is_array($tagIds)) {
|
||||
$tagIds = [$tagIds];
|
||||
}
|
||||
|
|
@ -116,7 +116,12 @@ class SystemTagManager implements ISystemTagManager {
|
|||
|
||||
$result = $query->execute();
|
||||
while ($row = $result->fetch()) {
|
||||
$tags[$row['id']] = $this->createSystemTagFromRow($row);
|
||||
$tag = $this->createSystemTagFromRow($row);
|
||||
if ($user && !$this->canUserSeeTag($tag, $user)) {
|
||||
// if a user is given, hide invisible tags
|
||||
continue;
|
||||
}
|
||||
$tags[$row['id']] = $tag;
|
||||
}
|
||||
|
||||
$result->closeCursor();
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
|
|||
while ($row = $result->fetch()) {
|
||||
$objectIds[] = $row['objectid'];
|
||||
}
|
||||
$result->closeCursor();
|
||||
|
||||
return $objectIds;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,16 @@ interface Folder extends Node {
|
|||
*/
|
||||
public function searchByTag($tag, $userId);
|
||||
|
||||
/**
|
||||
* search for files by system tag
|
||||
*
|
||||
* @param string|int $tag tag name
|
||||
* @param string $userId user id to ensure access on returned nodes
|
||||
* @return \OCP\Files\Node[]
|
||||
* @since 28.0.0
|
||||
*/
|
||||
public function searchBySystemTag(string $tagName, string $userId, int $limit = 0, int $offset = 0);
|
||||
|
||||
/**
|
||||
* get a file or folder inside the folder by it's internal id
|
||||
*
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ interface ISystemTagManager {
|
|||
* Returns the tag objects matching the given tag ids.
|
||||
*
|
||||
* @param array|string $tagIds id or array of unique ids of the tag to retrieve
|
||||
* @param ?IUser $user optional user to run a visibility check against for each tag
|
||||
*
|
||||
* @return ISystemTag[] array of system tags with tag id as key
|
||||
*
|
||||
|
|
@ -45,9 +46,9 @@ interface ISystemTagManager {
|
|||
* @throws TagNotFoundException if at least one given tag ids did no exist
|
||||
* The message contains a json_encoded array of the ids that could not be found
|
||||
*
|
||||
* @since 9.0.0
|
||||
* @since 9.0.0, optional parameter $user added in 28.0.0
|
||||
*/
|
||||
public function getTagsByIds($tagIds): array;
|
||||
public function getTagsByIds($tagIds, ?IUser $user = null): array;
|
||||
|
||||
/**
|
||||
* Returns the tag object matching the given attributes.
|
||||
|
|
|
|||
Loading…
Reference in a new issue