Passed
Push — master ( 10aa22...f23c21 )
by Morris
13:04 queued 10s
created
lib/private/Files/Cache/StorageGlobal.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -38,55 +38,55 @@
 block discarded – undo
38 38
  * @package OC\Files\Cache
39 39
  */
40 40
 class StorageGlobal {
41
-	/** @var IDBConnection */
42
-	private $connection;
41
+    /** @var IDBConnection */
42
+    private $connection;
43 43
 
44
-	/** @var array[] */
45
-	private $cache = [];
44
+    /** @var array[] */
45
+    private $cache = [];
46 46
 
47
-	public function __construct(IDBConnection $connection) {
48
-		$this->connection = $connection;
49
-	}
47
+    public function __construct(IDBConnection $connection) {
48
+        $this->connection = $connection;
49
+    }
50 50
 
51
-	/**
52
-	 * @param string[] $storageIds
53
-	 */
54
-	public function loadForStorageIds(array $storageIds) {
55
-		$builder = $this->connection->getQueryBuilder();
56
-		$query = $builder->select(['id', 'numeric_id', 'available', 'last_checked'])
57
-			->from('storages')
58
-			->where($builder->expr()->in('id', $builder->createNamedParameter(array_values($storageIds), IQueryBuilder::PARAM_STR_ARRAY)));
51
+    /**
52
+     * @param string[] $storageIds
53
+     */
54
+    public function loadForStorageIds(array $storageIds) {
55
+        $builder = $this->connection->getQueryBuilder();
56
+        $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked'])
57
+            ->from('storages')
58
+            ->where($builder->expr()->in('id', $builder->createNamedParameter(array_values($storageIds), IQueryBuilder::PARAM_STR_ARRAY)));
59 59
 
60
-		$result = $query->execute();
61
-		while ($row = $result->fetch()) {
62
-			$this->cache[$row['id']] = $row;
63
-		}
64
-		$result->closeCursor();
65
-	}
60
+        $result = $query->execute();
61
+        while ($row = $result->fetch()) {
62
+            $this->cache[$row['id']] = $row;
63
+        }
64
+        $result->closeCursor();
65
+    }
66 66
 
67
-	/**
68
-	 * @param string $storageId
69
-	 * @return array|null
70
-	 */
71
-	public function getStorageInfo($storageId) {
72
-		if (!isset($this->cache[$storageId])) {
73
-			$builder = $this->connection->getQueryBuilder();
74
-			$query = $builder->select(['id', 'numeric_id', 'available', 'last_checked'])
75
-				->from('storages')
76
-				->where($builder->expr()->eq('id', $builder->createNamedParameter($storageId)));
67
+    /**
68
+     * @param string $storageId
69
+     * @return array|null
70
+     */
71
+    public function getStorageInfo($storageId) {
72
+        if (!isset($this->cache[$storageId])) {
73
+            $builder = $this->connection->getQueryBuilder();
74
+            $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked'])
75
+                ->from('storages')
76
+                ->where($builder->expr()->eq('id', $builder->createNamedParameter($storageId)));
77 77
 
78
-			$result = $query->execute();
79
-			$row = $result->fetch();
80
-			$result->closeCursor();
78
+            $result = $query->execute();
79
+            $row = $result->fetch();
80
+            $result->closeCursor();
81 81
 
82
-			if ($row) {
83
-				$this->cache[$storageId] = $row;
84
-			}
85
-		}
86
-		return isset($this->cache[$storageId]) ? $this->cache[$storageId] : null;
87
-	}
82
+            if ($row) {
83
+                $this->cache[$storageId] = $row;
84
+            }
85
+        }
86
+        return isset($this->cache[$storageId]) ? $this->cache[$storageId] : null;
87
+    }
88 88
 
89
-	public function clearCache() {
90
-		$this->cache = [];
91
-	}
89
+    public function clearCache() {
90
+        $this->cache = [];
91
+    }
92 92
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/HomeCache.php 1 patch
Indentation   +56 added lines, -56 removed lines patch added patch discarded remove patch
@@ -31,66 +31,66 @@
 block discarded – undo
31 31
 use OCP\Files\Cache\ICacheEntry;
32 32
 
33 33
 class HomeCache extends Cache {
34
-	/**
35
-	 * get the size of a folder and set it in the cache
36
-	 *
37
-	 * @param string $path
38
-	 * @param array $entry (optional) meta data of the folder
39
-	 * @return int
40
-	 */
41
-	public function calculateFolderSize($path, $entry = null) {
42
-		if ($path !== '/' and $path !== '' and $path !== 'files' and $path !== 'files_trashbin' and $path !== 'files_versions') {
43
-			return parent::calculateFolderSize($path, $entry);
44
-		} elseif ($path === '' or $path === '/') {
45
-			// since the size of / isn't used (the size of /files is used instead) there is no use in calculating it
46
-			return 0;
47
-		}
34
+    /**
35
+     * get the size of a folder and set it in the cache
36
+     *
37
+     * @param string $path
38
+     * @param array $entry (optional) meta data of the folder
39
+     * @return int
40
+     */
41
+    public function calculateFolderSize($path, $entry = null) {
42
+        if ($path !== '/' and $path !== '' and $path !== 'files' and $path !== 'files_trashbin' and $path !== 'files_versions') {
43
+            return parent::calculateFolderSize($path, $entry);
44
+        } elseif ($path === '' or $path === '/') {
45
+            // since the size of / isn't used (the size of /files is used instead) there is no use in calculating it
46
+            return 0;
47
+        }
48 48
 
49
-		$totalSize = 0;
50
-		if (is_null($entry)) {
51
-			$entry = $this->get($path);
52
-		}
53
-		if ($entry && $entry['mimetype'] === 'httpd/unix-directory') {
54
-			$id = $entry['fileid'];
49
+        $totalSize = 0;
50
+        if (is_null($entry)) {
51
+            $entry = $this->get($path);
52
+        }
53
+        if ($entry && $entry['mimetype'] === 'httpd/unix-directory') {
54
+            $id = $entry['fileid'];
55 55
 
56
-			$query = $this->connection->getQueryBuilder();
57
-			$query->selectAlias($query->func()->sum('size'), 'f1')
58
-				->from('filecache')
59
-				->where($query->expr()->eq('parent', $query->createNamedParameter($id)))
60
-				->andWhere($query->expr()->eq('storage', $query->createNamedParameter($this->getNumericStorageId())))
61
-				->andWhere($query->expr()->gte('size', $query->createNamedParameter(0)));
56
+            $query = $this->connection->getQueryBuilder();
57
+            $query->selectAlias($query->func()->sum('size'), 'f1')
58
+                ->from('filecache')
59
+                ->where($query->expr()->eq('parent', $query->createNamedParameter($id)))
60
+                ->andWhere($query->expr()->eq('storage', $query->createNamedParameter($this->getNumericStorageId())))
61
+                ->andWhere($query->expr()->gte('size', $query->createNamedParameter(0)));
62 62
 
63
-			$result = $query->execute();
64
-			$row = $result->fetch();
65
-			$result->closeCursor();
63
+            $result = $query->execute();
64
+            $row = $result->fetch();
65
+            $result->closeCursor();
66 66
 
67
-			if ($row) {
68
-				list($sum) = array_values($row);
69
-				$totalSize = 0 + $sum;
70
-				$entry['size'] += 0;
71
-				if ($entry['size'] !== $totalSize) {
72
-					$this->update($id, ['size' => $totalSize]);
73
-				}
74
-			}
75
-			$result->closeCursor();
76
-		}
77
-		return $totalSize;
78
-	}
67
+            if ($row) {
68
+                list($sum) = array_values($row);
69
+                $totalSize = 0 + $sum;
70
+                $entry['size'] += 0;
71
+                if ($entry['size'] !== $totalSize) {
72
+                    $this->update($id, ['size' => $totalSize]);
73
+                }
74
+            }
75
+            $result->closeCursor();
76
+        }
77
+        return $totalSize;
78
+    }
79 79
 
80
-	/**
81
-	 * @param string $path
82
-	 * @return ICacheEntry
83
-	 */
84
-	public function get($path) {
85
-		$data = parent::get($path);
86
-		if ($path === '' or $path === '/') {
87
-			// only the size of the "files" dir counts
88
-			$filesData = parent::get('files');
80
+    /**
81
+     * @param string $path
82
+     * @return ICacheEntry
83
+     */
84
+    public function get($path) {
85
+        $data = parent::get($path);
86
+        if ($path === '' or $path === '/') {
87
+            // only the size of the "files" dir counts
88
+            $filesData = parent::get('files');
89 89
 
90
-			if (isset($filesData['size'])) {
91
-				$data['size'] = $filesData['size'];
92
-			}
93
-		}
94
-		return $data;
95
-	}
90
+            if (isset($filesData['size'])) {
91
+                $data['size'] = $filesData['size'];
92
+            }
93
+        }
94
+        return $data;
95
+    }
96 96
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/Cache.php 2 patches
Indentation   +981 added lines, -981 removed lines patch added patch discarded remove patch
@@ -62,985 +62,985 @@
 block discarded – undo
62 62
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
63 63
  */
64 64
 class Cache implements ICache {
65
-	use MoveFromCacheTrait {
66
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
67
-	}
68
-
69
-	/**
70
-	 * @var array partial data for the cache
71
-	 */
72
-	protected $partial = [];
73
-
74
-	/**
75
-	 * @var string
76
-	 */
77
-	protected $storageId;
78
-
79
-	private $storage;
80
-
81
-	/**
82
-	 * @var Storage $storageCache
83
-	 */
84
-	protected $storageCache;
85
-
86
-	/** @var IMimeTypeLoader */
87
-	protected $mimetypeLoader;
88
-
89
-	/**
90
-	 * @var IDBConnection
91
-	 */
92
-	protected $connection;
93
-
94
-	protected $eventDispatcher;
95
-
96
-	/** @var QuerySearchHelper */
97
-	protected $querySearchHelper;
98
-
99
-	/**
100
-	 * @param IStorage $storage
101
-	 */
102
-	public function __construct(IStorage $storage) {
103
-		$this->storageId = $storage->getId();
104
-		$this->storage = $storage;
105
-		if (strlen($this->storageId) > 64) {
106
-			$this->storageId = md5($this->storageId);
107
-		}
108
-
109
-		$this->storageCache = new Storage($storage);
110
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
111
-		$this->connection = \OC::$server->getDatabaseConnection();
112
-		$this->eventDispatcher = \OC::$server->getEventDispatcher();
113
-		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
114
-	}
115
-
116
-	private function getQueryBuilder() {
117
-		return new CacheQueryBuilder(
118
-			$this->connection,
119
-			\OC::$server->getSystemConfig(),
120
-			\OC::$server->getLogger(),
121
-			$this
122
-		);
123
-	}
124
-
125
-	/**
126
-	 * Get the numeric storage id for this cache's storage
127
-	 *
128
-	 * @return int
129
-	 */
130
-	public function getNumericStorageId() {
131
-		return $this->storageCache->getNumericId();
132
-	}
133
-
134
-	/**
135
-	 * get the stored metadata of a file or folder
136
-	 *
137
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
138
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
139
-	 */
140
-	public function get($file) {
141
-		$query = $this->getQueryBuilder();
142
-		$query->selectFileCache();
143
-
144
-		if (is_string($file) or $file == '') {
145
-			// normalize file
146
-			$file = $this->normalize($file);
147
-
148
-			$query->whereStorageId()
149
-				->wherePath($file);
150
-		} else { //file id
151
-			$query->whereFileId($file);
152
-		}
153
-
154
-		$result = $query->execute();
155
-		$data = $result->fetch();
156
-		$result->closeCursor();
157
-
158
-		//merge partial data
159
-		if (!$data and is_string($file) and isset($this->partial[$file])) {
160
-			return $this->partial[$file];
161
-		} elseif (!$data) {
162
-			return $data;
163
-		} else {
164
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
165
-		}
166
-	}
167
-
168
-	/**
169
-	 * Create a CacheEntry from database row
170
-	 *
171
-	 * @param array $data
172
-	 * @param IMimeTypeLoader $mimetypeLoader
173
-	 * @return CacheEntry
174
-	 */
175
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
176
-		//fix types
177
-		$data['fileid'] = (int)$data['fileid'];
178
-		$data['parent'] = (int)$data['parent'];
179
-		$data['size'] = 0 + $data['size'];
180
-		$data['mtime'] = (int)$data['mtime'];
181
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
182
-		$data['encryptedVersion'] = (int)$data['encrypted'];
183
-		$data['encrypted'] = (bool)$data['encrypted'];
184
-		$data['storage_id'] = $data['storage'];
185
-		$data['storage'] = (int)$data['storage'];
186
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
187
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
188
-		if ($data['storage_mtime'] == 0) {
189
-			$data['storage_mtime'] = $data['mtime'];
190
-		}
191
-		$data['permissions'] = (int)$data['permissions'];
192
-		if (isset($data['creation_time'])) {
193
-			$data['creation_time'] = (int) $data['creation_time'];
194
-		}
195
-		if (isset($data['upload_time'])) {
196
-			$data['upload_time'] = (int) $data['upload_time'];
197
-		}
198
-		return new CacheEntry($data);
199
-	}
200
-
201
-	/**
202
-	 * get the metadata of all files stored in $folder
203
-	 *
204
-	 * @param string $folder
205
-	 * @return ICacheEntry[]
206
-	 */
207
-	public function getFolderContents($folder) {
208
-		$fileId = $this->getId($folder);
209
-		return $this->getFolderContentsById($fileId);
210
-	}
211
-
212
-	/**
213
-	 * get the metadata of all files stored in $folder
214
-	 *
215
-	 * @param int $fileId the file id of the folder
216
-	 * @return ICacheEntry[]
217
-	 */
218
-	public function getFolderContentsById($fileId) {
219
-		if ($fileId > -1) {
220
-			$query = $this->getQueryBuilder();
221
-			$query->selectFileCache()
222
-				->whereParent($fileId)
223
-				->orderBy('name', 'ASC');
224
-
225
-			$result = $query->execute();
226
-			$files = $result->fetchAll();
227
-			$result->closeCursor();
228
-
229
-			return array_map(function (array $data) {
230
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);
231
-			}, $files);
232
-		}
233
-		return [];
234
-	}
235
-
236
-	/**
237
-	 * insert or update meta data for a file or folder
238
-	 *
239
-	 * @param string $file
240
-	 * @param array $data
241
-	 *
242
-	 * @return int file id
243
-	 * @throws \RuntimeException
244
-	 */
245
-	public function put($file, array $data) {
246
-		if (($id = $this->getId($file)) > -1) {
247
-			$this->update($id, $data);
248
-			return $id;
249
-		} else {
250
-			return $this->insert($file, $data);
251
-		}
252
-	}
253
-
254
-	/**
255
-	 * insert meta data for a new file or folder
256
-	 *
257
-	 * @param string $file
258
-	 * @param array $data
259
-	 *
260
-	 * @return int file id
261
-	 * @throws \RuntimeException
262
-	 */
263
-	public function insert($file, array $data) {
264
-		// normalize file
265
-		$file = $this->normalize($file);
266
-
267
-		if (isset($this->partial[$file])) { //add any saved partial data
268
-			$data = array_merge($this->partial[$file], $data);
269
-			unset($this->partial[$file]);
270
-		}
271
-
272
-		$requiredFields = ['size', 'mtime', 'mimetype'];
273
-		foreach ($requiredFields as $field) {
274
-			if (!isset($data[$field])) { //data not complete save as partial and return
275
-				$this->partial[$file] = $data;
276
-				return -1;
277
-			}
278
-		}
279
-
280
-		$data['path'] = $file;
281
-		if (!isset($data['parent'])) {
282
-			$data['parent'] = $this->getParentId($file);
283
-		}
284
-		$data['name'] = basename($file);
285
-
286
-		[$values, $extensionValues] = $this->normalizeData($data);
287
-		$values['storage'] = $this->getNumericStorageId();
288
-
289
-		try {
290
-			$builder = $this->connection->getQueryBuilder();
291
-			$builder->insert('filecache');
292
-
293
-			foreach ($values as $column => $value) {
294
-				$builder->setValue($column, $builder->createNamedParameter($value));
295
-			}
296
-
297
-			if ($builder->execute()) {
298
-				$fileId = $builder->getLastInsertId();
299
-
300
-				if (count($extensionValues)) {
301
-					$query = $this->getQueryBuilder();
302
-					$query->insert('filecache_extended');
303
-
304
-					$query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
305
-					foreach ($extensionValues as $column => $value) {
306
-						$query->setValue($column, $query->createNamedParameter($value));
307
-					}
308
-					$query->execute();
309
-				}
310
-
311
-				$this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
312
-				return $fileId;
313
-			}
314
-		} catch (UniqueConstraintViolationException $e) {
315
-			// entry exists already
316
-			if ($this->connection->inTransaction()) {
317
-				$this->connection->commit();
318
-				$this->connection->beginTransaction();
319
-			}
320
-		}
321
-
322
-		// The file was created in the mean time
323
-		if (($id = $this->getId($file)) > -1) {
324
-			$this->update($id, $data);
325
-			return $id;
326
-		} else {
327
-			throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
328
-		}
329
-	}
330
-
331
-	/**
332
-	 * update the metadata of an existing file or folder in the cache
333
-	 *
334
-	 * @param int $id the fileid of the existing file or folder
335
-	 * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
336
-	 */
337
-	public function update($id, array $data) {
338
-		if (isset($data['path'])) {
339
-			// normalize path
340
-			$data['path'] = $this->normalize($data['path']);
341
-		}
342
-
343
-		if (isset($data['name'])) {
344
-			// normalize path
345
-			$data['name'] = $this->normalize($data['name']);
346
-		}
347
-
348
-		[$values, $extensionValues] = $this->normalizeData($data);
349
-
350
-		if (count($values)) {
351
-			$query = $this->getQueryBuilder();
352
-
353
-			$query->update('filecache')
354
-				->whereFileId($id)
355
-				->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
356
-					return $query->expr()->orX(
357
-						$query->expr()->neq($key, $query->createNamedParameter($value)),
358
-						$query->expr()->isNull($key)
359
-					);
360
-				}, array_keys($values), array_values($values))));
361
-
362
-			foreach ($values as $key => $value) {
363
-				$query->set($key, $query->createNamedParameter($value));
364
-			}
365
-
366
-			$query->execute();
367
-		}
368
-
369
-		if (count($extensionValues)) {
370
-			try {
371
-				$query = $this->getQueryBuilder();
372
-				$query->insert('filecache_extended');
373
-
374
-				$query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
375
-				foreach ($extensionValues as $column => $value) {
376
-					$query->setValue($column, $query->createNamedParameter($value));
377
-				}
378
-
379
-				$query->execute();
380
-			} catch (UniqueConstraintViolationException $e) {
381
-				$query = $this->getQueryBuilder();
382
-				$query->update('filecache_extended')
383
-					->whereFileId($id)
384
-					->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
385
-						return $query->expr()->orX(
386
-							$query->expr()->neq($key, $query->createNamedParameter($value)),
387
-							$query->expr()->isNull($key)
388
-						);
389
-					}, array_keys($extensionValues), array_values($extensionValues))));
390
-
391
-				foreach ($extensionValues as $key => $value) {
392
-					$query->set($key, $query->createNamedParameter($value));
393
-				}
394
-
395
-				$query->execute();
396
-			}
397
-		}
398
-
399
-		$path = $this->getPathById($id);
400
-		// path can still be null if the file doesn't exist
401
-		if ($path !== null) {
402
-			$this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
403
-		}
404
-	}
405
-
406
-	/**
407
-	 * extract query parts and params array from data array
408
-	 *
409
-	 * @param array $data
410
-	 * @return array
411
-	 */
412
-	protected function normalizeData(array $data): array {
413
-		$fields = [
414
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
415
-			'etag', 'permissions', 'checksum', 'storage'];
416
-		$extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
417
-
418
-		$doNotCopyStorageMTime = false;
419
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
420
-			// this horrific magic tells it to not copy storage_mtime to mtime
421
-			unset($data['mtime']);
422
-			$doNotCopyStorageMTime = true;
423
-		}
424
-
425
-		$params = [];
426
-		$extensionParams = [];
427
-		foreach ($data as $name => $value) {
428
-			if (array_search($name, $fields) !== false) {
429
-				if ($name === 'path') {
430
-					$params['path_hash'] = md5($value);
431
-				} elseif ($name === 'mimetype') {
432
-					$params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
433
-					$value = $this->mimetypeLoader->getId($value);
434
-				} elseif ($name === 'storage_mtime') {
435
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
436
-						$params['mtime'] = $value;
437
-					}
438
-				} elseif ($name === 'encrypted') {
439
-					if (isset($data['encryptedVersion'])) {
440
-						$value = $data['encryptedVersion'];
441
-					} else {
442
-						// Boolean to integer conversion
443
-						$value = $value ? 1 : 0;
444
-					}
445
-				}
446
-				$params[$name] = $value;
447
-			}
448
-			if (array_search($name, $extensionFields) !== false) {
449
-				$extensionParams[$name] = $value;
450
-			}
451
-		}
452
-		return [$params, array_filter($extensionParams)];
453
-	}
454
-
455
-	/**
456
-	 * get the file id for a file
457
-	 *
458
-	 * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
459
-	 *
460
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
461
-	 *
462
-	 * @param string $file
463
-	 * @return int
464
-	 */
465
-	public function getId($file) {
466
-		// normalize file
467
-		$file = $this->normalize($file);
468
-
469
-		$query = $this->getQueryBuilder();
470
-		$query->select('fileid')
471
-			->from('filecache')
472
-			->whereStorageId()
473
-			->wherePath($file);
474
-
475
-		$result = $query->execute();
476
-		$id = $result->fetchColumn();
477
-		$result->closeCursor();
478
-
479
-		return $id === false ? -1 : (int)$id;
480
-	}
481
-
482
-	/**
483
-	 * get the id of the parent folder of a file
484
-	 *
485
-	 * @param string $file
486
-	 * @return int
487
-	 */
488
-	public function getParentId($file) {
489
-		if ($file === '') {
490
-			return -1;
491
-		} else {
492
-			$parent = $this->getParentPath($file);
493
-			return (int)$this->getId($parent);
494
-		}
495
-	}
496
-
497
-	private function getParentPath($path) {
498
-		$parent = dirname($path);
499
-		if ($parent === '.') {
500
-			$parent = '';
501
-		}
502
-		return $parent;
503
-	}
504
-
505
-	/**
506
-	 * check if a file is available in the cache
507
-	 *
508
-	 * @param string $file
509
-	 * @return bool
510
-	 */
511
-	public function inCache($file) {
512
-		return $this->getId($file) != -1;
513
-	}
514
-
515
-	/**
516
-	 * remove a file or folder from the cache
517
-	 *
518
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
519
-	 *
520
-	 * @param string $file
521
-	 */
522
-	public function remove($file) {
523
-		$entry = $this->get($file);
524
-
525
-		if ($entry) {
526
-			$query = $this->getQueryBuilder();
527
-			$query->delete('filecache')
528
-				->whereFileId($entry->getId());
529
-			$query->execute();
530
-
531
-			$query = $this->getQueryBuilder();
532
-			$query->delete('filecache_extended')
533
-				->whereFileId($entry->getId());
534
-			$query->execute();
535
-
536
-			if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
537
-				$this->removeChildren($entry);
538
-			}
539
-		}
540
-	}
541
-
542
-	/**
543
-	 * Get all sub folders of a folder
544
-	 *
545
-	 * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
546
-	 * @return ICacheEntry[] the cache entries for the subfolders
547
-	 */
548
-	private function getSubFolders(ICacheEntry $entry) {
549
-		$children = $this->getFolderContentsById($entry->getId());
550
-		return array_filter($children, function ($child) {
551
-			return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
552
-		});
553
-	}
554
-
555
-	/**
556
-	 * Recursively remove all children of a folder
557
-	 *
558
-	 * @param ICacheEntry $entry the cache entry of the folder to remove the children of
559
-	 * @throws \OC\DatabaseException
560
-	 */
561
-	private function removeChildren(ICacheEntry $entry) {
562
-		$parentIds = [$entry->getId()];
563
-		$queue = [$entry->getId()];
564
-
565
-		// we walk depth first trough the file tree, removing all filecache_extended attributes while we walk
566
-		// and collecting all folder ids to later use to delete the filecache entries
567
-		while ($entryId = array_pop($queue)) {
568
-			$children = $this->getFolderContentsById($entryId);
569
-			$childIds = array_map(function (ICacheEntry $cacheEntry) {
570
-				return $cacheEntry->getId();
571
-			}, $children);
572
-
573
-			$query = $this->getQueryBuilder();
574
-			$query->delete('filecache_extended')
575
-				->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY)));
576
-			$query->execute();
577
-
578
-			/** @var ICacheEntry[] $childFolders */
579
-			$childFolders = array_filter($children, function ($child) {
580
-				return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
581
-			});
582
-			foreach ($childFolders as $folder) {
583
-				$parentIds[] = $folder->getId();
584
-				$queue[] = $folder->getId();
585
-			}
586
-		}
587
-
588
-		$query = $this->getQueryBuilder();
589
-		$query->delete('filecache')
590
-			->whereParentIn($parentIds);
591
-		$query->execute();
592
-	}
593
-
594
-	/**
595
-	 * Move a file or folder in the cache
596
-	 *
597
-	 * @param string $source
598
-	 * @param string $target
599
-	 */
600
-	public function move($source, $target) {
601
-		$this->moveFromCache($this, $source, $target);
602
-	}
603
-
604
-	/**
605
-	 * Get the storage id and path needed for a move
606
-	 *
607
-	 * @param string $path
608
-	 * @return array [$storageId, $internalPath]
609
-	 */
610
-	protected function getMoveInfo($path) {
611
-		return [$this->getNumericStorageId(), $path];
612
-	}
613
-
614
-	/**
615
-	 * Move a file or folder in the cache
616
-	 *
617
-	 * @param \OCP\Files\Cache\ICache $sourceCache
618
-	 * @param string $sourcePath
619
-	 * @param string $targetPath
620
-	 * @throws \OC\DatabaseException
621
-	 * @throws \Exception if the given storages have an invalid id
622
-	 */
623
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
624
-		if ($sourceCache instanceof Cache) {
625
-			// normalize source and target
626
-			$sourcePath = $this->normalize($sourcePath);
627
-			$targetPath = $this->normalize($targetPath);
628
-
629
-			$sourceData = $sourceCache->get($sourcePath);
630
-			$sourceId = $sourceData['fileid'];
631
-			$newParentId = $this->getParentId($targetPath);
632
-
633
-			[$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath);
634
-			[$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath);
635
-
636
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
637
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
638
-			}
639
-			if (is_null($targetStorageId) || $targetStorageId === false) {
640
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
641
-			}
642
-
643
-			$this->connection->beginTransaction();
644
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
645
-				//update all child entries
646
-				$sourceLength = mb_strlen($sourcePath);
647
-				$query = $this->connection->getQueryBuilder();
648
-
649
-				$fun = $query->func();
650
-				$newPathFunction = $fun->concat(
651
-					$query->createNamedParameter($targetPath),
652
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
653
-				);
654
-				$query->update('filecache')
655
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
656
-					->set('path_hash', $fun->md5($newPathFunction))
657
-					->set('path', $newPathFunction)
658
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
659
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
660
-
661
-				try {
662
-					$query->execute();
663
-				} catch (\OC\DatabaseException $e) {
664
-					$this->connection->rollBack();
665
-					throw $e;
666
-				}
667
-			}
668
-
669
-			$query = $this->getQueryBuilder();
670
-			$query->update('filecache')
671
-				->set('storage', $query->createNamedParameter($targetStorageId))
672
-				->set('path', $query->createNamedParameter($targetPath))
673
-				->set('path_hash', $query->createNamedParameter(md5($targetPath)))
674
-				->set('name', $query->createNamedParameter(basename($targetPath)))
675
-				->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
676
-				->whereFileId($sourceId);
677
-			$query->execute();
678
-
679
-			$this->connection->commit();
680
-		} else {
681
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
682
-		}
683
-	}
684
-
685
-	/**
686
-	 * remove all entries for files that are stored on the storage from the cache
687
-	 */
688
-	public function clear() {
689
-		$query = $this->getQueryBuilder();
690
-		$query->delete('filecache')
691
-			->whereStorageId();
692
-		$query->execute();
693
-
694
-		$query = $this->connection->getQueryBuilder();
695
-		$query->delete('storages')
696
-			->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
697
-		$query->execute();
698
-	}
699
-
700
-	/**
701
-	 * Get the scan status of a file
702
-	 *
703
-	 * - Cache::NOT_FOUND: File is not in the cache
704
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
705
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
706
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
707
-	 *
708
-	 * @param string $file
709
-	 *
710
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
711
-	 */
712
-	public function getStatus($file) {
713
-		// normalize file
714
-		$file = $this->normalize($file);
715
-
716
-		$query = $this->getQueryBuilder();
717
-		$query->select('size')
718
-			->from('filecache')
719
-			->whereStorageId()
720
-			->wherePath($file);
721
-
722
-		$result = $query->execute();
723
-		$size = $result->fetchColumn();
724
-		$result->closeCursor();
725
-
726
-		if ($size !== false) {
727
-			if ((int)$size === -1) {
728
-				return self::SHALLOW;
729
-			} else {
730
-				return self::COMPLETE;
731
-			}
732
-		} else {
733
-			if (isset($this->partial[$file])) {
734
-				return self::PARTIAL;
735
-			} else {
736
-				return self::NOT_FOUND;
737
-			}
738
-		}
739
-	}
740
-
741
-	/**
742
-	 * search for files matching $pattern
743
-	 *
744
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
745
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
746
-	 */
747
-	public function search($pattern) {
748
-		// normalize pattern
749
-		$pattern = $this->normalize($pattern);
750
-
751
-		if ($pattern === '%%') {
752
-			return [];
753
-		}
754
-
755
-		$query = $this->getQueryBuilder();
756
-		$query->selectFileCache()
757
-			->whereStorageId()
758
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
759
-
760
-		$result = $query->execute();
761
-		$files = $result->fetchAll();
762
-		$result->closeCursor();
763
-
764
-		return array_map(function (array $data) {
765
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
766
-		}, $files);
767
-	}
768
-
769
-	/**
770
-	 * @param Statement $result
771
-	 * @return CacheEntry[]
772
-	 */
773
-	private function searchResultToCacheEntries(Statement $result) {
774
-		$files = $result->fetchAll();
775
-
776
-		return array_map(function (array $data) {
777
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
778
-		}, $files);
779
-	}
780
-
781
-	/**
782
-	 * search for files by mimetype
783
-	 *
784
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
785
-	 *        where it will search for all mimetypes in the group ('image/*')
786
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
787
-	 */
788
-	public function searchByMime($mimetype) {
789
-		$mimeId = $this->mimetypeLoader->getId($mimetype);
790
-
791
-		$query = $this->getQueryBuilder();
792
-		$query->selectFileCache()
793
-			->whereStorageId();
794
-
795
-		if (strpos($mimetype, '/')) {
796
-			$query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
797
-		} else {
798
-			$query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
799
-		}
800
-
801
-		$result = $query->execute();
802
-		$files = $result->fetchAll();
803
-		$result->closeCursor();
804
-
805
-		return array_map(function (array $data) {
806
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
807
-		}, $files);
808
-	}
809
-
810
-	public function searchQuery(ISearchQuery $searchQuery) {
811
-		$builder = $this->getQueryBuilder();
812
-
813
-		$query = $builder->selectFileCache('file');
814
-
815
-		$query->whereStorageId();
816
-
817
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
818
-			$query
819
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
820
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
821
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
822
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
823
-				))
824
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
825
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
826
-		}
827
-
828
-		$searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
829
-		if ($searchExpr) {
830
-			$query->andWhere($searchExpr);
831
-		}
832
-
833
-		if ($searchQuery->limitToHome() && ($this instanceof HomeCache)) {
834
-			$query->andWhere($builder->expr()->like('path', $query->expr()->literal('files/%')));
835
-		}
836
-
837
-		$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
838
-
839
-		if ($searchQuery->getLimit()) {
840
-			$query->setMaxResults($searchQuery->getLimit());
841
-		}
842
-		if ($searchQuery->getOffset()) {
843
-			$query->setFirstResult($searchQuery->getOffset());
844
-		}
845
-
846
-		$result = $query->execute();
847
-		return $this->searchResultToCacheEntries($result);
848
-	}
849
-
850
-	/**
851
-	 * Re-calculate the folder size and the size of all parent folders
852
-	 *
853
-	 * @param string|boolean $path
854
-	 * @param array $data (optional) meta data of the folder
855
-	 */
856
-	public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
857
-		$this->calculateFolderSize($path, $data);
858
-		if ($path !== '') {
859
-			$parent = dirname($path);
860
-			if ($parent === '.' or $parent === '/') {
861
-				$parent = '';
862
-			}
863
-			if ($isBackgroundScan) {
864
-				$parentData = $this->get($parent);
865
-				if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
866
-					$this->correctFolderSize($parent, $parentData, $isBackgroundScan);
867
-				}
868
-			} else {
869
-				$this->correctFolderSize($parent);
870
-			}
871
-		}
872
-	}
873
-
874
-	/**
875
-	 * get the incomplete count that shares parent $folder
876
-	 *
877
-	 * @param int $fileId the file id of the folder
878
-	 * @return int
879
-	 */
880
-	public function getIncompleteChildrenCount($fileId) {
881
-		if ($fileId > -1) {
882
-			$query = $this->getQueryBuilder();
883
-			$query->select($query->func()->count())
884
-				->from('filecache')
885
-				->whereParent($fileId)
886
-				->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
887
-
888
-			$result = $query->execute();
889
-			$size = (int)$result->fetchColumn();
890
-			$result->closeCursor();
891
-
892
-			return $size;
893
-		}
894
-		return -1;
895
-	}
896
-
897
-	/**
898
-	 * calculate the size of a folder and set it in the cache
899
-	 *
900
-	 * @param string $path
901
-	 * @param array $entry (optional) meta data of the folder
902
-	 * @return int
903
-	 */
904
-	public function calculateFolderSize($path, $entry = null) {
905
-		$totalSize = 0;
906
-		if (is_null($entry) or !isset($entry['fileid'])) {
907
-			$entry = $this->get($path);
908
-		}
909
-		if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
910
-			$id = $entry['fileid'];
911
-
912
-			$query = $this->getQueryBuilder();
913
-			$query->selectAlias($query->func()->sum('size'), 'f1')
914
-				->selectAlias($query->func()->min('size'), 'f2')
915
-				->from('filecache')
916
-				->whereStorageId()
917
-				->whereParent($id);
918
-
919
-			$result = $query->execute();
920
-			$row = $result->fetch();
921
-			$result->closeCursor();
922
-
923
-			if ($row) {
924
-				[$sum, $min] = array_values($row);
925
-				$sum = 0 + $sum;
926
-				$min = 0 + $min;
927
-				if ($min === -1) {
928
-					$totalSize = $min;
929
-				} else {
930
-					$totalSize = $sum;
931
-				}
932
-				if ($entry['size'] !== $totalSize) {
933
-					$this->update($id, ['size' => $totalSize]);
934
-				}
935
-			}
936
-		}
937
-		return $totalSize;
938
-	}
939
-
940
-	/**
941
-	 * get all file ids on the files on the storage
942
-	 *
943
-	 * @return int[]
944
-	 */
945
-	public function getAll() {
946
-		$query = $this->getQueryBuilder();
947
-		$query->select('fileid')
948
-			->from('filecache')
949
-			->whereStorageId();
950
-
951
-		$result = $query->execute();
952
-		$files = $result->fetchAll(\PDO::FETCH_COLUMN);
953
-		$result->closeCursor();
954
-
955
-		return array_map(function ($id) {
956
-			return (int)$id;
957
-		}, $files);
958
-	}
959
-
960
-	/**
961
-	 * find a folder in the cache which has not been fully scanned
962
-	 *
963
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
964
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
965
-	 * likely the folder where we stopped scanning previously
966
-	 *
967
-	 * @return string|bool the path of the folder or false when no folder matched
968
-	 */
969
-	public function getIncomplete() {
970
-		$query = $this->getQueryBuilder();
971
-		$query->select('path')
972
-			->from('filecache')
973
-			->whereStorageId()
974
-			->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
975
-			->orderBy('fileid', 'DESC');
976
-
977
-		$result = $query->execute();
978
-		$path = $result->fetchColumn();
979
-		$result->closeCursor();
980
-
981
-		return $path;
982
-	}
983
-
984
-	/**
985
-	 * get the path of a file on this storage by it's file id
986
-	 *
987
-	 * @param int $id the file id of the file or folder to search
988
-	 * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
989
-	 */
990
-	public function getPathById($id) {
991
-		$query = $this->getQueryBuilder();
992
-		$query->select('path')
993
-			->from('filecache')
994
-			->whereStorageId()
995
-			->whereFileId($id);
996
-
997
-		$result = $query->execute();
998
-		$path = $result->fetchColumn();
999
-		$result->closeCursor();
1000
-
1001
-		return $path === false ? null : $path;
1002
-	}
1003
-
1004
-	/**
1005
-	 * get the storage id of the storage for a file and the internal path of the file
1006
-	 * unlike getPathById this does not limit the search to files on this storage and
1007
-	 * instead does a global search in the cache table
1008
-	 *
1009
-	 * @param int $id
1010
-	 * @return array first element holding the storage id, second the path
1011
-	 * @deprecated use getPathById() instead
1012
-	 */
1013
-	public static function getById($id) {
1014
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
1015
-		$query->select('path', 'storage')
1016
-			->from('filecache')
1017
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
1018
-
1019
-		$result = $query->execute();
1020
-		$row = $result->fetch();
1021
-		$result->closeCursor();
1022
-
1023
-		if ($row) {
1024
-			$numericId = $row['storage'];
1025
-			$path = $row['path'];
1026
-		} else {
1027
-			return null;
1028
-		}
1029
-
1030
-		if ($id = Storage::getStorageId($numericId)) {
1031
-			return [$id, $path];
1032
-		} else {
1033
-			return null;
1034
-		}
1035
-	}
1036
-
1037
-	/**
1038
-	 * normalize the given path
1039
-	 *
1040
-	 * @param string $path
1041
-	 * @return string
1042
-	 */
1043
-	public function normalize($path) {
1044
-		return trim(\OC_Util::normalizeUnicode($path), '/');
1045
-	}
65
+    use MoveFromCacheTrait {
66
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
67
+    }
68
+
69
+    /**
70
+     * @var array partial data for the cache
71
+     */
72
+    protected $partial = [];
73
+
74
+    /**
75
+     * @var string
76
+     */
77
+    protected $storageId;
78
+
79
+    private $storage;
80
+
81
+    /**
82
+     * @var Storage $storageCache
83
+     */
84
+    protected $storageCache;
85
+
86
+    /** @var IMimeTypeLoader */
87
+    protected $mimetypeLoader;
88
+
89
+    /**
90
+     * @var IDBConnection
91
+     */
92
+    protected $connection;
93
+
94
+    protected $eventDispatcher;
95
+
96
+    /** @var QuerySearchHelper */
97
+    protected $querySearchHelper;
98
+
99
+    /**
100
+     * @param IStorage $storage
101
+     */
102
+    public function __construct(IStorage $storage) {
103
+        $this->storageId = $storage->getId();
104
+        $this->storage = $storage;
105
+        if (strlen($this->storageId) > 64) {
106
+            $this->storageId = md5($this->storageId);
107
+        }
108
+
109
+        $this->storageCache = new Storage($storage);
110
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
111
+        $this->connection = \OC::$server->getDatabaseConnection();
112
+        $this->eventDispatcher = \OC::$server->getEventDispatcher();
113
+        $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
114
+    }
115
+
116
+    private function getQueryBuilder() {
117
+        return new CacheQueryBuilder(
118
+            $this->connection,
119
+            \OC::$server->getSystemConfig(),
120
+            \OC::$server->getLogger(),
121
+            $this
122
+        );
123
+    }
124
+
125
+    /**
126
+     * Get the numeric storage id for this cache's storage
127
+     *
128
+     * @return int
129
+     */
130
+    public function getNumericStorageId() {
131
+        return $this->storageCache->getNumericId();
132
+    }
133
+
134
+    /**
135
+     * get the stored metadata of a file or folder
136
+     *
137
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
138
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
139
+     */
140
+    public function get($file) {
141
+        $query = $this->getQueryBuilder();
142
+        $query->selectFileCache();
143
+
144
+        if (is_string($file) or $file == '') {
145
+            // normalize file
146
+            $file = $this->normalize($file);
147
+
148
+            $query->whereStorageId()
149
+                ->wherePath($file);
150
+        } else { //file id
151
+            $query->whereFileId($file);
152
+        }
153
+
154
+        $result = $query->execute();
155
+        $data = $result->fetch();
156
+        $result->closeCursor();
157
+
158
+        //merge partial data
159
+        if (!$data and is_string($file) and isset($this->partial[$file])) {
160
+            return $this->partial[$file];
161
+        } elseif (!$data) {
162
+            return $data;
163
+        } else {
164
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
165
+        }
166
+    }
167
+
168
+    /**
169
+     * Create a CacheEntry from database row
170
+     *
171
+     * @param array $data
172
+     * @param IMimeTypeLoader $mimetypeLoader
173
+     * @return CacheEntry
174
+     */
175
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
176
+        //fix types
177
+        $data['fileid'] = (int)$data['fileid'];
178
+        $data['parent'] = (int)$data['parent'];
179
+        $data['size'] = 0 + $data['size'];
180
+        $data['mtime'] = (int)$data['mtime'];
181
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
182
+        $data['encryptedVersion'] = (int)$data['encrypted'];
183
+        $data['encrypted'] = (bool)$data['encrypted'];
184
+        $data['storage_id'] = $data['storage'];
185
+        $data['storage'] = (int)$data['storage'];
186
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
187
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
188
+        if ($data['storage_mtime'] == 0) {
189
+            $data['storage_mtime'] = $data['mtime'];
190
+        }
191
+        $data['permissions'] = (int)$data['permissions'];
192
+        if (isset($data['creation_time'])) {
193
+            $data['creation_time'] = (int) $data['creation_time'];
194
+        }
195
+        if (isset($data['upload_time'])) {
196
+            $data['upload_time'] = (int) $data['upload_time'];
197
+        }
198
+        return new CacheEntry($data);
199
+    }
200
+
201
+    /**
202
+     * get the metadata of all files stored in $folder
203
+     *
204
+     * @param string $folder
205
+     * @return ICacheEntry[]
206
+     */
207
+    public function getFolderContents($folder) {
208
+        $fileId = $this->getId($folder);
209
+        return $this->getFolderContentsById($fileId);
210
+    }
211
+
212
+    /**
213
+     * get the metadata of all files stored in $folder
214
+     *
215
+     * @param int $fileId the file id of the folder
216
+     * @return ICacheEntry[]
217
+     */
218
+    public function getFolderContentsById($fileId) {
219
+        if ($fileId > -1) {
220
+            $query = $this->getQueryBuilder();
221
+            $query->selectFileCache()
222
+                ->whereParent($fileId)
223
+                ->orderBy('name', 'ASC');
224
+
225
+            $result = $query->execute();
226
+            $files = $result->fetchAll();
227
+            $result->closeCursor();
228
+
229
+            return array_map(function (array $data) {
230
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);
231
+            }, $files);
232
+        }
233
+        return [];
234
+    }
235
+
236
+    /**
237
+     * insert or update meta data for a file or folder
238
+     *
239
+     * @param string $file
240
+     * @param array $data
241
+     *
242
+     * @return int file id
243
+     * @throws \RuntimeException
244
+     */
245
+    public function put($file, array $data) {
246
+        if (($id = $this->getId($file)) > -1) {
247
+            $this->update($id, $data);
248
+            return $id;
249
+        } else {
250
+            return $this->insert($file, $data);
251
+        }
252
+    }
253
+
254
+    /**
255
+     * insert meta data for a new file or folder
256
+     *
257
+     * @param string $file
258
+     * @param array $data
259
+     *
260
+     * @return int file id
261
+     * @throws \RuntimeException
262
+     */
263
+    public function insert($file, array $data) {
264
+        // normalize file
265
+        $file = $this->normalize($file);
266
+
267
+        if (isset($this->partial[$file])) { //add any saved partial data
268
+            $data = array_merge($this->partial[$file], $data);
269
+            unset($this->partial[$file]);
270
+        }
271
+
272
+        $requiredFields = ['size', 'mtime', 'mimetype'];
273
+        foreach ($requiredFields as $field) {
274
+            if (!isset($data[$field])) { //data not complete save as partial and return
275
+                $this->partial[$file] = $data;
276
+                return -1;
277
+            }
278
+        }
279
+
280
+        $data['path'] = $file;
281
+        if (!isset($data['parent'])) {
282
+            $data['parent'] = $this->getParentId($file);
283
+        }
284
+        $data['name'] = basename($file);
285
+
286
+        [$values, $extensionValues] = $this->normalizeData($data);
287
+        $values['storage'] = $this->getNumericStorageId();
288
+
289
+        try {
290
+            $builder = $this->connection->getQueryBuilder();
291
+            $builder->insert('filecache');
292
+
293
+            foreach ($values as $column => $value) {
294
+                $builder->setValue($column, $builder->createNamedParameter($value));
295
+            }
296
+
297
+            if ($builder->execute()) {
298
+                $fileId = $builder->getLastInsertId();
299
+
300
+                if (count($extensionValues)) {
301
+                    $query = $this->getQueryBuilder();
302
+                    $query->insert('filecache_extended');
303
+
304
+                    $query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
305
+                    foreach ($extensionValues as $column => $value) {
306
+                        $query->setValue($column, $query->createNamedParameter($value));
307
+                    }
308
+                    $query->execute();
309
+                }
310
+
311
+                $this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
312
+                return $fileId;
313
+            }
314
+        } catch (UniqueConstraintViolationException $e) {
315
+            // entry exists already
316
+            if ($this->connection->inTransaction()) {
317
+                $this->connection->commit();
318
+                $this->connection->beginTransaction();
319
+            }
320
+        }
321
+
322
+        // The file was created in the mean time
323
+        if (($id = $this->getId($file)) > -1) {
324
+            $this->update($id, $data);
325
+            return $id;
326
+        } else {
327
+            throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
328
+        }
329
+    }
330
+
331
+    /**
332
+     * update the metadata of an existing file or folder in the cache
333
+     *
334
+     * @param int $id the fileid of the existing file or folder
335
+     * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
336
+     */
337
+    public function update($id, array $data) {
338
+        if (isset($data['path'])) {
339
+            // normalize path
340
+            $data['path'] = $this->normalize($data['path']);
341
+        }
342
+
343
+        if (isset($data['name'])) {
344
+            // normalize path
345
+            $data['name'] = $this->normalize($data['name']);
346
+        }
347
+
348
+        [$values, $extensionValues] = $this->normalizeData($data);
349
+
350
+        if (count($values)) {
351
+            $query = $this->getQueryBuilder();
352
+
353
+            $query->update('filecache')
354
+                ->whereFileId($id)
355
+                ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
356
+                    return $query->expr()->orX(
357
+                        $query->expr()->neq($key, $query->createNamedParameter($value)),
358
+                        $query->expr()->isNull($key)
359
+                    );
360
+                }, array_keys($values), array_values($values))));
361
+
362
+            foreach ($values as $key => $value) {
363
+                $query->set($key, $query->createNamedParameter($value));
364
+            }
365
+
366
+            $query->execute();
367
+        }
368
+
369
+        if (count($extensionValues)) {
370
+            try {
371
+                $query = $this->getQueryBuilder();
372
+                $query->insert('filecache_extended');
373
+
374
+                $query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
375
+                foreach ($extensionValues as $column => $value) {
376
+                    $query->setValue($column, $query->createNamedParameter($value));
377
+                }
378
+
379
+                $query->execute();
380
+            } catch (UniqueConstraintViolationException $e) {
381
+                $query = $this->getQueryBuilder();
382
+                $query->update('filecache_extended')
383
+                    ->whereFileId($id)
384
+                    ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
385
+                        return $query->expr()->orX(
386
+                            $query->expr()->neq($key, $query->createNamedParameter($value)),
387
+                            $query->expr()->isNull($key)
388
+                        );
389
+                    }, array_keys($extensionValues), array_values($extensionValues))));
390
+
391
+                foreach ($extensionValues as $key => $value) {
392
+                    $query->set($key, $query->createNamedParameter($value));
393
+                }
394
+
395
+                $query->execute();
396
+            }
397
+        }
398
+
399
+        $path = $this->getPathById($id);
400
+        // path can still be null if the file doesn't exist
401
+        if ($path !== null) {
402
+            $this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
403
+        }
404
+    }
405
+
406
+    /**
407
+     * extract query parts and params array from data array
408
+     *
409
+     * @param array $data
410
+     * @return array
411
+     */
412
+    protected function normalizeData(array $data): array {
413
+        $fields = [
414
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
415
+            'etag', 'permissions', 'checksum', 'storage'];
416
+        $extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
417
+
418
+        $doNotCopyStorageMTime = false;
419
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
420
+            // this horrific magic tells it to not copy storage_mtime to mtime
421
+            unset($data['mtime']);
422
+            $doNotCopyStorageMTime = true;
423
+        }
424
+
425
+        $params = [];
426
+        $extensionParams = [];
427
+        foreach ($data as $name => $value) {
428
+            if (array_search($name, $fields) !== false) {
429
+                if ($name === 'path') {
430
+                    $params['path_hash'] = md5($value);
431
+                } elseif ($name === 'mimetype') {
432
+                    $params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
433
+                    $value = $this->mimetypeLoader->getId($value);
434
+                } elseif ($name === 'storage_mtime') {
435
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
436
+                        $params['mtime'] = $value;
437
+                    }
438
+                } elseif ($name === 'encrypted') {
439
+                    if (isset($data['encryptedVersion'])) {
440
+                        $value = $data['encryptedVersion'];
441
+                    } else {
442
+                        // Boolean to integer conversion
443
+                        $value = $value ? 1 : 0;
444
+                    }
445
+                }
446
+                $params[$name] = $value;
447
+            }
448
+            if (array_search($name, $extensionFields) !== false) {
449
+                $extensionParams[$name] = $value;
450
+            }
451
+        }
452
+        return [$params, array_filter($extensionParams)];
453
+    }
454
+
455
+    /**
456
+     * get the file id for a file
457
+     *
458
+     * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
459
+     *
460
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
461
+     *
462
+     * @param string $file
463
+     * @return int
464
+     */
465
+    public function getId($file) {
466
+        // normalize file
467
+        $file = $this->normalize($file);
468
+
469
+        $query = $this->getQueryBuilder();
470
+        $query->select('fileid')
471
+            ->from('filecache')
472
+            ->whereStorageId()
473
+            ->wherePath($file);
474
+
475
+        $result = $query->execute();
476
+        $id = $result->fetchColumn();
477
+        $result->closeCursor();
478
+
479
+        return $id === false ? -1 : (int)$id;
480
+    }
481
+
482
+    /**
483
+     * get the id of the parent folder of a file
484
+     *
485
+     * @param string $file
486
+     * @return int
487
+     */
488
+    public function getParentId($file) {
489
+        if ($file === '') {
490
+            return -1;
491
+        } else {
492
+            $parent = $this->getParentPath($file);
493
+            return (int)$this->getId($parent);
494
+        }
495
+    }
496
+
497
+    private function getParentPath($path) {
498
+        $parent = dirname($path);
499
+        if ($parent === '.') {
500
+            $parent = '';
501
+        }
502
+        return $parent;
503
+    }
504
+
505
+    /**
506
+     * check if a file is available in the cache
507
+     *
508
+     * @param string $file
509
+     * @return bool
510
+     */
511
+    public function inCache($file) {
512
+        return $this->getId($file) != -1;
513
+    }
514
+
515
+    /**
516
+     * remove a file or folder from the cache
517
+     *
518
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
519
+     *
520
+     * @param string $file
521
+     */
522
+    public function remove($file) {
523
+        $entry = $this->get($file);
524
+
525
+        if ($entry) {
526
+            $query = $this->getQueryBuilder();
527
+            $query->delete('filecache')
528
+                ->whereFileId($entry->getId());
529
+            $query->execute();
530
+
531
+            $query = $this->getQueryBuilder();
532
+            $query->delete('filecache_extended')
533
+                ->whereFileId($entry->getId());
534
+            $query->execute();
535
+
536
+            if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
537
+                $this->removeChildren($entry);
538
+            }
539
+        }
540
+    }
541
+
542
+    /**
543
+     * Get all sub folders of a folder
544
+     *
545
+     * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
546
+     * @return ICacheEntry[] the cache entries for the subfolders
547
+     */
548
+    private function getSubFolders(ICacheEntry $entry) {
549
+        $children = $this->getFolderContentsById($entry->getId());
550
+        return array_filter($children, function ($child) {
551
+            return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
552
+        });
553
+    }
554
+
555
+    /**
556
+     * Recursively remove all children of a folder
557
+     *
558
+     * @param ICacheEntry $entry the cache entry of the folder to remove the children of
559
+     * @throws \OC\DatabaseException
560
+     */
561
+    private function removeChildren(ICacheEntry $entry) {
562
+        $parentIds = [$entry->getId()];
563
+        $queue = [$entry->getId()];
564
+
565
+        // we walk depth first trough the file tree, removing all filecache_extended attributes while we walk
566
+        // and collecting all folder ids to later use to delete the filecache entries
567
+        while ($entryId = array_pop($queue)) {
568
+            $children = $this->getFolderContentsById($entryId);
569
+            $childIds = array_map(function (ICacheEntry $cacheEntry) {
570
+                return $cacheEntry->getId();
571
+            }, $children);
572
+
573
+            $query = $this->getQueryBuilder();
574
+            $query->delete('filecache_extended')
575
+                ->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY)));
576
+            $query->execute();
577
+
578
+            /** @var ICacheEntry[] $childFolders */
579
+            $childFolders = array_filter($children, function ($child) {
580
+                return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
581
+            });
582
+            foreach ($childFolders as $folder) {
583
+                $parentIds[] = $folder->getId();
584
+                $queue[] = $folder->getId();
585
+            }
586
+        }
587
+
588
+        $query = $this->getQueryBuilder();
589
+        $query->delete('filecache')
590
+            ->whereParentIn($parentIds);
591
+        $query->execute();
592
+    }
593
+
594
+    /**
595
+     * Move a file or folder in the cache
596
+     *
597
+     * @param string $source
598
+     * @param string $target
599
+     */
600
+    public function move($source, $target) {
601
+        $this->moveFromCache($this, $source, $target);
602
+    }
603
+
604
+    /**
605
+     * Get the storage id and path needed for a move
606
+     *
607
+     * @param string $path
608
+     * @return array [$storageId, $internalPath]
609
+     */
610
+    protected function getMoveInfo($path) {
611
+        return [$this->getNumericStorageId(), $path];
612
+    }
613
+
614
+    /**
615
+     * Move a file or folder in the cache
616
+     *
617
+     * @param \OCP\Files\Cache\ICache $sourceCache
618
+     * @param string $sourcePath
619
+     * @param string $targetPath
620
+     * @throws \OC\DatabaseException
621
+     * @throws \Exception if the given storages have an invalid id
622
+     */
623
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
624
+        if ($sourceCache instanceof Cache) {
625
+            // normalize source and target
626
+            $sourcePath = $this->normalize($sourcePath);
627
+            $targetPath = $this->normalize($targetPath);
628
+
629
+            $sourceData = $sourceCache->get($sourcePath);
630
+            $sourceId = $sourceData['fileid'];
631
+            $newParentId = $this->getParentId($targetPath);
632
+
633
+            [$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath);
634
+            [$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath);
635
+
636
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
637
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
638
+            }
639
+            if (is_null($targetStorageId) || $targetStorageId === false) {
640
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
641
+            }
642
+
643
+            $this->connection->beginTransaction();
644
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
645
+                //update all child entries
646
+                $sourceLength = mb_strlen($sourcePath);
647
+                $query = $this->connection->getQueryBuilder();
648
+
649
+                $fun = $query->func();
650
+                $newPathFunction = $fun->concat(
651
+                    $query->createNamedParameter($targetPath),
652
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
653
+                );
654
+                $query->update('filecache')
655
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
656
+                    ->set('path_hash', $fun->md5($newPathFunction))
657
+                    ->set('path', $newPathFunction)
658
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
659
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
660
+
661
+                try {
662
+                    $query->execute();
663
+                } catch (\OC\DatabaseException $e) {
664
+                    $this->connection->rollBack();
665
+                    throw $e;
666
+                }
667
+            }
668
+
669
+            $query = $this->getQueryBuilder();
670
+            $query->update('filecache')
671
+                ->set('storage', $query->createNamedParameter($targetStorageId))
672
+                ->set('path', $query->createNamedParameter($targetPath))
673
+                ->set('path_hash', $query->createNamedParameter(md5($targetPath)))
674
+                ->set('name', $query->createNamedParameter(basename($targetPath)))
675
+                ->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
676
+                ->whereFileId($sourceId);
677
+            $query->execute();
678
+
679
+            $this->connection->commit();
680
+        } else {
681
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
682
+        }
683
+    }
684
+
685
+    /**
686
+     * remove all entries for files that are stored on the storage from the cache
687
+     */
688
+    public function clear() {
689
+        $query = $this->getQueryBuilder();
690
+        $query->delete('filecache')
691
+            ->whereStorageId();
692
+        $query->execute();
693
+
694
+        $query = $this->connection->getQueryBuilder();
695
+        $query->delete('storages')
696
+            ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
697
+        $query->execute();
698
+    }
699
+
700
+    /**
701
+     * Get the scan status of a file
702
+     *
703
+     * - Cache::NOT_FOUND: File is not in the cache
704
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
705
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
706
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
707
+     *
708
+     * @param string $file
709
+     *
710
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
711
+     */
712
+    public function getStatus($file) {
713
+        // normalize file
714
+        $file = $this->normalize($file);
715
+
716
+        $query = $this->getQueryBuilder();
717
+        $query->select('size')
718
+            ->from('filecache')
719
+            ->whereStorageId()
720
+            ->wherePath($file);
721
+
722
+        $result = $query->execute();
723
+        $size = $result->fetchColumn();
724
+        $result->closeCursor();
725
+
726
+        if ($size !== false) {
727
+            if ((int)$size === -1) {
728
+                return self::SHALLOW;
729
+            } else {
730
+                return self::COMPLETE;
731
+            }
732
+        } else {
733
+            if (isset($this->partial[$file])) {
734
+                return self::PARTIAL;
735
+            } else {
736
+                return self::NOT_FOUND;
737
+            }
738
+        }
739
+    }
740
+
741
+    /**
742
+     * search for files matching $pattern
743
+     *
744
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
745
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
746
+     */
747
+    public function search($pattern) {
748
+        // normalize pattern
749
+        $pattern = $this->normalize($pattern);
750
+
751
+        if ($pattern === '%%') {
752
+            return [];
753
+        }
754
+
755
+        $query = $this->getQueryBuilder();
756
+        $query->selectFileCache()
757
+            ->whereStorageId()
758
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
759
+
760
+        $result = $query->execute();
761
+        $files = $result->fetchAll();
762
+        $result->closeCursor();
763
+
764
+        return array_map(function (array $data) {
765
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
766
+        }, $files);
767
+    }
768
+
769
+    /**
770
+     * @param Statement $result
771
+     * @return CacheEntry[]
772
+     */
773
+    private function searchResultToCacheEntries(Statement $result) {
774
+        $files = $result->fetchAll();
775
+
776
+        return array_map(function (array $data) {
777
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
778
+        }, $files);
779
+    }
780
+
781
+    /**
782
+     * search for files by mimetype
783
+     *
784
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
785
+     *        where it will search for all mimetypes in the group ('image/*')
786
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
787
+     */
788
+    public function searchByMime($mimetype) {
789
+        $mimeId = $this->mimetypeLoader->getId($mimetype);
790
+
791
+        $query = $this->getQueryBuilder();
792
+        $query->selectFileCache()
793
+            ->whereStorageId();
794
+
795
+        if (strpos($mimetype, '/')) {
796
+            $query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
797
+        } else {
798
+            $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
799
+        }
800
+
801
+        $result = $query->execute();
802
+        $files = $result->fetchAll();
803
+        $result->closeCursor();
804
+
805
+        return array_map(function (array $data) {
806
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
807
+        }, $files);
808
+    }
809
+
810
+    public function searchQuery(ISearchQuery $searchQuery) {
811
+        $builder = $this->getQueryBuilder();
812
+
813
+        $query = $builder->selectFileCache('file');
814
+
815
+        $query->whereStorageId();
816
+
817
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
818
+            $query
819
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
820
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
821
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
822
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
823
+                ))
824
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
825
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
826
+        }
827
+
828
+        $searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
829
+        if ($searchExpr) {
830
+            $query->andWhere($searchExpr);
831
+        }
832
+
833
+        if ($searchQuery->limitToHome() && ($this instanceof HomeCache)) {
834
+            $query->andWhere($builder->expr()->like('path', $query->expr()->literal('files/%')));
835
+        }
836
+
837
+        $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
838
+
839
+        if ($searchQuery->getLimit()) {
840
+            $query->setMaxResults($searchQuery->getLimit());
841
+        }
842
+        if ($searchQuery->getOffset()) {
843
+            $query->setFirstResult($searchQuery->getOffset());
844
+        }
845
+
846
+        $result = $query->execute();
847
+        return $this->searchResultToCacheEntries($result);
848
+    }
849
+
850
+    /**
851
+     * Re-calculate the folder size and the size of all parent folders
852
+     *
853
+     * @param string|boolean $path
854
+     * @param array $data (optional) meta data of the folder
855
+     */
856
+    public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
857
+        $this->calculateFolderSize($path, $data);
858
+        if ($path !== '') {
859
+            $parent = dirname($path);
860
+            if ($parent === '.' or $parent === '/') {
861
+                $parent = '';
862
+            }
863
+            if ($isBackgroundScan) {
864
+                $parentData = $this->get($parent);
865
+                if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
866
+                    $this->correctFolderSize($parent, $parentData, $isBackgroundScan);
867
+                }
868
+            } else {
869
+                $this->correctFolderSize($parent);
870
+            }
871
+        }
872
+    }
873
+
874
+    /**
875
+     * get the incomplete count that shares parent $folder
876
+     *
877
+     * @param int $fileId the file id of the folder
878
+     * @return int
879
+     */
880
+    public function getIncompleteChildrenCount($fileId) {
881
+        if ($fileId > -1) {
882
+            $query = $this->getQueryBuilder();
883
+            $query->select($query->func()->count())
884
+                ->from('filecache')
885
+                ->whereParent($fileId)
886
+                ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
887
+
888
+            $result = $query->execute();
889
+            $size = (int)$result->fetchColumn();
890
+            $result->closeCursor();
891
+
892
+            return $size;
893
+        }
894
+        return -1;
895
+    }
896
+
897
+    /**
898
+     * calculate the size of a folder and set it in the cache
899
+     *
900
+     * @param string $path
901
+     * @param array $entry (optional) meta data of the folder
902
+     * @return int
903
+     */
904
+    public function calculateFolderSize($path, $entry = null) {
905
+        $totalSize = 0;
906
+        if (is_null($entry) or !isset($entry['fileid'])) {
907
+            $entry = $this->get($path);
908
+        }
909
+        if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
910
+            $id = $entry['fileid'];
911
+
912
+            $query = $this->getQueryBuilder();
913
+            $query->selectAlias($query->func()->sum('size'), 'f1')
914
+                ->selectAlias($query->func()->min('size'), 'f2')
915
+                ->from('filecache')
916
+                ->whereStorageId()
917
+                ->whereParent($id);
918
+
919
+            $result = $query->execute();
920
+            $row = $result->fetch();
921
+            $result->closeCursor();
922
+
923
+            if ($row) {
924
+                [$sum, $min] = array_values($row);
925
+                $sum = 0 + $sum;
926
+                $min = 0 + $min;
927
+                if ($min === -1) {
928
+                    $totalSize = $min;
929
+                } else {
930
+                    $totalSize = $sum;
931
+                }
932
+                if ($entry['size'] !== $totalSize) {
933
+                    $this->update($id, ['size' => $totalSize]);
934
+                }
935
+            }
936
+        }
937
+        return $totalSize;
938
+    }
939
+
940
+    /**
941
+     * get all file ids on the files on the storage
942
+     *
943
+     * @return int[]
944
+     */
945
+    public function getAll() {
946
+        $query = $this->getQueryBuilder();
947
+        $query->select('fileid')
948
+            ->from('filecache')
949
+            ->whereStorageId();
950
+
951
+        $result = $query->execute();
952
+        $files = $result->fetchAll(\PDO::FETCH_COLUMN);
953
+        $result->closeCursor();
954
+
955
+        return array_map(function ($id) {
956
+            return (int)$id;
957
+        }, $files);
958
+    }
959
+
960
+    /**
961
+     * find a folder in the cache which has not been fully scanned
962
+     *
963
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
964
+     * use the one with the highest id gives the best result with the background scanner, since that is most
965
+     * likely the folder where we stopped scanning previously
966
+     *
967
+     * @return string|bool the path of the folder or false when no folder matched
968
+     */
969
+    public function getIncomplete() {
970
+        $query = $this->getQueryBuilder();
971
+        $query->select('path')
972
+            ->from('filecache')
973
+            ->whereStorageId()
974
+            ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
975
+            ->orderBy('fileid', 'DESC');
976
+
977
+        $result = $query->execute();
978
+        $path = $result->fetchColumn();
979
+        $result->closeCursor();
980
+
981
+        return $path;
982
+    }
983
+
984
+    /**
985
+     * get the path of a file on this storage by it's file id
986
+     *
987
+     * @param int $id the file id of the file or folder to search
988
+     * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
989
+     */
990
+    public function getPathById($id) {
991
+        $query = $this->getQueryBuilder();
992
+        $query->select('path')
993
+            ->from('filecache')
994
+            ->whereStorageId()
995
+            ->whereFileId($id);
996
+
997
+        $result = $query->execute();
998
+        $path = $result->fetchColumn();
999
+        $result->closeCursor();
1000
+
1001
+        return $path === false ? null : $path;
1002
+    }
1003
+
1004
+    /**
1005
+     * get the storage id of the storage for a file and the internal path of the file
1006
+     * unlike getPathById this does not limit the search to files on this storage and
1007
+     * instead does a global search in the cache table
1008
+     *
1009
+     * @param int $id
1010
+     * @return array first element holding the storage id, second the path
1011
+     * @deprecated use getPathById() instead
1012
+     */
1013
+    public static function getById($id) {
1014
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
1015
+        $query->select('path', 'storage')
1016
+            ->from('filecache')
1017
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
1018
+
1019
+        $result = $query->execute();
1020
+        $row = $result->fetch();
1021
+        $result->closeCursor();
1022
+
1023
+        if ($row) {
1024
+            $numericId = $row['storage'];
1025
+            $path = $row['path'];
1026
+        } else {
1027
+            return null;
1028
+        }
1029
+
1030
+        if ($id = Storage::getStorageId($numericId)) {
1031
+            return [$id, $path];
1032
+        } else {
1033
+            return null;
1034
+        }
1035
+    }
1036
+
1037
+    /**
1038
+     * normalize the given path
1039
+     *
1040
+     * @param string $path
1041
+     * @return string
1042
+     */
1043
+    public function normalize($path) {
1044
+        return trim(\OC_Util::normalizeUnicode($path), '/');
1045
+    }
1046 1046
 }
