Merge pull request #17941 from nextcloud/search-by-owner
Allow filtering the search results to the users home storage
This commit is contained in:
commit
63cb31542d
|
@ -119,6 +119,7 @@ class FileSearchBackend implements ISearchBackend {
|
|||
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
|
||||
new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
|
||||
new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
|
||||
new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, false),
|
||||
|
||||
// select only properties
|
||||
new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false),
|
||||
|
@ -126,7 +127,6 @@ class FileSearchBackend implements ISearchBackend {
|
|||
new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false),
|
||||
new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false),
|
||||
new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false),
|
||||
new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, false, true, false),
|
||||
new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false),
|
||||
new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false),
|
||||
new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN),
|
||||
|
@ -169,10 +169,12 @@ class FileSearchBackend implements ISearchBackend {
|
|||
return new SearchResult($davNode, $path);
|
||||
}, $results);
|
||||
|
||||
// Sort again, since the result from multiple storages is appended and not sorted
|
||||
usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
|
||||
return $this->sort($a, $b, $search->orderBy);
|
||||
});
|
||||
if (!$query->limitToHome()) {
|
||||
// Sort again, since the result from multiple storages is appended and not sorted
|
||||
usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
|
||||
return $this->sort($a, $b, $search->orderBy);
|
||||
});
|
||||
}
|
||||
|
||||
// If a limit is provided use only return that number of files
|
||||
if ($search->limit->maxResults !== 0) {
|
||||
|
@ -267,11 +269,29 @@ class FileSearchBackend implements ISearchBackend {
|
|||
* @param Query $query
|
||||
* @return ISearchQuery
|
||||
*/
|
||||
private function transformQuery(Query $query) {
|
||||
private function transformQuery(Query $query): ISearchQuery {
|
||||
// TODO offset
|
||||
$limit = $query->limit;
|
||||
$orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
|
||||
return new SearchQuery($this->transformSearchOperation($query->where), (int)$limit->maxResults, 0, $orders, $this->user);
|
||||
|
||||
$limitHome = false;
|
||||
$ownerProp = $this->extractWhereValue($query->where, FilesPlugin::OWNER_ID_PROPERTYNAME, Operator::OPERATION_EQUAL);
|
||||
if ($ownerProp !== null) {
|
||||
if ($ownerProp === $this->user->getUID()) {
|
||||
$limitHome = true;
|
||||
} else {
|
||||
throw new \InvalidArgumentException("Invalid search value for '{http://owncloud.org/ns}owner-id', only the current user id is allowed");
|
||||
}
|
||||
}
|
||||
|
||||
return new SearchQuery(
|
||||
$this->transformSearchOperation($query->where),
|
||||
(int)$limit->maxResults,
|
||||
0,
|
||||
$orders,
|
||||
$this->user,
|
||||
$limitHome
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -360,4 +380,52 @@ class FileSearchBackend implements ISearchBackend {
|
|||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific property from the were clause
|
||||
*/
|
||||
private function extractWhereValue(Operator &$operator, string $propertyName, string $comparison, bool $acceptableLocation = true): ?string {
|
||||
switch ($operator->type) {
|
||||
case Operator::OPERATION_AND:
|
||||
case Operator::OPERATION_OR:
|
||||
case Operator::OPERATION_NOT:
|
||||
foreach ($operator->arguments as &$argument) {
|
||||
$value = $this->extractWhereValue($argument, $propertyName, $comparison, $acceptableLocation && $operator->type === Operator::OPERATION_AND);
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case Operator::OPERATION_EQUAL:
|
||||
case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
|
||||
case Operator::OPERATION_GREATER_THAN:
|
||||
case Operator::OPERATION_LESS_OR_EQUAL_THAN:
|
||||
case Operator::OPERATION_LESS_THAN:
|
||||
case Operator::OPERATION_IS_LIKE:
|
||||
if ($operator->arguments[0]->name === $propertyName) {
|
||||
if ($operator->type === $comparison) {
|
||||
if ($acceptableLocation) {
|
||||
if ($operator->arguments[1] instanceof Literal) {
|
||||
$value = $operator->arguments[1]->value;
|
||||
|
||||
// to remove the comparison from the query, we replace it with an empty AND
|
||||
$operator = new Operator(Operator::OPERATION_AND);
|
||||
|
||||
return $value;
|
||||
} else {
|
||||
throw new \InvalidArgumentException("searching by '$propertyName' is only allowed with a literal value");
|
||||
}
|
||||
} else{
|
||||
throw new \InvalidArgumentException("searching by '$propertyName' is not allowed inside a '{DAV:}or' or '{DAV:}not'");
|
||||
}
|
||||
} else {
|
||||
throw new \InvalidArgumentException("searching by '$propertyName' is only allowed inside a '$comparison'");
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ use OCA\DAV\Files\FileSearchBackend;
|
|||
use OCP\Files\FileInfo;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\Search\ISearchBinaryOperator;
|
||||
use OCP\Files\Search\ISearchComparison;
|
||||
use OCP\Files\Search\ISearchQuery;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\IManager;
|
||||
use SearchDAV\Backend\SearchPropertyDefinition;
|
||||
|
@ -308,4 +310,81 @@ class FileSearchBackendTest extends TestCase {
|
|||
$query = $this->getBasicQuery(\SearchDAV\Query\Operator::OPERATION_EQUAL, '{DAV:}displayname', 'foo');
|
||||
$this->search->search($query);
|
||||
}
|
||||
|
||||
public function testSearchLimitOwnerBasic() {
|
||||
$this->tree->expects($this->any())
|
||||
->method('getNodeForPath')
|
||||
->willReturn($this->davFolder);
|
||||
|
||||
/** @var ISearchQuery|null $receivedQuery */
|
||||
$receivedQuery = null;
|
||||
$this->searchFolder
|
||||
->method('search')
|
||||
->will($this->returnCallback(function ($query) use (&$receivedQuery) {
|
||||
$receivedQuery = $query;
|
||||
return [
|
||||
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
|
||||
];
|
||||
}));
|
||||
|
||||
$query = $this->getBasicQuery(\SearchDAV\Query\Operator::OPERATION_EQUAL, FilesPlugin::OWNER_ID_PROPERTYNAME, $this->user->getUID());
|
||||
$this->search->search($query);
|
||||
|
||||
$this->assertNotNull($receivedQuery);
|
||||
$this->assertTrue($receivedQuery->limitToHome());
|
||||
|
||||
/** @var ISearchBinaryOperator $operator */
|
||||
$operator = $receivedQuery->getSearchOperation();
|
||||
$this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
|
||||
$this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
|
||||
$this->assertEmpty($operator->getArguments());
|
||||
}
|
||||
|
||||
public function testSearchLimitOwnerNested() {
|
||||
$this->tree->expects($this->any())
|
||||
->method('getNodeForPath')
|
||||
->willReturn($this->davFolder);
|
||||
|
||||
/** @var ISearchQuery|null $receivedQuery */
|
||||
$receivedQuery = null;
|
||||
$this->searchFolder
|
||||
->method('search')
|
||||
->will($this->returnCallback(function ($query) use (&$receivedQuery) {
|
||||
$receivedQuery = $query;
|
||||
return [
|
||||
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
|
||||
];
|
||||
}));
|
||||
|
||||
$query = $this->getBasicQuery(\SearchDAV\Query\Operator::OPERATION_EQUAL, FilesPlugin::OWNER_ID_PROPERTYNAME, $this->user->getUID());
|
||||
$query->where = new \SearchDAV\Query\Operator(
|
||||
\SearchDAV\Query\Operator::OPERATION_AND,
|
||||
[
|
||||
new \SearchDAV\Query\Operator(
|
||||
\SearchDAV\Query\Operator::OPERATION_EQUAL,
|
||||
[new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new \SearchDAV\Query\Literal('image/png')]
|
||||
),
|
||||
new \SearchDAV\Query\Operator(
|
||||
\SearchDAV\Query\Operator::OPERATION_EQUAL,
|
||||
[new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, true), new \SearchDAV\Query\Literal($this->user->getUID())]
|
||||
)
|
||||
]
|
||||
);
|
||||
$this->search->search($query);
|
||||
|
||||
$this->assertNotNull($receivedQuery);
|
||||
$this->assertTrue($receivedQuery->limitToHome());
|
||||
|
||||
/** @var ISearchBinaryOperator $operator */
|
||||
$operator = $receivedQuery->getSearchOperation();
|
||||
$this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
|
||||
$this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
|
||||
$this->assertCount(2, $operator->getArguments());
|
||||
|
||||
/** @var ISearchBinaryOperator $operator */
|
||||
$operator = $operator->getArguments()[1];
|
||||
$this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
|
||||
$this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
|
||||
$this->assertEmpty($operator->getArguments());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -793,7 +793,10 @@ class Cache implements ICache {
|
|||
->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
|
||||
}
|
||||
|
||||
$query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
|
||||
$searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
|
||||
if ($searchExpr) {
|
||||
$query->andWhere($searchExpr);
|
||||
}
|
||||
|
||||
$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
|
||||
|
||||
|
|
|
@ -88,14 +88,18 @@ class QuerySearchHelper {
|
|||
* @param ISearchOperator $operator
|
||||
*/
|
||||
public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
|
||||
return array_map(function ($operator) use ($builder) {
|
||||
return array_filter(array_map(function ($operator) use ($builder) {
|
||||
return $this->searchOperatorToDBExpr($builder, $operator);
|
||||
}, $operators);
|
||||
}, $operators));
|
||||
}
|
||||
|
||||
public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
|
||||
$expr = $builder->expr();
|
||||
if ($operator instanceof ISearchBinaryOperator) {
|
||||
if (count($operator->getArguments()) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($operator->getType()) {
|
||||
case ISearchBinaryOperator::OPERATOR_NOT:
|
||||
$negativeOperator = $operator->getArguments()[0];
|
||||
|
|
|
@ -34,7 +34,7 @@ use OCP\Files\FileInfo;
|
|||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\Search\ISearchOperator;
|
||||
use OCP\Files\Search\ISearchQuery;
|
||||
|
||||
class Folder extends Node implements \OCP\Files\Folder {
|
||||
/**
|
||||
|
@ -191,7 +191,7 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
/**
|
||||
* search for files with the name matching $query
|
||||
*
|
||||
* @param string|ISearchOperator $query
|
||||
* @param string|ISearchQuery $query
|
||||
* @return \OC\Files\Node\Node[]
|
||||
*/
|
||||
public function search($query) {
|
||||
|
@ -229,6 +229,11 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
* @return \OC\Files\Node\Node[]
|
||||
*/
|
||||
private function searchCommon($method, $args) {
|
||||
$limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
|
||||
if ($limitToHome && count(explode('/', $this->path)) !== 3) {
|
||||
throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
|
||||
}
|
||||
|
||||
$files = array();
|
||||
$rootLength = strlen($this->path);
|
||||
$mount = $this->root->getMount($this->path);
|
||||
|
@ -252,19 +257,22 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
}
|
||||
}
|
||||
|
||||
$mounts = $this->root->getMountsIn($this->path);
|
||||
foreach ($mounts as $mount) {
|
||||
$storage = $mount->getStorage();
|
||||
if ($storage) {
|
||||
$cache = $storage->getCache('');
|
||||
if (!$limitToHome) {
|
||||
$mounts = $this->root->getMountsIn($this->path);
|
||||
foreach ($mounts as $mount) {
|
||||
$storage = $mount->getStorage();
|
||||
if ($storage) {
|
||||
$cache = $storage->getCache('');
|
||||
|
||||
$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
|
||||
$results = call_user_func_array(array($cache, $method), $args);
|
||||
foreach ($results as $result) {
|
||||
$result['internalPath'] = $result['path'];
|
||||
$result['path'] = $relativeMountPoint . $result['path'];
|
||||
$result['storage'] = $storage;
|
||||
$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
|
||||
$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
|
||||
$results = call_user_func_array([$cache, $method], $args);
|
||||
foreach ($results as $result) {
|
||||
$result['internalPath'] = $result['path'];
|
||||
$result['path'] = $relativeMountPoint . $result['path'];
|
||||
$result['storage'] = $storage;
|
||||
$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
|
||||
$result['internalPath'], $result, $mount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ class SearchQuery implements ISearchQuery {
|
|||
private $order;
|
||||
/** @var IUser */
|
||||
private $user;
|
||||
private $limitToHome;
|
||||
|
||||
/**
|
||||
* SearchQuery constructor.
|
||||
|
@ -48,13 +49,22 @@ class SearchQuery implements ISearchQuery {
|
|||
* @param int $offset
|
||||
* @param array $order
|
||||
* @param IUser $user
|
||||
* @param bool $limitToHome
|
||||
*/
|
||||
public function __construct(ISearchOperator $searchOperation, $limit, $offset, array $order, IUser $user) {
|
||||
public function __construct(
|
||||
ISearchOperator $searchOperation,
|
||||
int $limit,
|
||||
int $offset,
|
||||
array $order,
|
||||
IUser $user,
|
||||
bool $limitToHome = false
|
||||
) {
|
||||
$this->searchOperation = $searchOperation;
|
||||
$this->limit = $limit;
|
||||
$this->offset = $offset;
|
||||
$this->order = $order;
|
||||
$this->user = $user;
|
||||
$this->limitToHome = $limitToHome;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,4 +101,8 @@ class SearchQuery implements ISearchQuery {
|
|||
public function getUser() {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function limitToHome(): bool {
|
||||
return $this->limitToHome;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,4 +66,12 @@ interface ISearchQuery {
|
|||
* @since 12.0.0
|
||||
*/
|
||||
public function getUser();
|
||||
|
||||
/**
|
||||
* Whether or not the search should be limited to the users home storage
|
||||
*
|
||||
* @return bool
|
||||
* @since 18.0.0
|
||||
*/
|
||||
public function limitToHome(): bool;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue