summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/dav/lib/Files/FileSearchBackend.php16
-rw-r--r--apps/dav/tests/unit/Files/FileSearchBackendTest.php15
-rw-r--r--lib/private/Files/Cache/Cache.php21
-rw-r--r--lib/private/Files/Cache/QuerySearchHelper.php32
-rw-r--r--lib/private/Files/Search/SearchQuery.php14
-rw-r--r--lib/public/Files/Search/ISearchQuery.php10
-rw-r--r--tests/lib/Files/Cache/CacheTest.php70
7 files changed, 155 insertions, 23 deletions
diff --git a/apps/dav/lib/Files/FileSearchBackend.php b/apps/dav/lib/Files/FileSearchBackend.php
index c429a1727f8..afdb425e8ed 100644
--- a/apps/dav/lib/Files/FileSearchBackend.php
+++ b/apps/dav/lib/Files/FileSearchBackend.php
@@ -28,6 +28,7 @@ use OC\Files\Search\SearchQuery;
use OC\Files\View;
use OCA\DAV\Connector\Sabre\Directory;
use OCA\DAV\Connector\Sabre\FilesPlugin;
+use OCA\DAV\Connector\Sabre\TagsPlugin;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
@@ -114,6 +115,7 @@ class FileSearchBackend implements ISearchBackend {
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
new SearchPropertyDefinition('{DAV:}getlastmodifed', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
+ new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
// select only properties
new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false),
@@ -178,7 +180,7 @@ class FileSearchBackend implements ISearchBackend {
private function transformQuery(BasicSearch $query) {
// TODO offset, limit
$orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
- return new SearchQuery($this->transformSearchOperation($query->where), 0, 0, $orders);
+ return new SearchQuery($this->transformSearchOperation($query->where), 0, 0, $orders, $this->user);
}
/**
@@ -186,7 +188,7 @@ class FileSearchBackend implements ISearchBackend {
* @return ISearchOrder
*/
private function mapSearchOrder(Order $order) {
- return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToCollumn($order->property));
+ return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToColumn($order->property));
}
/**
@@ -210,13 +212,13 @@ class FileSearchBackend implements ISearchBackend {
if (count($operator->arguments) !== 2) {
throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation');
}
- if (gettype($operator->arguments[0]) !== 'string') {
+ if (!is_string($operator->arguments[0])) {
throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
}
if (!($operator->arguments[1] instanceof Literal)) {
throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
}
- return new SearchComparison($trimmedType, $this->mapPropertyNameToCollumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));
+ return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));
case Operator::OPERATION_IS_COLLECTION:
return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
default:
@@ -228,7 +230,7 @@ class FileSearchBackend implements ISearchBackend {
* @param string $propertyName
* @return string
*/
- private function mapPropertyNameToCollumn($propertyName) {
+ private function mapPropertyNameToColumn($propertyName) {
switch ($propertyName) {
case '{DAV:}displayname':
return 'name';
@@ -238,6 +240,10 @@ class FileSearchBackend implements ISearchBackend {
return 'mtime';
case FilesPlugin::SIZE_PROPERTYNAME:
return 'size';
+ case TagsPlugin::FAVORITE_PROPERTYNAME:
+ return 'favorite';
+ case TagsPlugin::TAGS_PROPERTYNAME:
+ return 'tagname';
default:
throw new \InvalidArgumentException('Unsupported property for search or order: ' . $propertyName);
}
diff --git a/apps/dav/tests/unit/Files/FileSearchBackendTest.php b/apps/dav/tests/unit/Files/FileSearchBackendTest.php
index 24b9a9c51e6..539344b22d5 100644
--- a/apps/dav/tests/unit/Files/FileSearchBackendTest.php
+++ b/apps/dav/tests/unit/Files/FileSearchBackendTest.php
@@ -122,7 +122,8 @@ class FileSearchBackendTest extends TestCase {
),
0,
0,
- []
+ [],
+ $this->user
))
->will($this->returnValue([
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
@@ -150,7 +151,8 @@ class FileSearchBackendTest extends TestCase {
),
0,
0,
- []
+ [],
+ $this->user
))
->will($this->returnValue([
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
@@ -178,7 +180,8 @@ class FileSearchBackendTest extends TestCase {
),
0,
0,
- []
+ [],
+ $this->user
))
->will($this->returnValue([
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
@@ -206,7 +209,8 @@ class FileSearchBackendTest extends TestCase {
),
0,
0,
- []
+ [],
+ $this->user
))
->will($this->returnValue([
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
@@ -234,7 +238,8 @@ class FileSearchBackendTest extends TestCase {
),
0,
0,
- []
+ [],
+ $this->user
))
->will($this->returnValue([
new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index b0527d801d6..c3ac0f8444f 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -645,9 +645,22 @@ class Cache implements ICache {
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
- ->from('filecache')
- ->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())))
- ->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
+ ->from('filecache', 'file');
+
+ $query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
+
+ if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
+ $query
+ ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
+ ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
+ $builder->expr()->eq('tagmap.type', 'tag.type'),
+ $builder->expr()->eq('tagmap.categoryid', 'tag.id')
+ ))
+ ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
+ ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
+ }
+
+ $query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
if ($searchQuery->getLimit()) {
$query->setMaxResults($searchQuery->getLimit());
@@ -660,7 +673,7 @@ class Cache implements ICache {
return $this->searchResultToCacheEntries($result);
}
- /**
+ /**
* Search for files by tag of a given users.
*
* Note that every user can tag files differently.
diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php
index 931f258ec5b..7d8098f0efa 100644
--- a/lib/private/Files/Cache/QuerySearchHelper.php
+++ b/lib/private/Files/Cache/QuerySearchHelper.php
@@ -49,6 +49,8 @@ class QuerySearchHelper {
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lt'
];
+ const TAG_FAVORITE = '_$!<Favorite>!$_';
+
/** @var IMimeTypeLoader */
private $mimetypeLoader;
@@ -61,6 +63,23 @@ class QuerySearchHelper {
$this->mimetypeLoader = $mimetypeLoader;
}
+ /**
+ * Whether or not the tag tables should be joined to complete the search
+ *
+ * @param ISearchOperator $operator
+ * @return boolean
+ */
+ public function shouldJoinTags(ISearchOperator $operator) {
+ if ($operator instanceof ISearchBinaryOperator) {
+ return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) {
+ return $shouldJoin || $this->shouldJoinTags($operator);
+ }, false);
+ } else if ($operator instanceof ISearchComparison) {
+ return $operator->getField() === 'tagname' || $operator->getField() === 'favorite';
+ }
+ return false;
+ }
+
public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
$expr = $builder->expr();
if ($operator instanceof ISearchBinaryOperator) {
@@ -116,6 +135,11 @@ class QuerySearchHelper {
throw new \InvalidArgumentException('Unsupported query value for mimetype: ' . $value . ', only values in the format "mime/type" or "mime/%" are supported');
}
}
+ } else if ($field === 'favorite') {
+ $field = 'tag.category';
+ $value = self::TAG_FAVORITE;
+ } else if ($field === 'tagname') {
+ $field = 'tag.category';
}
return [$field, $value, $type];
}
@@ -125,13 +149,17 @@ class QuerySearchHelper {
'mimetype' => 'string',
'mtime' => 'integer',
'name' => 'string',
- 'size' => 'integer'
+ 'size' => 'integer',
+ 'tagname' => 'string',
+ 'favorite' => 'boolean'
];
$comparisons = [
'mimetype' => ['eq', 'like'],
'mtime' => ['eq', 'gt', 'lt', 'gte', 'lte'],
'name' => ['eq', 'like'],
- 'size' => ['eq', 'gt', 'lt', 'gte', 'lte']
+ 'size' => ['eq', 'gt', 'lt', 'gte', 'lte'],
+ 'tagname' => ['eq', 'like'],
+ 'favorite' => ['eq'],
];
if (!isset($types[$operator->getField()])) {
diff --git a/lib/private/Files/Search/SearchQuery.php b/lib/private/Files/Search/SearchQuery.php
index 8a0478ae98e..c1da5220516 100644
--- a/lib/private/Files/Search/SearchQuery.php
+++ b/lib/private/Files/Search/SearchQuery.php
@@ -24,6 +24,7 @@ namespace OC\Files\Search;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchOrder;
use OCP\Files\Search\ISearchQuery;
+use OCP\IUser;
class SearchQuery implements ISearchQuery {
/** @var ISearchOperator */
@@ -34,6 +35,8 @@ class SearchQuery implements ISearchQuery {
private $offset;
/** @var ISearchOrder[] */
private $order;
+ /** @var IUser */
+ private $user;
/**
* SearchQuery constructor.
@@ -42,12 +45,14 @@ class SearchQuery implements ISearchQuery {
* @param int $limit
* @param int $offset
* @param array $order
+ * @param IUser $user
*/
- public function __construct(ISearchOperator $searchOperation, $limit, $offset, array $order) {
+ public function __construct(ISearchOperator $searchOperation, $limit, $offset, array $order, IUser $user) {
$this->searchOperation = $searchOperation;
$this->limit = $limit;
$this->offset = $offset;
$this->order = $order;
+ $this->user = $user;
}
/**
@@ -77,4 +82,11 @@ class SearchQuery implements ISearchQuery {
public function getOrder() {
return $this->order;
}
+
+ /**
+ * @return IUser
+ */
+ public function getUser() {
+ return $this->user;
+ }
}
diff --git a/lib/public/Files/Search/ISearchQuery.php b/lib/public/Files/Search/ISearchQuery.php
index 5a701b321b1..531e285a593 100644
--- a/lib/public/Files/Search/ISearchQuery.php
+++ b/lib/public/Files/Search/ISearchQuery.php
@@ -21,6 +21,8 @@
namespace OCP\Files\Search;
+use OCP\IUser;
+
/**
* @since 12.0.0
*/
@@ -54,4 +56,12 @@ interface ISearchQuery {
* @since 12.0.0
*/
public function getOrder();
+
+ /**
+ * The user that issued the search
+ *
+ * @return IUser
+ * @since 12.0.0
+ */
+ public function getUser();
}
diff --git a/tests/lib/Files/Cache/CacheTest.php b/tests/lib/Files/Cache/CacheTest.php
index 1bcf8832c63..0038cef1f63 100644
--- a/tests/lib/Files/Cache/CacheTest.php
+++ b/tests/lib/Files/Cache/CacheTest.php
@@ -14,6 +14,7 @@ use OC\Files\Cache\Cache;
use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchQuery;
use OCP\Files\Search\ISearchComparison;
+use OCP\IUser;
class LongId extends \OC\Files\Storage\Temporary {
public function getId() {
@@ -397,6 +398,61 @@ class CacheTest extends \Test\TestCase {
}
}
+ function testSearchQueryByTag() {
+ $userId = static::getUniqueID('user');
+ \OC::$server->getUserManager()->createUser($userId, $userId);
+ static::loginAsUser($userId);
+ $user = new \OC\User\User($userId, null);
+
+ $file1 = 'folder';
+ $file2 = 'folder/foobar';
+ $file3 = 'folder/foo';
+ $file4 = 'folder/foo2';
+ $file5 = 'folder/foo3';
+ $data1 = array('size' => 100, 'mtime' => 50, 'mimetype' => 'foo/folder');
+ $fileData = array();
+ $fileData['foobar'] = array('size' => 1000, 'mtime' => 20, 'mimetype' => 'foo/file');
+ $fileData['foo'] = array('size' => 20, 'mtime' => 25, 'mimetype' => 'foo/file');
+ $fileData['foo2'] = array('size' => 25, 'mtime' => 28, 'mimetype' => 'foo/file');
+ $fileData['foo3'] = array('size' => 88, 'mtime' => 34, 'mimetype' => 'foo/file');
+
+ $id1 = $this->cache->put($file1, $data1);
+ $id2 = $this->cache->put($file2, $fileData['foobar']);
+ $id3 = $this->cache->put($file3, $fileData['foo']);
+ $id4 = $this->cache->put($file4, $fileData['foo2']);
+ $id5 = $this->cache->put($file5, $fileData['foo3']);
+
+ $tagManager = \OC::$server->getTagManager()->load('files', null, null, $userId);
+ $this->assertTrue($tagManager->tagAs($id1, 'tag1'));
+ $this->assertTrue($tagManager->tagAs($id1, 'tag2'));
+ $this->assertTrue($tagManager->tagAs($id2, 'tag2'));
+ $this->assertTrue($tagManager->tagAs($id3, 'tag1'));
+ $this->assertTrue($tagManager->tagAs($id4, 'tag2'));
+
+ $results = $this->cache->searchQuery(new SearchQuery(
+ new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', 'tag2'),
+ 0, 0, [], $user
+ ));
+ $this->assertEquals(3, count($results));
+
+ usort($results, function ($value1, $value2) {
+ return $value1['name'] >= $value2['name'];
+ });
+
+ $this->assertEquals('folder', $results[0]['name']);
+ $this->assertEquals('foo2', $results[1]['name']);
+ $this->assertEquals('foobar', $results[2]['name']);
+
+ $tagManager->delete('tag1');
+ $tagManager->delete('tag2');
+
+ static::logout();
+ $user = \OC::$server->getUserManager()->get($userId);
+ if ($user !== null) {
+ $user->delete();
+ }
+ }
+
function testSearchByQuery() {
$file1 = 'folder';
$file2 = 'folder/foobar';
@@ -409,25 +465,27 @@ class CacheTest extends \Test\TestCase {
$this->cache->put($file1, $data1);
$this->cache->put($file2, $fileData['foobar']);
$this->cache->put($file3, $fileData['foo']);
+ /** @var IUser $user */
+ $user = $this->createMock(IUser::class);
$this->assertCount(1, $this->cache->searchQuery(new SearchQuery(
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'name', 'foo')
- , 10, 0, [])));
+ , 10, 0, [], $user)));
$this->assertCount(2, $this->cache->searchQuery(new SearchQuery(
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', 'foo%')
- , 10, 0, [])));
+ , 10, 0, [], $user)));
$this->assertCount(2, $this->cache->searchQuery(new SearchQuery(
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', 'foo/file')
- , 10, 0, [])));
+ , 10, 0, [], $user)));
$this->assertCount(3, $this->cache->searchQuery(new SearchQuery(
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', 'foo/%')
- , 10, 0, [])));
+ , 10, 0, [], $user)));
$this->assertCount(1, $this->cache->searchQuery(new SearchQuery(
new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN, 'size', 100)
- , 10, 0, [])));
+ , 10, 0, [], $user)));
$this->assertCount(2, $this->cache->searchQuery(new SearchQuery(
new SearchComparison(ISearchComparison::COMPARE_GREATER_THAN_EQUAL, 'size', 100)
- , 10, 0, [])));
+ , 10, 0, [], $user)));
}
function testMove() {