Please login to merge, or discard this patch.
Spacing   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -174,21 +174,21 @@  discard block
 block discarded – undo
174 174
 	 */
175 175
 	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
176 176
 		//fix types
177
-		$data['fileid'] = (int)$data['fileid'];
178
-		$data['parent'] = (int)$data['parent'];
177
+		$data['fileid'] = (int) $data['fileid'];
178
+		$data['parent'] = (int) $data['parent'];
179 179
 		$data['size'] = 0 + $data['size'];
180
-		$data['mtime'] = (int)$data['mtime'];
181
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
182
-		$data['encryptedVersion'] = (int)$data['encrypted'];
183
-		$data['encrypted'] = (bool)$data['encrypted'];
180
+		$data['mtime'] = (int) $data['mtime'];
181
+		$data['storage_mtime'] = (int) $data['storage_mtime'];
182
+		$data['encryptedVersion'] = (int) $data['encrypted'];
183
+		$data['encrypted'] = (bool) $data['encrypted'];
184 184
 		$data['storage_id'] = $data['storage'];
185
-		$data['storage'] = (int)$data['storage'];
185
+		$data['storage'] = (int) $data['storage'];
186 186
 		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
187 187
 		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
188 188
 		if ($data['storage_mtime'] == 0) {
189 189
 			$data['storage_mtime'] = $data['mtime'];
190 190
 		}
191
-		$data['permissions'] = (int)$data['permissions'];
191
+		$data['permissions'] = (int) $data['permissions'];
192 192
 		if (isset($data['creation_time'])) {
193 193
 			$data['creation_time'] = (int) $data['creation_time'];
194 194
 		}
@@ -226,7 +226,7 @@  discard block
 block discarded – undo
226 226
 			$files = $result->fetchAll();
227 227
 			$result->closeCursor();
228 228
 
