Passed
Push — master ( ee7a48...6c65c9 )
by Blizzz
32:32 queued 16:45
created
apps/dav/lib/Files/FileSearchBackend.php 1 patch
Indentation   +378 added lines, -378 removed lines patch added patch discarded remove patch
@@ -53,382 +53,382 @@
 block discarded – undo
53 53
 use SearchDAV\Query\Query;
54 54
 
55 55
 class FileSearchBackend implements ISearchBackend {
56
-	/** @var CachingTree */
57
-	private $tree;
58
-
59
-	/** @var IUser */
60
-	private $user;
61
-
62
-	/** @var IRootFolder */
63
-	private $rootFolder;
64
-
65
-	/** @var IManager */
66
-	private $shareManager;
67
-
68
-	/** @var View */
69
-	private $view;
70
-
71
-	/**
72
-	 * FileSearchBackend constructor.
73
-	 *
74
-	 * @param CachingTree $tree
75
-	 * @param IUser $user
76
-	 * @param IRootFolder $rootFolder
77
-	 * @param IManager $shareManager
78
-	 * @param View $view
79
-	 * @internal param IRootFolder $rootFolder
80
-	 */
81
-	public function __construct(CachingTree $tree, IUser $user, IRootFolder $rootFolder, IManager $shareManager, View $view) {
82
-		$this->tree = $tree;
83
-		$this->user = $user;
84
-		$this->rootFolder = $rootFolder;
85
-		$this->shareManager = $shareManager;
86
-		$this->view = $view;
87
-	}
88
-
89
-	/**
90
-	 * Search endpoint will be remote.php/dav
91
-	 *
92
-	 * @return string
93
-	 */
94
-	public function getArbiterPath() {
95
-		return '';
96
-	}
97
-
98
-	public function isValidScope($href, $depth, $path) {
99
-		// only allow scopes inside the dav server
100
-		if (is_null($path)) {
101
-			return false;
102
-		}
103
-
104
-		try {
105
-			$node = $this->tree->getNodeForPath($path);
106
-			return $node instanceof Directory;
107
-		} catch (NotFound $e) {
108
-			return false;
109
-		}
110
-	}
111
-
112
-	public function getPropertyDefinitionsForScope($href, $path) {
113
-		// all valid scopes support the same schema
114
-
115
-		//todo dynamically load all propfind properties that are supported
116
-		return [
117
-			// queryable properties
118
-			new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
119
-			new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
120
-			new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
121
-			new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
122
-			new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
123
-			new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
124
-			new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, false),
125
-
126
-			// select only properties
127
-			new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false),
128
-			new SearchPropertyDefinition('{DAV:}getcontentlength', false, true, false),
129
-			new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false),
130
-			new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false),
131
-			new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false),
132
-			new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false),
133
-			new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false),
134
-			new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN),
135
-			new SearchPropertyDefinition(FilesPlugin::FILEID_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
136
-		];
137
-	}
138
-
139
-	/**
140
-	 * @param Query $search
141
-	 * @return SearchResult[]
142
-	 */
143
-	public function search(Query $search) {
144
-		if (count($search->from) !== 1) {
145
-			throw new \InvalidArgumentException('Searching more than one folder is not supported');
146
-		}
147
-		$query = $this->transformQuery($search);
148
-		$scope = $search->from[0];
149
-		if ($scope->path === null) {
150
-			throw new \InvalidArgumentException('Using uri\'s as scope is not supported, please use a path relative to the search arbiter instead');
151
-		}
152
-		$node = $this->tree->getNodeForPath($scope->path);
153
-		if (!$node instanceof Directory) {
154
-			throw new \InvalidArgumentException('Search is only supported on directories');
155
-		}
156
-
157
-		$fileInfo = $node->getFileInfo();
158
-		$folder = $this->rootFolder->get($fileInfo->getPath());
159
-		/** @var Folder $folder $results */
160
-		$results = $folder->search($query);
161
-
162
-		/** @var SearchResult[] $nodes */
163
-		$nodes = array_map(function (Node $node) {
164
-			if ($node instanceof Folder) {
165
-				$davNode = new \OCA\DAV\Connector\Sabre\Directory($this->view, $node, $this->tree, $this->shareManager);
166
-			} else {
167
-				$davNode = new \OCA\DAV\Connector\Sabre\File($this->view, $node, $this->shareManager);
168
-			}
169
-			$path = $this->getHrefForNode($node);
170
-			$this->tree->cacheNode($davNode, $path);
171
-			return new SearchResult($davNode, $path);
172
-		}, $results);
173
-
174
-		if (!$query->limitToHome()) {
175
-			// Sort again, since the result from multiple storages is appended and not sorted
176
-			usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
177
-				return $this->sort($a, $b, $search->orderBy);
178
-			});
179
-		}
180
-
181
-		// If a limit is provided use only return that number of files
182
-		if ($search->limit->maxResults !== 0) {
183
-			$nodes = \array_slice($nodes, 0, $search->limit->maxResults);
184
-		}
185
-
186
-		return $nodes;
187
-	}
188
-
189
-	private function sort(SearchResult $a, SearchResult $b, array $orders) {
190
-		/** @var Order $order */
191
-		foreach ($orders as $order) {
192
-			$v1 = $this->getSearchResultProperty($a, $order->property);
193
-			$v2 = $this->getSearchResultProperty($b, $order->property);
194
-
195
-
196
-			if ($v1 === null && $v2 === null) {
197
-				continue;
198
-			}
199
-			if ($v1 === null) {
200
-				return $order->order === Order::ASC ? 1 : -1;
201
-			}
202
-			if ($v2 === null) {
203
-				return $order->order === Order::ASC ? -1 : 1;
204
-			}
205
-
206
-			$s = $this->compareProperties($v1, $v2, $order);
207
-			if ($s === 0) {
208
-				continue;
209
-			}
210
-
211
-			if ($order->order === Order::DESC) {
212
-				$s = -$s;
213
-			}
214
-			return $s;
215
-		}
216
-
217
-		return 0;
218
-	}
219
-
220
-	private function compareProperties($a, $b, Order $order) {
221
-		switch ($order->property->dataType) {
222
-			case SearchPropertyDefinition::DATATYPE_STRING:
223
-				return strcmp($a, $b);
224
-			case SearchPropertyDefinition::DATATYPE_BOOLEAN:
225
-				if ($a === $b) {
226
-					return 0;
227
-				}
228
-				if ($a === false) {
229
-					return -1;
230
-				}
231
-				return 1;
232
-			default:
233
-				if ($a === $b) {
234
-					return 0;
235
-				}
236
-				if ($a < $b) {
237
-					return -1;
238
-				}
239
-				return 1;
240
-		}
241
-	}
242
-
243
-	private function getSearchResultProperty(SearchResult $result, SearchPropertyDefinition $property) {
244
-		/** @var \OCA\DAV\Connector\Sabre\Node $node */
245
-		$node = $result->node;
246
-
247
-		switch ($property->name) {
248
-			case '{DAV:}displayname':
249
-				return $node->getName();
250
-			case '{DAV:}getlastmodified':
251
-				return $node->getLastModified();
252
-			case FilesPlugin::SIZE_PROPERTYNAME:
253
-				return $node->getSize();
254
-			case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
255
-				return $node->getInternalFileId();
256
-			default:
257
-				return null;
258
-		}
259
-	}
260
-
261
-	/**
262
-	 * @param Node $node
263
-	 * @return string
264
-	 */
265
-	private function getHrefForNode(Node $node) {
266
-		$base = '/files/' . $this->user->getUID();
267
-		return $base . $this->view->getRelativePath($node->getPath());
268
-	}
269
-
270
-	/**
271
-	 * @param Query $query
272
-	 * @return ISearchQuery
273
-	 */
274
-	private function transformQuery(Query $query): ISearchQuery {
275
-		$limit = $query->limit;
276
-		$orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
277
-		$offset = $limit->firstResult;
278
-
279
-		$limitHome = false;
280
-		$ownerProp = $this->extractWhereValue($query->where, FilesPlugin::OWNER_ID_PROPERTYNAME, Operator::OPERATION_EQUAL);
281
-		if ($ownerProp !== null) {
282
-			if ($ownerProp === $this->user->getUID()) {
283
-				$limitHome = true;
284
-			} else {
285
-				throw new \InvalidArgumentException("Invalid search value for '{http://owncloud.org/ns}owner-id', only the current user id is allowed");
286
-			}
287
-		}
288
-
289
-		return new SearchQuery(
290
-			$this->transformSearchOperation($query->where),
291
-			(int)$limit->maxResults,
292
-			$offset,
293
-			$orders,
294
-			$this->user,
295
-			$limitHome
296
-		);
297
-	}
298
-
299
-	/**
300
-	 * @param Order $order
301
-	 * @return ISearchOrder
302
-	 */
303
-	private function mapSearchOrder(Order $order) {
304
-		return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToColumn($order->property));
305
-	}
306
-
307
-	/**
308
-	 * @param Operator $operator
309
-	 * @return ISearchOperator
310
-	 */
311
-	private function transformSearchOperation(Operator $operator) {
312
-		[, $trimmedType] = explode('}', $operator->type);
313
-		switch ($operator->type) {
314
-			case Operator::OPERATION_AND:
315
-			case Operator::OPERATION_OR:
316
-			case Operator::OPERATION_NOT:
317
-				$arguments = array_map([$this, 'transformSearchOperation'], $operator->arguments);
318
-				return new SearchBinaryOperator($trimmedType, $arguments);
319
-			case Operator::OPERATION_EQUAL:
320
-			case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
321
-			case Operator::OPERATION_GREATER_THAN:
322
-			case Operator::OPERATION_LESS_OR_EQUAL_THAN:
323
-			case Operator::OPERATION_LESS_THAN:
324
-			case Operator::OPERATION_IS_LIKE:
325
-				if (count($operator->arguments) !== 2) {
326
-					throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation');
327
-				}
328
-				if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) {
329
-					throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
330
-				}
331
-				if (!($operator->arguments[1] instanceof Literal)) {
332
-					throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
333
-				}
334
-				return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));
335
-			case Operator::OPERATION_IS_COLLECTION:
336
-				return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
337
-			default:
338
-				throw new \InvalidArgumentException('Unsupported operation ' . $trimmedType . ' (' . $operator->type . ')');
339
-		}
340
-	}
341
-
342
-	/**
343
-	 * @param SearchPropertyDefinition $property
344
-	 * @return string
345
-	 */
346
-	private function mapPropertyNameToColumn(SearchPropertyDefinition $property) {
347
-		switch ($property->name) {
348
-			case '{DAV:}displayname':
349
-				return 'name';
350
-			case '{DAV:}getcontenttype':
351
-				return 'mimetype';
352
-			case '{DAV:}getlastmodified':
353
-				return 'mtime';
354
-			case FilesPlugin::SIZE_PROPERTYNAME:
355
-				return 'size';
356
-			case TagsPlugin::FAVORITE_PROPERTYNAME:
357
-				return 'favorite';
358
-			case TagsPlugin::TAGS_PROPERTYNAME:
359
-				return 'tagname';
360
-			case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
361
-				return 'fileid';
362
-			default:
363
-				throw new \InvalidArgumentException('Unsupported property for search or order: ' . $property->name);
364
-		}
365
-	}
366
-
367
-	private function castValue(SearchPropertyDefinition $property, $value) {
368
-		switch ($property->dataType) {
369
-			case SearchPropertyDefinition::DATATYPE_BOOLEAN:
370
-				return $value === 'yes';
371
-			case SearchPropertyDefinition::DATATYPE_DECIMAL:
372
-			case SearchPropertyDefinition::DATATYPE_INTEGER:
373
-			case SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER:
374
-				return 0 + $value;
375
-			case SearchPropertyDefinition::DATATYPE_DATETIME:
376
-				if (is_numeric($value)) {
377
-					return max(0, 0 + $value);
378
-				}
379
-				$date = \DateTime::createFromFormat(\DateTime::ATOM, $value);
380
-				return ($date instanceof \DateTime && $date->getTimestamp() !== false) ? $date->getTimestamp() : 0;
381
-			default:
382
-				return $value;
383
-		}
384
-	}
385
-
386
-	/**
387
-	 * Get a specific property from the were clause
388
-	 */
389
-	private function extractWhereValue(Operator &$operator, string $propertyName, string $comparison, bool $acceptableLocation = true): ?string {
390
-		switch ($operator->type) {
391
-			case Operator::OPERATION_AND:
392
-			case Operator::OPERATION_OR:
393
-			case Operator::OPERATION_NOT:
394
-				foreach ($operator->arguments as &$argument) {
395
-					$value = $this->extractWhereValue($argument, $propertyName, $comparison, $acceptableLocation && $operator->type === Operator::OPERATION_AND);
396
-					if ($value !== null) {
397
-						return $value;
398
-					}
399
-				}
400
-				return null;
401
-			case Operator::OPERATION_EQUAL:
402
-			case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
403
-			case Operator::OPERATION_GREATER_THAN:
404
-			case Operator::OPERATION_LESS_OR_EQUAL_THAN:
405
-			case Operator::OPERATION_LESS_THAN:
406
-			case Operator::OPERATION_IS_LIKE:
407
-				if ($operator->arguments[0]->name === $propertyName) {
408
-					if ($operator->type === $comparison) {
409
-						if ($acceptableLocation) {
410
-							if ($operator->arguments[1] instanceof Literal) {
411
-								$value = $operator->arguments[1]->value;
412
-
413
-								// to remove the comparison from the query, we replace it with an empty AND
414
-								$operator = new Operator(Operator::OPERATION_AND);
415
-
416
-								return $value;
417
-							} else {
418
-								throw new \InvalidArgumentException("searching by '$propertyName' is only allowed with a literal value");
419
-							}
420
-						} else {
421
-							throw new \InvalidArgumentException("searching by '$propertyName' is not allowed inside a '{DAV:}or' or '{DAV:}not'");
422
-						}
423
-					} else {
424
-						throw new \InvalidArgumentException("searching by '$propertyName' is only allowed inside a '$comparison'");
425
-					}
426
-				} else {
427
-					return null;
428
-				}
429
-				// no break
430
-			default:
431
-				return null;
432
-		}
433
-	}
56
+    /** @var CachingTree */
57
+    private $tree;
58
+
59
+    /** @var IUser */
60
+    private $user;
61
+
62
+    /** @var IRootFolder */
63
+    private $rootFolder;
64
+
65
+    /** @var IManager */
66
+    private $shareManager;
67
+
68
+    /** @var View */
69
+    private $view;
70
+
71
+    /**
72
+     * FileSearchBackend constructor.
73
+     *
74
+     * @param CachingTree $tree
75
+     * @param IUser $user
76
+     * @param IRootFolder $rootFolder
77
+     * @param IManager $shareManager
78
+     * @param View $view
79
+     * @internal param IRootFolder $rootFolder
80
+     */
81
+    public function __construct(CachingTree $tree, IUser $user, IRootFolder $rootFolder, IManager $shareManager, View $view) {
82
+        $this->tree = $tree;
83
+        $this->user = $user;
84
+        $this->rootFolder = $rootFolder;
85
+        $this->shareManager = $shareManager;
86
+        $this->view = $view;
87
+    }
88
+
89
+    /**
90
+     * Search endpoint will be remote.php/dav
91
+     *
92
+     * @return string
93
+     */
94
+    public function getArbiterPath() {
95
+        return '';
96
+    }
97
+
98
+    public function isValidScope($href, $depth, $path) {
99
+        // only allow scopes inside the dav server
100
+        if (is_null($path)) {
101
+            return false;
102
+        }
103
+
104
+        try {
105
+            $node = $this->tree->getNodeForPath($path);
106
+            return $node instanceof Directory;
107
+        } catch (NotFound $e) {
108
+            return false;
109
+        }
110
+    }
111
+
112
+    public function getPropertyDefinitionsForScope($href, $path) {
113
+        // all valid scopes support the same schema
114
+
115
+        //todo dynamically load all propfind properties that are supported
116
+        return [
117
+            // queryable properties
118
+            new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
119
+            new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
120
+            new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
121
+            new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
122
+            new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
123
+            new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
124
+            new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, false),
125
+
126
+            // select only properties
127
+            new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false),
128
+            new SearchPropertyDefinition('{DAV:}getcontentlength', false, true, false),
129
+            new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false),
130
+            new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false),
131
+            new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false),
132
+            new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false),
133
+            new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false),
134
+            new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN),
135
+            new SearchPropertyDefinition(FilesPlugin::FILEID_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
136
+        ];
137
+    }
138
+
139
+    /**
140
+     * @param Query $search
141
+     * @return SearchResult[]
142
+     */
143
+    public function search(Query $search) {
144
+        if (count($search->from) !== 1) {
145
+            throw new \InvalidArgumentException('Searching more than one folder is not supported');
146
+        }
147
+        $query = $this->transformQuery($search);
148
+        $scope = $search->from[0];
149
+        if ($scope->path === null) {
150
+            throw new \InvalidArgumentException('Using uri\'s as scope is not supported, please use a path relative to the search arbiter instead');
151
+        }
152
+        $node = $this->tree->getNodeForPath($scope->path);
153
+        if (!$node instanceof Directory) {
154
+            throw new \InvalidArgumentException('Search is only supported on directories');
155
+        }
156
+
157
+        $fileInfo = $node->getFileInfo();
158
+        $folder = $this->rootFolder->get($fileInfo->getPath());
159
+        /** @var Folder $folder $results */
160
+        $results = $folder->search($query);
161
+
162
+        /** @var SearchResult[] $nodes */
163
+        $nodes = array_map(function (Node $node) {
164
+            if ($node instanceof Folder) {
165
+                $davNode = new \OCA\DAV\Connector\Sabre\Directory($this->view, $node, $this->tree, $this->shareManager);
166
+            } else {
167
+                $davNode = new \OCA\DAV\Connector\Sabre\File($this->view, $node, $this->shareManager);
168
+            }
169
+            $path = $this->getHrefForNode($node);
170
+            $this->tree->cacheNode($davNode, $path);
171
+            return new SearchResult($davNode, $path);
172
+        }, $results);
173
+
174
+        if (!$query->limitToHome()) {
175
+            // Sort again, since the result from multiple storages is appended and not sorted
176
+            usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
177
+                return $this->sort($a, $b, $search->orderBy);
178
+            });
179
+        }
180
+
181
+        // If a limit is provided use only return that number of files
182
+        if ($search->limit->maxResults !== 0) {
183
+            $nodes = \array_slice($nodes, 0, $search->limit->maxResults);
184
+        }
185
+
186
+        return $nodes;
187
+    }
188
+
189
+    private function sort(SearchResult $a, SearchResult $b, array $orders) {
190
+        /** @var Order $order */
191
+        foreach ($orders as $order) {
192
+            $v1 = $this->getSearchResultProperty($a, $order->property);
193
+            $v2 = $this->getSearchResultProperty($b, $order->property);
194
+
195
+
196
+            if ($v1 === null && $v2 === null) {
197
+                continue;
198
+            }
199
+            if ($v1 === null) {
200
+                return $order->order === Order::ASC ? 1 : -1;
201
+            }
202
+            if ($v2 === null) {
203
+                return $order->order === Order::ASC ? -1 : 1;
204
+            }
205
+
206
+            $s = $this->compareProperties($v1, $v2, $order);
207
+            if ($s === 0) {
208
+                continue;
209
+            }
210
+
211
+            if ($order->order === Order::DESC) {
212
+                $s = -$s;
213
+            }
214
+            return $s;
215
+        }
216
+
217
+        return 0;
218
+    }
219
+
220
+    private function compareProperties($a, $b, Order $order) {
221
+        switch ($order->property->dataType) {
222
+            case SearchPropertyDefinition::DATATYPE_STRING:
223
+                return strcmp($a, $b);
224
+            case SearchPropertyDefinition::DATATYPE_BOOLEAN:
225
+                if ($a === $b) {
226
+                    return 0;
227
+                }
228
+                if ($a === false) {
229
+                    return -1;
230
+                }
231
+                return 1;
232
+            default:
233
+                if ($a === $b) {
234
+                    return 0;
235
+                }
236
+                if ($a < $b) {
237
+                    return -1;
238
+                }
239
+                return 1;
240
+        }
241
+    }
242
+
243
+    private function getSearchResultProperty(SearchResult $result, SearchPropertyDefinition $property) {
244
+        /** @var \OCA\DAV\Connector\Sabre\Node $node */
245
+        $node = $result->node;
246
+
247
+        switch ($property->name) {
248
+            case '{DAV:}displayname':
249
+                return $node->getName();
250
+            case '{DAV:}getlastmodified':
251
+                return $node->getLastModified();
252
+            case FilesPlugin::SIZE_PROPERTYNAME:
253
+                return $node->getSize();
254
+            case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
255
+                return $node->getInternalFileId();
256
+            default:
257
+                return null;
258
+        }
259
+    }
260
+
261
+    /**
262
+     * @param Node $node
263
+     * @return string
264
+     */
265
+    private function getHrefForNode(Node $node) {
266
+        $base = '/files/' . $this->user->getUID();
267
+        return $base . $this->view->getRelativePath($node->getPath());
268
+    }
269
+
270
+    /**
271
+     * @param Query $query
272
+     * @return ISearchQuery
273
+     */
274
+    private function transformQuery(Query $query): ISearchQuery {
275
+        $limit = $query->limit;
276
+        $orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
277
+        $offset = $limit->firstResult;
278
+
279
+        $limitHome = false;
280
+        $ownerProp = $this->extractWhereValue($query->where, FilesPlugin::OWNER_ID_PROPERTYNAME, Operator::OPERATION_EQUAL);
281
+        if ($ownerProp !== null) {
282
+            if ($ownerProp === $this->user->getUID()) {
283
+                $limitHome = true;
284
+            } else {
285
+                throw new \InvalidArgumentException("Invalid search value for '{http://owncloud.org/ns}owner-id', only the current user id is allowed");
286
+            }
287
+        }
288
+
289
+        return new SearchQuery(
290
+            $this->transformSearchOperation($query->where),
291
+            (int)$limit->maxResults,
292
+            $offset,
293
+            $orders,
294
+            $this->user,
295
+            $limitHome
296
+        );
297
+    }
298
+
299
+    /**
300
+     * @param Order $order
301
+     * @return ISearchOrder
302
+     */
303
+    private function mapSearchOrder(Order $order) {
304
+        return new SearchOrder($order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING, $this->mapPropertyNameToColumn($order->property));
305
+    }
306
+
307
+    /**
308
+     * @param Operator $operator
309
+     * @return ISearchOperator
310
+     */
311
+    private function transformSearchOperation(Operator $operator) {
312
+        [, $trimmedType] = explode('}', $operator->type);
313
+        switch ($operator->type) {
314
+            case Operator::OPERATION_AND:
315
+            case Operator::OPERATION_OR:
316
+            case Operator::OPERATION_NOT:
317
+                $arguments = array_map([$this, 'transformSearchOperation'], $operator->arguments);
318
+                return new SearchBinaryOperator($trimmedType, $arguments);
319
+            case Operator::OPERATION_EQUAL:
320
+            case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
321
+            case Operator::OPERATION_GREATER_THAN:
322
+            case Operator::OPERATION_LESS_OR_EQUAL_THAN:
323
+            case Operator::OPERATION_LESS_THAN:
324
+            case Operator::OPERATION_IS_LIKE:
325
+                if (count($operator->arguments) !== 2) {
326
+                    throw new \InvalidArgumentException('Invalid number of arguments for ' . $trimmedType . ' operation');
327
+                }
328
+                if (!($operator->arguments[0] instanceof SearchPropertyDefinition)) {
329
+                    throw new \InvalidArgumentException('Invalid argument 1 for ' . $trimmedType . ' operation, expected property');
330
+                }
331
+                if (!($operator->arguments[1] instanceof Literal)) {
332
+                    throw new \InvalidArgumentException('Invalid argument 2 for ' . $trimmedType . ' operation, expected literal');
333
+                }
334
+                return new SearchComparison($trimmedType, $this->mapPropertyNameToColumn($operator->arguments[0]), $this->castValue($operator->arguments[0], $operator->arguments[1]->value));
335
+            case Operator::OPERATION_IS_COLLECTION:
336
+                return new SearchComparison('eq', 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE);
337
+            default:
338
+                throw new \InvalidArgumentException('Unsupported operation ' . $trimmedType . ' (' . $operator->type . ')');
339
+        }
340
+    }
341
+
342
+    /**
343
+     * @param SearchPropertyDefinition $property
344
+     * @return string
345
+     */
346
+    private function mapPropertyNameToColumn(SearchPropertyDefinition $property) {
347
+        switch ($property->name) {
348
+            case '{DAV:}displayname':
349
+                return 'name';
350
+            case '{DAV:}getcontenttype':
351
+                return 'mimetype';
352
+            case '{DAV:}getlastmodified':
353
+                return 'mtime';
354
+            case FilesPlugin::SIZE_PROPERTYNAME:
355
+                return 'size';
356
+            case TagsPlugin::FAVORITE_PROPERTYNAME:
357
+                return 'favorite';
358
+            case TagsPlugin::TAGS_PROPERTYNAME:
359
+                return 'tagname';
360
+            case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
361
+                return 'fileid';
362
+            default:
363
+                throw new \InvalidArgumentException('Unsupported property for search or order: ' . $property->name);
364
+        }
365
+    }
366
+
367
+    private function castValue(SearchPropertyDefinition $property, $value) {
368
+        switch ($property->dataType) {
369
+            case SearchPropertyDefinition::DATATYPE_BOOLEAN:
370
+                return $value === 'yes';
371
+            case SearchPropertyDefinition::DATATYPE_DECIMAL:
372
+            case SearchPropertyDefinition::DATATYPE_INTEGER:
373
+            case SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER:
374
+                return 0 + $value;
375
+            case SearchPropertyDefinition::DATATYPE_DATETIME:
376
+                if (is_numeric($value)) {
377
+                    return max(0, 0 + $value);
378
+                }
379
+                $date = \DateTime::createFromFormat(\DateTime::ATOM, $value);
380
+                return ($date instanceof \DateTime && $date->getTimestamp() !== false) ? $date->getTimestamp() : 0;
381
+            default:
382
+                return $value;
383
+        }
384
+    }
385
+
386
+    /**
387
+     * Get a specific property from the were clause
388
+     */
389
+    private function extractWhereValue(Operator &$operator, string $propertyName, string $comparison, bool $acceptableLocation = true): ?string {
390
+        switch ($operator->type) {
391
+            case Operator::OPERATION_AND:
392
+            case Operator::OPERATION_OR:
393
+            case Operator::OPERATION_NOT:
394
+                foreach ($operator->arguments as &$argument) {
395
+                    $value = $this->extractWhereValue($argument, $propertyName, $comparison, $acceptableLocation && $operator->type === Operator::OPERATION_AND);
396
+                    if ($value !== null) {
397
+                        return $value;
398
+                    }
399
+                }
400
+                return null;
401
+            case Operator::OPERATION_EQUAL:
402
+            case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
403
+            case Operator::OPERATION_GREATER_THAN:
404
+            case Operator::OPERATION_LESS_OR_EQUAL_THAN:
405
+            case Operator::OPERATION_LESS_THAN:
406
+            case Operator::OPERATION_IS_LIKE:
407
+                if ($operator->arguments[0]->name === $propertyName) {
408
+                    if ($operator->type === $comparison) {
409
+                        if ($acceptableLocation) {
410
+                            if ($operator->arguments[1] instanceof Literal) {
411
+                                $value = $operator->arguments[1]->value;
412
+
413
+                                // to remove the comparison from the query, we replace it with an empty AND
414
+                                $operator = new Operator(Operator::OPERATION_AND);
415
+
416
+                                return $value;
417
+                            } else {
418
+                                throw new \InvalidArgumentException("searching by '$propertyName' is only allowed with a literal value");
419
+                            }
420
+                        } else {
421
+                            throw new \InvalidArgumentException("searching by '$propertyName' is not allowed inside a '{DAV:}or' or '{DAV:}not'");
422
+                        }
423
+                    } else {
424
+                        throw new \InvalidArgumentException("searching by '$propertyName' is only allowed inside a '$comparison'");
425
+                    }
426
+                } else {
427
+                    return null;
428
+                }
429
+                // no break
430
+            default:
431
+                return null;
432
+        }
433
+    }
434 434
 }
Please login to merge, or discard this patch.