229
-			return array_map(function (array $data) {
229
+			return array_map(function(array $data) {
230 230
 				return self::cacheEntryFromData($data, $this->mimetypeLoader);
231 231
 			}, $files);
232 232
 		}
@@ -352,7 +352,7 @@  discard block
 block discarded – undo
352 352
 
353 353
 			$query->update('filecache')
354 354
 				->whereFileId($id)
355
-				->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
355
+				->andWhere($query->expr()->orX(...array_map(function($key, $value) use ($query) {
356 356
 					return $query->expr()->orX(
357 357
 						$query->expr()->neq($key, $query->createNamedParameter($value)),
358 358
 						$query->expr()->isNull($key)
@@ -381,7 +381,7 @@  discard block
 block discarded – undo
381 381
 				$query = $this->getQueryBuilder();
382 382
 				$query->update('filecache_extended')
383 383
 					->whereFileId($id)
384
-					->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
384
+					->andWhere($query->expr()->orX(...array_map(function($key, $value) use ($query) {
385 385
 						return $query->expr()->orX(
386 386
 							$query->expr()->neq($key, $query->createNamedParameter($value)),
387 387
 							$query->expr()->isNull($key)
@@ -476,7 +476,7 @@  discard block
 block discarded – undo
476 476
 		$id = $result->fetchColumn();
477 477
 		$result->closeCursor();
478 478
 
479
-		return $id === false ? -1 : (int)$id;
479
+		return $id === false ? -1 : (int) $id;
480 480
 	}
481 481
 
482 482
 	/**
@@ -490,7 +490,7 @@  discard block
 block discarded – undo
490 490
 			return -1;
491 491
 		} else {
492 492
 			$parent = $this->getParentPath($file);
493
-			return (int)$this->getId($parent);
493
+			return (int) $this->getId($parent);
494 494
 		}
495 495
 	}
496 496
 
@@ -547,7 +547,7 @@  discard block
 block discarded – undo
547 547
 	 */
548 548
 	private function getSubFolders(ICacheEntry $entry) {
549 549
 		$children = $this->getFolderContentsById($entry->getId());
550
-		return array_filter($children, function ($child) {
550
+		return array_filter($children, function($child) {
551 551
 			return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
552 552
 		});
553 553
 	}
@@ -566,7 +566,7 @@  discard block
 block discarded – undo
566 566
 		// and collecting all folder ids to later use to delete the filecache entries
567 567
 		while ($entryId = array_pop($queue)) {
568 568
 			$children = $this->getFolderContentsById($entryId);
569
-			$childIds = array_map(function (ICacheEntry $cacheEntry) {
569
+			$childIds = array_map(function(ICacheEntry $cacheEntry) {
570 570
 				return $cacheEntry->getId();
571 571
 			}, $children);
572 572
 
@@ -576,7 +576,7 @@  discard block
 block discarded – undo
576 576
 			$query->execute();
577 577
 
578 578
 			/** @var ICacheEntry[] $childFolders */
579
-			$childFolders = array_filter($children, function ($child) {
579
+			$childFolders = array_filter($children, function($child) {
580 580
 				return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
581 581
 			});
582 582
 			foreach ($childFolders as $folder) {
@@ -634,10 +634,10 @@  discard block
 block discarded – undo
634 634
 			[$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath);
635 635
 
636 636
 			if (is_null($sourceStorageId) || $sourceStorageId === false) {
637
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
637
+				throw new \Exception('Invalid source storage id: '.$sourceStorageId);
638 638
 			}
639 639
 			if (is_null($targetStorageId) || $targetStorageId === false) {
640
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
640
+				throw new \Exception('Invalid target storage id: '.$targetStorageId);
641 641
 			}
642 642
 
643 643
 			$this->connection->beginTransaction();
@@ -656,7 +656,7 @@  discard block
 block discarded – undo
656 656
 					->set('path_hash', $fun->md5($newPathFunction))
657 657
 					->set('path', $newPathFunction)
658 658
 					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
659
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
659
+					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath).'/%')));
660 660
 
661 661
 				try {
662 662
 					$query->execute();
@@ -724,7 +724,7 @@  discard block
 block discarded – undo
724 724
 		$result->closeCursor();
725 725
 
726 726
 		if ($size !== false) {
727
-			if ((int)$size === -1) {
727
+			if ((int) $size === -1) {
728 728
 				return self::SHALLOW;
729 729
 			} else {
730 730
 				return self::COMPLETE;
@@ -761,7 +761,7 @@  discard block
 block discarded – undo
761 761
 		$files = $result->fetchAll();
762 762
 		$result->closeCursor();
763 763
 
764
-		return array_map(function (array $data) {
764
+		return array_map(function(array $data) {
765 765
 			return self::cacheEntryFromData($data, $this->mimetypeLoader);
766 766
 		}, $files);
767 767
 	}
@@ -773,7 +773,7 @@  discard block
 block discarded – undo
773 773
 	private function searchResultToCacheEntries(Statement $result) {
774 774
 		$files = $result->fetchAll();
775 775
 
776
-		return array_map(function (array $data) {
776
+		return array_map(function(array $data) {
777 777
 			return self::cacheEntryFromData($data, $this->mimetypeLoader);
778 778
 		}, $files);
779 779
 	}
@@ -802,7 +802,7 @@  discard block
 block discarded – undo
802 802
 		$files = $result->fetchAll();
803 803
 		$result->closeCursor();
804 804
 
805
-		return array_map(function (array $data) {
805
+		return array_map(function(array $data) {
806 806
 			return self::cacheEntryFromData($data, $this->mimetypeLoader);
807 807
 		}, $files);
808 808
 	}
@@ -886,7 +886,7 @@  discard block
 block discarded – undo
886 886
 				->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
887 887
 
888 888
 			$result = $query->execute();
889
-			$size = (int)$result->fetchColumn();
889
+			$size = (int) $result->fetchColumn();
890 890
 			$result->closeCursor();
891 891
 
892 892
 			return $size;
@@ -952,8 +952,8 @@  discard block
 block discarded – undo
952 952
 		$files = $result->fetchAll(\PDO::FETCH_COLUMN);
953 953
 		$result->closeCursor();
954 954
 
955
-		return array_map(function ($id) {
956
-			return (int)$id;
955
+		return array_map(function($id) {
956
+			return (int) $id;
957 957
 		}, $files);
958 958
 	}
959 959
 
Please login to merge, or discard this patch.
lib/private/Files/Config/UserMountCache.php 2 patches
Indentation   +376 added lines, -376 removed lines patch added patch discarded remove patch
@@ -47,380 +47,380 @@
 block discarded – undo
47 47
  * Cache mounts points per user in the cache so we can easilly look them up
48 48
  */
49 49
 class UserMountCache implements IUserMountCache {
50
-	/**
51
-	 * @var IDBConnection
52
-	 */
53
-	private $connection;
54
-
55
-	/**
56
-	 * @var IUserManager
57
-	 */
58
-	private $userManager;
59
-
60
-	/**
61
-	 * Cached mount info.
62
-	 * Map of $userId to ICachedMountInfo.
63
-	 *
64
-	 * @var ICache
65
-	 **/
66
-	private $mountsForUsers;
67
-
68
-	/**
69
-	 * @var ILogger
70
-	 */
71
-	private $logger;
72
-
73
-	/**
74
-	 * @var ICache
75
-	 */
76
-	private $cacheInfoCache;
77
-
78
-	/**
79
-	 * UserMountCache constructor.
80
-	 *
81
-	 * @param IDBConnection $connection
82
-	 * @param IUserManager $userManager
83
-	 * @param ILogger $logger
84
-	 */
85
-	public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) {
86
-		$this->connection = $connection;
87
-		$this->userManager = $userManager;
88
-		$this->logger = $logger;
89
-		$this->cacheInfoCache = new CappedMemoryCache();
90
-		$this->mountsForUsers = new CappedMemoryCache();
91
-	}
92
-
93
-	public function registerMounts(IUser $user, array $mounts) {
94
-		// filter out non-proper storages coming from unit tests
95
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
96
-			return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache();
97
-		});
98
-		/** @var ICachedMountInfo[] $newMounts */
99
-		$newMounts = array_map(function (IMountPoint $mount) use ($user) {
100
-			// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
101
-			if ($mount->getStorageRootId() === -1) {
102
-				return null;
103
-			} else {
104
-				return new LazyStorageMountInfo($user, $mount);
105
-			}
106
-		}, $mounts);
107
-		$newMounts = array_values(array_filter($newMounts));
108
-		$newMountRootIds = array_map(function (ICachedMountInfo $mount) {
109
-			return $mount->getRootId();
110
-		}, $newMounts);
111
-		$newMounts = array_combine($newMountRootIds, $newMounts);
112
-
113
-		$cachedMounts = $this->getMountsForUser($user);
114
-		$cachedMountRootIds = array_map(function (ICachedMountInfo $mount) {
115
-			return $mount->getRootId();
116
-		}, $cachedMounts);
117
-		$cachedMounts = array_combine($cachedMountRootIds, $cachedMounts);
118
-
119
-		$addedMounts = [];
120
-		$removedMounts = [];
121
-
122
-		foreach ($newMounts as $rootId => $newMount) {
123
-			if (!isset($cachedMounts[$rootId])) {
124
-				$addedMounts[] = $newMount;
125
-			}
126
-		}
127
-
128
-		foreach ($cachedMounts as $rootId => $cachedMount) {
129
-			if (!isset($newMounts[$rootId])) {
130
-				$removedMounts[] = $cachedMount;
131
-			}
132
-		}
133
-
134
-		$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
135
-
136
-		foreach ($addedMounts as $mount) {
137
-			$this->addToCache($mount);
138
-			$this->mountsForUsers[$user->getUID()][] = $mount;
139
-		}
140
-		foreach ($removedMounts as $mount) {
141
-			$this->removeFromCache($mount);
142
-			$index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
143
-			unset($this->mountsForUsers[$user->getUID()][$index]);
144
-		}
145
-		foreach ($changedMounts as $mount) {
146
-			$this->updateCachedMount($mount);
147
-		}
148
-	}
149
-
150
-	/**
151
-	 * @param ICachedMountInfo[] $newMounts
152
-	 * @param ICachedMountInfo[] $cachedMounts
153
-	 * @return ICachedMountInfo[]
154
-	 */
155
-	private function findChangedMounts(array $newMounts, array $cachedMounts) {
156
-		$new = [];
157
-		foreach ($newMounts as $mount) {
158
-			$new[$mount->getRootId()] = $mount;
159
-		}
160
-		$changed = [];
161
-		foreach ($cachedMounts as $cachedMount) {
162
-			$rootId = $cachedMount->getRootId();
163
-			if (isset($new[$rootId])) {
164
-				$newMount = $new[$rootId];
165
-				if (
166
-					$newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
167
-					$newMount->getStorageId() !== $cachedMount->getStorageId() ||
168
-					$newMount->getMountId() !== $cachedMount->getMountId()
169
-				) {
170
-					$changed[] = $newMount;
171
-				}
172
-			}
173
-		}
174
-		return $changed;
175
-	}
176
-
177
-	private function addToCache(ICachedMountInfo $mount) {
178
-		if ($mount->getStorageId() !== -1) {
179
-			$this->connection->insertIfNotExist('*PREFIX*mounts', [
180
-				'storage_id' => $mount->getStorageId(),
181
-				'root_id' => $mount->getRootId(),
182
-				'user_id' => $mount->getUser()->getUID(),
183
-				'mount_point' => $mount->getMountPoint(),
184
-				'mount_id' => $mount->getMountId()
185
-			], ['root_id', 'user_id']);
186
-		} else {
187
-			// in some cases this is legitimate, like orphaned shares
188
-			$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
189
-		}
190
-	}
191
-
192
-	private function updateCachedMount(ICachedMountInfo $mount) {
193
-		$builder = $this->connection->getQueryBuilder();
194
-
195
-		$query = $builder->update('mounts')
196
-			->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
197
-			->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
198
-			->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
199
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
200
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
201
-
202
-		$query->execute();
203
-	}
204
-
205
-	private function removeFromCache(ICachedMountInfo $mount) {
206
-		$builder = $this->connection->getQueryBuilder();
207
-
208
-		$query = $builder->delete('mounts')
209
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
210
-			->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
211
-		$query->execute();
212
-	}
213
-
214
-	private function dbRowToMountInfo(array $row) {
215
-		$user = $this->userManager->get($row['user_id']);
216
-		if (is_null($user)) {
217
-			return null;
218
-		}
219
-		$mount_id = $row['mount_id'];
220
-		if (!is_null($mount_id)) {
221
-			$mount_id = (int)$mount_id;
222
-		}
223
-		return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : '');
224
-	}
225
-
226
-	/**
227
-	 * @param IUser $user
228
-	 * @return ICachedMountInfo[]
229
-	 */
230
-	public function getMountsForUser(IUser $user) {
231
-		if (!isset($this->mountsForUsers[$user->getUID()])) {
232
-			$builder = $this->connection->getQueryBuilder();
233
-			$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
234
-				->from('mounts', 'm')
235
-				->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
236
-				->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
237
-
238
-			$result = $query->execute();
239
-			$rows = $result->fetchAll();
240
-			$result->closeCursor();
241
-
242
-			$this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
243
-		}
244
-		return $this->mountsForUsers[$user->getUID()];
245
-	}
246
-
247
-	/**
248
-	 * @param int $numericStorageId
249
-	 * @param string|null $user limit the results to a single user
250
-	 * @return CachedMountInfo[]
251
-	 */
252
-	public function getMountsForStorageId($numericStorageId, $user = null) {
253
-		$builder = $this->connection->getQueryBuilder();
254
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
255
-			->from('mounts', 'm')
256
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
257
-			->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
258
-
259
-		if ($user) {
260
-			$query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
261
-		}
262
-
263
-		$result = $query->execute();
264
-		$rows = $result->fetchAll();
265
-		$result->closeCursor();
266
-
267
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
268
-	}
269
-
270
-	/**
271
-	 * @param int $rootFileId
272
-	 * @return CachedMountInfo[]
273
-	 */
274
-	public function getMountsForRootId($rootFileId) {
275
-		$builder = $this->connection->getQueryBuilder();
276
-		$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
277
-			->from('mounts', 'm')
278
-			->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
279
-			->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
280
-
281
-		$result = $query->execute();
282
-		$rows = $result->fetchAll();
283
-		$result->closeCursor();
284
-
285
-		return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
286
-	}
287
-
288
-	/**
289
-	 * @param $fileId
290
-	 * @return array
291
-	 * @throws \OCP\Files\NotFoundException
292
-	 */
293
-	private function getCacheInfoFromFileId($fileId) {
294
-		if (!isset($this->cacheInfoCache[$fileId])) {
295
-			$builder = $this->connection->getQueryBuilder();
296
-			$query = $builder->select('storage', 'path', 'mimetype')
297
-				->from('filecache')
298
-				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
299
-
300
-			$result = $query->execute();
301
-			$row = $result->fetch();
302
-			$result->closeCursor();
303
-
304
-			if (is_array($row)) {
305
-				$this->cacheInfoCache[$fileId] = [
306
-					(int)$row['storage'],
307
-					$row['path'],
308
-					(int)$row['mimetype']
309
-				];
310
-			} else {
311
-				throw new NotFoundException('File with id "' . $fileId . '" not found');
312
-			}
313
-		}
314
-		return $this->cacheInfoCache[$fileId];
315
-	}
316
-
317
-	/**
318
-	 * @param int $fileId
319
-	 * @param string|null $user optionally restrict the results to a single user
320
-	 * @return ICachedMountFileInfo[]
321
-	 * @since 9.0.0
322
-	 */
323
-	public function getMountsForFileId($fileId, $user = null) {
324
-		try {
325
-			list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId);
326
-		} catch (NotFoundException $e) {
327
-			return [];
328
-		}
329
-		$mountsForStorage = $this->getMountsForStorageId($storageId, $user);
330
-
331
-		// filter mounts that are from the same storage but a different directory
332
-		$filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
333
-			if ($fileId === $mount->getRootId()) {
334
-				return true;
335
-			}
336
-			$internalMountPath = $mount->getRootInternalPath();
337
-
338
-			return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
339
-		});
340
-
341
-		return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
342
-			return new CachedMountFileInfo(
343
-				$mount->getUser(),
344
-				$mount->getStorageId(),
345
-				$mount->getRootId(),
346
-				$mount->getMountPoint(),
347
-				$mount->getMountId(),
348
-				$mount->getRootInternalPath(),
349
-				$internalPath
350
-			);
351
-		}, $filteredMounts);
352
-	}
353
-
354
-	/**
355
-	 * Remove all cached mounts for a user
356
-	 *
357
-	 * @param IUser $user
358
-	 */
359
-	public function removeUserMounts(IUser $user) {
360
-		$builder = $this->connection->getQueryBuilder();
361
-
362
-		$query = $builder->delete('mounts')
363
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
364
-		$query->execute();
365
-	}
366
-
367
-	public function removeUserStorageMount($storageId, $userId) {
368
-		$builder = $this->connection->getQueryBuilder();
369
-
370
-		$query = $builder->delete('mounts')
371
-			->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
372
-			->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
373
-		$query->execute();
374
-	}
375
-
376
-	public function remoteStorageMounts($storageId) {
377
-		$builder = $this->connection->getQueryBuilder();
378
-
379
-		$query = $builder->delete('mounts')
380
-			->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
381
-		$query->execute();
382
-	}
383
-
384
-	/**
385
-	 * @param array $users
386
-	 * @return array
387
-	 */
388
-	public function getUsedSpaceForUsers(array $users) {
389
-		$builder = $this->connection->getQueryBuilder();
390
-
391
-		$slash = $builder->createNamedParameter('/');
392
-
393
-		$mountPoint = $builder->func()->concat(
394
-			$builder->func()->concat($slash, 'user_id'),
395
-			$slash
396
-		);
397
-
398
-		$userIds = array_map(function (IUser $user) {
399
-			return $user->getUID();
400
-		}, $users);
401
-
402
-		$query = $builder->select('m.user_id', 'f.size')
403
-			->from('mounts', 'm')
404
-			->innerJoin('m', 'filecache', 'f',
405
-				$builder->expr()->andX(
406
-					$builder->expr()->eq('m.storage_id', 'f.storage'),
407
-					$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
408
-				))
409
-			->where($builder->expr()->eq('m.mount_point', $mountPoint))
410
-			->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
411
-
412
-		$result = $query->execute();
413
-
414
-		$results = [];
415
-		while ($row = $result->fetch()) {
416
-			$results[$row['user_id']] = $row['size'];
417
-		}
418
-		$result->closeCursor();
419
-		return $results;
420
-	}
421
-
422
-	public function clear(): void {
423
-		$this->cacheInfoCache = new CappedMemoryCache();
424
-		$this->mountsForUsers = new CappedMemoryCache();
425
-	}
50
+    /**
51
+     * @var IDBConnection
52
+     */
53
+    private $connection;
54
+
55
+    /**
56
+     * @var IUserManager
57
+     */
58
+    private $userManager;
59
+
60
+    /**
61
+     * Cached mount info.
62
+     * Map of $userId to ICachedMountInfo.
63
+     *
64
+     * @var ICache
65
+     **/
66
+    private $mountsForUsers;
67
+
68
+    /**
69
+     * @var ILogger
70
+     */
71
+    private $logger;
72
+
73
+    /**
74
+     * @var ICache
75
+     */
76
+    private $cacheInfoCache;
77
+
78
+    /**
79
+     * UserMountCache constructor.
80
+     *
81
+     * @param IDBConnection $connection
82
+     * @param IUserManager $userManager
83
+     * @param ILogger $logger
84
+     */
85
+    public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) {
86
+        $this->connection = $connection;
87
+        $this->userManager = $userManager;
88
+        $this->logger = $logger;
89
+        $this->cacheInfoCache = new CappedMemoryCache();
90
+        $this->mountsForUsers = new CappedMemoryCache();
91
+    }
92
+
93
+    public function registerMounts(IUser $user, array $mounts) {
94
+        // filter out non-proper storages coming from unit tests
95
+        $mounts = array_filter($mounts, function (IMountPoint $mount) {
96
+            return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache();
97
+        });
98
+        /** @var ICachedMountInfo[] $newMounts */
99
+        $newMounts = array_map(function (IMountPoint $mount) use ($user) {
100
+            // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
101
+            if ($mount->getStorageRootId() === -1) {
102
+                return null;
103
+            } else {
104
+                return new LazyStorageMountInfo($user, $mount);
105
+            }
106
+        }, $mounts);
107
+        $newMounts = array_values(array_filter($newMounts));
108
+        $newMountRootIds = array_map(function (ICachedMountInfo $mount) {
109
+            return $mount->getRootId();
110
+        }, $newMounts);
111
+        $newMounts = array_combine($newMountRootIds, $newMounts);
112
+
113
+        $cachedMounts = $this->getMountsForUser($user);
114
+        $cachedMountRootIds = array_map(function (ICachedMountInfo $mount) {
115
+            return $mount->getRootId();
116
+        }, $cachedMounts);
117
+        $cachedMounts = array_combine($cachedMountRootIds, $cachedMounts);
118
+
119
+        $addedMounts = [];
120
+        $removedMounts = [];
121
+
122
+        foreach ($newMounts as $rootId => $newMount) {
123
+            if (!isset($cachedMounts[$rootId])) {
124
+                $addedMounts[] = $newMount;
125
+            }
126
+        }
127
+
128
+        foreach ($cachedMounts as $rootId => $cachedMount) {
129
+            if (!isset($newMounts[$rootId])) {
130
+                $removedMounts[] = $cachedMount;
131
+            }
132
+        }
133
+
134
+        $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
135
+
136
+        foreach ($addedMounts as $mount) {
137
+            $this->addToCache($mount);
138
+            $this->mountsForUsers[$user->getUID()][] = $mount;
139
+        }
140
+        foreach ($removedMounts as $mount) {
141
+            $this->removeFromCache($mount);
142
+            $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
143
+            unset($this->mountsForUsers[$user->getUID()][$index]);
144
+        }
145
+        foreach ($changedMounts as $mount) {
146
+            $this->updateCachedMount($mount);
147
+        }
148
+    }
149
+
150
+    /**
151
+     * @param ICachedMountInfo[] $newMounts
152
+     * @param ICachedMountInfo[] $cachedMounts
153
+     * @return ICachedMountInfo[]
154
+     */
155
+    private function findChangedMounts(array $newMounts, array $cachedMounts) {
156
+        $new = [];
157
+        foreach ($newMounts as $mount) {
158
+            $new[$mount->getRootId()] = $mount;
159
+        }
160
+        $changed = [];
161
+        foreach ($cachedMounts as $cachedMount) {
162
+            $rootId = $cachedMount->getRootId();
163
+            if (isset($new[$rootId])) {
164
+                $newMount = $new[$rootId];
165
+                if (
166
+                    $newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
167
+                    $newMount->getStorageId() !== $cachedMount->getStorageId() ||
168
+                    $newMount->getMountId() !== $cachedMount->getMountId()
169
+                ) {
170
+                    $changed[] = $newMount;
171
+                }
172
+            }
173
+        }
174
+        return $changed;
175
+    }
176
+
177
+    private function addToCache(ICachedMountInfo $mount) {
178
+        if ($mount->getStorageId() !== -1) {
179
+            $this->connection->insertIfNotExist('*PREFIX*mounts', [
180
+                'storage_id' => $mount->getStorageId(),
181
+                'root_id' => $mount->getRootId(),
182
+                'user_id' => $mount->getUser()->getUID(),
183
+                'mount_point' => $mount->getMountPoint(),
184
+                'mount_id' => $mount->getMountId()
185
+            ], ['root_id', 'user_id']);
186
+        } else {
187
+            // in some cases this is legitimate, like orphaned shares
188
+            $this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
189
+        }
190
+    }
191
+
192
+    private function updateCachedMount(ICachedMountInfo $mount) {
193
+        $builder = $this->connection->getQueryBuilder();
194
+
195
+        $query = $builder->update('mounts')
196
+            ->set('storage_id', $builder->createNamedParameter($mount->getStorageId()))
197
+            ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint()))
198
+            ->set('mount_id', $builder->createNamedParameter($mount->getMountId(), IQueryBuilder::PARAM_INT))
199
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
200
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
201
+
202
+        $query->execute();
203
+    }
204
+
205
+    private function removeFromCache(ICachedMountInfo $mount) {
206
+        $builder = $this->connection->getQueryBuilder();
207
+
208
+        $query = $builder->delete('mounts')
209
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
210
+            ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
211
+        $query->execute();
212
+    }
213
+
214
+    private function dbRowToMountInfo(array $row) {
215
+        $user = $this->userManager->get($row['user_id']);
216
+        if (is_null($user)) {
217
+            return null;
218
+        }
219
+        $mount_id = $row['mount_id'];
220
+        if (!is_null($mount_id)) {
221
+            $mount_id = (int)$mount_id;
222
+        }
223
+        return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : '');
224
+    }
225
+
226
+    /**
227
+     * @param IUser $user
228
+     * @return ICachedMountInfo[]
229
+     */
230
+    public function getMountsForUser(IUser $user) {
231
+        if (!isset($this->mountsForUsers[$user->getUID()])) {
232
+            $builder = $this->connection->getQueryBuilder();
233
+            $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
234
+                ->from('mounts', 'm')
235
+                ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
236
+                ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
237
+
238
+            $result = $query->execute();
239
+            $rows = $result->fetchAll();
240
+            $result->closeCursor();
241
+
242
+            $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
243
+        }
244
+        return $this->mountsForUsers[$user->getUID()];
245
+    }
246
+
247
+    /**
248
+     * @param int $numericStorageId
249
+     * @param string|null $user limit the results to a single user
250
+     * @return CachedMountInfo[]
251
+     */
252
+    public function getMountsForStorageId($numericStorageId, $user = null) {
253
+        $builder = $this->connection->getQueryBuilder();
254
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
255
+            ->from('mounts', 'm')
256
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
257
+            ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
258
+
259
+        if ($user) {
260
+            $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
261
+        }
262
+
263
+        $result = $query->execute();
264
+        $rows = $result->fetchAll();
265
+        $result->closeCursor();
266
+
267
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
268
+    }
269
+
270
+    /**
271
+     * @param int $rootFileId
272
+     * @return CachedMountInfo[]
273
+     */
274
+    public function getMountsForRootId($rootFileId) {
275
+        $builder = $this->connection->getQueryBuilder();
276
+        $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path')
277
+            ->from('mounts', 'm')
278
+            ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
279
+            ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
280
+
281
+        $result = $query->execute();
282
+        $rows = $result->fetchAll();
283
+        $result->closeCursor();
284
+
285
+        return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
286
+    }
287
+
288
+    /**
289
+     * @param $fileId
290
+     * @return array
291
+     * @throws \OCP\Files\NotFoundException
292
+     */
293
+    private function getCacheInfoFromFileId($fileId) {
294
+        if (!isset($this->cacheInfoCache[$fileId])) {
295
+            $builder = $this->connection->getQueryBuilder();
296
+            $query = $builder->select('storage', 'path', 'mimetype')
297
+                ->from('filecache')
298
+                ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
299
+
300
+            $result = $query->execute();
301
+            $row = $result->fetch();
302
+            $result->closeCursor();
303
+
304
+            if (is_array($row)) {
305
+                $this->cacheInfoCache[$fileId] = [
306
+                    (int)$row['storage'],
307
+                    $row['path'],
308
+                    (int)$row['mimetype']
309
+                ];
310
+            } else {
311
+                throw new NotFoundException('File with id "' . $fileId . '" not found');
312
+            }
313
+        }
314
+        return $this->cacheInfoCache[$fileId];
315
+    }
316
+
317
+    /**
318
+     * @param int $fileId
319
+     * @param string|null $user optionally restrict the results to a single user
320
+     * @return ICachedMountFileInfo[]
321
+     * @since 9.0.0
322
+     */
323
+    public function getMountsForFileId($fileId, $user = null) {
324
+        try {
325
+            list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId);
326
+        } catch (NotFoundException $e) {
327
+            return [];
328
+        }
329
+        $mountsForStorage = $this->getMountsForStorageId($storageId, $user);
330
+
331
+        // filter mounts that are from the same storage but a different directory
332
+        $filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
333
+            if ($fileId === $mount->getRootId()) {
334
+                return true;
335
+            }
336
+            $internalMountPath = $mount->getRootInternalPath();
337
+
338
+            return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
339
+        });
340
+
341
+        return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
342
+            return new CachedMountFileInfo(
343
+                $mount->getUser(),
344
+                $mount->getStorageId(),
345
+                $mount->getRootId(),
346
+                $mount->getMountPoint(),
347
+                $mount->getMountId(),
348
+                $mount->getRootInternalPath(),
349
+                $internalPath
350
+            );
351
+        }, $filteredMounts);
352
+    }
353
+
354
+    /**
355
+     * Remove all cached mounts for a user
356
+     *
357
+     * @param IUser $user
358
+     */
359
+    public function removeUserMounts(IUser $user) {
360
+        $builder = $this->connection->getQueryBuilder();
361
+
362
+        $query = $builder->delete('mounts')
363
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
364
+        $query->execute();
365
+    }
366
+
367
+    public function removeUserStorageMount($storageId, $userId) {
368
+        $builder = $this->connection->getQueryBuilder();
369
+
370
+        $query = $builder->delete('mounts')
371
+            ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
372
+            ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
373
+        $query->execute();
374
+    }
375
+
376
+    public function remoteStorageMounts($storageId) {
377
+        $builder = $this->connection->getQueryBuilder();
378
+
379
+        $query = $builder->delete('mounts')
380
+            ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
381
+        $query->execute();
382
+    }
383
+
384
+    /**
385
+     * @param array $users
386
+     * @return array
387
+     */
388
+    public function getUsedSpaceForUsers(array $users) {
389
+        $builder = $this->connection->getQueryBuilder();
390
+
391
+        $slash = $builder->createNamedParameter('/');
392
+
393
+        $mountPoint = $builder->func()->concat(
394
+            $builder->func()->concat($slash, 'user_id'),
395
+            $slash
396
+        );
397
+
398
+        $userIds = array_map(function (IUser $user) {
399
+            return $user->getUID();
400
+        }, $users);
401
+
402
+        $query = $builder->select('m.user_id', 'f.size')
403
+            ->from('mounts', 'm')
404
+            ->innerJoin('m', 'filecache', 'f',
405
+                $builder->expr()->andX(
406
+                    $builder->expr()->eq('m.storage_id', 'f.storage'),
407
+                    $builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
408
+                ))
409
+            ->where($builder->expr()->eq('m.mount_point', $mountPoint))
410
+            ->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
411
+
412
+        $result = $query->execute();
413
+
414
+        $results = [];
415
+        while ($row = $result->fetch()) {
416
+            $results[$row['user_id']] = $row['size'];
417
+        }
418
+        $result->closeCursor();
419
+        return $results;
420
+    }
421
+
422
+    public function clear(): void {
423
+        $this->cacheInfoCache = new CappedMemoryCache();
424
+        $this->mountsForUsers = new CappedMemoryCache();
425
+    }
426 426
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -92,11 +92,11 @@  discard block
 block discarded – undo
92 92
 
93 93
 	public function registerMounts(IUser $user, array $mounts) {
94 94
 		// filter out non-proper storages coming from unit tests
95
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
95
+		$mounts = array_filter($mounts, function(IMountPoint $mount) {
96 96
 			return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache();
97 97
 		});
98 98
 		/** @var ICachedMountInfo[] $newMounts */
99
-		$newMounts = array_map(function (IMountPoint $mount) use ($user) {
99
+		$newMounts = array_map(function(IMountPoint $mount) use ($user) {
100 100
 			// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
101 101
 			if ($mount->getStorageRootId() === -1) {
102 102
 				return null;
@@ -105,13 +105,13 @@  discard block
 block discarded – undo
105 105
 			}
106 106
 		}, $mounts);
107 107
 		$newMounts = array_values(array_filter($newMounts));
108
-		$newMountRootIds = array_map(function (ICachedMountInfo $mount) {
108
+		$newMountRootIds = array_map(function(ICachedMountInfo $mount) {
109 109
 			return $mount->getRootId();
110 110
 		}, $newMounts);
111 111
 		$newMounts = array_combine($newMountRootIds, $newMounts);
112 112
 
113 113
 		$cachedMounts = $this->getMountsForUser($user);
114
-		$cachedMountRootIds = array_map(function (ICachedMountInfo $mount) {
114
+		$cachedMountRootIds = array_map(function(ICachedMountInfo $mount) {
115 115
 			return $mount->getRootId();
116 116
 		}, $cachedMounts);
117 117
 		$cachedMounts = array_combine($cachedMountRootIds, $cachedMounts);
@@ -185,7 +185,7 @@  discard block
 block discarded – undo
185 185
 			], ['root_id', 'user_id']);
186 186
 		} else {
187 187
 			// in some cases this is legitimate, like orphaned shares
188
-			$this->logger->debug('Could not get storage info for mount at ' . $mount->getMountPoint());
188
+			$this->logger->debug('Could not get storage info for mount at '.$mount->getMountPoint());
189 189
 		}
190 190
 	}
191 191
 
@@ -218,9 +218,9 @@  discard block
 block discarded – undo
218 218
 		}
219 219
 		$mount_id = $row['mount_id'];
220 220
 		if (!is_null($mount_id)) {
221
-			$mount_id = (int)$mount_id;
221
+			$mount_id = (int) $mount_id;
222 222
 		}
223
-		return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : '');
223
+		return new CachedMountInfo($user, (int) $row['storage_id'], (int) $row['root_id'], $row['mount_point'], $mount_id, isset($row['path']) ? $row['path'] : '');
224 224
 	}
225 225
 
226 226
 	/**
@@ -303,12 +303,12 @@  discard block
 block discarded – undo
303 303
 
304 304
 			if (is_array($row)) {
305 305
 				$this->cacheInfoCache[$fileId] = [
306
-					(int)$row['storage'],
306
+					(int) $row['storage'],
307 307
 					$row['path'],
308
-					(int)$row['mimetype']
308
+					(int) $row['mimetype']
309 309
 				];
310 310
 			} else {
311
-				throw new NotFoundException('File with id "' . $fileId . '" not found');
311
+				throw new NotFoundException('File with id "'.$fileId.'" not found');
312 312
 			}
313 313
 		}
314 314
 		return $this->cacheInfoCache[$fileId];
@@ -329,16 +329,16 @@  discard block
 block discarded – undo
329 329
 		$mountsForStorage = $this->getMountsForStorageId($storageId, $user);
330 330
 
331 331
 		// filter mounts that are from the same storage but a different directory
332
-		$filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
332
+		$filteredMounts = array_filter($mountsForStorage, function(ICachedMountInfo $mount) use ($internalPath, $fileId) {
333 333
 			if ($fileId === $mount->getRootId()) {
334 334
 				return true;
335 335
 			}
336 336
 			$internalMountPath = $mount->getRootInternalPath();
337 337
 
338
-			return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/';
338
+			return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath.'/';
339 339
 		});
340 340
 
341
-		return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
341
+		return array_map(function(ICachedMountInfo $mount) use ($internalPath) {
342 342
 			return new CachedMountFileInfo(
343 343
 				$mount->getUser(),
344 344
 				$mount->getStorageId(),
@@ -395,7 +395,7 @@  discard block
 block discarded – undo
395 395
 			$slash
396 396
 		);
397 397
 
398
-		$userIds = array_map(function (IUser $user) {
398
+		$userIds = array_map(function(IUser $user) {
399 399
 			return $user->getUID();
400 400
 		}, $users);
401 401
 
Please login to merge, or discard this patch.
lib/private/Files/Node/Folder.php 1 patch
Indentation   +530 added lines, -530 removed lines patch added patch discarded remove patch
@@ -43,541 +43,541 @@
 block discarded – undo
43 43
 use OCP\Files\Search\ISearchQuery;
44 44
 
45 45
 class Folder extends Node implements \OCP\Files\Folder {
46
-	/**
47
-	 * Creates a Folder that represents a non-existing path
48
-	 *
49
-	 * @param string $path path
50
-	 * @return string non-existing node class
51
-	 */
52
-	protected function createNonExistingNode($path) {
53
-		return new NonExistingFolder($this->root, $this->view, $path);
54
-	}
55
-
56
-	/**
57
-	 * @param string $path path relative to the folder
58
-	 * @return string
59
-	 * @throws \OCP\Files\NotPermittedException
60
-	 */
61
-	public function getFullPath($path) {
62
-		if (!$this->isValidPath($path)) {
63
-			throw new NotPermittedException('Invalid path');
64
-		}
65
-		return $this->path . $this->normalizePath($path);
66
-	}
67
-
68
-	/**
69
-	 * @param string $path
70
-	 * @return string
71
-	 */
72
-	public function getRelativePath($path) {
73
-		if ($this->path === '' or $this->path === '/') {
74
-			return $this->normalizePath($path);
75
-		}
76
-		if ($path === $this->path) {
77
-			return '/';
78
-		} elseif (strpos($path, $this->path . '/') !== 0) {
79
-			return null;
80
-		} else {
81
-			$path = substr($path, strlen($this->path));
82
-			return $this->normalizePath($path);
83
-		}
84
-	}
85
-
86
-	/**
87
-	 * check if a node is a (grand-)child of the folder
88
-	 *
89
-	 * @param \OC\Files\Node\Node $node
90
-	 * @return bool
91
-	 */
92
-	public function isSubNode($node) {
93
-		return strpos($node->getPath(), $this->path . '/') === 0;
94
-	}
95
-
96
-	/**
97
-	 * get the content of this directory
98
-	 *
99
-	 * @throws \OCP\Files\NotFoundException
100
-	 * @return Node[]
101
-	 */
102
-	public function getDirectoryListing() {
103
-		$folderContent = $this->view->getDirectoryContent($this->path);
104
-
105
-		return array_map(function (FileInfo $info) {
106
-			if ($info->getMimetype() === 'httpd/unix-directory') {
107
-				return new Folder($this->root, $this->view, $info->getPath(), $info);
108
-			} else {
109
-				return new File($this->root, $this->view, $info->getPath(), $info);
110
-			}
111
-		}, $folderContent);
112
-	}
113
-
114
-	/**
115
-	 * @param string $path
116
-	 * @param FileInfo $info
117
-	 * @return File|Folder
118
-	 */
119
-	protected function createNode($path, FileInfo $info = null) {
120
-		if (is_null($info)) {
121
-			$isDir = $this->view->is_dir($path);
122
-		} else {
123
-			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
124
-		}
125
-		if ($isDir) {
126
-			return new Folder($this->root, $this->view, $path, $info);
127
-		} else {
128
-			return new File($this->root, $this->view, $path, $info);
129
-		}
130
-	}
131
-
132
-	/**
133
-	 * Get the node at $path
134
-	 *
135
-	 * @param string $path
136
-	 * @return \OC\Files\Node\Node
137
-	 * @throws \OCP\Files\NotFoundException
138
-	 */
139
-	public function get($path) {
140
-		return $this->root->get($this->getFullPath($path));
141
-	}
142
-
143
-	/**
144
-	 * @param string $path
145
-	 * @return bool
146
-	 */
147
-	public function nodeExists($path) {
148
-		try {
149
-			$this->get($path);
150
-			return true;
151
-		} catch (NotFoundException $e) {
152
-			return false;
153
-		}
154
-	}
155
-
156
-	/**
157
-	 * @param string $path
158
-	 * @return \OC\Files\Node\Folder
159
-	 * @throws \OCP\Files\NotPermittedException
160
-	 */
161
-	public function newFolder($path) {
162
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
163
-			$fullPath = $this->getFullPath($path);
164
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
165
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
166
-			if (!$this->view->mkdir($fullPath)) {
167
-				throw new NotPermittedException('Could not create folder');
168
-			}
169
-			$node = new Folder($this->root, $this->view, $fullPath);
170
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
171
-			return $node;
172
-		} else {
173
-			throw new NotPermittedException('No create permission for folder');
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * @param string $path
179
-	 * @param string | resource | null $content
180
-	 * @return \OC\Files\Node\File
181
-	 * @throws \OCP\Files\NotPermittedException
182
-	 */
183
-	public function newFile($path, $content = null) {
184
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
185
-			$fullPath = $this->getFullPath($path);
186
-			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
187
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
188
-			if ($content !== null) {
189
-				$result = $this->view->file_put_contents($fullPath, $content);
190
-			} else {
191
-				$result = $this->view->touch($fullPath);
192
-			}
193
-			if ($result === false) {
194
-				throw new NotPermittedException('Could not create path');
195
-			}
196
-			$node = new File($this->root, $this->view, $fullPath);
197
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
198
-			return $node;
199
-		}
200
-		throw new NotPermittedException('No create permission for path');
201
-	}
202
-
203
-	/**
204
-	 * search for files with the name matching $query
205
-	 *
206
-	 * @param string|ISearchQuery $query
207
-	 * @return \OC\Files\Node\Node[]
208
-	 */
209
-	public function search($query) {
210
-		if (is_string($query)) {
211
-			return $this->searchCommon('search', ['%' . $query . '%']);
212
-		} else {
213
-			return $this->searchCommon('searchQuery', [$query]);
214
-		}
215
-	}
216
-
217
-	/**
218
-	 * search for files by mimetype
219
-	 *
220
-	 * @param string $mimetype
221
-	 * @return Node[]
222
-	 */
223
-	public function searchByMime($mimetype) {
224
-		return $this->searchCommon('searchByMime', [$mimetype]);
225
-	}
226
-
227
-	/**
228
-	 * search for files by tag
229
-	 *
230
-	 * @param string|int $tag name or tag id
231
-	 * @param string $userId owner of the tags
232
-	 * @return Node[]
233
-	 */
234
-	public function searchByTag($tag, $userId) {
235
-		return $this->searchCommon('searchByTag', [$tag, $userId]);
236
-	}
237
-
238
-	/**
239
-	 * @param string $method cache method
240
-	 * @param array $args call args
241
-	 * @return \OC\Files\Node\Node[]
242
-	 */
243
-	private function searchCommon($method, $args) {
244
-		$limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
245
-		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
246
-			throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
247
-		}
248
-
249
-		$files = [];
250
-		$rootLength = strlen($this->path);
251
-		$mount = $this->root->getMount($this->path);
252
-		$storage = $mount->getStorage();
253
-		$internalPath = $mount->getInternalPath($this->path);
254
-		$internalPath = rtrim($internalPath, '/');
255
-		if ($internalPath !== '') {
256
-			$internalPath = $internalPath . '/';
257
-		}
258
-		$internalRootLength = strlen($internalPath);
259
-
260
-		$cache = $storage->getCache('');
261
-
262
-		$results = call_user_func_array([$cache, $method], $args);
263
-		foreach ($results as $result) {
264
-			if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
265
-				$result['internalPath'] = $result['path'];
266
-				$result['path'] = substr($result['path'], $internalRootLength);
267
-				$result['storage'] = $storage;
268
-				$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
269
-			}
270
-		}
271
-
272
-		if (!$limitToHome) {
273
-			$mounts = $this->root->getMountsIn($this->path);
274
-			foreach ($mounts as $mount) {
275
-				$storage = $mount->getStorage();
276
-				if ($storage) {
277
-					$cache = $storage->getCache('');
278
-
279
-					$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
280
-					$results = call_user_func_array([$cache, $method], $args);
281
-					foreach ($results as $result) {
282
-						$result['internalPath'] = $result['path'];
283
-						$result['path'] = $relativeMountPoint . $result['path'];
284
-						$result['storage'] = $storage;
285
-						$files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
286
-							$result['internalPath'], $result, $mount);
287
-					}
288
-				}
289
-			}
290
-		}
291
-
292
-		return array_map(function (FileInfo $file) {
293
-			return $this->createNode($file->getPath(), $file);
294
-		}, $files);
295
-	}
296
-
297
-	/**
298
-	 * @param int $id
299
-	 * @return \OC\Files\Node\Node[]
300
-	 */
301
-	public function getById($id) {
302
-		$mountCache = $this->root->getUserMountCache();
303
-		if (strpos($this->getPath(), '/', 1) > 0) {
304
-			list(, $user) = explode('/', $this->getPath());
305
-		} else {
306
-			$user = null;
307
-		}
308
-		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
309
-		$mounts = $this->root->getMountsIn($this->path);
310
-		$mounts[] = $this->root->getMount($this->path);
311
-		/** @var IMountPoint[] $folderMounts */
312
-		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
313
-			return $mountPoint->getMountPoint();
314
-		}, $mounts), $mounts);
315
-
316
-		/** @var ICachedMountInfo[] $mountsContainingFile */
317
-		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
318
-			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
319
-		}));
320
-
321
-		if (count($mountsContainingFile) === 0) {
322
-			if ($user === $this->getAppDataDirectoryName()) {
323
-				return $this->getByIdInRootMount((int) $id);
324
-			}
325
-			return [];
326
-		}
327
-
328
-		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
329
-			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
330
-			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
331
-			if (!$cacheEntry) {
332
-				return null;
333
-			}
334
-
335
-			// cache jails will hide the "true" internal path
336
-			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
337
-			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
338
-			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
339
-			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
340
-			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
341
-				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
342
-				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
343
-			));
344
-		}, $mountsContainingFile);
345
-
346
-		$nodes = array_filter($nodes);
347
-
348
-		return array_filter($nodes, function (Node $node) {
349
-			return $this->getRelativePath($node->getPath());
350
-		});
351
-	}
352
-
353
-	protected function getAppDataDirectoryName(): string {
354
-		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
355
-		return 'appdata_' . $instanceId;
356
-	}
357
-
358
-	/**
359
-	 * In case the path we are currently in is inside the appdata_* folder,
360
-	 * the original getById method does not work, because it can only look inside
361
-	 * the user's mount points. But the user has no mount point for the root storage.
362
-	 *
363
-	 * So in that case we directly check the mount of the root if it contains
364
-	 * the id. If it does we check if the path is inside the path we are working
365
-	 * in.
366
-	 *
367
-	 * @param int $id
368
-	 * @return array
369
-	 */
370
-	protected function getByIdInRootMount(int $id): array {
371
-		$mount = $this->root->getMount('');
372
-		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
373
-		if (!$cacheEntry) {
374
-			return [];
375
-		}
376
-
377
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
378
-		$currentPath = rtrim($this->path, '/') . '/';
379
-
380
-		if (strpos($absolutePath, $currentPath) !== 0) {
381
-			return [];
382
-		}
383
-
384
-		return [$this->root->createNode(
385
-			$absolutePath, new \OC\Files\FileInfo(
386
-				$absolutePath,
387
-				$mount->getStorage(),
388
-				$cacheEntry->getPath(),
389
-				$cacheEntry,
390
-				$mount
391
-		))];
392
-	}
393
-
394
-	public function getFreeSpace() {
395
-		return $this->view->free_space($this->path);
396
-	}
397
-
398
-	public function delete() {
399
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
400
-			$this->sendHooks(['preDelete']);
401
-			$fileInfo = $this->getFileInfo();
402
-			$this->view->rmdir($this->path);
403
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
404
-			$this->sendHooks(['postDelete'], [$nonExisting]);
405
-			$this->exists = false;
406
-		} else {
407
-			throw new NotPermittedException('No delete permission for path');
408
-		}
409
-	}
410
-
411
-	/**
412
-	 * Add a suffix to the name in case the file exists
413
-	 *
414
-	 * @param string $name
415
-	 * @return string
416
-	 * @throws NotPermittedException
417
-	 */
418
-	public function getNonExistingName($name) {
419
-		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
420
-		return trim($this->getRelativePath($uniqueName), '/');
421
-	}
422
-
423
-	/**
424
-	 * @param int $limit
425
-	 * @param int $offset
426
-	 * @return \OCP\Files\Node[]
427
-	 */
428
-	public function getRecent($limit, $offset = 0) {
429
-		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
430
-		$mounts = $this->root->getMountsIn($this->path);
431
-		$mounts[] = $this->getMountPoint();
432
-
433
-		$mounts = array_filter($mounts, function (IMountPoint $mount) {
434
-			return $mount->getStorage();
435
-		});
436
-		$storageIds = array_map(function (IMountPoint $mount) {
437
-			return $mount->getStorage()->getCache()->getNumericStorageId();
438
-		}, $mounts);
439
-		/** @var IMountPoint[] $mountMap */
440
-		$mountMap = array_combine($storageIds, $mounts);
441
-		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
442
-
443
-		/*
46
+    /**
47
+     * Creates a Folder that represents a non-existing path
48
+     *
49
+     * @param string $path path
50
+     * @return string non-existing node class
51
+     */
52
+    protected function createNonExistingNode($path) {
53
+        return new NonExistingFolder($this->root, $this->view, $path);
54
+    }
55
+
56
+    /**
57
+     * @param string $path path relative to the folder
58
+     * @return string
59
+     * @throws \OCP\Files\NotPermittedException
60
+     */
61
+    public function getFullPath($path) {
62
+        if (!$this->isValidPath($path)) {
63
+            throw new NotPermittedException('Invalid path');
64
+        }
65
+        return $this->path . $this->normalizePath($path);
66
+    }
67
+
68
+    /**
69
+     * @param string $path
70
+     * @return string
71
+     */
72
+    public function getRelativePath($path) {
73
+        if ($this->path === '' or $this->path === '/') {
74
+            return $this->normalizePath($path);
75
+        }
76
+        if ($path === $this->path) {
77
+            return '/';
78
+        } elseif (strpos($path, $this->path . '/') !== 0) {
79
+            return null;
80
+        } else {
81
+            $path = substr($path, strlen($this->path));
82
+            return $this->normalizePath($path);
83
+        }
84
+    }
85
+
86
+    /**
87
+     * check if a node is a (grand-)child of the folder
88
+     *
89
+     * @param \OC\Files\Node\Node $node
90
+     * @return bool
91
+     */
92
+    public function isSubNode($node) {
93
+        return strpos($node->getPath(), $this->path . '/') === 0;
94
+    }
95
+
96
+    /**
97
+     * get the content of this directory
98
+     *
99
+     * @throws \OCP\Files\NotFoundException
100
+     * @return Node[]
101
+     */
102
+    public function getDirectoryListing() {
103
+        $folderContent = $this->view->getDirectoryContent($this->path);
104
+
105
+        return array_map(function (FileInfo $info) {
106
+            if ($info->getMimetype() === 'httpd/unix-directory') {
107
+                return new Folder($this->root, $this->view, $info->getPath(), $info);
108
+            } else {
109
+                return new File($this->root, $this->view, $info->getPath(), $info);
110
+            }
111
+        }, $folderContent);
112
+    }
113
+
114
+    /**
115
+     * @param string $path
116
+     * @param FileInfo $info
117
+     * @return File|Folder
118
+     */
119
+    protected function createNode($path, FileInfo $info = null) {
120
+        if (is_null($info)) {
121
+            $isDir = $this->view->is_dir($path);
122
+        } else {
123
+            $isDir = $info->getType() === FileInfo::TYPE_FOLDER;
124
+        }
125
+        if ($isDir) {
126
+            return new Folder($this->root, $this->view, $path, $info);
127
+        } else {
128
+            return new File($this->root, $this->view, $path, $info);
129
+        }
130
+    }
131
+
132
+    /**
133
+     * Get the node at $path
134
+     *
135
+     * @param string $path
136
+     * @return \OC\Files\Node\Node
137
+     * @throws \OCP\Files\NotFoundException
138
+     */
139
+    public function get($path) {
140
+        return $this->root->get($this->getFullPath($path));
141
+    }
142
+
143
+    /**
144
+     * @param string $path
145
+     * @return bool
146
+     */
147
+    public function nodeExists($path) {
148
+        try {
149
+            $this->get($path);
150
+            return true;
151
+        } catch (NotFoundException $e) {
152
+            return false;
153
+        }
154
+    }
155
+
156
+    /**
157
+     * @param string $path
158
+     * @return \OC\Files\Node\Folder
159
+     * @throws \OCP\Files\NotPermittedException
160
+     */
161
+    public function newFolder($path) {
162
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
163
+            $fullPath = $this->getFullPath($path);
164
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
165
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
166
+            if (!$this->view->mkdir($fullPath)) {
167
+                throw new NotPermittedException('Could not create folder');
168
+            }
169
+            $node = new Folder($this->root, $this->view, $fullPath);
170
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
171
+            return $node;
172
+        } else {
173
+            throw new NotPermittedException('No create permission for folder');
174
+        }
175
+    }
176
+
177
+    /**
178
+     * @param string $path
179
+     * @param string | resource | null $content
180
+     * @return \OC\Files\Node\File
181
+     * @throws \OCP\Files\NotPermittedException
182
+     */
183
+    public function newFile($path, $content = null) {
184
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
185
+            $fullPath = $this->getFullPath($path);
186
+            $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
187
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
188
+            if ($content !== null) {
189
+                $result = $this->view->file_put_contents($fullPath, $content);
190
+            } else {
191
+                $result = $this->view->touch($fullPath);
192
+            }
193
+            if ($result === false) {
194
+                throw new NotPermittedException('Could not create path');
195
+            }
196
+            $node = new File($this->root, $this->view, $fullPath);
197
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
198
+            return $node;
199
+        }
200
+        throw new NotPermittedException('No create permission for path');
201
+    }
202
+
203
+    /**
204
+     * search for files with the name matching $query
205
+     *
206
+     * @param string|ISearchQuery $query
207
+     * @return \OC\Files\Node\Node[]
208
+     */
209
+    public function search($query) {
210
+        if (is_string($query)) {
211
+            return $this->searchCommon('search', ['%' . $query . '%']);
212
+        } else {
213
+            return $this->searchCommon('searchQuery', [$query]);
214
+        }
215
+    }
216
+
217
+    /**
218
+     * search for files by mimetype
219
+     *
220
+     * @param string $mimetype
221
+     * @return Node[]
222
+     */
223
+    public function searchByMime($mimetype) {
224
+        return $this->searchCommon('searchByMime', [$mimetype]);
225
+    }
226
+
227
+    /**
228
+     * search for files by tag
229
+     *
230
+     * @param string|int $tag name or tag id
231
+     * @param string $userId owner of the tags
232
+     * @return Node[]
233
+     */
234
+    public function searchByTag($tag, $userId) {
235
+        return $this->searchCommon('searchByTag', [$tag, $userId]);
236
+    }
237
+
238
+    /**
239
+     * @param string $method cache method
240
+     * @param array $args call args
241
+     * @return \OC\Files\Node\Node[]
242
+     */
243
+    private function searchCommon($method, $args) {
244
+        $limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
245
+        if ($limitToHome && count(explode('/', $this->path)) !== 3) {
246
+            throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
247
+        }
248
+
249
+        $files = [];
250
+        $rootLength = strlen($this->path);
251
+        $mount = $this->root->getMount($this->path);
252
+        $storage = $mount->getStorage();
253
+        $internalPath = $mount->getInternalPath($this->path);
254
+        $internalPath = rtrim($internalPath, '/');
255
+        if ($internalPath !== '') {
256
+            $internalPath = $internalPath . '/';
257
+        }
258
+        $internalRootLength = strlen($internalPath);
259
+
260
+        $cache = $storage->getCache('');
261
+
262
+        $results = call_user_func_array([$cache, $method], $args);
263
+        foreach ($results as $result) {
264
+            if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) {
265
+                $result['internalPath'] = $result['path'];
266
+                $result['path'] = substr($result['path'], $internalRootLength);
267
+                $result['storage'] = $storage;
268
+                $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
269
+            }
270
+        }
271
+
272
+        if (!$limitToHome) {
273
+            $mounts = $this->root->getMountsIn($this->path);
274
+            foreach ($mounts as $mount) {
275
+                $storage = $mount->getStorage();
276
+                if ($storage) {
277
+                    $cache = $storage->getCache('');
278
+
279
+                    $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
280
+                    $results = call_user_func_array([$cache, $method], $args);
281
+                    foreach ($results as $result) {
282
+                        $result['internalPath'] = $result['path'];
283
+                        $result['path'] = $relativeMountPoint . $result['path'];
284
+                        $result['storage'] = $storage;
285
+                        $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
286
+                            $result['internalPath'], $result, $mount);
287
+                    }
288
+                }
289
+            }
290
+        }
291
+
292
+        return array_map(function (FileInfo $file) {
293
+            return $this->createNode($file->getPath(), $file);
294
+        }, $files);
295
+    }
296
+
297
+    /**
298
+     * @param int $id
299
+     * @return \OC\Files\Node\Node[]
300
+     */
301
+    public function getById($id) {
302
+        $mountCache = $this->root->getUserMountCache();
303
+        if (strpos($this->getPath(), '/', 1) > 0) {
304
+            list(, $user) = explode('/', $this->getPath());
305
+        } else {
306
+            $user = null;
307
+        }
308
+        $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
309
+        $mounts = $this->root->getMountsIn($this->path);
310
+        $mounts[] = $this->root->getMount($this->path);
311
+        /** @var IMountPoint[] $folderMounts */
312
+        $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
313
+            return $mountPoint->getMountPoint();
314
+        }, $mounts), $mounts);
315
+
316
+        /** @var ICachedMountInfo[] $mountsContainingFile */
317
+        $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
318
+            return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
319
+        }));
320
+
321
+        if (count($mountsContainingFile) === 0) {
322
+            if ($user === $this->getAppDataDirectoryName()) {
323
+                return $this->getByIdInRootMount((int) $id);
324
+            }
325
+            return [];
326
+        }
327
+
328
+        $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
329
+            $mount = $folderMounts[$cachedMountInfo->getMountPoint()];
330
+            $cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
331
+            if (!$cacheEntry) {
332
+                return null;
333
+            }
334
+
335
+            // cache jails will hide the "true" internal path
336
+            $internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
337
+            $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
338
+            $pathRelativeToMount = ltrim($pathRelativeToMount, '/');
339
+            $absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
340
+            return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
341
+                $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
342
+                \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
343
+            ));
344
+        }, $mountsContainingFile);
345
+
346
+        $nodes = array_filter($nodes);
347
+
348
+        return array_filter($nodes, function (Node $node) {
349
+            return $this->getRelativePath($node->getPath());
350
+        });
351
+    }
352
+
353
+    protected function getAppDataDirectoryName(): string {
354
+        $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
355
+        return 'appdata_' . $instanceId;
356
+    }
357
+
358
+    /**
359
+     * In case the path we are currently in is inside the appdata_* folder,
360
+     * the original getById method does not work, because it can only look inside
361
+     * the user's mount points. But the user has no mount point for the root storage.
362
+     *
363
+     * So in that case we directly check the mount of the root if it contains
364
+     * the id. If it does we check if the path is inside the path we are working
365
+     * in.
366
+     *
367
+     * @param int $id
368
+     * @return array
369
+     */
370
+    protected function getByIdInRootMount(int $id): array {
371
+        $mount = $this->root->getMount('');
372
+        $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
373
+        if (!$cacheEntry) {
374
+            return [];
375
+        }
376
+
377
+        $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
378
+        $currentPath = rtrim($this->path, '/') . '/';
379
+
380
+        if (strpos($absolutePath, $currentPath) !== 0) {
381
+            return [];
382
+        }
383
+
384
+        return [$this->root->createNode(
385
+            $absolutePath, new \OC\Files\FileInfo(
386
+                $absolutePath,
387
+                $mount->getStorage(),
388
+                $cacheEntry->getPath(),
389
+                $cacheEntry,
390
+                $mount
391
+        ))];
392
+    }
393
+
394
+    public function getFreeSpace() {
395
+        return $this->view->free_space($this->path);
396
+    }
397
+
398
+    public function delete() {
399
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
400
+            $this->sendHooks(['preDelete']);
401
+            $fileInfo = $this->getFileInfo();
402
+            $this->view->rmdir($this->path);
403
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
404
+            $this->sendHooks(['postDelete'], [$nonExisting]);
405
+            $this->exists = false;
406
+        } else {
407
+            throw new NotPermittedException('No delete permission for path');
408
+        }
409
+    }
410
+
411
+    /**
412
+     * Add a suffix to the name in case the file exists
413
+     *
414
+     * @param string $name
415
+     * @return string
416
+     * @throws NotPermittedException
417
+     */
418
+    public function getNonExistingName($name) {
419
+        $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
420
+        return trim($this->getRelativePath($uniqueName), '/');
421
+    }
422
+
423
+    /**
424
+     * @param int $limit
425
+     * @param int $offset
426
+     * @return \OCP\Files\Node[]
427
+     */
428
+    public function getRecent($limit, $offset = 0) {
429
+        $mimetypeLoader = \OC::$server->getMimeTypeLoader();
430
+        $mounts = $this->root->getMountsIn($this->path);
431
+        $mounts[] = $this->getMountPoint();
432
+
433
+        $mounts = array_filter($mounts, function (IMountPoint $mount) {
434
+            return $mount->getStorage();
435
+        });
436
+        $storageIds = array_map(function (IMountPoint $mount) {
437
+            return $mount->getStorage()->getCache()->getNumericStorageId();
438
+        }, $mounts);
439
+        /** @var IMountPoint[] $mountMap */
440
+        $mountMap = array_combine($storageIds, $mounts);
441
+        $folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
442
+
443
+        /*
444 444
 		 * Construct an array of the storage id with their prefix path
445 445
 		 * This helps us to filter in the final query
446 446
 		 */
447
-		$filters = array_map(function (IMountPoint $mount) {
448
-			$storage = $mount->getStorage();
449
-
450
-			$storageId = $storage->getCache()->getNumericStorageId();
451
-			$prefix = '';
452
-
453
-			if ($storage->instanceOfStorage(Jail::class)) {
454
-				$prefix = $storage->getUnJailedPath('');
455
-			}
456
-
457
-			return [
458
-				'storageId' => $storageId,
459
-				'pathPrefix' => $prefix,
460
-			];
461
-		}, $mounts);
462
-
463
-		// Search in batches of 500 entries
464
-		$searchLimit = 500;
465
-		$results = [];
466
-		$searchResultCount = 0;
467
-		$count = 0;
468
-		do {
469
-			$searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters);
470
-
471
-			// Exit condition if there are no more results
472
-			if (count($searchResult) === 0) {
473
-				break;
474
-			}
475
-
476
-			$searchResultCount += count($searchResult);
477
-
478
-			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
479
-
480
-			foreach ($parseResult as $result) {
481
-				$results[] = $result;
482
-			}
483
-
484
-			$offset += $searchLimit;
485
-			$count++;
486
-		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
487
-
488
-		return array_slice($results, 0, $limit);
489
-	}
490
-
491
-	private function recentSearch($limit, $offset, $folderMimetype, $filters) {
492
-		$dbconn = \OC::$server->getDatabaseConnection();
493
-		$builder = $dbconn->getQueryBuilder();
494
-		$query = $builder
495
-			->select('f.*')
496
-			->from('filecache', 'f');
497
-
498
-		/*
447
+        $filters = array_map(function (IMountPoint $mount) {
448
+            $storage = $mount->getStorage();
449
+
450
+            $storageId = $storage->getCache()->getNumericStorageId();
451
+            $prefix = '';
452
+
453
+            if ($storage->instanceOfStorage(Jail::class)) {
454
+                $prefix = $storage->getUnJailedPath('');
455
+            }
456
+
457
+            return [
458
+                'storageId' => $storageId,
459
+                'pathPrefix' => $prefix,
460
+            ];
461
+        }, $mounts);
462
+
463
+        // Search in batches of 500 entries
464
+        $searchLimit = 500;
465
+        $results = [];
466
+        $searchResultCount = 0;
467
+        $count = 0;
468
+        do {
469
+            $searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters);
470
+
471
+            // Exit condition if there are no more results
472
+            if (count($searchResult) === 0) {
473
+                break;
474
+            }
475
+
476
+            $searchResultCount += count($searchResult);
477
+
478
+            $parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
479
+
480
+            foreach ($parseResult as $result) {
481
+                $results[] = $result;
482
+            }
483
+
484
+            $offset += $searchLimit;
485
+            $count++;
486
+        } while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
487
+
488
+        return array_slice($results, 0, $limit);
489
+    }
490
+
491
+    private function recentSearch($limit, $offset, $folderMimetype, $filters) {
492
+        $dbconn = \OC::$server->getDatabaseConnection();
493
+        $builder = $dbconn->getQueryBuilder();
494
+        $query = $builder
495
+            ->select('f.*')
496
+            ->from('filecache', 'f');
497
+
498
+        /*
499 499
 		 * Here is where we construct the filtering.
500 500
 		 * Note that this is expensive filtering as it is a lot of like queries.
501 501
 		 * However the alternative is we do this filtering and parsing later in php with the risk of looping endlessly
502 502
 		 */
503
-		$storageFilters = $builder->expr()->orX();
504
-		foreach ($filters as $filter) {
505
-			$storageFilter = $builder->expr()->andX(
506
-				$builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId']))
507
-			);
508
-
509
-			if ($filter['pathPrefix'] !== '') {
510
-				$storageFilter->add(
511
-					$builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%'))
512
-				);
513
-			}
514
-
515
-			$storageFilters->add($storageFilter);
516
-		}
517
-
518
-		$query->andWhere($storageFilters);
519
-
520
-		$query->andWhere($builder->expr()->orX(
521
-			// handle non empty folders separate
522
-				$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
523
-				$builder->expr()->eq('f.size', new Literal(0))
524
-			))
525
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
526
-			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
527
-			->orderBy('f.mtime', 'DESC')
528
-			->setMaxResults($limit)
529
-			->setFirstResult($offset);
530
-
531
-		$result = $query->execute();
532
-		$rows = $result->fetchAll();
533
-		$result->closeCursor();
534
-
535
-		return $rows;
536
-	}
537
-
538
-	private function recentParse($result, $mountMap, $mimetypeLoader) {
539
-		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
540
-			$mount = $mountMap[$entry['storage']];
541
-			$entry['internalPath'] = $entry['path'];
542
-			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
543
-			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
544
-			$path = $this->getAbsolutePath($mount, $entry['path']);
545
-			if (is_null($path)) {
546
-				return null;
547
-			}
548
-			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
549
-			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
550
-		}, $result));
551
-
552
-		return array_values(array_filter($files, function (Node $node) {
553
-			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
554
-			if (!$cacheEntry) {
555
-				return false;
556
-			}
557
-			$relative = $this->getRelativePath($node->getPath());
558
-			return $relative !== null && $relative !== '/'
559
-				&& ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
560
-		}));
561
-	}
562
-
563
-	private function getAbsolutePath(IMountPoint $mount, $path) {
564
-		$storage = $mount->getStorage();
565
-		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
566
-			if ($storage->instanceOfStorage(SharedStorage::class)) {
567
-				$storage->getSourceStorage();
568
-			}
569
-			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
570
-			$jailRoot = $storage->getUnjailedPath('');
571
-			$rootLength = strlen($jailRoot) + 1;
572
-			if ($path === $jailRoot) {
573
-				return $mount->getMountPoint();
574
-			} elseif (substr($path, 0, $rootLength) === $jailRoot . '/') {
575
-				return $mount->getMountPoint() . substr($path, $rootLength);
576
-			} else {
577
-				return null;
578
-			}
579
-		} else {
580
-			return $mount->getMountPoint() . $path;
581
-		}
582
-	}
503
+        $storageFilters = $builder->expr()->orX();
504
+        foreach ($filters as $filter) {
505
+            $storageFilter = $builder->expr()->andX(
506
+                $builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId']))
507
+            );
508
+
509
+            if ($filter['pathPrefix'] !== '') {
510
+                $storageFilter->add(
511
+                    $builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%'))
512
+                );
513
+            }
514
+
515
+            $storageFilters->add($storageFilter);
516
+        }
517
+
518
+        $query->andWhere($storageFilters);
519
+
520
+        $query->andWhere($builder->expr()->orX(
521
+            // handle non empty folders separate
522
+                $builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
523
+                $builder->expr()->eq('f.size', new Literal(0))
524
+            ))
525
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
526
+            ->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
527
+            ->orderBy('f.mtime', 'DESC')
528
+            ->setMaxResults($limit)
529
+            ->setFirstResult($offset);
530
+
531
+        $result = $query->execute();
532
+        $rows = $result->fetchAll();
533
+        $result->closeCursor();
534
+
535
+        return $rows;
536
+    }
537
+
538
+    private function recentParse($result, $mountMap, $mimetypeLoader) {
539
+        $files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
540
+            $mount = $mountMap[$entry['storage']];
541
+            $entry['internalPath'] = $entry['path'];
542
+            $entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
543
+            $entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
544
+            $path = $this->getAbsolutePath($mount, $entry['path']);
545
+            if (is_null($path)) {
546
+                return null;
547
+            }
548
+            $fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
549
+            return $this->root->createNode($fileInfo->getPath(), $fileInfo);
550
+        }, $result));
551
+
552
+        return array_values(array_filter($files, function (Node $node) {
553
+            $cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
554
+            if (!$cacheEntry) {
555
+                return false;
556
+            }
557
+            $relative = $this->getRelativePath($node->getPath());
558
+            return $relative !== null && $relative !== '/'
559
+                && ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
560
+        }));
561
+    }
562
+
563
+    private function getAbsolutePath(IMountPoint $mount, $path) {
564
+        $storage = $mount->getStorage();
565
+        if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
566
+            if ($storage->instanceOfStorage(SharedStorage::class)) {
567
+                $storage->getSourceStorage();
568
+            }
569
+            /** @var \OC\Files\Storage\Wrapper\Jail $storage */
570
+            $jailRoot = $storage->getUnjailedPath('');
571
+            $rootLength = strlen($jailRoot) + 1;
572
+            if ($path === $jailRoot) {
573
+                return $mount->getMountPoint();
574
+            } elseif (substr($path, 0, $rootLength) === $jailRoot . '/') {
575
+                return $mount->getMountPoint() . substr($path, $rootLength);
576
+            } else {
577
+                return null;
578
+            }
579
+        } else {
580
+            return $mount->getMountPoint() . $path;
581
+        }
582
+    }
583 583
 }
Please login to merge, or discard this patch.
lib/private/Files/Type/Loader.php 1 patch
Indentation   +144 added lines, -144 removed lines patch added patch discarded remove patch
@@ -35,148 +35,148 @@
 block discarded – undo
35 35
  */
36 36
 class Loader implements IMimeTypeLoader {
37 37
 
38
-	/** @var IDBConnection */
39
-	private $dbConnection;
40
-
41
-	/** @var array [id => mimetype] */
42
-	protected $mimetypes;
43
-
44
-	/** @var array [mimetype => id] */
45
-	protected $mimetypeIds;
46
-
47
-	/**
48
-	 * @param IDBConnection $dbConnection
49
-	 */
50
-	public function __construct(IDBConnection $dbConnection) {
51
-		$this->dbConnection = $dbConnection;
52
-		$this->mimetypes = [];
53
-		$this->mimetypeIds = [];
54
-	}
55
-
56
-	/**
57
-	 * Get a mimetype from its ID
58
-	 *
59
-	 * @param int $id
60
-	 * @return string|null
61
-	 */
62
-	public function getMimetypeById($id) {
63
-		if (!$this->mimetypes) {
64
-			$this->loadMimetypes();
65
-		}
66
-		if (isset($this->mimetypes[$id])) {
67
-			return $this->mimetypes[$id];
68
-		}
69
-		return null;
70
-	}
71
-
72
-	/**
73
-	 * Get a mimetype ID, adding the mimetype to the DB if it does not exist
74
-	 *
75
-	 * @param string $mimetype
76
-	 * @return int
77
-	 */
78
-	public function getId($mimetype) {
79
-		if (!$this->mimetypeIds) {
80
-			$this->loadMimetypes();
81
-		}
82
-		if (isset($this->mimetypeIds[$mimetype])) {
83
-			return $this->mimetypeIds[$mimetype];
84
-		}
85
-		return $this->store($mimetype);
86
-	}
87
-
88
-	/**
89
-	 * Test if a mimetype exists in the database
90
-	 *
91
-	 * @param string $mimetype
92
-	 * @return bool
93
-	 */
94
-	public function exists($mimetype) {
95
-		if (!$this->mimetypeIds) {
96
-			$this->loadMimetypes();
97
-		}
98
-		return isset($this->mimetypeIds[$mimetype]);
99
-	}
100
-
101
-	/**
102
-	 * Clear all loaded mimetypes, allow for re-loading
103
-	 */
104
-	public function reset() {
105
-		$this->mimetypes = [];
106
-		$this->mimetypeIds = [];
107
-	}
108
-
109
-	/**
110
-	 * Store a mimetype in the DB
111
-	 *
112
-	 * @param string $mimetype
113
-	 * @param int inserted ID
114
-	 */
115
-	protected function store($mimetype) {
116
-		$this->dbConnection->insertIfNotExist('*PREFIX*mimetypes', [
117
-			'mimetype' => $mimetype
118
-		]);
119
-
120
-		$fetch = $this->dbConnection->getQueryBuilder();
121
-		$fetch->select('id')
122
-			->from('mimetypes')
123
-			->where(
124
-				$fetch->expr()->eq('mimetype', $fetch->createNamedParameter($mimetype)
125
-			));
126
-
127
-		$result = $fetch->execute();
128
-		$row = $result->fetch();
129
-		$result->closeCursor();
130
-
131
-		if (!$row) {
132
-			throw new \Exception("Failed to get mimetype id for $mimetype after trying to store it");
133
-		}
134
-
135
-		$this->mimetypes[$row['id']] = $mimetype;
136
-		$this->mimetypeIds[$mimetype] = $row['id'];
137
-		return $row['id'];
138
-	}
139
-
140
-	/**
141
-	 * Load all mimetypes from DB
142
-	 */
143
-	private function loadMimetypes() {
144
-		$qb = $this->dbConnection->getQueryBuilder();
145
-		$qb->select('id', 'mimetype')
146
-			->from('mimetypes');
147
-
148
-		$result = $qb->execute();
149
-		$results = $result->fetchAll();
150
-		$result->closeCursor();
151
-
152
-		foreach ($results as $row) {
153
-			$this->mimetypes[$row['id']] = $row['mimetype'];
154
-			$this->mimetypeIds[$row['mimetype']] = $row['id'];
155
-		}
156
-	}
157
-
158
-	/**
159
-	 * Update filecache mimetype based on file extension
160
-	 *
161
-	 * @param string $ext file extension
162
-	 * @param int $mimeTypeId
163
-	 * @return int number of changed rows
164
-	 */
165
-	public function updateFilecache($ext, $mimeTypeId) {
166
-		$folderMimeTypeId = $this->getId('httpd/unix-directory');
167
-		$update = $this->dbConnection->getQueryBuilder();
168
-		$update->update('filecache')
169
-			->set('mimetype', $update->createNamedParameter($mimeTypeId))
170
-			->where($update->expr()->neq(
171
-				'mimetype', $update->createNamedParameter($mimeTypeId)
172
-			))
173
-			->andWhere($update->expr()->neq(
174
-				'mimetype', $update->createNamedParameter($folderMimeTypeId)
175
-			))
176
-			->andWhere($update->expr()->like(
177
-				$update->func()->lower('name'),
178
-				$update->createNamedParameter('%' . $this->dbConnection->escapeLikeParameter('.' . $ext))
179
-			));
180
-		return $update->execute();
181
-	}
38
+    /** @var IDBConnection */
39
+    private $dbConnection;
40
+
41
+    /** @var array [id => mimetype] */
42
+    protected $mimetypes;
43
+
44
+    /** @var array [mimetype => id] */
45
+    protected $mimetypeIds;
46
+
47
+    /**
48
+     * @param IDBConnection $dbConnection
49
+     */
50
+    public function __construct(IDBConnection $dbConnection) {
51
+        $this->dbConnection = $dbConnection;
52
+        $this->mimetypes = [];
53
+        $this->mimetypeIds = [];
54
+    }
55
+
56
+    /**
57
+     * Get a mimetype from its ID
58
+     *
59
+     * @param int $id
60
+     * @return string|null
61
+     */
62
+    public function getMimetypeById($id) {
63
+        if (!$this->mimetypes) {
64
+            $this->loadMimetypes();
65
+        }
66
+        if (isset($this->mimetypes[$id])) {
67
+            return $this->mimetypes[$id];
68
+        }
69
+        return null;
70
+    }
71
+
72
+    /**
73
+     * Get a mimetype ID, adding the mimetype to the DB if it does not exist
74
+     *
75
+     * @param string $mimetype
76
+     * @return int
77
+     */
78
+    public function getId($mimetype) {
79
+        if (!$this->mimetypeIds) {
80
+            $this->loadMimetypes();
81
+        }
82
+        if (isset($this->mimetypeIds[$mimetype])) {
83
+            return $this->mimetypeIds[$mimetype];
84
+        }
85
+        return $this->store($mimetype);
86
+    }
87
+
88
+    /**
89
+     * Test if a mimetype exists in the database
90
+     *
91
+     * @param string $mimetype
92
+     * @return bool
93
+     */
94
+    public function exists($mimetype) {
95
+        if (!$this->mimetypeIds) {
96
+            $this->loadMimetypes();
97
+        }
98
+        return isset($this->mimetypeIds[$mimetype]);
99
+    }
100
+
101
+    /**
102
+     * Clear all loaded mimetypes, allow for re-loading
103
+     */
104
+    public function reset() {
105
+        $this->mimetypes = [];
106
+        $this->mimetypeIds = [];
107
+    }
108
+
109
+    /**
110
+     * Store a mimetype in the DB
111
+     *
112
+     * @param string $mimetype
113
+     * @param int inserted ID
114
+     */
115
+    protected function store($mimetype) {
116
+        $this->dbConnection->insertIfNotExist('*PREFIX*mimetypes', [
117
+            'mimetype' => $mimetype
118
+        ]);
119
+
120
+        $fetch = $this->dbConnection->getQueryBuilder();
121
+        $fetch->select('id')
122
+            ->from('mimetypes')
123
+            ->where(
124
+                $fetch->expr()->eq('mimetype', $fetch->createNamedParameter($mimetype)
125
+            ));
126
+
127
+        $result = $fetch->execute();
128
+        $row = $result->fetch();
129
+        $result->closeCursor();
130
+
131
+        if (!$row) {
132
+            throw new \Exception("Failed to get mimetype id for $mimetype after trying to store it");
133
+        }
134
+
135
+        $this->mimetypes[$row['id']] = $mimetype;
136
+        $this->mimetypeIds[$mimetype] = $row['id'];
137
+        return $row['id'];
138
+    }
139
+
140
+    /**
141
+     * Load all mimetypes from DB
142
+     */
143
+    private function loadMimetypes() {
144
+        $qb = $this->dbConnection->getQueryBuilder();
145
+        $qb->select('id', 'mimetype')
146
+            ->from('mimetypes');
147
+
148
+        $result = $qb->execute();
149
+        $results = $result->fetchAll();
150
+        $result->closeCursor();
151
+
152
+        foreach ($results as $row) {
153
+            $this->mimetypes[$row['id']] = $row['mimetype'];
154
+            $this->mimetypeIds[$row['mimetype']] = $row['id'];
155
+        }
156
+    }
157
+
158
+    /**
159
+     * Update filecache mimetype based on file extension
160
+     *
161
+     * @param string $ext file extension
162
+     * @param int $mimeTypeId
163
+     * @return int number of changed rows
164
+     */
165
+    public function updateFilecache($ext, $mimeTypeId) {
166
+        $folderMimeTypeId = $this->getId('httpd/unix-directory');
167
+        $update = $this->dbConnection->getQueryBuilder();
168
+        $update->update('filecache')
169
+            ->set('mimetype', $update->createNamedParameter($mimeTypeId))
170
+            ->where($update->expr()->neq(
171
+                'mimetype', $update->createNamedParameter($mimeTypeId)
172
+            ))
173
+            ->andWhere($update->expr()->neq(
174
+                'mimetype', $update->createNamedParameter($folderMimeTypeId)
175
+            ))
176
+            ->andWhere($update->expr()->like(
177
+                $update->func()->lower('name'),
178
+                $update->createNamedParameter('%' . $this->dbConnection->escapeLikeParameter('.' . $ext))
179
+            ));
180
+        return $update->execute();
181
+    }
182 182
 }
Please login to merge, or discard this patch.
lib/private/TagManager.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -46,66 +46,66 @@
 block discarded – undo
46 46
 
47 47
 class TagManager implements ITagManager {
48 48
 
49
-	/** @var TagMapper */
50
-	private $mapper;
49
+    /** @var TagMapper */
50
+    private $mapper;
51 51
 
52
-	/** @var IUserSession */
53
-	private $userSession;
52
+    /** @var IUserSession */
53
+    private $userSession;
54 54
 
55
-	/** @var IDBConnection */
56
-	private $connection;
55
+    /** @var IDBConnection */
56
+    private $connection;
57 57
 
58
-	public function __construct(TagMapper $mapper, IUserSession $userSession, IDBConnection $connection) {
59
-		$this->mapper = $mapper;
60
-		$this->userSession = $userSession;
61
-		$this->connection = $connection;
62
-	}
58
+    public function __construct(TagMapper $mapper, IUserSession $userSession, IDBConnection $connection) {
59
+        $this->mapper = $mapper;
60
+        $this->userSession = $userSession;
61
+        $this->connection = $connection;
62
+    }
63 63
 
64
-	/**
65
-	 * Create a new \OCP\ITags instance and load tags from db.
66
-	 *
67
-	 * @see \OCP\ITags
68
-	 * @param string $type The type identifier e.g. 'contact' or 'event'.
69
-	 * @param array $defaultTags An array of default tags to be used if none are stored.
70
-	 * @param boolean $includeShared Whether to include tags for items shared with this user by others.
71
-	 * @param string $userId user for which to retrieve the tags, defaults to the currently
72
-	 * logged in user
73
-	 * @return \OCP\ITags
74
-	 *
75
-	 * since 20.0.0 $includeShared isn't used anymore
76
-	 */
77
-	public function load($type, $defaultTags = [], $includeShared = false, $userId = null) {
78
-		if (is_null($userId)) {
79
-			$user = $this->userSession->getUser();
80
-			if ($user === null) {
81
-				// nothing we can do without a user
82
-				return null;
83
-			}
84
-			$userId = $this->userSession->getUser()->getUId();
85
-		}
86
-		return new Tags($this->mapper, $userId, $type, $defaultTags);
87
-	}
64
+    /**
65
+     * Create a new \OCP\ITags instance and load tags from db.
66
+     *
67
+     * @see \OCP\ITags
68
+     * @param string $type The type identifier e.g. 'contact' or 'event'.
69
+     * @param array $defaultTags An array of default tags to be used if none are stored.
70
+     * @param boolean $includeShared Whether to include tags for items shared with this user by others.
71
+     * @param string $userId user for which to retrieve the tags, defaults to the currently
72
+     * logged in user
73
+     * @return \OCP\ITags
74
+     *
75
+     * since 20.0.0 $includeShared isn't used anymore
76
+     */
77
+    public function load($type, $defaultTags = [], $includeShared = false, $userId = null) {
78
+        if (is_null($userId)) {
79
+            $user = $this->userSession->getUser();
80
+            if ($user === null) {
81
+                // nothing we can do without a user
82
+                return null;
83
+            }
84
+            $userId = $this->userSession->getUser()->getUId();
85
+        }
86
+        return new Tags($this->mapper, $userId, $type, $defaultTags);
87
+    }
88 88
 
89
-	/**
90
-	 * Get all users who favorited an object
91
-	 *
92
-	 * @param string $objectType
93
-	 * @param int $objectId
94
-	 * @return array
95
-	 */
96
-	public function getUsersFavoritingObject(string $objectType, int $objectId): array {
97
-		$query = $this->connection->getQueryBuilder();
98
-		$query->select('uid')
99
-			->from('vcategory_to_object', 'o')
100
-			->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id'))
101
-			->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT)))
102
-			->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType)))
103
-			->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE)));
89
+    /**
90
+     * Get all users who favorited an object
91
+     *
92
+     * @param string $objectType
93
+     * @param int $objectId
94
+     * @return array
95
+     */
96
+    public function getUsersFavoritingObject(string $objectType, int $objectId): array {
97
+        $query = $this->connection->getQueryBuilder();
98
+        $query->select('uid')
99
+            ->from('vcategory_to_object', 'o')
100
+            ->innerJoin('o', 'vcategory', 'c', $query->expr()->eq('o.categoryid', 'c.id'))
101
+            ->where($query->expr()->eq('objid', $query->createNamedParameter($objectId, IQueryBuilder::PARAM_INT)))
102
+            ->andWhere($query->expr()->eq('c.type', $query->createNamedParameter($objectType)))
103
+            ->andWhere($query->expr()->eq('c.category', $query->createNamedParameter(ITags::TAG_FAVORITE)));
104 104
 
105
-		$result = $query->execute();
106
-		$users = $result->fetchAll(\PDO::FETCH_COLUMN);
107
-		$result->closeCursor();
105
+        $result = $query->execute();
106
+        $users = $result->fetchAll(\PDO::FETCH_COLUMN);
107
+        $result->closeCursor();
108 108
 
109
-		return $users;
110
-	}
109
+        return $users;
110
+    }
111 111
 }
Please login to merge, or discard this patch.
lib/private/Tags.php 2 patches
Indentation   +788 added lines, -788 removed lines patch added patch discarded remove patch
@@ -51,792 +51,792 @@
 block discarded – undo
51 51
 
52 52
 class Tags implements ITags {
53 53
 
54
-	/**
55
-	 * Tags
56
-	 *
57
-	 * @var array
58
-	 */
59
-	private $tags = [];
60
-
61
-	/**
62
-	 * Used for storing objectid/categoryname pairs while rescanning.
63
-	 *
64
-	 * @var array
65
-	 */
66
-	private static $relations = [];
67
-
68
-	/**
69
-	 * Type
70
-	 *
71
-	 * @var string
72
-	 */
73
-	private $type;
74
-
75
-	/**
76
-	 * User
77
-	 *
78
-	 * @var string
79
-	 */
80
-	private $user;
81
-
82
-	/**
83
-	 * Are we including tags for shared items?
84
-	 *
85
-	 * @var bool
86
-	 */
87
-	private $includeShared = false;
88
-
89
-	/**
90
-	 * The current user, plus any owners of the items shared with the current
91
-	 * user, if $this->includeShared === true.
92
-	 *
93
-	 * @var array
94
-	 */
95
-	private $owners = [];
96
-
97
-	/**
98
-	 * The Mapper we're using to communicate our Tag objects to the database.
99
-	 *
100
-	 * @var TagMapper
101
-	 */
102
-	private $mapper;
103
-
104
-	/**
105
-	 * The sharing backend for objects of $this->type. Required if
106
-	 * $this->includeShared === true to determine ownership of items.
107
-	 *
108
-	 * @var \OCP\Share_Backend
109
-	 */
110
-	private $backend;
111
-
112
-	public const TAG_TABLE = '*PREFIX*vcategory';
113
-	public const RELATION_TABLE = '*PREFIX*vcategory_to_object';
114
-
115
-	/**
116
-	 * Constructor.
117
-	 *
118
-	 * @param TagMapper $mapper Instance of the TagMapper abstraction layer.
119
-	 * @param string $user The user whose data the object will operate on.
120
-	 * @param string $type The type of items for which tags will be loaded.
121
-	 * @param array $defaultTags Tags that should be created at construction.
122
-	 *
123
-	 * since 20.0.0 $includeShared isn't used anymore
124
-	 */
125
-	public function __construct(TagMapper $mapper, $user, $type, $defaultTags = []) {
126
-		$this->mapper = $mapper;
127
-		$this->user = $user;
128
-		$this->type = $type;
129
-		$this->owners = [$this->user];
130
-		$this->tags = $this->mapper->loadTags($this->owners, $this->type);
131
-
132
-		if (count($defaultTags) > 0 && count($this->tags) === 0) {
133
-			$this->addMultiple($defaultTags, true);
134
-		}
135
-	}
136
-
137
-	/**
138
-	 * Check if any tags are saved for this type and user.
139
-	 *
140
-	 * @return boolean
141
-	 */
142
-	public function isEmpty() {
143
-		return count($this->tags) === 0;
144
-	}
145
-
146
-	/**
147
-	 * Returns an array mapping a given tag's properties to its values:
148
-	 * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
149
-	 *
150
-	 * @param string $id The ID of the tag that is going to be mapped
151
-	 * @return array|false
152
-	 */
153
-	public function getTag($id) {
154
-		$key = $this->getTagById($id);
155
-		if ($key !== false) {
156
-			return $this->tagMap($this->tags[$key]);
157
-		}
158
-		return false;
159
-	}
160
-
161
-	/**
162
-	 * Get the tags for a specific user.
163
-	 *
164
-	 * This returns an array with maps containing each tag's properties:
165
-	 * [
166
-	 * 	['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
167
-	 * 	['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
168
-	 * ]
169
-	 *
170
-	 * @return array
171
-	 */
172
-	public function getTags() {
173
-		if (!count($this->tags)) {
174
-			return [];
175
-		}
176
-
177
-		usort($this->tags, function ($a, $b) {
178
-			return strnatcasecmp($a->getName(), $b->getName());
179
-		});
180
-		$tagMap = [];
181
-
182
-		foreach ($this->tags as $tag) {
183
-			if ($tag->getName() !== ITags::TAG_FAVORITE) {
184
-				$tagMap[] = $this->tagMap($tag);
185
-			}
186
-		}
187
-		return $tagMap;
188
-	}
189
-
190
-	/**
191
-	 * Return only the tags owned by the given user, omitting any tags shared
192
-	 * by other users.
193
-	 *
194
-	 * @param string $user The user whose tags are to be checked.
195
-	 * @return array An array of Tag objects.
196
-	 */
197
-	public function getTagsForUser($user) {
198
-		return array_filter($this->tags,
199
-			function ($tag) use ($user) {
200
-				return $tag->getOwner() === $user;
201
-			}
202
-		);
203
-	}
204
-
205
-	/**
206
-	 * Get the list of tags for the given ids.
207
-	 *
208
-	 * @param array $objIds array of object ids
209
-	 * @return array|boolean of tags id as key to array of tag names
210
-	 * or false if an error occurred
211
-	 */
212
-	public function getTagsForObjects(array $objIds) {
213
-		$entries = [];
214
-
215
-		try {
216
-			$conn = \OC::$server->getDatabaseConnection();
217
-			$chunks = array_chunk($objIds, 900, false);
218
-			foreach ($chunks as $chunk) {
219
-				$result = $conn->executeQuery(
220
-					'SELECT `category`, `categoryid`, `objid` ' .
221
-					'FROM `' . self::RELATION_TABLE . '` r, `' . self::TAG_TABLE . '` ' .
222
-					'WHERE `categoryid` = `id` AND `uid` = ? AND r.`type` = ? AND `objid` IN (?)',
223
-					[$this->user, $this->type, $chunk],
224
-					[null, null, IQueryBuilder::PARAM_INT_ARRAY]
225
-				);
226
-				while ($row = $result->fetch()) {
227
-					$objId = (int)$row['objid'];
228
-					if (!isset($entries[$objId])) {
229
-						$entries[$objId] = [];
230
-					}
231
-					$entries[$objId][] = $row['category'];
232
-				}
233
-				if ($result === null) {
234
-					\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
235
-					return false;
236
-				}
237
-			}
238
-		} catch (\Exception $e) {
239
-			\OC::$server->getLogger()->logException($e, [
240
-				'message' => __METHOD__,
241
-				'level' => ILogger::ERROR,
242
-				'app' => 'core',
243
-			]);
244
-			return false;
245
-		}
246
-
247
-		return $entries;
248
-	}
249
-
250
-	/**
251
-	 * Get the a list if items tagged with $tag.
252
-	 *
253
-	 * Throws an exception if the tag could not be found.
254
-	 *
255
-	 * @param string $tag Tag id or name.
256
-	 * @return array|false An array of object ids or false on error.
257
-	 * @throws \Exception
258
-	 */
259
-	public function getIdsForTag($tag) {
260
-		$result = null;
261
-		$tagId = false;
262
-		if (is_numeric($tag)) {
263
-			$tagId = $tag;
264
-		} elseif (is_string($tag)) {
265
-			$tag = trim($tag);
266
-			if ($tag === '') {
267
-				\OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
268
-				return false;
269
-			}
270
-			$tagId = $this->getTagId($tag);
271
-		}
272
-
273
-		if ($tagId === false) {
274
-			$l10n = \OC::$server->getL10N('core');
275
-			throw new \Exception(
276
-				$l10n->t('Could not find category "%s"', [$tag])
277
-			);
278
-		}
279
-
280
-		$ids = [];
281
-		$sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE
282
-			. '` WHERE `categoryid` = ?';
283
-
284
-		try {
285
-			$stmt = \OC_DB::prepare($sql);
286
-			$result = $stmt->execute([$tagId]);
287
-			if ($result === null) {
288
-				$stmt->closeCursor();
289
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
290
-				return false;
291
-			}
292
-		} catch (\Exception $e) {
293
-			\OC::$server->getLogger()->logException($e, [
294
-				'message' => __METHOD__,
295
-				'level' => ILogger::ERROR,
296
-				'app' => 'core',
297
-			]);
298
-			return false;
299
-		}
300
-
301
-		if (!is_null($result)) {
302
-			while ($row = $result->fetchRow()) {
303
-				$ids[] = (int)$row['objid'];
304
-			}
305
-			$result->closeCursor();
306
-		}
307
-
308
-		return $ids;
309
-	}
310
-
311
-	/**
312
-	 * Checks whether a tag is saved for the given user,
313
-	 * disregarding the ones shared with him or her.
314
-	 *
315
-	 * @param string $name The tag name to check for.
316
-	 * @param string $user The user whose tags are to be checked.
317
-	 * @return bool
318
-	 */
319
-	public function userHasTag($name, $user) {
320
-		$key = $this->array_searchi($name, $this->getTagsForUser($user));
321
-		return ($key !== false) ? $this->tags[$key]->getId() : false;
322
-	}
323
-
324
-	/**
325
-	 * Checks whether a tag is saved for or shared with the current user.
326
-	 *
327
-	 * @param string $name The tag name to check for.
328
-	 * @return bool
329
-	 */
330
-	public function hasTag($name) {
331
-		return $this->getTagId($name) !== false;
332
-	}
333
-
334
-	/**
335
-	 * Add a new tag.
336
-	 *
337
-	 * @param string $name A string with a name of the tag
338
-	 * @return false|int the id of the added tag or false on error.
339
-	 */
340
-	public function add($name) {
341
-		$name = trim($name);
342
-
343
-		if ($name === '') {
344
-			\OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
345
-			return false;
346
-		}
347
-		if ($this->userHasTag($name, $this->user)) {
348
-			\OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', ILogger::DEBUG);
349
-			return false;
350
-		}
351
-		try {
352
-			$tag = new Tag($this->user, $this->type, $name);
353
-			$tag = $this->mapper->insert($tag);
354
-			$this->tags[] = $tag;
355
-		} catch (\Exception $e) {
356
-			\OC::$server->getLogger()->logException($e, [
357
-				'message' => __METHOD__,
358
-				'level' => ILogger::ERROR,
359
-				'app' => 'core',
360
-			]);
361
-			return false;
362
-		}
363
-		\OCP\Util::writeLog('core', __METHOD__.', id: ' . $tag->getId(), ILogger::DEBUG);
364
-		return $tag->getId();
365
-	}
366
-
367
-	/**
368
-	 * Rename tag.
369
-	 *
370
-	 * @param string|integer $from The name or ID of the existing tag
371
-	 * @param string $to The new name of the tag.
372
-	 * @return bool
373
-	 */
374
-	public function rename($from, $to) {
375
-		$from = trim($from);
376
-		$to = trim($to);
377
-
378
-		if ($to === '' || $from === '') {
379
-			\OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
380
-			return false;
381
-		}
382
-
383
-		if (is_numeric($from)) {
384
-			$key = $this->getTagById($from);
385
-		} else {
386
-			$key = $this->getTagByName($from);
387
-		}
388
-		if ($key === false) {
389
-			\OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', ILogger::DEBUG);
390
-			return false;
391
-		}
392
-		$tag = $this->tags[$key];
393
-
394
-		if ($this->userHasTag($to, $tag->getOwner())) {
395
-			\OCP\Util::writeLog('core', __METHOD__.', A tag named ' . $to. ' already exists for user ' . $tag->getOwner() . '.', ILogger::DEBUG);
396
-			return false;
397
-		}
398
-
399
-		try {
400
-			$tag->setName($to);
401
-			$this->tags[$key] = $this->mapper->update($tag);
402
-		} catch (\Exception $e) {
403
-			\OC::$server->getLogger()->logException($e, [
404
-				'message' => __METHOD__,
405
-				'level' => ILogger::ERROR,
406
-				'app' => 'core',
407
-			]);
408
-			return false;
409
-		}
410
-		return true;
411
-	}
412
-
413
-	/**
414
-	 * Add a list of new tags.
415
-	 *
416
-	 * @param string[] $names A string with a name or an array of strings containing
417
-	 * the name(s) of the tag(s) to add.
418
-	 * @param bool $sync When true, save the tags
419
-	 * @param int|null $id int Optional object id to add to this|these tag(s)
420
-	 * @return bool Returns false on error.
421
-	 */
422
-	public function addMultiple($names, $sync = false, $id = null) {
423
-		if (!is_array($names)) {
424
-			$names = [$names];
425
-		}
426
-		$names = array_map('trim', $names);
427
-		array_filter($names);
428
-
429
-		$newones = [];
430
-		foreach ($names as $name) {
431
-			if (!$this->hasTag($name) && $name !== '') {
432
-				$newones[] = new Tag($this->user, $this->type, $name);
433
-			}
434
-			if (!is_null($id)) {
435
-				// Insert $objectid, $categoryid  pairs if not exist.
436
-				self::$relations[] = ['objid' => $id, 'tag' => $name];
437
-			}
438
-		}
439
-		$this->tags = array_merge($this->tags, $newones);
440
-		if ($sync === true) {
441
-			$this->save();
442
-		}
443
-
444
-		return true;
445
-	}
446
-
447
-	/**
448
-	 * Save the list of tags and their object relations
449
-	 */
450
-	protected function save() {
451
-		if (is_array($this->tags)) {
452
-			foreach ($this->tags as $tag) {
453
-				try {
454
-					if (!$this->mapper->tagExists($tag)) {
455
-						$this->mapper->insert($tag);
456
-					}
457
-				} catch (\Exception $e) {
458
-					\OC::$server->getLogger()->logException($e, [
459
-						'message' => __METHOD__,
460
-						'level' => ILogger::ERROR,
461
-						'app' => 'core',
462
-					]);
463
-				}
464
-			}
465
-
466
-			// reload tags to get the proper ids.
467
-			$this->tags = $this->mapper->loadTags($this->owners, $this->type);
468
-			\OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true),
469
-				ILogger::DEBUG);
470
-			// Loop through temporarily cached objectid/tagname pairs
471
-			// and save relations.
472
-			$tags = $this->tags;
473
-			// For some reason this is needed or array_search(i) will return 0..?
474
-			ksort($tags);
475
-			$dbConnection = \OC::$server->getDatabaseConnection();
476
-			foreach (self::$relations as $relation) {
477
-				$tagId = $this->getTagId($relation['tag']);
478
-				\OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, ILogger::DEBUG);
479
-				if ($tagId) {
480
-					try {
481
-						$dbConnection->insertIfNotExist(self::RELATION_TABLE,
482
-							[
483
-								'objid' => $relation['objid'],
484
-								'categoryid' => $tagId,
485
-								'type' => $this->type,
486
-							]);
487
-					} catch (\Exception $e) {
488
-						\OC::$server->getLogger()->logException($e, [
489
-							'message' => __METHOD__,
490
-							'level' => ILogger::ERROR,
491
-							'app' => 'core',
492
-						]);
493
-					}
494
-				}
495
-			}
496
-			self::$relations = []; // reset
497
-		} else {
498
-			\OCP\Util::writeLog('core', __METHOD__.', $this->tags is not an array! '
499
-				. print_r($this->tags, true), ILogger::ERROR);
500
-		}
501
-	}
502
-
503
-	/**
504
-	 * Delete tags and tag/object relations for a user.
505
-	 *
506
-	 * For hooking up on post_deleteUser
507
-	 *
508
-	 * @param array $arguments
509
-	 */
510
-	public static function post_deleteUser($arguments) {
511
-		// Find all objectid/tagId pairs.
512
-		$result = null;
513
-		try {
514
-			$stmt = \OC_DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` '
515
-				. 'WHERE `uid` = ?');
516
-			$result = $stmt->execute([$arguments['uid']]);
517
-			if ($result === null) {
518
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
519
-			}
520
-		} catch (\Exception $e) {
521
-			\OC::$server->getLogger()->logException($e, [
522
-				'message' => __METHOD__,
523
-				'level' => ILogger::ERROR,
524
-				'app' => 'core',
525
-			]);
526
-		}
527
-
528
-		if (!is_null($result)) {
529
-			try {
530
-				$stmt = \OC_DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
531
-					. 'WHERE `categoryid` = ?');
532
-				while ($row = $result->fetchRow()) {
533
-					try {
534
-						$stmt->execute([$row['id']]);
535
-					} catch (\Exception $e) {
536
-						\OC::$server->getLogger()->logException($e, [
537
-							'message' => __METHOD__,
538
-							'level' => ILogger::ERROR,
539
-							'app' => 'core',
540
-						]);
541
-					}
542
-				}
543
-				$result->closeCursor();
544
-			} catch (\Exception $e) {
545
-				\OC::$server->getLogger()->logException($e, [
546
-					'message' => __METHOD__,
547
-					'level' => ILogger::ERROR,
548
-					'app' => 'core',
549
-				]);
550
-			}
551
-		}
552
-		try {
553
-			$stmt = \OC_DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` '
554
-				. 'WHERE `uid` = ?');
555
-			$result = $stmt->execute([$arguments['uid']]);
556
-			if ($result === null) {
557
-				\OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
558
-			}
559
-		} catch (\Exception $e) {
560
-			\OC::$server->getLogger()->logException($e, [
561
-				'message' => __METHOD__,
562
-				'level' => ILogger::ERROR,
563
-				'app' => 'core',
564
-			]);
565
-		}
566
-	}
567
-
568
-	/**
569
-	 * Delete tag/object relations from the db
570
-	 *
571
-	 * @param array $ids The ids of the objects
572
-	 * @return boolean Returns false on error.
573
-	 */
574
-	public function purgeObjects(array $ids) {
575
-		if (count($ids) === 0) {
576
-			// job done ;)
577
-			return true;
578
-		}
579
-		$updates = $ids;
580
-		try {
581
-			$query = 'DELETE FROM `' . self::RELATION_TABLE . '` ';
582
-			$query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids) - 1) . '?) ';
583
-			$query .= 'AND `type`= ?';
584
-			$updates[] = $this->type;
585
-			$stmt = \OC_DB::prepare($query);
586
-			$result = $stmt->execute($updates);
587
-			if ($result === null) {
588
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
589
-				return false;
590
-			}
591
-		} catch (\Exception $e) {
592
-			\OC::$server->getLogger()->logException($e, [
593
-				'message' => __METHOD__,
594
-				'level' => ILogger::ERROR,
595
-				'app' => 'core',
596
-			]);
597
-			return false;
598
-		}
599
-		return true;
600
-	}
601
-
602
-	/**
603
-	 * Get favorites for an object type
604
-	 *
605
-	 * @return array|false An array of object ids.
606
-	 */
607
-	public function getFavorites() {
608
-		if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
609
-			return [];
610
-		}
611
-
612
-		try {
613
-			return $this->getIdsForTag(ITags::TAG_FAVORITE);
614
-		} catch (\Exception $e) {
615
-			\OC::$server->getLogger()->logException($e, [
616
-				'message' => __METHOD__,
617
-				'level' => ILogger::ERROR,
618
-				'app' => 'core',
619
-			]);
620
-			return [];
621
-		}
622
-	}
623
-
624
-	/**
625
-	 * Add an object to favorites
626
-	 *
627
-	 * @param int $objid The id of the object
628
-	 * @return boolean
629
-	 */
630
-	public function addToFavorites($objid) {
631
-		if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
632
-			$this->add(ITags::TAG_FAVORITE);
633
-		}
634
-		return $this->tagAs($objid, ITags::TAG_FAVORITE);
635
-	}
636
-
637
-	/**
638
-	 * Remove an object from favorites
639
-	 *
640
-	 * @param int $objid The id of the object
641
-	 * @return boolean
642
-	 */
643
-	public function removeFromFavorites($objid) {
644
-		return $this->unTag($objid, ITags::TAG_FAVORITE);
645
-	}
646
-
647
-	/**
648
-	 * Creates a tag/object relation.
649
-	 *
650
-	 * @param int $objid The id of the object
651
-	 * @param string $tag The id or name of the tag
652
-	 * @return boolean Returns false on error.
653
-	 */
654
-	public function tagAs($objid, $tag) {
655
-		if (is_string($tag) && !is_numeric($tag)) {
656
-			$tag = trim($tag);
657
-			if ($tag === '') {
658
-				\OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
659
-				return false;
660
-			}
661
-			if (!$this->hasTag($tag)) {
662
-				$this->add($tag);
663
-			}
664
-			$tagId = $this->getTagId($tag);
665
-		} else {
666
-			$tagId = $tag;
667
-		}
668
-		try {
669
-			\OC::$server->getDatabaseConnection()->insertIfNotExist(self::RELATION_TABLE,
670
-				[
671
-					'objid' => $objid,
672
-					'categoryid' => $tagId,
673
-					'type' => $this->type,
674
-				]);
675
-		} catch (\Exception $e) {
676
-			\OC::$server->getLogger()->logException($e, [
677
-				'message' => __METHOD__,
678
-				'level' => ILogger::ERROR,
679
-				'app' => 'core',
680
-			]);
681
-			return false;
682
-		}
683
-		return true;
684
-	}
685
-
686
-	/**
687
-	 * Delete single tag/object relation from the db
688
-	 *
689
-	 * @param int $objid The id of the object
690
-	 * @param string $tag The id or name of the tag
691
-	 * @return boolean
692
-	 */
693
-	public function unTag($objid, $tag) {
694
-		if (is_string($tag) && !is_numeric($tag)) {
695
-			$tag = trim($tag);
696
-			if ($tag === '') {
697
-				\OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', ILogger::DEBUG);
698
-				return false;
699
-			}
700
-			$tagId = $this->getTagId($tag);
701
-		} else {
702
-			$tagId = $tag;
703
-		}
704
-
705
-		try {
706
-			$sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
707
-					. 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?';
708
-			$stmt = \OC_DB::prepare($sql);
709
-			$stmt->execute([$objid, $tagId, $this->type]);
710
-		} catch (\Exception $e) {
711
-			\OC::$server->getLogger()->logException($e, [
712
-				'message' => __METHOD__,
713
-				'level' => ILogger::ERROR,
714
-				'app' => 'core',
715
-			]);
716
-			return false;
717
-		}
718
-		return true;
719
-	}
720
-
721
-	/**
722
-	 * Delete tags from the database.
723
-	 *
724
-	 * @param string[]|integer[] $names An array of tags (names or IDs) to delete
725
-	 * @return bool Returns false on error
726
-	 */
727
-	public function delete($names) {
728
-		if (!is_array($names)) {
729
-			$names = [$names];
730
-		}
731
-
732
-		$names = array_map('trim', $names);
733
-		array_filter($names);
734
-
735
-		\OCP\Util::writeLog('core', __METHOD__ . ', before: '
736
-			. print_r($this->tags, true), ILogger::DEBUG);
737
-		foreach ($names as $name) {
738
-			$id = null;
739
-
740
-			if (is_numeric($name)) {
741
-				$key = $this->getTagById($name);
742
-			} else {
743
-				$key = $this->getTagByName($name);
744
-			}
745
-			if ($key !== false) {
746
-				$tag = $this->tags[$key];
747
-				$id = $tag->getId();
748
-				unset($this->tags[$key]);
749
-				$this->mapper->delete($tag);
750
-			} else {
751
-				\OCP\Util::writeLog('core', __METHOD__ . 'Cannot delete tag ' . $name
752
-					. ': not found.', ILogger::ERROR);
753
-			}
754
-			if (!is_null($id) && $id !== false) {
755
-				try {
756
-					$sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
757
-							. 'WHERE `categoryid` = ?';
758
-					$stmt = \OC_DB::prepare($sql);
759
-					$result = $stmt->execute([$id]);
760
-					if ($result === null) {
761
-						\OCP\Util::writeLog('core',
762
-							__METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(),
763
-							ILogger::ERROR);
764
-						return false;
765
-					}
766
-				} catch (\Exception $e) {
767
-					\OC::$server->getLogger()->logException($e, [
768
-						'message' => __METHOD__,
769
-						'level' => ILogger::ERROR,
770
-						'app' => 'core',
771
-					]);
772
-					return false;
773
-				}
774
-			}
775
-		}
776
-		return true;
777
-	}
778
-
779
-	// case-insensitive array_search
780
-	protected function array_searchi($needle, $haystack, $mem = 'getName') {
781
-		if (!is_array($haystack)) {
782
-			return false;
783
-		}
784
-		return array_search(strtolower($needle), array_map(
785
-			function ($tag) use ($mem) {
786
-				return strtolower(call_user_func([$tag, $mem]));
787
-			}, $haystack)
788
-		);
789
-	}
790
-
791
-	/**
792
-	 * Get a tag's ID.
793
-	 *
794
-	 * @param string $name The tag name to look for.
795
-	 * @return string|bool The tag's id or false if no matching tag is found.
796
-	 */
797
-	private function getTagId($name) {
798
-		$key = $this->array_searchi($name, $this->tags);
799
-		if ($key !== false) {
800
-			return $this->tags[$key]->getId();
801
-		}
802
-		return false;
803
-	}
804
-
805
-	/**
806
-	 * Get a tag by its name.
807
-	 *
808
-	 * @param string $name The tag name.
809
-	 * @return integer|bool The tag object's offset within the $this->tags
810
-	 *                      array or false if it doesn't exist.
811
-	 */
812
-	private function getTagByName($name) {
813
-		return $this->array_searchi($name, $this->tags, 'getName');
814
-	}
815
-
816
-	/**
817
-	 * Get a tag by its ID.
818
-	 *
819
-	 * @param string $id The tag ID to look for.
820
-	 * @return integer|bool The tag object's offset within the $this->tags
821
-	 *                      array or false if it doesn't exist.
822
-	 */
823
-	private function getTagById($id) {
824
-		return $this->array_searchi($id, $this->tags, 'getId');
825
-	}
826
-
827
-	/**
828
-	 * Returns an array mapping a given tag's properties to its values:
829
-	 * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
830
-	 *
831
-	 * @param Tag $tag The tag that is going to be mapped
832
-	 * @return array
833
-	 */
834
-	private function tagMap(Tag $tag) {
835
-		return [
836
-			'id' => $tag->getId(),
837
-			'name' => $tag->getName(),
838
-			'owner' => $tag->getOwner(),
839
-			'type' => $tag->getType()
840
-		];
841
-	}
54
+    /**
55
+     * Tags
56
+     *
57
+     * @var array
58
+     */
59
+    private $tags = [];
60
+
61
+    /**
62
+     * Used for storing objectid/categoryname pairs while rescanning.
63
+     *
64
+     * @var array
65
+     */
66
+    private static $relations = [];
67
+
68
+    /**
69
+     * Type
70
+     *
71
+     * @var string
72
+     */
73
+    private $type;
74
+
75
+    /**
76
+     * User
77
+     *
78
+     * @var string
79
+     */
80
+    private $user;
81
+
82
+    /**
83
+     * Are we including tags for shared items?
84
+     *
85
+     * @var bool
86
+     */
87
+    private $includeShared = false;
88
+
89
+    /**
90
+     * The current user, plus any owners of the items shared with the current
91
+     * user, if $this->includeShared === true.
92
+     *
93
+     * @var array
94
+     */
95
+    private $owners = [];
96
+
97
+    /**
98
+     * The Mapper we're using to communicate our Tag objects to the database.
99
+     *
100
+     * @var TagMapper
101
+     */
102
+    private $mapper;
103
+
104
+    /**
105
+     * The sharing backend for objects of $this->type. Required if
106
+     * $this->includeShared === true to determine ownership of items.
107
+     *
108
+     * @var \OCP\Share_Backend
109
+     */
110
+    private $backend;
111
+
112
+    public const TAG_TABLE = '*PREFIX*vcategory';
113
+    public const RELATION_TABLE = '*PREFIX*vcategory_to_object';
114
+
115
+    /**
116
+     * Constructor.
117
+     *
118
+     * @param TagMapper $mapper Instance of the TagMapper abstraction layer.
119
+     * @param string $user The user whose data the object will operate on.
120
+     * @param string $type The type of items for which tags will be loaded.
121
+     * @param array $defaultTags Tags that should be created at construction.
122
+     *
123
+     * since 20.0.0 $includeShared isn't used anymore
124
+     */
125
+    public function __construct(TagMapper $mapper, $user, $type, $defaultTags = []) {
126
+        $this->mapper = $mapper;
127
+        $this->user = $user;
128
+        $this->type = $type;
129
+        $this->owners = [$this->user];
130
+        $this->tags = $this->mapper->loadTags($this->owners, $this->type);
131
+
132
+        if (count($defaultTags) > 0 && count($this->tags) === 0) {
133
+            $this->addMultiple($defaultTags, true);
134
+        }
135
+    }
136
+
137
+    /**
138
+     * Check if any tags are saved for this type and user.
139
+     *
140
+     * @return boolean
141
+     */
142
+    public function isEmpty() {
143
+        return count($this->tags) === 0;
144
+    }
145
+
146
+    /**
147
+     * Returns an array mapping a given tag's properties to its values:
148
+     * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
149
+     *
150
+     * @param string $id The ID of the tag that is going to be mapped
151
+     * @return array|false
152
+     */
153
+    public function getTag($id) {
154
+        $key = $this->getTagById($id);
155
+        if ($key !== false) {
156
+            return $this->tagMap($this->tags[$key]);
157
+        }
158
+        return false;
159
+    }
160
+
161
+    /**
162
+     * Get the tags for a specific user.
163
+     *
164
+     * This returns an array with maps containing each tag's properties:
165
+     * [
166
+     * 	['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
167
+     * 	['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
168
+     * ]
169
+     *
170
+     * @return array
171
+     */
172
+    public function getTags() {
173
+        if (!count($this->tags)) {
174
+            return [];
175
+        }
176
+
177
+        usort($this->tags, function ($a, $b) {
178
+            return strnatcasecmp($a->getName(), $b->getName());
179
+        });
180
+        $tagMap = [];
181
+
182
+        foreach ($this->tags as $tag) {
183
+            if ($tag->getName() !== ITags::TAG_FAVORITE) {
184
+                $tagMap[] = $this->tagMap($tag);
185
+            }
186
+        }
187
+        return $tagMap;
188
+    }
189
+
190
+    /**
191
+     * Return only the tags owned by the given user, omitting any tags shared
192
+     * by other users.
193
+     *
194
+     * @param string $user The user whose tags are to be checked.
195
+     * @return array An array of Tag objects.
196
+     */
197
+    public function getTagsForUser($user) {
198
+        return array_filter($this->tags,
199
+            function ($tag) use ($user) {
200
+                return $tag->getOwner() === $user;
201
+            }
202
+        );
203
+    }
204
+
205
+    /**
206
+     * Get the list of tags for the given ids.
207
+     *
208
+     * @param array $objIds array of object ids
209
+     * @return array|boolean of tags id as key to array of tag names
210
+     * or false if an error occurred
211
+     */
212
+    public function getTagsForObjects(array $objIds) {
213
+        $entries = [];
214
+
215
+        try {
216
+            $conn = \OC::$server->getDatabaseConnection();
217
+            $chunks = array_chunk($objIds, 900, false);
218
+            foreach ($chunks as $chunk) {
219
+                $result = $conn->executeQuery(
220
+                    'SELECT `category`, `categoryid`, `objid` ' .
221
+                    'FROM `' . self::RELATION_TABLE . '` r, `' . self::TAG_TABLE . '` ' .
222
+                    'WHERE `categoryid` = `id` AND `uid` = ? AND r.`type` = ? AND `objid` IN (?)',
223
+                    [$this->user, $this->type, $chunk],
224
+                    [null, null, IQueryBuilder::PARAM_INT_ARRAY]
225
+                );
226
+                while ($row = $result->fetch()) {
227
+                    $objId = (int)$row['objid'];
228
+                    if (!isset($entries[$objId])) {
229
+                        $entries[$objId] = [];
230
+                    }
231
+                    $entries[$objId][] = $row['category'];
232
+                }
233
+                if ($result === null) {
234
+                    \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
235
+                    return false;
236
+                }
237
+            }
238
+        } catch (\Exception $e) {
239
+            \OC::$server->getLogger()->logException($e, [
240
+                'message' => __METHOD__,
241
+                'level' => ILogger::ERROR,
242
+                'app' => 'core',
243
+            ]);
244
+            return false;
245
+        }
246
+
247
+        return $entries;
248
+    }
249
+
250
+    /**
251
+     * Get the a list if items tagged with $tag.
252
+     *
253
+     * Throws an exception if the tag could not be found.
254
+     *
255
+     * @param string $tag Tag id or name.
256
+     * @return array|false An array of object ids or false on error.
257
+     * @throws \Exception
258
+     */
259
+    public function getIdsForTag($tag) {
260
+        $result = null;
261
+        $tagId = false;
262
+        if (is_numeric($tag)) {
263
+            $tagId = $tag;
264
+        } elseif (is_string($tag)) {
265
+            $tag = trim($tag);
266
+            if ($tag === '') {
267
+                \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
268
+                return false;
269
+            }
270
+            $tagId = $this->getTagId($tag);
271
+        }
272
+
273
+        if ($tagId === false) {
274
+            $l10n = \OC::$server->getL10N('core');
275
+            throw new \Exception(
276
+                $l10n->t('Could not find category "%s"', [$tag])
277
+            );
278
+        }
279
+
280
+        $ids = [];
281
+        $sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE
282
+            . '` WHERE `categoryid` = ?';
283
+
284
+        try {
285
+            $stmt = \OC_DB::prepare($sql);
286
+            $result = $stmt->execute([$tagId]);
287
+            if ($result === null) {
288
+                $stmt->closeCursor();
289
+                \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
290
+                return false;
291
+            }
292
+        } catch (\Exception $e) {
293
+            \OC::$server->getLogger()->logException($e, [
294
+                'message' => __METHOD__,
295
+                'level' => ILogger::ERROR,
296
+                'app' => 'core',
297
+            ]);
298
+            return false;
299
+        }
300
+
301
+        if (!is_null($result)) {
302
+            while ($row = $result->fetchRow()) {
303
+                $ids[] = (int)$row['objid'];
304
+            }
305
+            $result->closeCursor();
306
+        }
307
+
308
+        return $ids;
309
+    }
310
+
311
+    /**
312
+     * Checks whether a tag is saved for the given user,
313
+     * disregarding the ones shared with him or her.
314
+     *
315
+     * @param string $name The tag name to check for.
316
+     * @param string $user The user whose tags are to be checked.
317
+     * @return bool
318
+     */
319
+    public function userHasTag($name, $user) {
320
+        $key = $this->array_searchi($name, $this->getTagsForUser($user));
321
+        return ($key !== false) ? $this->tags[$key]->getId() : false;
322
+    }
323
+
324
+    /**
325
+     * Checks whether a tag is saved for or shared with the current user.
326
+     *
327
+     * @param string $name The tag name to check for.
328
+     * @return bool
329
+     */
330
+    public function hasTag($name) {
331
+        return $this->getTagId($name) !== false;
332
+    }
333
+
334
+    /**
335
+     * Add a new tag.
336
+     *
337
+     * @param string $name A string with a name of the tag
338
+     * @return false|int the id of the added tag or false on error.
339
+     */
340
+    public function add($name) {
341
+        $name = trim($name);
342
+
343
+        if ($name === '') {
344
+            \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
345
+            return false;
346
+        }
347
+        if ($this->userHasTag($name, $this->user)) {
348
+            \OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', ILogger::DEBUG);
349
+            return false;
350
+        }
351
+        try {
352
+            $tag = new Tag($this->user, $this->type, $name);
353
+            $tag = $this->mapper->insert($tag);
354
+            $this->tags[] = $tag;
355
+        } catch (\Exception $e) {
356
+            \OC::$server->getLogger()->logException($e, [
357
+                'message' => __METHOD__,
358
+                'level' => ILogger::ERROR,
359
+                'app' => 'core',
360
+            ]);
361
+            return false;
362
+        }
363
+        \OCP\Util::writeLog('core', __METHOD__.', id: ' . $tag->getId(), ILogger::DEBUG);
364
+        return $tag->getId();
365
+    }
366
+
367
+    /**
368
+     * Rename tag.
369
+     *
370
+     * @param string|integer $from The name or ID of the existing tag
371
+     * @param string $to The new name of the tag.
372
+     * @return bool
373
+     */
374
+    public function rename($from, $to) {
375
+        $from = trim($from);
376
+        $to = trim($to);
377
+
378
+        if ($to === '' || $from === '') {
379
+            \OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', ILogger::DEBUG);
380
+            return false;
381
+        }
382
+
383
+        if (is_numeric($from)) {
384
+            $key = $this->getTagById($from);
385
+        } else {
386
+            $key = $this->getTagByName($from);
387
+        }
388
+        if ($key === false) {
389
+            \OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', ILogger::DEBUG);
390
+            return false;
391
+        }
392
+        $tag = $this->tags[$key];
393
+
394
+        if ($this->userHasTag($to, $tag->getOwner())) {
395
+            \OCP\Util::writeLog('core', __METHOD__.', A tag named ' . $to. ' already exists for user ' . $tag->getOwner() . '.', ILogger::DEBUG);
396
+            return false;
397
+        }
398
+
399
+        try {
400
+            $tag->setName($to);
401
+            $this->tags[$key] = $this->mapper->update($tag);
402
+        } catch (\Exception $e) {
403
+            \OC::$server->getLogger()->logException($e, [
404
+                'message' => __METHOD__,
405
+                'level' => ILogger::ERROR,
406
+                'app' => 'core',
407
+            ]);
408
+            return false;
409
+        }
410
+        return true;
411
+    }
412
+
413
+    /**
414
+     * Add a list of new tags.
415
+     *
416
+     * @param string[] $names A string with a name or an array of strings containing
417
+     * the name(s) of the tag(s) to add.
418
+     * @param bool $sync When true, save the tags
419
+     * @param int|null $id int Optional object id to add to this|these tag(s)
420
+     * @return bool Returns false on error.
421
+     */
422
+    public function addMultiple($names, $sync = false, $id = null) {
423
+        if (!is_array($names)) {
424
+            $names = [$names];
425
+        }
426
+        $names = array_map('trim', $names);
427
+        array_filter($names);
428
+
429
+        $newones = [];
430
+        foreach ($names as $name) {
431
+            if (!$this->hasTag($name) && $name !== '') {
432
+                $newones[] = new Tag($this->user, $this->type, $name);
433
+            }
434
+            if (!is_null($id)) {
435
+                // Insert $objectid, $categoryid  pairs if not exist.
436
+                self::$relations[] = ['objid' => $id, 'tag' => $name];
437
+            }
438
+        }
439
+        $this->tags = array_merge($this->tags, $newones);
440
+        if ($sync === true) {
441
+            $this->save();
442
+        }
443
+
444
+        return true;
445
+    }
446
+
447
+    /**
448
+     * Save the list of tags and their object relations
449
+     */
450
+    protected function save() {
451
+        if (is_array($this->tags)) {
452
+            foreach ($this->tags as $tag) {
453
+                try {
454
+                    if (!$this->mapper->tagExists($tag)) {
455
+                        $this->mapper->insert($tag);
456
+                    }
457
+                } catch (\Exception $e) {
458
+                    \OC::$server->getLogger()->logException($e, [
459
+                        'message' => __METHOD__,
460
+                        'level' => ILogger::ERROR,
461
+                        'app' => 'core',
462
+                    ]);
463
+                }
464
+            }
465
+
466
+            // reload tags to get the proper ids.
467
+            $this->tags = $this->mapper->loadTags($this->owners, $this->type);
468
+            \OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true),
469
+                ILogger::DEBUG);
470
+            // Loop through temporarily cached objectid/tagname pairs
471
+            // and save relations.
472
+            $tags = $this->tags;
473
+            // For some reason this is needed or array_search(i) will return 0..?
474
+            ksort($tags);
475
+            $dbConnection = \OC::$server->getDatabaseConnection();
476
+            foreach (self::$relations as $relation) {
477
+                $tagId = $this->getTagId($relation['tag']);
478
+                \OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, ILogger::DEBUG);
479
+                if ($tagId) {
480
+                    try {
481
+                        $dbConnection->insertIfNotExist(self::RELATION_TABLE,
482
+                            [
483
+                                'objid' => $relation['objid'],
484
+                                'categoryid' => $tagId,
485
+                                'type' => $this->type,
486
+                            ]);
487
+                    } catch (\Exception $e) {
488
+                        \OC::$server->getLogger()->logException($e, [
489
+                            'message' => __METHOD__,
490
+                            'level' => ILogger::ERROR,
491
+                            'app' => 'core',
492
+                        ]);
493
+                    }
494
+                }
495
+            }
496
+            self::$relations = []; // reset
497
+        } else {
498
+            \OCP\Util::writeLog('core', __METHOD__.', $this->tags is not an array! '
499
+                . print_r($this->tags, true), ILogger::ERROR);
500
+        }
501
+    }
502
+
503
+    /**
504
+     * Delete tags and tag/object relations for a user.
505
+     *
506
+     * For hooking up on post_deleteUser
507
+     *
508
+     * @param array $arguments
509
+     */
510
+    public static function post_deleteUser($arguments) {
511
+        // Find all objectid/tagId pairs.
512
+        $result = null;
513
+        try {
514
+            $stmt = \OC_DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` '
515
+                . 'WHERE `uid` = ?');
516
+            $result = $stmt->execute([$arguments['uid']]);
517
+            if ($result === null) {
518
+                \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
519
+            }
520
+        } catch (\Exception $e) {
521
+            \OC::$server->getLogger()->logException($e, [
522
+                'message' => __METHOD__,
523
+                'level' => ILogger::ERROR,
524
+                'app' => 'core',
525
+            ]);
526
+        }
527
+
528
+        if (!is_null($result)) {
529
+            try {
530
+                $stmt = \OC_DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
531
+                    . 'WHERE `categoryid` = ?');
532
+                while ($row = $result->fetchRow()) {
533
+                    try {
534
+                        $stmt->execute([$row['id']]);
535
+                    } catch (\Exception $e) {
536
+                        \OC::$server->getLogger()->logException($e, [
537
+                            'message' => __METHOD__,
538
+                            'level' => ILogger::ERROR,
539
+                            'app' => 'core',
540
+                        ]);
541
+                    }
542
+                }
543
+                $result->closeCursor();
544
+            } catch (\Exception $e) {
545
+                \OC::$server->getLogger()->logException($e, [
546
+                    'message' => __METHOD__,
547
+                    'level' => ILogger::ERROR,
548
+                    'app' => 'core',
549
+                ]);
550
+            }
551
+        }
552
+        try {
553
+            $stmt = \OC_DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` '
554
+                . 'WHERE `uid` = ?');
555
+            $result = $stmt->execute([$arguments['uid']]);
556
+            if ($result === null) {
557
+                \OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
558
+            }
559
+        } catch (\Exception $e) {
560
+            \OC::$server->getLogger()->logException($e, [
561
+                'message' => __METHOD__,
562
+                'level' => ILogger::ERROR,
563
+                'app' => 'core',
564
+            ]);
565
+        }
566
+    }
567
+
568
+    /**
569
+     * Delete tag/object relations from the db
570
+     *
571
+     * @param array $ids The ids of the objects
572
+     * @return boolean Returns false on error.
573
+     */
574
+    public function purgeObjects(array $ids) {
575
+        if (count($ids) === 0) {
576
+            // job done ;)
577
+            return true;
578
+        }
579
+        $updates = $ids;
580
+        try {
581
+            $query = 'DELETE FROM `' . self::RELATION_TABLE . '` ';
582
+            $query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids) - 1) . '?) ';
583
+            $query .= 'AND `type`= ?';
584
+            $updates[] = $this->type;
585
+            $stmt = \OC_DB::prepare($query);
586
+            $result = $stmt->execute($updates);
587
+            if ($result === null) {
588
+                \OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
589
+                return false;
590
+            }
591
+        } catch (\Exception $e) {
592
+            \OC::$server->getLogger()->logException($e, [
593
+                'message' => __METHOD__,
594
+                'level' => ILogger::ERROR,
595
+                'app' => 'core',
596
+            ]);
597
+            return false;
598
+        }
599
+        return true;
600
+    }
601
+
602
+    /**
603
+     * Get favorites for an object type
604
+     *
605
+     * @return array|false An array of object ids.
606
+     */
607
+    public function getFavorites() {
608
+        if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
609
+            return [];
610
+        }
611
+
612
+        try {
613
+            return $this->getIdsForTag(ITags::TAG_FAVORITE);
614
+        } catch (\Exception $e) {
615
+            \OC::$server->getLogger()->logException($e, [
616
+                'message' => __METHOD__,
617
+                'level' => ILogger::ERROR,
618
+                'app' => 'core',
619
+            ]);
620
+            return [];
621
+        }
622
+    }
623
+
624
+    /**
625
+     * Add an object to favorites
626
+     *
627
+     * @param int $objid The id of the object
628
+     * @return boolean
629
+     */
630
+    public function addToFavorites($objid) {
631
+        if (!$this->userHasTag(ITags::TAG_FAVORITE, $this->user)) {
632
+            $this->add(ITags::TAG_FAVORITE);
633
+        }
634
+        return $this->tagAs($objid, ITags::TAG_FAVORITE);
635
+    }
636
+
637
+    /**
638
+     * Remove an object from favorites
639
+     *
640
+     * @param int $objid The id of the object
641
+     * @return boolean
642
+     */
643
+    public function removeFromFavorites($objid) {
644
+        return $this->unTag($objid, ITags::TAG_FAVORITE);
645
+    }
646
+
647
+    /**
648
+     * Creates a tag/object relation.
649
+     *
650
+     * @param int $objid The id of the object
651
+     * @param string $tag The id or name of the tag
652
+     * @return boolean Returns false on error.
653
+     */
654
+    public function tagAs($objid, $tag) {
655
+        if (is_string($tag) && !is_numeric($tag)) {
656
+            $tag = trim($tag);
657
+            if ($tag === '') {
658
+                \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
659
+                return false;
660
+            }
661
+            if (!$this->hasTag($tag)) {
662
+                $this->add($tag);
663
+            }
664
+            $tagId = $this->getTagId($tag);
665
+        } else {
666
+            $tagId = $tag;
667
+        }
668
+        try {
669
+            \OC::$server->getDatabaseConnection()->insertIfNotExist(self::RELATION_TABLE,
670
+                [
671
+                    'objid' => $objid,
672
+                    'categoryid' => $tagId,
673
+                    'type' => $this->type,
674
+                ]);
675
+        } catch (\Exception $e) {
676
+            \OC::$server->getLogger()->logException($e, [
677
+                'message' => __METHOD__,
678
+                'level' => ILogger::ERROR,
679
+                'app' => 'core',
680
+            ]);
681
+            return false;
682
+        }
683
+        return true;
684
+    }
685
+
686
+    /**
687
+     * Delete single tag/object relation from the db
688
+     *
689
+     * @param int $objid The id of the object
690
+     * @param string $tag The id or name of the tag
691
+     * @return boolean
692
+     */
693
+    public function unTag($objid, $tag) {
694
+        if (is_string($tag) && !is_numeric($tag)) {
695
+            $tag = trim($tag);
696
+            if ($tag === '') {
697
+                \OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', ILogger::DEBUG);
698
+                return false;
699
+            }
700
+            $tagId = $this->getTagId($tag);
701
+        } else {
702
+            $tagId = $tag;
703
+        }
704
+
705
+        try {
706
+            $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
707
+                    . 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?';
708
+            $stmt = \OC_DB::prepare($sql);
709
+            $stmt->execute([$objid, $tagId, $this->type]);
710
+        } catch (\Exception $e) {
711
+            \OC::$server->getLogger()->logException($e, [
712
+                'message' => __METHOD__,
713
+                'level' => ILogger::ERROR,
714
+                'app' => 'core',
715
+            ]);
716
+            return false;
717
+        }
718
+        return true;
719
+    }
720
+
721
+    /**
722
+     * Delete tags from the database.
723
+     *
724
+     * @param string[]|integer[] $names An array of tags (names or IDs) to delete
725
+     * @return bool Returns false on error
726
+     */
727
+    public function delete($names) {
728
+        if (!is_array($names)) {
729
+            $names = [$names];
730
+        }
731
+
732
+        $names = array_map('trim', $names);
733
+        array_filter($names);
734
+
735
+        \OCP\Util::writeLog('core', __METHOD__ . ', before: '
736
+            . print_r($this->tags, true), ILogger::DEBUG);
737
+        foreach ($names as $name) {
738
+            $id = null;
739
+
740
+            if (is_numeric($name)) {
741
+                $key = $this->getTagById($name);
742
+            } else {
743
+                $key = $this->getTagByName($name);
744
+            }
745
+            if ($key !== false) {
746
+                $tag = $this->tags[$key];
747
+                $id = $tag->getId();
748
+                unset($this->tags[$key]);
749
+                $this->mapper->delete($tag);
750
+            } else {
751
+                \OCP\Util::writeLog('core', __METHOD__ . 'Cannot delete tag ' . $name
752
+                    . ': not found.', ILogger::ERROR);
753
+            }
754
+            if (!is_null($id) && $id !== false) {
755
+                try {
756
+                    $sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
757
+                            . 'WHERE `categoryid` = ?';
758
+                    $stmt = \OC_DB::prepare($sql);
759
+                    $result = $stmt->execute([$id]);
760
+                    if ($result === null) {
761
+                        \OCP\Util::writeLog('core',
762
+                            __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(),
763
+                            ILogger::ERROR);
764
+                        return false;
765
+                    }
766
+                } catch (\Exception $e) {
767
+                    \OC::$server->getLogger()->logException($e, [
768
+                        'message' => __METHOD__,
769
+                        'level' => ILogger::ERROR,
770
+                        'app' => 'core',
771
+                    ]);
772
+                    return false;
773
+                }
774
+            }
775
+        }
776
+        return true;
777
+    }
778
+
779
+    // case-insensitive array_search
780
+    protected function array_searchi($needle, $haystack, $mem = 'getName') {
781
+        if (!is_array($haystack)) {
782
+            return false;
783
+        }
784
+        return array_search(strtolower($needle), array_map(
785
+            function ($tag) use ($mem) {
786
+                return strtolower(call_user_func([$tag, $mem]));
787
+            }, $haystack)
788
+        );
789
+    }
790
+
791
+    /**
792
+     * Get a tag's ID.
793
+     *
794
+     * @param string $name The tag name to look for.
795
+     * @return string|bool The tag's id or false if no matching tag is found.
796
+     */
797
+    private function getTagId($name) {
798
+        $key = $this->array_searchi($name, $this->tags);
799
+        if ($key !== false) {
800
+            return $this->tags[$key]->getId();
801
+        }
802
+        return false;
803
+    }
804
+
805
+    /**
806
+     * Get a tag by its name.
807
+     *
808
+     * @param string $name The tag name.
809
+     * @return integer|bool The tag object's offset within the $this->tags
810
+     *                      array or false if it doesn't exist.
811
+     */
812
+    private function getTagByName($name) {
813
+        return $this->array_searchi($name, $this->tags, 'getName');
814
+    }
815
+
816
+    /**
817
+     * Get a tag by its ID.
818
+     *
819
+     * @param string $id The tag ID to look for.
820
+     * @return integer|bool The tag object's offset within the $this->tags
821
+     *                      array or false if it doesn't exist.
822
+     */
823
+    private function getTagById($id) {
824
+        return $this->array_searchi($id, $this->tags, 'getId');
825
+    }
826
+
827
+    /**
828
+     * Returns an array mapping a given tag's properties to its values:
829
+     * ['id' => 0, 'name' = 'Tag', 'owner' = 'User', 'type' => 'tagtype']
830
+     *
831
+     * @param Tag $tag The tag that is going to be mapped
832
+     * @return array
833
+     */
834
+    private function tagMap(Tag $tag) {
835
+        return [
836
+            'id' => $tag->getId(),
837
+            'name' => $tag->getName(),
838
+            'owner' => $tag->getOwner(),
839
+            'type' => $tag->getType()
840
+        ];
841
+    }
842 842
 }
Please login to merge, or discard this patch.
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -174,7 +174,7 @@  discard block
 block discarded – undo
174 174
 			return [];
175 175
 		}
176 176
 
177
-		usort($this->tags, function ($a, $b) {
177
+		usort($this->tags, function($a, $b) {
178 178
 			return strnatcasecmp($a->getName(), $b->getName());
179 179
 		});
180 180
 		$tagMap = [];
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 	 */
197 197
 	public function getTagsForUser($user) {
198 198
 		return array_filter($this->tags,
199
-			function ($tag) use ($user) {
199
+			function($tag) use ($user) {
200 200
 				return $tag->getOwner() === $user;
201 201
 			}
202 202
 		);
@@ -217,21 +217,21 @@  discard block
 block discarded – undo
217 217
 			$chunks = array_chunk($objIds, 900, false);
218 218
 			foreach ($chunks as $chunk) {
219 219
 				$result = $conn->executeQuery(
220
-					'SELECT `category`, `categoryid`, `objid` ' .
221
-					'FROM `' . self::RELATION_TABLE . '` r, `' . self::TAG_TABLE . '` ' .
220
+					'SELECT `category`, `categoryid`, `objid` '.
221
+					'FROM `'.self::RELATION_TABLE.'` r, `'.self::TAG_TABLE.'` '.
222 222
 					'WHERE `categoryid` = `id` AND `uid` = ? AND r.`type` = ? AND `objid` IN (?)',
223 223
 					[$this->user, $this->type, $chunk],
224 224
 					[null, null, IQueryBuilder::PARAM_INT_ARRAY]
225 225
 				);
226 226
 				while ($row = $result->fetch()) {
227
-					$objId = (int)$row['objid'];
227
+					$objId = (int) $row['objid'];
228 228
 					if (!isset($entries[$objId])) {
229 229
 						$entries[$objId] = [];
230 230
 					}
231 231
 					$entries[$objId][] = $row['category'];
232 232
 				}
233 233
 				if ($result === null) {
234
-					\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
234
+					\OCP\Util::writeLog('core', __METHOD__.'DB error: '.\OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
235 235
 					return false;
236 236
 				}
237 237
 			}
@@ -278,7 +278,7 @@  discard block
 block discarded – undo
278 278
 		}
279 279
 
280 280
 		$ids = [];
281
-		$sql = 'SELECT `objid` FROM `' . self::RELATION_TABLE
281
+		$sql = 'SELECT `objid` FROM `'.self::RELATION_TABLE
282 282
 			. '` WHERE `categoryid` = ?';
283 283
 
284 284
 		try {
@@ -286,7 +286,7 @@  discard block
 block discarded – undo
286 286
 			$result = $stmt->execute([$tagId]);
287 287
 			if ($result === null) {
288 288
 				$stmt->closeCursor();
289
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
289
+				\OCP\Util::writeLog('core', __METHOD__.'DB error: '.\OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
290 290
 				return false;
291 291
 			}
292 292
 		} catch (\Exception $e) {
@@ -300,7 +300,7 @@  discard block
 block discarded – undo
300 300
 
301 301
 		if (!is_null($result)) {
302 302
 			while ($row = $result->fetchRow()) {
303
-				$ids[] = (int)$row['objid'];
303
+				$ids[] = (int) $row['objid'];
304 304
 			}
305 305
 			$result->closeCursor();
306 306
 		}
@@ -345,7 +345,7 @@  discard block
 block discarded – undo
345 345
 			return false;
346 346
 		}
347 347
 		if ($this->userHasTag($name, $this->user)) {
348
-			\OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', ILogger::DEBUG);
348
+			\OCP\Util::writeLog('core', __METHOD__.', name: '.$name.' exists already', ILogger::DEBUG);
349 349
 			return false;
350 350
 		}
351 351
 		try {
@@ -360,7 +360,7 @@  discard block
 block discarded – undo
360 360
 			]);
361 361
 			return false;
362 362
 		}
363
-		\OCP\Util::writeLog('core', __METHOD__.', id: ' . $tag->getId(), ILogger::DEBUG);
363
+		\OCP\Util::writeLog('core', __METHOD__.', id: '.$tag->getId(), ILogger::DEBUG);
364 364
 		return $tag->getId();
365 365
 	}
366 366
 
@@ -386,13 +386,13 @@  discard block
 block discarded – undo
386 386
 			$key = $this->getTagByName($from);
387 387
 		}
388 388
 		if ($key === false) {
389
-			\OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', ILogger::DEBUG);
389
+			\OCP\Util::writeLog('core', __METHOD__.', tag: '.$from.' does not exist', ILogger::DEBUG);
390 390
 			return false;
391 391
 		}
392 392
 		$tag = $this->tags[$key];
393 393
 
394 394
 		if ($this->userHasTag($to, $tag->getOwner())) {
395
-			\OCP\Util::writeLog('core', __METHOD__.', A tag named ' . $to. ' already exists for user ' . $tag->getOwner() . '.', ILogger::DEBUG);
395
+			\OCP\Util::writeLog('core', __METHOD__.', A tag named '.$to.' already exists for user '.$tag->getOwner().'.', ILogger::DEBUG);
396 396
 			return false;
397 397
 		}
398 398
 
@@ -465,7 +465,7 @@  discard block
 block discarded – undo
465 465
 
466 466
 			// reload tags to get the proper ids.
467 467
 			$this->tags = $this->mapper->loadTags($this->owners, $this->type);
468
-			\OCP\Util::writeLog('core', __METHOD__.', tags: ' . print_r($this->tags, true),
468
+			\OCP\Util::writeLog('core', __METHOD__.', tags: '.print_r($this->tags, true),
469 469
 				ILogger::DEBUG);
470 470
 			// Loop through temporarily cached objectid/tagname pairs
471 471
 			// and save relations.
@@ -475,7 +475,7 @@  discard block
 block discarded – undo
475 475
 			$dbConnection = \OC::$server->getDatabaseConnection();
476 476
 			foreach (self::$relations as $relation) {
477 477
 				$tagId = $this->getTagId($relation['tag']);
478
-				\OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, ILogger::DEBUG);
478
+				\OCP\Util::writeLog('core', __METHOD__.'catid, '.$relation['tag'].' '.$tagId, ILogger::DEBUG);
479 479
 				if ($tagId) {
480 480
 					try {
481 481
 						$dbConnection->insertIfNotExist(self::RELATION_TABLE,
@@ -511,11 +511,11 @@  discard block
 block discarded – undo
511 511
 		// Find all objectid/tagId pairs.
512 512
 		$result = null;
513 513
 		try {
514
-			$stmt = \OC_DB::prepare('SELECT `id` FROM `' . self::TAG_TABLE . '` '
514
+			$stmt = \OC_DB::prepare('SELECT `id` FROM `'.self::TAG_TABLE.'` '
515 515
 				. 'WHERE `uid` = ?');
516 516
 			$result = $stmt->execute([$arguments['uid']]);
517 517
 			if ($result === null) {
518
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
518
+				\OCP\Util::writeLog('core', __METHOD__.'DB error: '.\OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
519 519
 			}
520 520
 		} catch (\Exception $e) {
521 521
 			\OC::$server->getLogger()->logException($e, [
@@ -527,7 +527,7 @@  discard block
 block discarded – undo
527 527
 
528 528
 		if (!is_null($result)) {
529 529
 			try {
530
-				$stmt = \OC_DB::prepare('DELETE FROM `' . self::RELATION_TABLE . '` '
530
+				$stmt = \OC_DB::prepare('DELETE FROM `'.self::RELATION_TABLE.'` '
531 531
 					. 'WHERE `categoryid` = ?');
532 532
 				while ($row = $result->fetchRow()) {
533 533
 					try {
@@ -550,11 +550,11 @@  discard block
 block discarded – undo
550 550
 			}
551 551
 		}
552 552
 		try {
553
-			$stmt = \OC_DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` '
553
+			$stmt = \OC_DB::prepare('DELETE FROM `'.self::TAG_TABLE.'` '
554 554
 				. 'WHERE `uid` = ?');
555 555
 			$result = $stmt->execute([$arguments['uid']]);
556 556
 			if ($result === null) {
557
-				\OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
557
+				\OCP\Util::writeLog('core', __METHOD__.', DB error: '.\OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
558 558
 			}
559 559
 		} catch (\Exception $e) {
560 560
 			\OC::$server->getLogger()->logException($e, [
@@ -578,14 +578,14 @@  discard block
 block discarded – undo
578 578
 		}
579 579
 		$updates = $ids;
580 580
 		try {
581
-			$query = 'DELETE FROM `' . self::RELATION_TABLE . '` ';
582
-			$query .= 'WHERE `objid` IN (' . str_repeat('?,', count($ids) - 1) . '?) ';
581
+			$query = 'DELETE FROM `'.self::RELATION_TABLE.'` ';
582
+			$query .= 'WHERE `objid` IN ('.str_repeat('?,', count($ids) - 1).'?) ';
583 583
 			$query .= 'AND `type`= ?';
584 584
 			$updates[] = $this->type;
585 585
 			$stmt = \OC_DB::prepare($query);
586 586
 			$result = $stmt->execute($updates);
587 587
 			if ($result === null) {
588
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
588
+				\OCP\Util::writeLog('core', __METHOD__.'DB error: '.\OC::$server->getDatabaseConnection()->getError(), ILogger::ERROR);
589 589
 				return false;
590 590
 			}
591 591
 		} catch (\Exception $e) {
@@ -703,7 +703,7 @@  discard block
 block discarded – undo
703 703
 		}
704 704
 
705 705
 		try {
706
-			$sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
706
+			$sql = 'DELETE FROM `'.self::RELATION_TABLE.'` '
707 707
 					. 'WHERE `objid` = ? AND `categoryid` = ? AND `type` = ?';
708 708
 			$stmt = \OC_DB::prepare($sql);
709 709
 			$stmt->execute([$objid, $tagId, $this->type]);
@@ -732,7 +732,7 @@  discard block
 block discarded – undo
732 732
 		$names = array_map('trim', $names);
733 733
 		array_filter($names);
734 734
 
735
-		\OCP\Util::writeLog('core', __METHOD__ . ', before: '
735
+		\OCP\Util::writeLog('core', __METHOD__.', before: '
736 736
 			. print_r($this->tags, true), ILogger::DEBUG);
737 737
 		foreach ($names as $name) {
738 738
 			$id = null;
@@ -748,18 +748,18 @@  discard block
 block discarded – undo
748 748
 				unset($this->tags[$key]);
749 749
 				$this->mapper->delete($tag);
750 750
 			} else {
751
-				\OCP\Util::writeLog('core', __METHOD__ . 'Cannot delete tag ' . $name
751
+				\OCP\Util::writeLog('core', __METHOD__.'Cannot delete tag '.$name
752 752
 					. ': not found.', ILogger::ERROR);
753 753
 			}
754 754
 			if (!is_null($id) && $id !== false) {
755 755
 				try {
756
-					$sql = 'DELETE FROM `' . self::RELATION_TABLE . '` '
756
+					$sql = 'DELETE FROM `'.self::RELATION_TABLE.'` '
757 757
 							. 'WHERE `categoryid` = ?';
758 758
 					$stmt = \OC_DB::prepare($sql);
759 759
 					$result = $stmt->execute([$id]);
760 760
 					if ($result === null) {
761 761
 						\OCP\Util::writeLog('core',
762
-							__METHOD__. 'DB error: ' . \OC::$server->getDatabaseConnection()->getError(),
762
+							__METHOD__.'DB error: '.\OC::$server->getDatabaseConnection()->getError(),
763 763
 							ILogger::ERROR);
764 764
 						return false;
765 765
 					}
@@ -782,7 +782,7 @@  discard block
 block discarded – undo
782 782
 			return false;
783 783
 		}
784 784
 		return array_search(strtolower($needle), array_map(
785
-			function ($tag) use ($mem) {
785
+			function($tag) use ($mem) {
786 786
 				return strtolower(call_user_func([$tag, $mem]));
787 787
 			}, $haystack)
788 788
 		);
Please login to merge, or discard this patch.
lib/private/Share/Share.php 2 patches
Indentation   +1097 added lines, -1097 removed lines patch added patch discarded remove patch
@@ -50,1101 +50,1101 @@
 block discarded – undo
50 50
  */
51 51
 class Share extends Constants {
52 52
 
53
-	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
54
-	 * Construct permissions for share() and setPermissions with Or (|) e.g.
55
-	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
56
-	 *
57
-	 * Check if permission is granted with And (&) e.g. Check if delete is
58
-	 * granted: if ($permissions & PERMISSION_DELETE)
59
-	 *
60
-	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
61
-	 * permission: $permissions &= ~PERMISSION_UPDATE
62
-	 *
63
-	 * Apps are required to handle permissions on their own, this class only
64
-	 * stores and manages the permissions of shares
65
-	 * @see lib/public/constants.php
66
-	 */
67
-
68
-	/**
69
-	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
70
-	 * @param string $itemType Item type
71
-	 * @param string $class Backend class
72
-	 * @param string $collectionOf (optional) Depends on item type
73
-	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
74
-	 * @return boolean true if backend is registered or false if error
75
-	 */
76
-	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
77
-		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
78
-			if (!isset(self::$backendTypes[$itemType])) {
79
-				self::$backendTypes[$itemType] = [
80
-					'class' => $class,
81
-					'collectionOf' => $collectionOf,
82
-					'supportedFileExtensions' => $supportedFileExtensions
83
-				];
84
-				return true;
85
-			}
86
-			\OCP\Util::writeLog('OCP\Share',
87
-				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
88
-				.' is already registered for '.$itemType,
89
-				ILogger::WARN);
90
-		}
91
-		return false;
92
-	}
93
-
94
-	/**
95
-	 * Get the items of item type shared with the current user
96
-	 * @param string $itemType
97
-	 * @param int $format (optional) Format type must be defined by the backend
98
-	 * @param mixed $parameters (optional)
99
-	 * @param int $limit Number of items to return (optional) Returns all by default
100
-	 * @param boolean $includeCollections (optional)
101
-	 * @return mixed Return depends on format
102
-	 * @deprecated TESTS ONLY - this methods is only used by tests
103
-	 * called like this:
104
-	 * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php)
105
-	 */
106
-	public static function getItemsSharedWith() {
107
-		return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, self::FORMAT_NONE,
108
-			null, -1, false);
109
-	}
110
-
111
-	/**
112
-	 * Get the items of item type shared with a user
113
-	 * @param string $itemType
114
-	 * @param string $user id for which user we want the shares
115
-	 * @param int $format (optional) Format type must be defined by the backend
116
-	 * @param mixed $parameters (optional)
117
-	 * @param int $limit Number of items to return (optional) Returns all by default
118
-	 * @param boolean $includeCollections (optional)
119
-	 * @return mixed Return depends on format
120
-	 */
121
-	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
122
-												  $parameters = null, $limit = -1, $includeCollections = false) {
123
-		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
124
-			$parameters, $limit, $includeCollections);
125
-	}
126
-
127
-	/**
128
-	 * Get the item of item type shared with a given user by source
129
-	 * @param string $itemType
130
-	 * @param string $itemSource
131
-	 * @param string $user User to whom the item was shared
132
-	 * @param string $owner Owner of the share
133
-	 * @param int $shareType only look for a specific share type
134
-	 * @return array Return list of items with file_target, permissions and expiration
135
-	 */
136
-	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
137
-		$shares = [];
138
-		$fileDependent = false;
139
-
140
-		$where = 'WHERE';
141
-		$fileDependentWhere = '';
142
-		if ($itemType === 'file' || $itemType === 'folder') {
143
-			$fileDependent = true;
144
-			$column = 'file_source';
145
-			$fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
146
-			$fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
147
-		} else {
148
-			$column = 'item_source';
149
-		}
150
-
151
-		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
152
-
153
-		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
154
-		$arguments = [$itemSource, $itemType];
155
-		// for link shares $user === null
156
-		if ($user !== null) {
157
-			$where .= ' AND `share_with` = ? ';
158
-			$arguments[] = $user;
159
-		}
160
-
161
-		if ($shareType !== null) {
162
-			$where .= ' AND `share_type` = ? ';
163
-			$arguments[] = $shareType;
164
-		}
165
-
166
-		if ($owner !== null) {
167
-			$where .= ' AND `uid_owner` = ? ';
168
-			$arguments[] = $owner;
169
-		}
170
-
171
-		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
172
-
173
-		$result = \OC_DB::executeAudited($query, $arguments);
174
-
175
-		while ($row = $result->fetchRow()) {
176
-			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
177
-				continue;
178
-			}
179
-			if ($fileDependent && (int)$row['file_parent'] === -1) {
180
-				// if it is a mount point we need to get the path from the mount manager
181
-				$mountManager = \OC\Files\Filesystem::getMountManager();
182
-				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
183
-				if (!empty($mountPoint)) {
184
-					$path = $mountPoint[0]->getMountPoint();
185
-					$path = trim($path, '/');
186
-					$path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
187
-					$row['path'] = $path;
188
-				} else {
189
-					\OC::$server->getLogger()->warning(
190
-						'Could not resolve mount point for ' . $row['storage_id'],
191
-						['app' => 'OCP\Share']
192
-					);
193
-				}
194
-			}
195
-			$shares[] = $row;
196
-		}
197
-		$result->closeCursor();
198
-
199
-		//if didn't found a result than let's look for a group share.
200
-		if (empty($shares) && $user !== null) {
201
-			$userObject = \OC::$server->getUserManager()->get($user);
202
-			$groups = [];
203
-			if ($userObject) {
204
-				$groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
205
-			}
206
-
207
-			if (!empty($groups)) {
208
-				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
209
-				$arguments = [$itemSource, $itemType, $groups];
210
-				$types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
211
-
212
-				if ($owner !== null) {
213
-					$where .= ' AND `uid_owner` = ?';
214
-					$arguments[] = $owner;
215
-					$types[] = null;
216
-				}
217
-
218
-				// TODO: inject connection, hopefully one day in the future when this
219
-				// class isn't static anymore...
220
-				$conn = \OC::$server->getDatabaseConnection();
221
-				$result = $conn->executeQuery(
222
-					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
223
-					$arguments,
224
-					$types
225
-				);
226
-
227
-				while ($row = $result->fetch()) {
228
-					$shares[] = $row;
229
-				}
230
-			}
231
-		}
232
-
233
-		return $shares;
234
-	}
235
-
236
-	/**
237
-	 * Get the item of item type shared with the current user by source
238
-	 * @param string $itemType
239
-	 * @param string $itemSource
240
-	 * @param int $format (optional) Format type must be defined by the backend
241
-	 * @param mixed $parameters
242
-	 * @param boolean $includeCollections
243
-	 * @param string $shareWith (optional) define against which user should be checked, default: current user
244
-	 * @return array
245
-	 */
246
-	public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
247
-													 $parameters = null, $includeCollections = false, $shareWith = null) {
248
-		$shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
249
-		return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
250
-			$parameters, 1, $includeCollections, true);
251
-	}
252
-
253
-	/**
254
-	 * Get the shared item of item type owned by the current user
255
-	 * @param string $itemType
256
-	 * @param string $itemSource
257
-	 * @param int $format (optional) Format type must be defined by the backend
258
-	 * @param mixed $parameters
259
-	 * @param boolean $includeCollections
260
-	 * @return mixed Return depends on format
261
-	 */
262
-	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
263
-										 $parameters = null, $includeCollections = false) {
264
-		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
265
-			$parameters, -1, $includeCollections);
266
-	}
267
-
268
-	/**
269
-	 * Unshare an item from a user, group, or delete a private link
270
-	 * @param string $itemType
271
-	 * @param string $itemSource
272
-	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
273
-	 * @param string $shareWith User or group the item is being shared with
274
-	 * @param string $owner owner of the share, if null the current user is used
275
-	 * @return boolean true on success or false on failure
276
-	 */
277
-	public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
278
-
279
-		// check if it is a valid itemType
280
-		self::getBackend($itemType);
281
-
282
-		$items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
283
-
284
-		$toDelete = [];
285
-		$newParent = null;
286
-		$currentUser = $owner ? $owner : \OC_User::getUser();
287
-		foreach ($items as $item) {
288
-			// delete the item with the expected share_type and owner
289
-			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
290
-				$toDelete = $item;
291
-			// if there is more then one result we don't have to delete the children
292
-				// but update their parent. For group shares the new parent should always be
293
-				// the original group share and not the db entry with the unique name
294
-			} elseif ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
295
-				$newParent = $item['parent'];
296
-			} else {
297
-				$newParent = $item['id'];
298
-			}
299
-		}
300
-
301
-		if (!empty($toDelete)) {
302
-			self::unshareItem($toDelete, $newParent);
303
-			return true;
304
-		}
305
-		return false;
306
-	}
307
-
308
-	/**
309
-	 * Checks whether a share has expired, calls unshareItem() if yes.
310
-	 * @param array $item Share data (usually database row)
311
-	 * @return boolean True if item was expired, false otherwise.
312
-	 */
313
-	protected static function expireItem(array $item) {
314
-		$result = false;
315
-
316
-		// only use default expiration date for link shares
317
-		if ((int) $item['share_type'] === IShare::TYPE_LINK) {
318
-
319
-			// calculate expiration date
320
-			if (!empty($item['expiration'])) {
321
-				$userDefinedExpire = new \DateTime($item['expiration']);
322
-				$expires = $userDefinedExpire->getTimestamp();
323
-			} else {
324
-				$expires = null;
325
-			}
326
-
327
-
328
-			// get default expiration settings
329
-			$defaultSettings = Helper::getDefaultExpireSetting();
330
-			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
331
-
332
-
333
-			if (is_int($expires)) {
334
-				$now = time();
335
-				if ($now > $expires) {
336
-					self::unshareItem($item);
337
-					$result = true;
338
-				}
339
-			}
340
-		}
341
-		return $result;
342
-	}
343
-
344
-	/**
345
-	 * Unshares a share given a share data array
346
-	 * @param array $item Share data (usually database row)
347
-	 * @param int $newParent parent ID
348
-	 * @return null
349
-	 */
350
-	protected static function unshareItem(array $item, $newParent = null) {
351
-		$shareType = (int)$item['share_type'];
352
-		$shareWith = null;
353
-		if ($shareType !== IShare::TYPE_LINK) {
354
-			$shareWith = $item['share_with'];
355
-		}
356
-
357
-		// Pass all the vars we have for now, they may be useful
358
-		$hookParams = [
359
-			'id' => $item['id'],
360
-			'itemType' => $item['item_type'],
361
-			'itemSource' => $item['item_source'],
362
-			'shareType' => $shareType,
363
-			'shareWith' => $shareWith,
364
-			'itemParent' => $item['parent'],
365
-			'uidOwner' => $item['uid_owner'],
366
-		];
367
-		if ($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
368
-			$hookParams['fileSource'] = $item['file_source'];
369
-			$hookParams['fileTarget'] = $item['file_target'];
370
-		}
371
-
372
-		\OC_Hook::emit(\OCP\Share::class, 'pre_unshare', $hookParams);
373
-		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
374
-		$deletedShares[] = $hookParams;
375
-		$hookParams['deletedShares'] = $deletedShares;
376
-		\OC_Hook::emit(\OCP\Share::class, 'post_unshare', $hookParams);
377
-		if ((int)$item['share_type'] === IShare::TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
378
-			list(, $remote) = Helper::splitUserRemote($item['share_with']);
379
-			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
380
-		}
381
-	}
382
-
383
-	/**
384
-	 * Get the backend class for the specified item type
385
-	 * @param string $itemType
386
-	 * @throws \Exception
387
-	 * @return \OCP\Share_Backend
388
-	 */
389
-	public static function getBackend($itemType) {
390
-		$l = \OC::$server->getL10N('lib');
391
-		if (isset(self::$backends[$itemType])) {
392
-			return self::$backends[$itemType];
393
-		} elseif (isset(self::$backendTypes[$itemType]['class'])) {
394
-			$class = self::$backendTypes[$itemType]['class'];
395
-			if (class_exists($class)) {
396
-				self::$backends[$itemType] = new $class;
397
-				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
398
-					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
399
-					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
400
-					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
401
-					throw new \Exception($message_t);
402
-				}
403
-				return self::$backends[$itemType];
404
-			} else {
405
-				$message = 'Sharing backend %s not found';
406
-				$message_t = $l->t('Sharing backend %s not found', [$class]);
407
-				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
408
-				throw new \Exception($message_t);
409
-			}
410
-		}
411
-		$message = 'Sharing backend for %s not found';
412
-		$message_t = $l->t('Sharing backend for %s not found', [$itemType]);
413
-		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
414
-		throw new \Exception($message_t);
415
-	}
416
-
417
-	/**
418
-	 * Check if resharing is allowed
419
-	 * @return boolean true if allowed or false
420
-	 *
421
-	 * Resharing is allowed by default if not configured
422
-	 */
423
-	public static function isResharingAllowed() {
424
-		if (!isset(self::$isResharingAllowed)) {
425
-			if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
426
-				self::$isResharingAllowed = true;
427
-			} else {
428
-				self::$isResharingAllowed = false;
429
-			}
430
-		}
431
-		return self::$isResharingAllowed;
432
-	}
433
-
434
-	/**
435
-	 * Get a list of collection item types for the specified item type
436
-	 * @param string $itemType
437
-	 * @return array
438
-	 */
439
-	private static function getCollectionItemTypes($itemType) {
440
-		$collectionTypes = [$itemType];
441
-		foreach (self::$backendTypes as $type => $backend) {
442
-			if (in_array($backend['collectionOf'], $collectionTypes)) {
443
-				$collectionTypes[] = $type;
444
-			}
445
-		}
446
-		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
447
-		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
448
-			unset($collectionTypes[0]);
449
-		}
450
-		// Return array if collections were found or the item type is a
451
-		// collection itself - collections can be inside collections
452
-		if (count($collectionTypes) > 0) {
453
-			return $collectionTypes;
454
-		}
455
-		return false;
456
-	}
457
-
458
-	/**
459
-	 * Get shared items from the database
460
-	 * @param string $itemType
461
-	 * @param string $item Item source or target (optional)
462
-	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
463
-	 * @param string $shareWith User or group the item is being shared with
464
-	 * @param string $uidOwner User that is the owner of shared items (optional)
465
-	 * @param int $format Format to convert items to with formatItems() (optional)
466
-	 * @param mixed $parameters to pass to formatItems() (optional)
467
-	 * @param int $limit Number of items to return, -1 to return all matches (optional)
468
-	 * @param boolean $includeCollections Include collection item types (optional)
469
-	 * @param boolean $itemShareWithBySource (optional)
470
-	 * @param boolean $checkExpireDate
471
-	 * @return array
472
-	 *
473
-	 * See public functions getItem(s)... for parameter usage
474
-	 *
475
-	 */
476
-	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
477
-									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
478
-									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
479
-		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
480
-			return [];
481
-		}
482
-		$backend = self::getBackend($itemType);
483
-		$collectionTypes = false;
484
-		// Get filesystem root to add it to the file target and remove from the
485
-		// file source, match file_source with the file cache
486
-		if ($itemType == 'file' || $itemType == 'folder') {
487
-			if (!is_null($uidOwner)) {
488
-				$root = \OC\Files\Filesystem::getRoot();
489
-			} else {
490
-				$root = '';
491
-			}
492
-			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
493
-			if (!isset($item)) {
494
-				$where .= ' AND `file_target` IS NOT NULL ';
495
-			}
496
-			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
497
-			$fileDependent = true;
498
-			$queryArgs = [];
499
-		} else {
500
-			$fileDependent = false;
501
-			$root = '';
502
-			$collectionTypes = self::getCollectionItemTypes($itemType);
503
-			if ($includeCollections && !isset($item) && $collectionTypes) {
504
-				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
505
-				if (!in_array($itemType, $collectionTypes)) {
506
-					$itemTypes = array_merge([$itemType], $collectionTypes);
507
-				} else {
508
-					$itemTypes = $collectionTypes;
509
-				}
510
-				$placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
511
-				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
512
-				$queryArgs = $itemTypes;
513
-			} else {
514
-				$where = ' WHERE `item_type` = ?';
515
-				$queryArgs = [$itemType];
516
-			}
517
-		}
518
-		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
519
-			$where .= ' AND `share_type` != ?';
520
-			$queryArgs[] = IShare::TYPE_LINK;
521
-		}
522
-		if (isset($shareType)) {
523
-			// Include all user and group items
524
-			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
525
-				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
526
-				$queryArgs[] = IShare::TYPE_USER;
527
-				$queryArgs[] = self::$shareTypeGroupUserUnique;
528
-				$queryArgs[] = $shareWith;
529
-
530
-				$user = \OC::$server->getUserManager()->get($shareWith);
531
-				$groups = [];
532
-				if ($user) {
533
-					$groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
534
-				}
535
-				if (!empty($groups)) {
536
-					$placeholders = implode(',', array_fill(0, count($groups), '?'));
537
-					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
538
-					$queryArgs[] = IShare::TYPE_GROUP;
539
-					$queryArgs = array_merge($queryArgs, $groups);
540
-				}
541
-				$where .= ')';
542
-				// Don't include own group shares
543
-				$where .= ' AND `uid_owner` != ?';
544
-				$queryArgs[] = $shareWith;
545
-			} else {
546
-				$where .= ' AND `share_type` = ?';
547
-				$queryArgs[] = $shareType;
548
-				if (isset($shareWith)) {
549
-					$where .= ' AND `share_with` = ?';
550
-					$queryArgs[] = $shareWith;
551
-				}
552
-			}
553
-		}
554
-		if (isset($uidOwner)) {
555
-			$where .= ' AND `uid_owner` = ?';
556
-			$queryArgs[] = $uidOwner;
557
-			if (!isset($shareType)) {
558
-				// Prevent unique user targets for group shares from being selected
559
-				$where .= ' AND `share_type` != ?';
560
-				$queryArgs[] = self::$shareTypeGroupUserUnique;
561
-			}
562
-			if ($fileDependent) {
563
-				$column = 'file_source';
564
-			} else {
565
-				$column = 'item_source';
566
-			}
567
-		} else {
568
-			if ($fileDependent) {
569
-				$column = 'file_target';
570
-			} else {
571
-				$column = 'item_target';
572
-			}
573
-		}
574
-		if (isset($item)) {
575
-			$collectionTypes = self::getCollectionItemTypes($itemType);
576
-			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
577
-				$where .= ' AND (';
578
-			} else {
579
-				$where .= ' AND';
580
-			}
581
-			// If looking for own shared items, check item_source else check item_target
582
-			if (isset($uidOwner) || $itemShareWithBySource) {
583
-				// If item type is a file, file source needs to be checked in case the item was converted
584
-				if ($fileDependent) {
585
-					$where .= ' `file_source` = ?';
586
-					$column = 'file_source';
587
-				} else {
588
-					$where .= ' `item_source` = ?';
589
-					$column = 'item_source';
590
-				}
591
-			} else {
592
-				if ($fileDependent) {
593
-					$where .= ' `file_target` = ?';
594
-					$item = \OC\Files\Filesystem::normalizePath($item);
595
-				} else {
596
-					$where .= ' `item_target` = ?';
597
-				}
598
-			}
599
-			$queryArgs[] = $item;
600
-			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
601
-				$placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
602
-				$where .= ' OR `item_type` IN ('.$placeholders.'))';
603
-				$queryArgs = array_merge($queryArgs, $collectionTypes);
604
-			}
605
-		}
606
-
607
-		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
608
-			// Make sure the unique user target is returned if it exists,
609
-			// unique targets should follow the group share in the database
610
-			// If the limit is not 1, the filtering can be done later
611
-			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
612
-		} else {
613
-			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
614
-		}
615
-
616
-		if ($limit != -1 && !$includeCollections) {
617
-			// The limit must be at least 3, because filtering needs to be done
618
-			if ($limit < 3) {
619
-				$queryLimit = 3;
620
-			} else {
621
-				$queryLimit = $limit;
622
-			}
623
-		} else {
624
-			$queryLimit = null;
625
-		}
626
-		$select = self::createSelectStatement($format, $fileDependent, $uidOwner);
627
-		$root = strlen($root);
628
-		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
629
-		$result = $query->execute($queryArgs);
630
-		if ($result === false) {
631
-			\OCP\Util::writeLog('OCP\Share',
632
-				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
633
-				ILogger::ERROR);
634
-		}
635
-		$items = [];
636
-		$targets = [];
637
-		$switchedItems = [];
638
-		$mounts = [];
639
-		while ($row = $result->fetchRow()) {
640
-			self::transformDBResults($row);
641
-			// Filter out duplicate group shares for users with unique targets
642
-			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
643
-				continue;
644
-			}
645
-			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
646
-				$row['share_type'] = IShare::TYPE_GROUP;
647
-				$row['unique_name'] = true; // remember that we use a unique name for this user
648
-				$row['share_with'] = $items[$row['parent']]['share_with'];
649
-				// if the group share was unshared from the user we keep the permission, otherwise
650
-				// we take the permission from the parent because this is always the up-to-date
651
-				// permission for the group share
652
-				if ($row['permissions'] > 0) {
653
-					$row['permissions'] = $items[$row['parent']]['permissions'];
654
-				}
655
-				// Remove the parent group share
656
-				unset($items[$row['parent']]);
657
-				if ($row['permissions'] == 0) {
658
-					continue;
659
-				}
660
-			} elseif (!isset($uidOwner)) {
661
-				// Check if the same target already exists
662
-				if (isset($targets[$row['id']])) {
663
-					// Check if the same owner shared with the user twice
664
-					// through a group and user share - this is allowed
665
-					$id = $targets[$row['id']];
666
-					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
667
-						// Switch to group share type to ensure resharing conditions aren't bypassed
668
-						if ($items[$id]['share_type'] != IShare::TYPE_GROUP) {
669
-							$items[$id]['share_type'] = IShare::TYPE_GROUP;
670
-							$items[$id]['share_with'] = $row['share_with'];
671
-						}
672
-						// Switch ids if sharing permission is granted on only
673
-						// one share to ensure correct parent is used if resharing
674
-						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
675
-							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
676
-							$items[$row['id']] = $items[$id];
677
-							$switchedItems[$id] = $row['id'];
678
-							unset($items[$id]);
679
-							$id = $row['id'];
680
-						}
681
-						$items[$id]['permissions'] |= (int)$row['permissions'];
682
-					}
683
-					continue;
684
-				} elseif (!empty($row['parent'])) {
685
-					$targets[$row['parent']] = $row['id'];
686
-				}
687
-			}
688
-			// Remove root from file source paths if retrieving own shared items
689
-			if (isset($uidOwner) && isset($row['path'])) {
690
-				if (isset($row['parent'])) {
691
-					$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
692
-					$query->select('file_target')
693
-						->from('share')
694
-						->where($query->expr()->eq('id', $query->createNamedParameter($row['parent'])));
695
-
696
-					$parentResult = $query->execute();
697
-					$parentRow = $parentResult->fetch();
698
-					$parentResult->closeCursor();
699
-
700
-					if ($parentRow === false) {
701
-						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
702
-							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
703
-							ILogger::ERROR);
704
-					} else {
705
-						$tmpPath = $parentRow['file_target'];
706
-						// find the right position where the row path continues from the target path
707
-						$pos = strrpos($row['path'], $parentRow['file_target']);
708
-						$subPath = substr($row['path'], $pos);
709
-						$splitPath = explode('/', $subPath);
710
-						foreach (array_slice($splitPath, 2) as $pathPart) {
711
-							$tmpPath = $tmpPath . '/' . $pathPart;
712
-						}
713
-						$row['path'] = $tmpPath;
714
-					}
715
-				} else {
716
-					if (!isset($mounts[$row['storage']])) {
717
-						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
718
-						if (is_array($mountPoints) && !empty($mountPoints)) {
719
-							$mounts[$row['storage']] = current($mountPoints);
720
-						}
721
-					}
722
-					if (!empty($mounts[$row['storage']])) {
723
-						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
724
-						$relPath = substr($path, $root); // path relative to data/user
725
-						$row['path'] = rtrim($relPath, '/');
726
-					}
727
-				}
728
-			}
729
-
730
-			if ($checkExpireDate) {
731
-				if (self::expireItem($row)) {
732
-					continue;
733
-				}
734
-			}
735
-			// Check if resharing is allowed, if not remove share permission
736
-			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
737
-				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
738
-			}
739
-			// Add display names to result
740
-			$row['share_with_displayname'] = $row['share_with'];
741
-			if (isset($row['share_with']) && $row['share_with'] != '' &&
742
-				$row['share_type'] === IShare::TYPE_USER) {
743
-				$shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
744
-				$row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
745
-			} elseif (isset($row['share_with']) && $row['share_with'] != '' &&
746
-				$row['share_type'] === IShare::TYPE_REMOTE) {
747
-				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
748
-				foreach ($addressBookEntries as $entry) {
749
-					foreach ($entry['CLOUD'] as $cloudID) {
750
-						if ($cloudID === $row['share_with']) {
751
-							$row['share_with_displayname'] = $entry['FN'];
752
-						}
753
-					}
754
-				}
755
-			}
756
-			if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
757
-				$ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
758
-				$row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
759
-			}
760
-
761
-			if ($row['permissions'] > 0) {
762
-				$items[$row['id']] = $row;
763
-			}
764
-		}
765
-
766
-		// group items if we are looking for items shared with the current user
767
-		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
768
-			$items = self::groupItems($items, $itemType);
769
-		}
770
-
771
-		if (!empty($items)) {
772
-			$collectionItems = [];
773
-			foreach ($items as &$row) {
774
-				// Return only the item instead of a 2-dimensional array
775
-				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
776
-					if ($format == self::FORMAT_NONE) {
777
-						return $row;
778
-					} else {
779
-						break;
780
-					}
781
-				}
782
-				// Check if this is a collection of the requested item type
783
-				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
784
-					if (($collectionBackend = self::getBackend($row['item_type']))
785
-						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
786
-						// Collections can be inside collections, check if the item is a collection
787
-						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
788
-							$collectionItems[] = $row;
789
-						} else {
790
-							$collection = [];
791
-							$collection['item_type'] = $row['item_type'];
792
-							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
793
-								$collection['path'] = basename($row['path']);
794
-							}
795
-							$row['collection'] = $collection;
796
-							// Fetch all of the children sources
797
-							$children = $collectionBackend->getChildren($row[$column]);
798
-							foreach ($children as $child) {
799
-								$childItem = $row;
800
-								$childItem['item_type'] = $itemType;
801
-								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
802
-									$childItem['item_source'] = $child['source'];
803
-									$childItem['item_target'] = $child['target'];
804
-								}
805
-								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
806
-									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
807
-										$childItem['file_source'] = $child['source'];
808
-									} else { // TODO is this really needed if we already know that we use the file backend?
809
-										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
810
-										$childItem['file_source'] = $meta['fileid'];
811
-									}
812
-									$childItem['file_target'] =
813
-										\OC\Files\Filesystem::normalizePath($child['file_path']);
814
-								}
815
-								if (isset($item)) {
816
-									if ($childItem[$column] == $item) {
817
-										// Return only the item instead of a 2-dimensional array
818
-										if ($limit == 1) {
819
-											if ($format == self::FORMAT_NONE) {
820
-												return $childItem;
821
-											} else {
822
-												// Unset the items array and break out of both loops
823
-												$items = [];
824
-												$items[] = $childItem;
825
-												break 2;
826
-											}
827
-										} else {
828
-											$collectionItems[] = $childItem;
829
-										}
830
-									}
831
-								} else {
832
-									$collectionItems[] = $childItem;
833
-								}
834
-							}
835
-						}
836
-					}
837
-					// Remove collection item
838
-					$toRemove = $row['id'];
839
-					if (array_key_exists($toRemove, $switchedItems)) {
840
-						$toRemove = $switchedItems[$toRemove];
841
-					}
842
-					unset($items[$toRemove]);
843
-				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
844
-					// FIXME: Thats a dirty hack to improve file sharing performance,
845
-					// see github issue #10588 for more details
846
-					// Need to find a solution which works for all back-ends
847
-					$collectionBackend = self::getBackend($row['item_type']);
848
-					$sharedParents = $collectionBackend->getParents($row['item_source']);
849
-					foreach ($sharedParents as $parent) {
850
-						$collectionItems[] = $parent;
851
-					}
852
-				}
853
-			}
854
-			if (!empty($collectionItems)) {
855
-				$collectionItems = array_unique($collectionItems, SORT_REGULAR);
856
-				$items = array_merge($items, $collectionItems);
857
-			}
858
-
859
-			// filter out invalid items, these can appear when subshare entries exist
860
-			// for a group in which the requested user isn't a member any more
861
-			$items = array_filter($items, function ($item) {
862
-				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
863
-			});
864
-
865
-			return self::formatResult($items, $column, $backend, $format, $parameters);
866
-		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
867
-			// FIXME: Thats a dirty hack to improve file sharing performance,
868
-			// see github issue #10588 for more details
869
-			// Need to find a solution which works for all back-ends
870
-			$collectionItems = [];
871
-			$collectionBackend = self::getBackend('folder');
872
-			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
873
-			foreach ($sharedParents as $parent) {
874
-				$collectionItems[] = $parent;
875
-			}
876
-			if ($limit === 1) {
877
-				return reset($collectionItems);
878
-			}
879
-			return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
880
-		}
881
-
882
-		return [];
883
-	}
884
-
885
-	/**
886
-	 * group items with link to the same source
887
-	 *
888
-	 * @param array $items
889
-	 * @param string $itemType
890
-	 * @return array of grouped items
891
-	 */
892
-	protected static function groupItems($items, $itemType) {
893
-		$fileSharing = $itemType === 'file' || $itemType === 'folder';
894
-
895
-		$result = [];
896
-
897
-		foreach ($items as $item) {
898
-			$grouped = false;
899
-			foreach ($result as $key => $r) {
900
-				// for file/folder shares we need to compare file_source, otherwise we compare item_source
901
-				// only group shares if they already point to the same target, otherwise the file where shared
902
-				// before grouping of shares was added. In this case we don't group them toi avoid confusions
903
-				if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
904
-					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
905
-					// add the first item to the list of grouped shares
906
-					if (!isset($result[$key]['grouped'])) {
907
-						$result[$key]['grouped'][] = $result[$key];
908
-					}
909
-					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
910
-					$result[$key]['grouped'][] = $item;
911
-					$grouped = true;
912
-					break;
913
-				}
914
-			}
915
-
916
-			if (!$grouped) {
917
-				$result[] = $item;
918
-			}
919
-		}
920
-
921
-		return $result;
922
-	}
923
-
924
-	/**
925
-	 * construct select statement
926
-	 * @param int $format
927
-	 * @param boolean $fileDependent ist it a file/folder share or a generla share
928
-	 * @param string $uidOwner
929
-	 * @return string select statement
930
-	 */
931
-	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
932
-		$select = '*';
933
-		if ($format == self::FORMAT_STATUSES) {
934
-			if ($fileDependent) {
935
-				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
936
-					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
937
-					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
938
-					. '`uid_initiator`';
939
-			} else {
940
-				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
941
-			}
942
-		} else {
943
-			if (isset($uidOwner)) {
944
-				if ($fileDependent) {
945
-					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
946
-						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
947
-						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
948
-						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
949
-				} else {
950
-					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
951
-						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
952
-				}
953
-			} else {
954
-				if ($fileDependent) {
955
-					if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
956
-						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
957
-							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
958
-							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
959
-							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
960
-					} else {
961
-						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
962
-							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
963
-							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
964
-							. '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
965
-							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
966
-					}
967
-				}
968
-			}
969
-		}
970
-		return $select;
971
-	}
972
-
973
-
974
-	/**
975
-	 * transform db results
976
-	 * @param array $row result
977
-	 */
978
-	private static function transformDBResults(&$row) {
979
-		if (isset($row['id'])) {
980
-			$row['id'] = (int) $row['id'];
981
-		}
982
-		if (isset($row['share_type'])) {
983
-			$row['share_type'] = (int) $row['share_type'];
984
-		}
985
-		if (isset($row['parent'])) {
986
-			$row['parent'] = (int) $row['parent'];
987
-		}
988
-		if (isset($row['file_parent'])) {
989
-			$row['file_parent'] = (int) $row['file_parent'];
990
-		}
991
-		if (isset($row['file_source'])) {
992
-			$row['file_source'] = (int) $row['file_source'];
993
-		}
994
-		if (isset($row['permissions'])) {
995
-			$row['permissions'] = (int) $row['permissions'];
996
-		}
997
-		if (isset($row['storage'])) {
998
-			$row['storage'] = (int) $row['storage'];
999
-		}
1000
-		if (isset($row['stime'])) {
1001
-			$row['stime'] = (int) $row['stime'];
1002
-		}
1003
-		if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) {
1004
-			// discard expiration date for non-link shares, which might have been
1005
-			// set by ancient bugs
1006
-			$row['expiration'] = null;
1007
-		}
1008
-	}
1009
-
1010
-	/**
1011
-	 * format result
1012
-	 * @param array $items result
1013
-	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
1014
-	 * @param \OCP\Share_Backend $backend sharing backend
1015
-	 * @param int $format
1016
-	 * @param array $parameters additional format parameters
1017
-	 * @return array format result
1018
-	 */
1019
-	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
1020
-		if ($format === self::FORMAT_NONE) {
1021
-			return $items;
1022
-		} elseif ($format === self::FORMAT_STATUSES) {
1023
-			$statuses = [];
1024
-			foreach ($items as $item) {
1025
-				if ($item['share_type'] === IShare::TYPE_LINK) {
1026
-					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
1027
-						continue;
1028
-					}
1029
-					$statuses[$item[$column]]['link'] = true;
1030
-				} elseif (!isset($statuses[$item[$column]])) {
1031
-					$statuses[$item[$column]]['link'] = false;
1032
-				}
1033
-				if (!empty($item['file_target'])) {
1034
-					$statuses[$item[$column]]['path'] = $item['path'];
1035
-				}
1036
-			}
1037
-			return $statuses;
1038
-		} else {
1039
-			return $backend->formatItems($items, $format, $parameters);
1040
-		}
1041
-	}
1042
-
1043
-	/**
1044
-	 * remove protocol from URL
1045
-	 *
1046
-	 * @param string $url
1047
-	 * @return string
1048
-	 */
1049
-	public static function removeProtocolFromUrl($url) {
1050
-		if (strpos($url, 'https://') === 0) {
1051
-			return substr($url, strlen('https://'));
1052
-		} elseif (strpos($url, 'http://') === 0) {
1053
-			return substr($url, strlen('http://'));
1054
-		}
1055
-
1056
-		return $url;
1057
-	}
1058
-
1059
-	/**
1060
-	 * try http post first with https and then with http as a fallback
1061
-	 *
1062
-	 * @param string $remoteDomain
1063
-	 * @param string $urlSuffix
1064
-	 * @param array $fields post parameters
1065
-	 * @return array
1066
-	 */
1067
-	private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
1068
-		$protocol = 'https://';
1069
-		$result = [
1070
-			'success' => false,
1071
-			'result' => '',
1072
-		];
1073
-		$try = 0;
1074
-		$discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
1075
-		while ($result['success'] === false && $try < 2) {
1076
-			$federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
1077
-			$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
1078
-			$client = \OC::$server->getHTTPClientService()->newClient();
1079
-
1080
-			try {
1081
-				$response = $client->post(
1082
-					$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
1083
-					[
1084
-						'body' => $fields,
1085
-						'connect_timeout' => 10,
1086
-					]
1087
-				);
1088
-
1089
-				$result = ['success' => true, 'result' => $response->getBody()];
1090
-			} catch (\Exception $e) {
1091
-				$result = ['success' => false, 'result' => $e->getMessage()];
1092
-			}
1093
-
1094
-			$try++;
1095
-			$protocol = 'http://';
1096
-		}
1097
-
1098
-		return $result;
1099
-	}
1100
-
1101
-	/**
1102
-	 * send server-to-server unshare to remote server
1103
-	 *
1104
-	 * @param string $remote url
1105
-	 * @param int $id share id
1106
-	 * @param string $token
1107
-	 * @return bool
1108
-	 */
1109
-	private static function sendRemoteUnshare($remote, $id, $token) {
1110
-		$url = rtrim($remote, '/');
1111
-		$fields = ['token' => $token, 'format' => 'json'];
1112
-		$url = self::removeProtocolFromUrl($url);
1113
-		$result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
1114
-		$status = json_decode($result['result'], true);
1115
-
1116
-		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
1117
-	}
1118
-
1119
-	/**
1120
-	 * @return int
1121
-	 */
1122
-	public static function getExpireInterval() {
1123
-		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1124
-	}
1125
-
1126
-	/**
1127
-	 * Checks whether the given path is reachable for the given owner
1128
-	 *
1129
-	 * @param string $path path relative to files
1130
-	 * @param string $ownerStorageId storage id of the owner
1131
-	 *
1132
-	 * @return boolean true if file is reachable, false otherwise
1133
-	 */
1134
-	private static function isFileReachable($path, $ownerStorageId) {
1135
-		// if outside the home storage, file is always considered reachable
1136
-		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
1137
-			substr($ownerStorageId, 0, 13) === 'object::user:'
1138
-		)) {
1139
-			return true;
1140
-		}
1141
-
1142
-		// if inside the home storage, the file has to be under "/files/"
1143
-		$path = ltrim($path, '/');
1144
-		if (substr($path, 0, 6) === 'files/') {
1145
-			return true;
1146
-		}
1147
-
1148
-		return false;
1149
-	}
53
+    /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
54
+     * Construct permissions for share() and setPermissions with Or (|) e.g.
55
+     * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
56
+     *
57
+     * Check if permission is granted with And (&) e.g. Check if delete is
58
+     * granted: if ($permissions & PERMISSION_DELETE)
59
+     *
60
+     * Remove permissions with And (&) and Not (~) e.g. Remove the update
61
+     * permission: $permissions &= ~PERMISSION_UPDATE
62
+     *
63
+     * Apps are required to handle permissions on their own, this class only
64
+     * stores and manages the permissions of shares
65
+     * @see lib/public/constants.php
66
+     */
67
+
68
+    /**
69
+     * Register a sharing backend class that implements OCP\Share_Backend for an item type
70
+     * @param string $itemType Item type
71
+     * @param string $class Backend class
72
+     * @param string $collectionOf (optional) Depends on item type
73
+     * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
74
+     * @return boolean true if backend is registered or false if error
75
+     */
76
+    public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
77
+        if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
78
+            if (!isset(self::$backendTypes[$itemType])) {
79
+                self::$backendTypes[$itemType] = [
80
+                    'class' => $class,
81
+                    'collectionOf' => $collectionOf,
82
+                    'supportedFileExtensions' => $supportedFileExtensions
83
+                ];
84
+                return true;
85
+            }
86
+            \OCP\Util::writeLog('OCP\Share',
87
+                'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
88
+                .' is already registered for '.$itemType,
89
+                ILogger::WARN);
90
+        }
91
+        return false;
92
+    }
93
+
94
+    /**
95
+     * Get the items of item type shared with the current user
96
+     * @param string $itemType
97
+     * @param int $format (optional) Format type must be defined by the backend
98
+     * @param mixed $parameters (optional)
99
+     * @param int $limit Number of items to return (optional) Returns all by default
100
+     * @param boolean $includeCollections (optional)
101
+     * @return mixed Return depends on format
102
+     * @deprecated TESTS ONLY - this methods is only used by tests
103
+     * called like this:
104
+     * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php)
105
+     */
106
+    public static function getItemsSharedWith() {
107
+        return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, self::FORMAT_NONE,
108
+            null, -1, false);
109
+    }
110
+
111
+    /**
112
+     * Get the items of item type shared with a user
113
+     * @param string $itemType
114
+     * @param string $user id for which user we want the shares
115
+     * @param int $format (optional) Format type must be defined by the backend
116
+     * @param mixed $parameters (optional)
117
+     * @param int $limit Number of items to return (optional) Returns all by default
118
+     * @param boolean $includeCollections (optional)
119
+     * @return mixed Return depends on format
120
+     */
121
+    public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
122
+                                                    $parameters = null, $limit = -1, $includeCollections = false) {
123
+        return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
124
+            $parameters, $limit, $includeCollections);
125
+    }
126
+
127
+    /**
128
+     * Get the item of item type shared with a given user by source
129
+     * @param string $itemType
130
+     * @param string $itemSource
131
+     * @param string $user User to whom the item was shared
132
+     * @param string $owner Owner of the share
133
+     * @param int $shareType only look for a specific share type
134
+     * @return array Return list of items with file_target, permissions and expiration
135
+     */
136
+    public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
137
+        $shares = [];
138
+        $fileDependent = false;
139
+
140
+        $where = 'WHERE';
141
+        $fileDependentWhere = '';
142
+        if ($itemType === 'file' || $itemType === 'folder') {
143
+            $fileDependent = true;
144
+            $column = 'file_source';
145
+            $fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
146
+            $fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
147
+        } else {
148
+            $column = 'item_source';
149
+        }
150
+
151
+        $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
152
+
153
+        $where .= ' `' . $column . '` = ? AND `item_type` = ? ';
154
+        $arguments = [$itemSource, $itemType];
155
+        // for link shares $user === null
156
+        if ($user !== null) {
157
+            $where .= ' AND `share_with` = ? ';
158
+            $arguments[] = $user;
159
+        }
160
+
161
+        if ($shareType !== null) {
162
+            $where .= ' AND `share_type` = ? ';
163
+            $arguments[] = $shareType;
164
+        }
165
+
166
+        if ($owner !== null) {
167
+            $where .= ' AND `uid_owner` = ? ';
168
+            $arguments[] = $owner;
169
+        }
170
+
171
+        $query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
172
+
173
+        $result = \OC_DB::executeAudited($query, $arguments);
174
+
175
+        while ($row = $result->fetchRow()) {
176
+            if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
177
+                continue;
178
+            }
179
+            if ($fileDependent && (int)$row['file_parent'] === -1) {
180
+                // if it is a mount point we need to get the path from the mount manager
181
+                $mountManager = \OC\Files\Filesystem::getMountManager();
182
+                $mountPoint = $mountManager->findByStorageId($row['storage_id']);
183
+                if (!empty($mountPoint)) {
184
+                    $path = $mountPoint[0]->getMountPoint();
185
+                    $path = trim($path, '/');
186
+                    $path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
187
+                    $row['path'] = $path;
188
+                } else {
189
+                    \OC::$server->getLogger()->warning(
190
+                        'Could not resolve mount point for ' . $row['storage_id'],
191
+                        ['app' => 'OCP\Share']
192
+                    );
193
+                }
194
+            }
195
+            $shares[] = $row;
196
+        }
197
+        $result->closeCursor();
198
+
199
+        //if didn't found a result than let's look for a group share.
200
+        if (empty($shares) && $user !== null) {
201
+            $userObject = \OC::$server->getUserManager()->get($user);
202
+            $groups = [];
203
+            if ($userObject) {
204
+                $groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
205
+            }
206
+
207
+            if (!empty($groups)) {
208
+                $where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
209
+                $arguments = [$itemSource, $itemType, $groups];
210
+                $types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
211
+
212
+                if ($owner !== null) {
213
+                    $where .= ' AND `uid_owner` = ?';
214
+                    $arguments[] = $owner;
215
+                    $types[] = null;
216
+                }
217
+
218
+                // TODO: inject connection, hopefully one day in the future when this
219
+                // class isn't static anymore...
220
+                $conn = \OC::$server->getDatabaseConnection();
221
+                $result = $conn->executeQuery(
222
+                    'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
223
+                    $arguments,
224
+                    $types
225
+                );
226
+
227
+                while ($row = $result->fetch()) {
228
+                    $shares[] = $row;
229
+                }
230
+            }
231
+        }
232
+
233
+        return $shares;
234
+    }
235
+
236
+    /**
237
+     * Get the item of item type shared with the current user by source
238
+     * @param string $itemType
239
+     * @param string $itemSource
240
+     * @param int $format (optional) Format type must be defined by the backend
241
+     * @param mixed $parameters
242
+     * @param boolean $includeCollections
243
+     * @param string $shareWith (optional) define against which user should be checked, default: current user
244
+     * @return array
245
+     */
246
+    public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
247
+                                                        $parameters = null, $includeCollections = false, $shareWith = null) {
248
+        $shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
249
+        return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
250
+            $parameters, 1, $includeCollections, true);
251
+    }
252
+
253
+    /**
254
+     * Get the shared item of item type owned by the current user
255
+     * @param string $itemType
256
+     * @param string $itemSource
257
+     * @param int $format (optional) Format type must be defined by the backend
258
+     * @param mixed $parameters
259
+     * @param boolean $includeCollections
260
+     * @return mixed Return depends on format
261
+     */
262
+    public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
263
+                                            $parameters = null, $includeCollections = false) {
264
+        return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
265
+            $parameters, -1, $includeCollections);
266
+    }
267
+
268
+    /**
269
+     * Unshare an item from a user, group, or delete a private link
270
+     * @param string $itemType
271
+     * @param string $itemSource
272
+     * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
273
+     * @param string $shareWith User or group the item is being shared with
274
+     * @param string $owner owner of the share, if null the current user is used
275
+     * @return boolean true on success or false on failure
276
+     */
277
+    public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
278
+
279
+        // check if it is a valid itemType
280
+        self::getBackend($itemType);
281
+
282
+        $items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
283
+
284
+        $toDelete = [];
285
+        $newParent = null;
286
+        $currentUser = $owner ? $owner : \OC_User::getUser();
287
+        foreach ($items as $item) {
288
+            // delete the item with the expected share_type and owner
289
+            if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
290
+                $toDelete = $item;
291
+            // if there is more then one result we don't have to delete the children
292
+                // but update their parent. For group shares the new parent should always be
293
+                // the original group share and not the db entry with the unique name
294
+            } elseif ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
295
+                $newParent = $item['parent'];
296
+            } else {
297
+                $newParent = $item['id'];
298
+            }
299
+        }
300
+
301
+        if (!empty($toDelete)) {
302
+            self::unshareItem($toDelete, $newParent);
303
+            return true;
304
+        }
305
+        return false;
306
+    }
307
+
308
+    /**
309
+     * Checks whether a share has expired, calls unshareItem() if yes.
310
+     * @param array $item Share data (usually database row)
311
+     * @return boolean True if item was expired, false otherwise.
312
+     */
313
+    protected static function expireItem(array $item) {
314
+        $result = false;
315
+
316
+        // only use default expiration date for link shares
317
+        if ((int) $item['share_type'] === IShare::TYPE_LINK) {
318
+
319
+            // calculate expiration date
320
+            if (!empty($item['expiration'])) {
321
+                $userDefinedExpire = new \DateTime($item['expiration']);
322
+                $expires = $userDefinedExpire->getTimestamp();
323
+            } else {
324
+                $expires = null;
325
+            }
326
+
327
+
328
+            // get default expiration settings
329
+            $defaultSettings = Helper::getDefaultExpireSetting();
330
+            $expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
331
+
332
+
333
+            if (is_int($expires)) {
334
+                $now = time();
335
+                if ($now > $expires) {
336
+                    self::unshareItem($item);
337
+                    $result = true;
338
+                }
339
+            }
340
+        }
341
+        return $result;
342
+    }
343
+
344
+    /**
345
+     * Unshares a share given a share data array
346
+     * @param array $item Share data (usually database row)
347
+     * @param int $newParent parent ID
348
+     * @return null
349
+     */
350
+    protected static function unshareItem(array $item, $newParent = null) {
351
+        $shareType = (int)$item['share_type'];
352
+        $shareWith = null;
353
+        if ($shareType !== IShare::TYPE_LINK) {
354
+            $shareWith = $item['share_with'];
355
+        }
356
+
357
+        // Pass all the vars we have for now, they may be useful
358
+        $hookParams = [
359
+            'id' => $item['id'],
360
+            'itemType' => $item['item_type'],
361
+            'itemSource' => $item['item_source'],
362
+            'shareType' => $shareType,
363
+            'shareWith' => $shareWith,
364
+            'itemParent' => $item['parent'],
365
+            'uidOwner' => $item['uid_owner'],
366
+        ];
367
+        if ($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
368
+            $hookParams['fileSource'] = $item['file_source'];
369
+            $hookParams['fileTarget'] = $item['file_target'];
370
+        }
371
+
372
+        \OC_Hook::emit(\OCP\Share::class, 'pre_unshare', $hookParams);
373
+        $deletedShares = Helper::delete($item['id'], false, null, $newParent);
374
+        $deletedShares[] = $hookParams;
375
+        $hookParams['deletedShares'] = $deletedShares;
376
+        \OC_Hook::emit(\OCP\Share::class, 'post_unshare', $hookParams);
377
+        if ((int)$item['share_type'] === IShare::TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
378
+            list(, $remote) = Helper::splitUserRemote($item['share_with']);
379
+            self::sendRemoteUnshare($remote, $item['id'], $item['token']);
380
+        }
381
+    }
382
+
383
+    /**
384
+     * Get the backend class for the specified item type
385
+     * @param string $itemType
386
+     * @throws \Exception
387
+     * @return \OCP\Share_Backend
388
+     */
389
+    public static function getBackend($itemType) {
390
+        $l = \OC::$server->getL10N('lib');
391
+        if (isset(self::$backends[$itemType])) {
392
+            return self::$backends[$itemType];
393
+        } elseif (isset(self::$backendTypes[$itemType]['class'])) {
394
+            $class = self::$backendTypes[$itemType]['class'];
395
+            if (class_exists($class)) {
396
+                self::$backends[$itemType] = new $class;
397
+                if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
398
+                    $message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
399
+                    $message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
400
+                    \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
401
+                    throw new \Exception($message_t);
402
+                }
403
+                return self::$backends[$itemType];
404
+            } else {
405
+                $message = 'Sharing backend %s not found';
406
+                $message_t = $l->t('Sharing backend %s not found', [$class]);
407
+                \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
408
+                throw new \Exception($message_t);
409
+            }
410
+        }
411
+        $message = 'Sharing backend for %s not found';
412
+        $message_t = $l->t('Sharing backend for %s not found', [$itemType]);
413
+        \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
414
+        throw new \Exception($message_t);
415
+    }
416
+
417
+    /**
418
+     * Check if resharing is allowed
419
+     * @return boolean true if allowed or false
420
+     *
421
+     * Resharing is allowed by default if not configured
422
+     */
423
+    public static function isResharingAllowed() {
424
+        if (!isset(self::$isResharingAllowed)) {
425
+            if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
426
+                self::$isResharingAllowed = true;
427
+            } else {
428
+                self::$isResharingAllowed = false;
429
+            }
430
+        }
431
+        return self::$isResharingAllowed;
432
+    }
433
+
434
+    /**
435
+     * Get a list of collection item types for the specified item type
436
+     * @param string $itemType
437
+     * @return array
438
+     */
439
+    private static function getCollectionItemTypes($itemType) {
440
+        $collectionTypes = [$itemType];
441
+        foreach (self::$backendTypes as $type => $backend) {
442
+            if (in_array($backend['collectionOf'], $collectionTypes)) {
443
+                $collectionTypes[] = $type;
444
+            }
445
+        }
446
+        // TODO Add option for collections to be collection of themselves, only 'folder' does it now...
447
+        if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
448
+            unset($collectionTypes[0]);
449
+        }
450
+        // Return array if collections were found or the item type is a
451
+        // collection itself - collections can be inside collections
452
+        if (count($collectionTypes) > 0) {
453
+            return $collectionTypes;
454
+        }
455
+        return false;
456
+    }
457
+
458
+    /**
459
+     * Get shared items from the database
460
+     * @param string $itemType
461
+     * @param string $item Item source or target (optional)
462
+     * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
463
+     * @param string $shareWith User or group the item is being shared with
464
+     * @param string $uidOwner User that is the owner of shared items (optional)
465
+     * @param int $format Format to convert items to with formatItems() (optional)
466
+     * @param mixed $parameters to pass to formatItems() (optional)
467
+     * @param int $limit Number of items to return, -1 to return all matches (optional)
468
+     * @param boolean $includeCollections Include collection item types (optional)
469
+     * @param boolean $itemShareWithBySource (optional)
470
+     * @param boolean $checkExpireDate
471
+     * @return array
472
+     *
473
+     * See public functions getItem(s)... for parameter usage
474
+     *
475
+     */
476
+    public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
477
+                                    $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
478
+                                    $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
479
+        if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
480
+            return [];
481
+        }
482
+        $backend = self::getBackend($itemType);
483
+        $collectionTypes = false;
484
+        // Get filesystem root to add it to the file target and remove from the
485
+        // file source, match file_source with the file cache
486
+        if ($itemType == 'file' || $itemType == 'folder') {
487
+            if (!is_null($uidOwner)) {
488
+                $root = \OC\Files\Filesystem::getRoot();
489
+            } else {
490
+                $root = '';
491
+            }
492
+            $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
493
+            if (!isset($item)) {
494
+                $where .= ' AND `file_target` IS NOT NULL ';
495
+            }
496
+            $where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
497
+            $fileDependent = true;
498
+            $queryArgs = [];
499
+        } else {
500
+            $fileDependent = false;
501
+            $root = '';
502
+            $collectionTypes = self::getCollectionItemTypes($itemType);
503
+            if ($includeCollections && !isset($item) && $collectionTypes) {
504
+                // If includeCollections is true, find collections of this item type, e.g. a music album contains songs
505
+                if (!in_array($itemType, $collectionTypes)) {
506
+                    $itemTypes = array_merge([$itemType], $collectionTypes);
507
+                } else {
508
+                    $itemTypes = $collectionTypes;
509
+                }
510
+                $placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
511
+                $where = ' WHERE `item_type` IN ('.$placeholders.'))';
512
+                $queryArgs = $itemTypes;
513
+            } else {
514
+                $where = ' WHERE `item_type` = ?';
515
+                $queryArgs = [$itemType];
516
+            }
517
+        }
518
+        if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
519
+            $where .= ' AND `share_type` != ?';
520
+            $queryArgs[] = IShare::TYPE_LINK;
521
+        }
522
+        if (isset($shareType)) {
523
+            // Include all user and group items
524
+            if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
525
+                $where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
526
+                $queryArgs[] = IShare::TYPE_USER;
527
+                $queryArgs[] = self::$shareTypeGroupUserUnique;
528
+                $queryArgs[] = $shareWith;
529
+
530
+                $user = \OC::$server->getUserManager()->get($shareWith);
531
+                $groups = [];
532
+                if ($user) {
533
+                    $groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
534
+                }
535
+                if (!empty($groups)) {
536
+                    $placeholders = implode(',', array_fill(0, count($groups), '?'));
537
+                    $where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
538
+                    $queryArgs[] = IShare::TYPE_GROUP;
539
+                    $queryArgs = array_merge($queryArgs, $groups);
540
+                }
541
+                $where .= ')';
542
+                // Don't include own group shares
543
+                $where .= ' AND `uid_owner` != ?';
544
+                $queryArgs[] = $shareWith;
545
+            } else {
546
+                $where .= ' AND `share_type` = ?';
547
+                $queryArgs[] = $shareType;
548
+                if (isset($shareWith)) {
549
+                    $where .= ' AND `share_with` = ?';
550
+                    $queryArgs[] = $shareWith;
551
+                }
552
+            }
553
+        }
554
+        if (isset($uidOwner)) {
555
+            $where .= ' AND `uid_owner` = ?';
556
+            $queryArgs[] = $uidOwner;
557
+            if (!isset($shareType)) {
558
+                // Prevent unique user targets for group shares from being selected
559
+                $where .= ' AND `share_type` != ?';
560
+                $queryArgs[] = self::$shareTypeGroupUserUnique;
561
+            }
562
+            if ($fileDependent) {
563
+                $column = 'file_source';
564
+            } else {
565
+                $column = 'item_source';
566
+            }
567
+        } else {
568
+            if ($fileDependent) {
569
+                $column = 'file_target';
570
+            } else {
571
+                $column = 'item_target';
572
+            }
573
+        }
574
+        if (isset($item)) {
575
+            $collectionTypes = self::getCollectionItemTypes($itemType);
576
+            if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
577
+                $where .= ' AND (';
578
+            } else {
579
+                $where .= ' AND';
580
+            }
581
+            // If looking for own shared items, check item_source else check item_target
582
+            if (isset($uidOwner) || $itemShareWithBySource) {
583
+                // If item type is a file, file source needs to be checked in case the item was converted
584
+                if ($fileDependent) {
585
+                    $where .= ' `file_source` = ?';
586
+                    $column = 'file_source';
587
+                } else {
588
+                    $where .= ' `item_source` = ?';
589
+                    $column = 'item_source';
590
+                }
591
+            } else {
592
+                if ($fileDependent) {
593
+                    $where .= ' `file_target` = ?';
594
+                    $item = \OC\Files\Filesystem::normalizePath($item);
595
+                } else {
596
+                    $where .= ' `item_target` = ?';
597
+                }
598
+            }
599
+            $queryArgs[] = $item;
600
+            if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
601
+                $placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
602
+                $where .= ' OR `item_type` IN ('.$placeholders.'))';
603
+                $queryArgs = array_merge($queryArgs, $collectionTypes);
604
+            }
605
+        }
606
+
607
+        if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
608
+            // Make sure the unique user target is returned if it exists,
609
+            // unique targets should follow the group share in the database
610
+            // If the limit is not 1, the filtering can be done later
611
+            $where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
612
+        } else {
613
+            $where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
614
+        }
615
+
616
+        if ($limit != -1 && !$includeCollections) {
617
+            // The limit must be at least 3, because filtering needs to be done
618
+            if ($limit < 3) {
619
+                $queryLimit = 3;
620
+            } else {
621
+                $queryLimit = $limit;
622
+            }
623
+        } else {
624
+            $queryLimit = null;
625
+        }
626
+        $select = self::createSelectStatement($format, $fileDependent, $uidOwner);
627
+        $root = strlen($root);
628
+        $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
629
+        $result = $query->execute($queryArgs);
630
+        if ($result === false) {
631
+            \OCP\Util::writeLog('OCP\Share',
632
+                \OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
633
+                ILogger::ERROR);
634
+        }
635
+        $items = [];
636
+        $targets = [];
637
+        $switchedItems = [];
638
+        $mounts = [];
639
+        while ($row = $result->fetchRow()) {
640
+            self::transformDBResults($row);
641
+            // Filter out duplicate group shares for users with unique targets
642
+            if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
643
+                continue;
644
+            }
645
+            if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
646
+                $row['share_type'] = IShare::TYPE_GROUP;
647
+                $row['unique_name'] = true; // remember that we use a unique name for this user
648
+                $row['share_with'] = $items[$row['parent']]['share_with'];
649
+                // if the group share was unshared from the user we keep the permission, otherwise
650
+                // we take the permission from the parent because this is always the up-to-date
651
+                // permission for the group share
652
+                if ($row['permissions'] > 0) {
653
+                    $row['permissions'] = $items[$row['parent']]['permissions'];
654
+                }
655
+                // Remove the parent group share
656
+                unset($items[$row['parent']]);
657
+                if ($row['permissions'] == 0) {
658
+                    continue;
659
+                }
660
+            } elseif (!isset($uidOwner)) {
661
+                // Check if the same target already exists
662
+                if (isset($targets[$row['id']])) {
663
+                    // Check if the same owner shared with the user twice
664
+                    // through a group and user share - this is allowed
665
+                    $id = $targets[$row['id']];
666
+                    if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
667
+                        // Switch to group share type to ensure resharing conditions aren't bypassed
668
+                        if ($items[$id]['share_type'] != IShare::TYPE_GROUP) {
669
+                            $items[$id]['share_type'] = IShare::TYPE_GROUP;
670
+                            $items[$id]['share_with'] = $row['share_with'];
671
+                        }
672
+                        // Switch ids if sharing permission is granted on only
673
+                        // one share to ensure correct parent is used if resharing
674
+                        if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
675
+                            && (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
676
+                            $items[$row['id']] = $items[$id];
677
+                            $switchedItems[$id] = $row['id'];
678
+                            unset($items[$id]);
679
+                            $id = $row['id'];
680
+                        }
681
+                        $items[$id]['permissions'] |= (int)$row['permissions'];
682
+                    }
683
+                    continue;
684
+                } elseif (!empty($row['parent'])) {
685
+                    $targets[$row['parent']] = $row['id'];
686
+                }
687
+            }
688
+            // Remove root from file source paths if retrieving own shared items
689
+            if (isset($uidOwner) && isset($row['path'])) {
690
+                if (isset($row['parent'])) {
691
+                    $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
692
+                    $query->select('file_target')
693
+                        ->from('share')
694
+                        ->where($query->expr()->eq('id', $query->createNamedParameter($row['parent'])));
695
+
696
+                    $parentResult = $query->execute();
697
+                    $parentRow = $parentResult->fetch();
698
+                    $parentResult->closeCursor();
699
+
700
+                    if ($parentRow === false) {
701
+                        \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
702
+                            \OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
703
+                            ILogger::ERROR);
704
+                    } else {
705
+                        $tmpPath = $parentRow['file_target'];
706
+                        // find the right position where the row path continues from the target path
707
+                        $pos = strrpos($row['path'], $parentRow['file_target']);
708
+                        $subPath = substr($row['path'], $pos);
709
+                        $splitPath = explode('/', $subPath);
710
+                        foreach (array_slice($splitPath, 2) as $pathPart) {
711
+                            $tmpPath = $tmpPath . '/' . $pathPart;
712
+                        }
713
+                        $row['path'] = $tmpPath;
714
+                    }
715
+                } else {
716
+                    if (!isset($mounts[$row['storage']])) {
717
+                        $mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
718
+                        if (is_array($mountPoints) && !empty($mountPoints)) {
719
+                            $mounts[$row['storage']] = current($mountPoints);
720
+                        }
721
+                    }
722
+                    if (!empty($mounts[$row['storage']])) {
723
+                        $path = $mounts[$row['storage']]->getMountPoint().$row['path'];
724
+                        $relPath = substr($path, $root); // path relative to data/user
725
+                        $row['path'] = rtrim($relPath, '/');
726
+                    }
727
+                }
728
+            }
729
+
730
+            if ($checkExpireDate) {
731
+                if (self::expireItem($row)) {
732
+                    continue;
733
+                }
734
+            }
735
+            // Check if resharing is allowed, if not remove share permission
736
+            if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
737
+                $row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
738
+            }
739
+            // Add display names to result
740
+            $row['share_with_displayname'] = $row['share_with'];
741
+            if (isset($row['share_with']) && $row['share_with'] != '' &&
742
+                $row['share_type'] === IShare::TYPE_USER) {
743
+                $shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
744
+                $row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
745
+            } elseif (isset($row['share_with']) && $row['share_with'] != '' &&
746
+                $row['share_type'] === IShare::TYPE_REMOTE) {
747
+                $addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
748
+                foreach ($addressBookEntries as $entry) {
749
+                    foreach ($entry['CLOUD'] as $cloudID) {
750
+                        if ($cloudID === $row['share_with']) {
751
+                            $row['share_with_displayname'] = $entry['FN'];
752
+                        }
753
+                    }
754
+                }
755
+            }
756
+            if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
757
+                $ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
758
+                $row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
759
+            }
760
+
761
+            if ($row['permissions'] > 0) {
762
+                $items[$row['id']] = $row;
763
+            }
764
+        }
765
+
766
+        // group items if we are looking for items shared with the current user
767
+        if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
768
+            $items = self::groupItems($items, $itemType);
769
+        }
770
+
771
+        if (!empty($items)) {
772
+            $collectionItems = [];
773
+            foreach ($items as &$row) {
774
+                // Return only the item instead of a 2-dimensional array
775
+                if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
776
+                    if ($format == self::FORMAT_NONE) {
777
+                        return $row;
778
+                    } else {
779
+                        break;
780
+                    }
781
+                }
782
+                // Check if this is a collection of the requested item type
783
+                if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
784
+                    if (($collectionBackend = self::getBackend($row['item_type']))
785
+                        && $collectionBackend instanceof \OCP\Share_Backend_Collection) {
786
+                        // Collections can be inside collections, check if the item is a collection
787
+                        if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
788
+                            $collectionItems[] = $row;
789
+                        } else {
790
+                            $collection = [];
791
+                            $collection['item_type'] = $row['item_type'];
792
+                            if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
793
+                                $collection['path'] = basename($row['path']);
794
+                            }
795
+                            $row['collection'] = $collection;
796
+                            // Fetch all of the children sources
797
+                            $children = $collectionBackend->getChildren($row[$column]);
798
+                            foreach ($children as $child) {
799
+                                $childItem = $row;
800
+                                $childItem['item_type'] = $itemType;
801
+                                if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
802
+                                    $childItem['item_source'] = $child['source'];
803
+                                    $childItem['item_target'] = $child['target'];
804
+                                }
805
+                                if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
806
+                                    if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
807
+                                        $childItem['file_source'] = $child['source'];
808
+                                    } else { // TODO is this really needed if we already know that we use the file backend?
809
+                                        $meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
810
+                                        $childItem['file_source'] = $meta['fileid'];
811
+                                    }
812
+                                    $childItem['file_target'] =
813
+                                        \OC\Files\Filesystem::normalizePath($child['file_path']);
814
+                                }
815
+                                if (isset($item)) {
816
+                                    if ($childItem[$column] == $item) {
817
+                                        // Return only the item instead of a 2-dimensional array
818
+                                        if ($limit == 1) {
819
+                                            if ($format == self::FORMAT_NONE) {
820
+                                                return $childItem;
821
+                                            } else {
822
+                                                // Unset the items array and break out of both loops
823
+                                                $items = [];
824
+                                                $items[] = $childItem;
825
+                                                break 2;
826
+                                            }
827
+                                        } else {
828
+                                            $collectionItems[] = $childItem;
829
+                                        }
830
+                                    }
831
+                                } else {
832
+                                    $collectionItems[] = $childItem;
833
+                                }
834
+                            }
835
+                        }
836
+                    }
837
+                    // Remove collection item
838
+                    $toRemove = $row['id'];
839
+                    if (array_key_exists($toRemove, $switchedItems)) {
840
+                        $toRemove = $switchedItems[$toRemove];
841
+                    }
842
+                    unset($items[$toRemove]);
843
+                } elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
844
+                    // FIXME: Thats a dirty hack to improve file sharing performance,
845
+                    // see github issue #10588 for more details
846
+                    // Need to find a solution which works for all back-ends
847
+                    $collectionBackend = self::getBackend($row['item_type']);
848
+                    $sharedParents = $collectionBackend->getParents($row['item_source']);
849
+                    foreach ($sharedParents as $parent) {
850
+                        $collectionItems[] = $parent;
851
+                    }
852
+                }
853
+            }
854
+            if (!empty($collectionItems)) {
855
+                $collectionItems = array_unique($collectionItems, SORT_REGULAR);
856
+                $items = array_merge($items, $collectionItems);
857
+            }
858
+
859
+            // filter out invalid items, these can appear when subshare entries exist
860
+            // for a group in which the requested user isn't a member any more
861
+            $items = array_filter($items, function ($item) {
862
+                return $item['share_type'] !== self::$shareTypeGroupUserUnique;
863
+            });
864
+
865
+            return self::formatResult($items, $column, $backend, $format, $parameters);
866
+        } elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
867
+            // FIXME: Thats a dirty hack to improve file sharing performance,
868
+            // see github issue #10588 for more details
869
+            // Need to find a solution which works for all back-ends
870
+            $collectionItems = [];
871
+            $collectionBackend = self::getBackend('folder');
872
+            $sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
873
+            foreach ($sharedParents as $parent) {
874
+                $collectionItems[] = $parent;
875
+            }
876
+            if ($limit === 1) {
877
+                return reset($collectionItems);
878
+            }
879
+            return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
880
+        }
881
+
882
+        return [];
883
+    }
884
+
885
+    /**
886
+     * group items with link to the same source
887
+     *
888
+     * @param array $items
889
+     * @param string $itemType
890
+     * @return array of grouped items
891
+     */
892
+    protected static function groupItems($items, $itemType) {
893
+        $fileSharing = $itemType === 'file' || $itemType === 'folder';
894
+
895
+        $result = [];
896
+
897
+        foreach ($items as $item) {
898
+            $grouped = false;
899
+            foreach ($result as $key => $r) {
900
+                // for file/folder shares we need to compare file_source, otherwise we compare item_source
901
+                // only group shares if they already point to the same target, otherwise the file where shared
902
+                // before grouping of shares was added. In this case we don't group them toi avoid confusions
903
+                if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
904
+                    (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
905
+                    // add the first item to the list of grouped shares
906
+                    if (!isset($result[$key]['grouped'])) {
907
+                        $result[$key]['grouped'][] = $result[$key];
908
+                    }
909
+                    $result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
910
+                    $result[$key]['grouped'][] = $item;
911
+                    $grouped = true;
912
+                    break;
913
+                }
914
+            }
915
+
916
+            if (!$grouped) {
917
+                $result[] = $item;
918
+            }
919
+        }
920
+
921
+        return $result;
922
+    }
923
+
924
+    /**
925
+     * construct select statement
926
+     * @param int $format
927
+     * @param boolean $fileDependent ist it a file/folder share or a generla share
928
+     * @param string $uidOwner
929
+     * @return string select statement
930
+     */
931
+    private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
932
+        $select = '*';
933
+        if ($format == self::FORMAT_STATUSES) {
934
+            if ($fileDependent) {
935
+                $select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
936
+                    . '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
937
+                    . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
938
+                    . '`uid_initiator`';
939
+            } else {
940
+                $select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
941
+            }
942
+        } else {
943
+            if (isset($uidOwner)) {
944
+                if ($fileDependent) {
945
+                    $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
946
+                        . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
947
+                        . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
948
+                        . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
949
+                } else {
950
+                    $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
951
+                        . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
952
+                }
953
+            } else {
954
+                if ($fileDependent) {
955
+                    if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
956
+                        $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
957
+                            . '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
958
+                            . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
959
+                            . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
960
+                    } else {
961
+                        $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
962
+                            . '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
963
+                            . '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
964
+                            . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
965
+                            . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
966
+                    }
967
+                }
968
+            }
969
+        }
970
+        return $select;
971
+    }
972
+
973
+
974
+    /**
975
+     * transform db results
976
+     * @param array $row result
977
+     */
978
+    private static function transformDBResults(&$row) {
979
+        if (isset($row['id'])) {
980
+            $row['id'] = (int) $row['id'];
981
+        }
982
+        if (isset($row['share_type'])) {
983
+            $row['share_type'] = (int) $row['share_type'];
984
+        }
985
+        if (isset($row['parent'])) {
986
+            $row['parent'] = (int) $row['parent'];
987
+        }
988
+        if (isset($row['file_parent'])) {
989
+            $row['file_parent'] = (int) $row['file_parent'];
990
+        }
991
+        if (isset($row['file_source'])) {
992
+            $row['file_source'] = (int) $row['file_source'];
993
+        }
994
+        if (isset($row['permissions'])) {
995
+            $row['permissions'] = (int) $row['permissions'];
996
+        }
997
+        if (isset($row['storage'])) {
998
+            $row['storage'] = (int) $row['storage'];
999
+        }
1000
+        if (isset($row['stime'])) {
1001
+            $row['stime'] = (int) $row['stime'];
1002
+        }
1003
+        if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) {
1004
+            // discard expiration date for non-link shares, which might have been
1005
+            // set by ancient bugs
1006
+            $row['expiration'] = null;
1007
+        }
1008
+    }
1009
+
1010
+    /**
1011
+     * format result
1012
+     * @param array $items result
1013
+     * @param string $column is it a file share or a general share ('file_target' or 'item_target')
1014
+     * @param \OCP\Share_Backend $backend sharing backend
1015
+     * @param int $format
1016
+     * @param array $parameters additional format parameters
1017
+     * @return array format result
1018
+     */
1019
+    private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
1020
+        if ($format === self::FORMAT_NONE) {
1021
+            return $items;
1022
+        } elseif ($format === self::FORMAT_STATUSES) {
1023
+            $statuses = [];
1024
+            foreach ($items as $item) {
1025
+                if ($item['share_type'] === IShare::TYPE_LINK) {
1026
+                    if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
1027
+                        continue;
1028
+                    }
1029
+                    $statuses[$item[$column]]['link'] = true;
1030
+                } elseif (!isset($statuses[$item[$column]])) {
1031
+                    $statuses[$item[$column]]['link'] = false;
1032
+                }
1033
+                if (!empty($item['file_target'])) {
1034
+                    $statuses[$item[$column]]['path'] = $item['path'];
1035
+                }
1036
+            }
1037
+            return $statuses;
1038
+        } else {
1039
+            return $backend->formatItems($items, $format, $parameters);
1040
+        }
1041
+    }
1042
+
1043
+    /**
1044
+     * remove protocol from URL
1045
+     *
1046
+     * @param string $url
1047
+     * @return string
1048
+     */
1049
+    public static function removeProtocolFromUrl($url) {
1050
+        if (strpos($url, 'https://') === 0) {
1051
+            return substr($url, strlen('https://'));
1052
+        } elseif (strpos($url, 'http://') === 0) {
1053
+            return substr($url, strlen('http://'));
1054
+        }
1055
+
1056
+        return $url;
1057
+    }
1058
+
1059
+    /**
1060
+     * try http post first with https and then with http as a fallback
1061
+     *
1062
+     * @param string $remoteDomain
1063
+     * @param string $urlSuffix
1064
+     * @param array $fields post parameters
1065
+     * @return array
1066
+     */
1067
+    private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
1068
+        $protocol = 'https://';
1069
+        $result = [
1070
+            'success' => false,
1071
+            'result' => '',
1072
+        ];
1073
+        $try = 0;
1074
+        $discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
1075
+        while ($result['success'] === false && $try < 2) {
1076
+            $federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
1077
+            $endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
1078
+            $client = \OC::$server->getHTTPClientService()->newClient();
1079
+
1080
+            try {
1081
+                $response = $client->post(
1082
+                    $protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
1083
+                    [
1084
+                        'body' => $fields,
1085
+                        'connect_timeout' => 10,
1086
+                    ]
1087
+                );
1088
+
1089
+                $result = ['success' => true, 'result' => $response->getBody()];
1090
+            } catch (\Exception $e) {
1091
+                $result = ['success' => false, 'result' => $e->getMessage()];
1092
+            }
1093
+
1094
+            $try++;
1095
+            $protocol = 'http://';
1096
+        }
1097
+
1098
+        return $result;
1099
+    }
1100
+
1101
+    /**
1102
+     * send server-to-server unshare to remote server
1103
+     *
1104
+     * @param string $remote url
1105
+     * @param int $id share id
1106
+     * @param string $token
1107
+     * @return bool
1108
+     */
1109
+    private static function sendRemoteUnshare($remote, $id, $token) {
1110
+        $url = rtrim($remote, '/');
1111
+        $fields = ['token' => $token, 'format' => 'json'];
1112
+        $url = self::removeProtocolFromUrl($url);
1113
+        $result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
1114
+        $status = json_decode($result['result'], true);
1115
+
1116
+        return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
1117
+    }
1118
+
1119
+    /**
1120
+     * @return int
1121
+     */
1122
+    public static function getExpireInterval() {
1123
+        return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1124
+    }
1125
+
1126
+    /**
1127
+     * Checks whether the given path is reachable for the given owner
1128
+     *
1129
+     * @param string $path path relative to files
1130
+     * @param string $ownerStorageId storage id of the owner
1131
+     *
1132
+     * @return boolean true if file is reachable, false otherwise
1133
+     */
1134
+    private static function isFileReachable($path, $ownerStorageId) {
1135
+        // if outside the home storage, file is always considered reachable
1136
+        if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
1137
+            substr($ownerStorageId, 0, 13) === 'object::user:'
1138
+        )) {
1139
+            return true;
1140
+        }
1141
+
1142
+        // if inside the home storage, the file has to be under "/files/"
1143
+        $path = ltrim($path, '/');
1144
+        if (substr($path, 0, 6) === 'files/') {
1145
+            return true;
1146
+        }
1147
+
1148
+        return false;
1149
+    }
1150 1150
 }
Please login to merge, or discard this patch.
Spacing   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -150,7 +150,7 @@  discard block
 block discarded – undo
150 150
 
151 151
 		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
152 152
 
153
-		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
153
+		$where .= ' `'.$column.'` = ? AND `item_type` = ? ';
154 154
 		$arguments = [$itemSource, $itemType];
155 155
 		// for link shares $user === null
156 156
 		if ($user !== null) {
@@ -168,7 +168,7 @@  discard block
 block discarded – undo
168 168
 			$arguments[] = $owner;
169 169
 		}
170 170
 
171
-		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
171
+		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$fileDependentWhere.$where);
172 172
 
173 173
 		$result = \OC_DB::executeAudited($query, $arguments);
174 174
 
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
 			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
177 177
 				continue;
178 178
 			}
179
-			if ($fileDependent && (int)$row['file_parent'] === -1) {
179
+			if ($fileDependent && (int) $row['file_parent'] === -1) {
180 180
 				// if it is a mount point we need to get the path from the mount manager
181 181
 				$mountManager = \OC\Files\Filesystem::getMountManager();
182 182
 				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
@@ -187,7 +187,7 @@  discard block
 block discarded – undo
187 187
 					$row['path'] = $path;
188 188
 				} else {
189 189
 					\OC::$server->getLogger()->warning(
190
-						'Could not resolve mount point for ' . $row['storage_id'],
190
+						'Could not resolve mount point for '.$row['storage_id'],
191 191
 						['app' => 'OCP\Share']
192 192
 					);
193 193
 				}
@@ -205,7 +205,7 @@  discard block
 block discarded – undo
205 205
 			}
206 206
 
207 207
 			if (!empty($groups)) {
208
-				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
208
+				$where = $fileDependentWhere.' WHERE `'.$column.'` = ? AND `item_type` = ? AND `share_with` in (?)';
209 209
 				$arguments = [$itemSource, $itemType, $groups];
210 210
 				$types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
211 211
 
@@ -219,7 +219,7 @@  discard block
 block discarded – undo
219 219
 				// class isn't static anymore...
220 220
 				$conn = \OC::$server->getDatabaseConnection();
221 221
 				$result = $conn->executeQuery(
222
-					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
222
+					'SELECT '.$select.' FROM `*PREFIX*share` '.$where,
223 223
 					$arguments,
224 224
 					$types
225 225
 				);
@@ -286,12 +286,12 @@  discard block
 block discarded – undo
286 286
 		$currentUser = $owner ? $owner : \OC_User::getUser();
287 287
 		foreach ($items as $item) {
288 288
 			// delete the item with the expected share_type and owner
289
-			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
289
+			if ((int) $item['share_type'] === (int) $shareType && $item['uid_owner'] === $currentUser) {
290 290
 				$toDelete = $item;
291 291
 			// if there is more then one result we don't have to delete the children
292 292
 				// but update their parent. For group shares the new parent should always be
293 293
 				// the original group share and not the db entry with the unique name
294
-			} elseif ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
294
+			} elseif ((int) $item['share_type'] === self::$shareTypeGroupUserUnique) {
295 295
 				$newParent = $item['parent'];
296 296
 			} else {
297 297
 				$newParent = $item['id'];
@@ -348,7 +348,7 @@  discard block
 block discarded – undo
348 348
 	 * @return null
349 349
 	 */
350 350
 	protected static function unshareItem(array $item, $newParent = null) {
351
-		$shareType = (int)$item['share_type'];
351
+		$shareType = (int) $item['share_type'];
352 352
 		$shareWith = null;
353 353
 		if ($shareType !== IShare::TYPE_LINK) {
354 354
 			$shareWith = $item['share_with'];
@@ -374,7 +374,7 @@  discard block
 block discarded – undo
374 374
 		$deletedShares[] = $hookParams;
375 375
 		$hookParams['deletedShares'] = $deletedShares;
376 376
 		\OC_Hook::emit(\OCP\Share::class, 'post_unshare', $hookParams);
377
-		if ((int)$item['share_type'] === IShare::TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
377
+		if ((int) $item['share_type'] === IShare::TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
378 378
 			list(, $remote) = Helper::splitUserRemote($item['share_with']);
379 379
 			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
380 380
 		}
@@ -629,7 +629,7 @@  discard block
 block discarded – undo
629 629
 		$result = $query->execute($queryArgs);
630 630
 		if ($result === false) {
631 631
 			\OCP\Util::writeLog('OCP\Share',
632
-				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
632
+				\OC_DB::getErrorMessage().', select='.$select.' where=',
633 633
 				ILogger::ERROR);
634 634
 		}
635 635
 		$items = [];
@@ -671,14 +671,14 @@  discard block
 block discarded – undo
671 671
 						}
672 672
 						// Switch ids if sharing permission is granted on only
673 673
 						// one share to ensure correct parent is used if resharing
674
-						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
675
-							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
674
+						if (~(int) $items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
675
+							&& (int) $row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
676 676
 							$items[$row['id']] = $items[$id];
677 677
 							$switchedItems[$id] = $row['id'];
678 678
 							unset($items[$id]);
679 679
 							$id = $row['id'];
680 680
 						}
681
-						$items[$id]['permissions'] |= (int)$row['permissions'];
681
+						$items[$id]['permissions'] |= (int) $row['permissions'];
682 682
 					}
683 683
 					continue;
684 684
 				} elseif (!empty($row['parent'])) {
@@ -698,8 +698,8 @@  discard block
 block discarded – undo
698 698
 					$parentResult->closeCursor();
699 699
 
700 700
 					if ($parentRow === false) {
701
-						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
702
-							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
701
+						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: '.
702
+							\OC_DB::getErrorMessage().', select='.$select.' where='.$where,
703 703
 							ILogger::ERROR);
704 704
 					} else {
705 705
 						$tmpPath = $parentRow['file_target'];
@@ -708,7 +708,7 @@  discard block
 block discarded – undo
708 708
 						$subPath = substr($row['path'], $pos);
709 709
 						$splitPath = explode('/', $subPath);
710 710
 						foreach (array_slice($splitPath, 2) as $pathPart) {
711
-							$tmpPath = $tmpPath . '/' . $pathPart;
711
+							$tmpPath = $tmpPath.'/'.$pathPart;
712 712
 						}
713 713
 						$row['path'] = $tmpPath;
714 714
 					}
@@ -858,7 +858,7 @@  discard block
 block discarded – undo
858 858
 
859 859
 			// filter out invalid items, these can appear when subshare entries exist
860 860
 			// for a group in which the requested user isn't a member any more
861
-			$items = array_filter($items, function ($item) {
861
+			$items = array_filter($items, function($item) {
862 862
 				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
863 863
 			});
864 864
 
@@ -1016,7 +1016,7 @@  discard block
 block discarded – undo
1016 1016
 	 * @param array $parameters additional format parameters
1017 1017
 	 * @return array format result
1018 1018
 	 */
1019
-	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
1019
+	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE, $parameters = null) {
1020 1020
 		if ($format === self::FORMAT_NONE) {
1021 1021
 			return $items;
1022 1022
 		} elseif ($format === self::FORMAT_STATUSES) {
@@ -1073,13 +1073,13 @@  discard block
 block discarded – undo
1073 1073
 		$try = 0;
1074 1074
 		$discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
1075 1075
 		while ($result['success'] === false && $try < 2) {
1076
-			$federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
1076
+			$federationEndpoints = $discoveryService->discover($protocol.$remoteDomain, 'FEDERATED_SHARING');
1077 1077
 			$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
1078 1078
 			$client = \OC::$server->getHTTPClientService()->newClient();
1079 1079
 
1080 1080
 			try {
1081 1081
 				$response = $client->post(
1082
-					$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
1082
+					$protocol.$remoteDomain.$endpoint.$urlSuffix.'?format='.self::RESPONSE_FORMAT,
1083 1083
 					[
1084 1084
 						'body' => $fields,
1085 1085
 						'connect_timeout' => 10,
@@ -1120,7 +1120,7 @@  discard block
 block discarded – undo
1120 1120
 	 * @return int
1121 1121
 	 */
1122 1122
 	public static function getExpireInterval() {
1123
-		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1123
+		return (int) \OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1124 1124
 	}
1125 1125
 
1126 1126
 	/**
Please login to merge, or discard this patch.