Passed
Push — master ( 1eb084...1c074e )
by Morris
13:12 queued 11s
created

Folder::cacheEntryToFileInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Georg Ehrke <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Julius Härtl <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Robin McCorkell <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Vincent Petry <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program. If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\Files\Node;
33
34
use OC\DB\QueryBuilder\Literal;
35
use OC\Files\Search\SearchBinaryOperator;
36
use OC\Files\Search\SearchComparison;
37
use OC\Files\Search\SearchQuery;
38
use OC\Files\Storage\Wrapper\Jail;
39
use OC\Files\Storage\Storage;
40
use OCA\Files_Sharing\SharedStorage;
41
use OCP\DB\QueryBuilder\IQueryBuilder;
42
use OCP\Files\Cache\ICacheEntry;
43
use OCP\Files\Config\ICachedMountInfo;
44
use OCP\Files\FileInfo;
45
use OCP\Files\Mount\IMountPoint;
46
use OCP\Files\NotFoundException;
47
use OCP\Files\NotPermittedException;
48
use OCP\Files\Search\ISearchBinaryOperator;
49
use OCP\Files\Search\ISearchComparison;
50
use OCP\Files\Search\ISearchOperator;
51
use OCP\Files\Search\ISearchQuery;
52
use OCP\IUserManager;
53
54
class Folder extends Node implements \OCP\Files\Folder {
55
	/**
56
	 * Creates a Folder that represents a non-existing path
57
	 *
58
	 * @param string $path path
59
	 * @return string non-existing node class
60
	 */
61
	protected function createNonExistingNode($path) {
62
		return new NonExistingFolder($this->root, $this->view, $path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new OC\Files\Node...ot, $this->view, $path) returns the type OC\Files\Node\NonExistingFolder which is incompatible with the documented return type string.
Loading history...
63
	}
64
65
	/**
66
	 * @param string $path path relative to the folder
67
	 * @return string
68
	 * @throws \OCP\Files\NotPermittedException
69
	 */
70
	public function getFullPath($path) {
71
		if (!$this->isValidPath($path)) {
72
			throw new NotPermittedException('Invalid path');
73
		}
74
		return $this->path . $this->normalizePath($path);
75
	}
76
77
	/**
78
	 * @param string $path
79
	 * @return string
80
	 */
81
	public function getRelativePath($path) {
82
		if ($this->path === '' or $this->path === '/') {
83
			return $this->normalizePath($path);
84
		}
85
		if ($path === $this->path) {
86
			return '/';
87
		} elseif (strpos($path, $this->path . '/') !== 0) {
88
			return null;
89
		} else {
90
			$path = substr($path, strlen($this->path));
91
			return $this->normalizePath($path);
92
		}
93
	}
94
95
	/**
96
	 * check if a node is a (grand-)child of the folder
97
	 *
98
	 * @param \OC\Files\Node\Node $node
99
	 * @return bool
100
	 */
101
	public function isSubNode($node) {
102
		return strpos($node->getPath(), $this->path . '/') === 0;
103
	}
104
105
	/**
106
	 * get the content of this directory
107
	 *
108
	 * @return Node[]
109
	 * @throws \OCP\Files\NotFoundException
110
	 */
111
	public function getDirectoryListing() {
112
		$folderContent = $this->view->getDirectoryContent($this->path);
113
114
		return array_map(function (FileInfo $info) {
115
			if ($info->getMimetype() === 'httpd/unix-directory') {
116
				return new Folder($this->root, $this->view, $info->getPath(), $info);
117
			} else {
118
				return new File($this->root, $this->view, $info->getPath(), $info);
119
			}
120
		}, $folderContent);
121
	}
122
123
	/**
124
	 * @param string $path
125
	 * @param FileInfo $info
126
	 * @return File|Folder
127
	 */
128
	protected function createNode($path, FileInfo $info = null) {
129
		if (is_null($info)) {
130
			$isDir = $this->view->is_dir($path);
131
		} else {
132
			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
133
		}
134
		if ($isDir) {
135
			return new Folder($this->root, $this->view, $path, $info);
136
		} else {
137
			return new File($this->root, $this->view, $path, $info);
138
		}
139
	}
140
141
	/**
142
	 * Get the node at $path
143
	 *
144
	 * @param string $path
145
	 * @return \OC\Files\Node\Node
146
	 * @throws \OCP\Files\NotFoundException
147
	 */
148
	public function get($path) {
149
		return $this->root->get($this->getFullPath($path));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->root->get(...is->getFullPath($path)) returns the type string which is incompatible with the documented return type OC\Files\Node\Node.
Loading history...
150
	}
151
152
	/**
153
	 * @param string $path
154
	 * @return bool
155
	 */
156
	public function nodeExists($path) {
157
		try {
158
			$this->get($path);
159
			return true;
160
		} catch (NotFoundException $e) {
161
			return false;
162
		}
163
	}
164
165
	/**
166
	 * @param string $path
167
	 * @return \OC\Files\Node\Folder
168
	 * @throws \OCP\Files\NotPermittedException
169
	 */
170
	public function newFolder($path) {
171
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
172
			$fullPath = $this->getFullPath($path);
173
			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
174
			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
175
			if (!$this->view->mkdir($fullPath)) {
176
				throw new NotPermittedException('Could not create folder');
177
			}
178
			$node = new Folder($this->root, $this->view, $fullPath);
179
			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
180
			return $node;
181
		} else {
182
			throw new NotPermittedException('No create permission for folder');
183
		}
184
	}
185
186
	/**
187
	 * @param string $path
188
	 * @param string | resource | null $content
189
	 * @return \OC\Files\Node\File
190
	 * @throws \OCP\Files\NotPermittedException
191
	 */
192
	public function newFile($path, $content = null) {
193
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
194
			$fullPath = $this->getFullPath($path);
195
			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
196
			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
197
			if ($content !== null) {
198
				$result = $this->view->file_put_contents($fullPath, $content);
199
			} else {
200
				$result = $this->view->touch($fullPath);
201
			}
202
			if ($result === false) {
203
				throw new NotPermittedException('Could not create path');
204
			}
205
			$node = new File($this->root, $this->view, $fullPath);
206
			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
207
			return $node;
208
		}
209
		throw new NotPermittedException('No create permission for path');
210
	}
211
212
	private function queryFromOperator(ISearchOperator $operator, string $uid = null): ISearchQuery {
213
		if ($uid === null) {
214
			$user = null;
215
		} else {
216
			/** @var IUserManager $userManager */
217
			$userManager = \OC::$server->query(IUserManager::class);
218
			$user = $userManager->get($uid);
219
		}
220
		return new SearchQuery($operator, 0, 0, [], $user);
221
	}
222
223
	/**
224
	 * search for files with the name matching $query
225
	 *
226
	 * @param string|ISearchQuery $query
227
	 * @return \OC\Files\Node\Node[]
228
	 */
229
	public function search($query) {
230
		if (is_string($query)) {
231
			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
232
		}
233
234
		// Limit+offset for queries with ordering
235
		//
236
		// Because we currently can't do ordering between the results from different storages in sql
237
		// The only way to do ordering is requesting the $limit number of entries from all storages
238
		// sorting them and returning the first $limit entries.
239
		//
240
		// For offset we have the same problem, we don't know how many entries from each storage should be skipped
241
		// by a given $offset, so instead we query $offset + $limit from each storage and return entries $offset..($offset+$limit)
242
		// after merging and sorting them.
243
		//
244
		// This is suboptimal but because limit and offset tend to be fairly small in real world use cases it should
245
		// still be significantly better than disabling paging altogether
246
247
		$limitToHome = $query->limitToHome();
248
		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
249
			throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
250
		}
251
252
		$rootLength = strlen($this->path);
253
		$mount = $this->root->getMount($this->path);
254
		$storage = $mount->getStorage();
255
		$internalPath = $mount->getInternalPath($this->path);
256
		$internalPath = rtrim($internalPath, '/');
257
		if ($internalPath !== '') {
258
			$internalPath = $internalPath . '/';
259
		}
260
261
		$subQueryLimit = $query->getLimit() > 0 ? $query->getLimit() + $query->getOffset() : 0;
262
		$rootQuery = new SearchQuery(
263
			new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
264
				new SearchComparison(ISearchComparison::COMPARE_LIKE, 'path', $internalPath . '%'),
265
				$query->getSearchOperation(),
266
			]
267
			),
268
			$subQueryLimit,
269
			0,
270
			$query->getOrder(),
271
			$query->getUser()
272
		);
273
274
		$files = [];
275
276
		$cache = $storage->getCache('');
277
278
		$results = $cache->searchQuery($rootQuery);
279
		foreach ($results as $result) {
280
			$files[] = $this->cacheEntryToFileInfo($mount, '', $internalPath, $result);
281
		}
282
283
		if (!$limitToHome) {
284
			$mounts = $this->root->getMountsIn($this->path);
285
			foreach ($mounts as $mount) {
286
				$subQuery = new SearchQuery(
287
					$query->getSearchOperation(),
288
					$subQueryLimit,
289
					0,
290
					$query->getOrder(),
291
					$query->getUser()
292
				);
293
294
				$storage = $mount->getStorage();
295
				if ($storage) {
296
					$cache = $storage->getCache('');
297
298
					$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
299
					$results = $cache->searchQuery($subQuery);
300
					foreach ($results as $result) {
301
						$files[] = $this->cacheEntryToFileInfo($mount, $relativeMountPoint, '', $result);
302
					}
303
				}
304
			}
305
		}
306
307
		$order = $query->getOrder();
308
		if ($order) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $order of type OCP\Files\Search\ISearchOrder[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
309
			usort($files, function (FileInfo $a,FileInfo  $b) use ($order) {
310
				foreach ($order as $orderField) {
311
					$cmp = $orderField->sortFileInfo($a, $b);
312
					if ($cmp !== 0) {
313
						return $cmp;
314
					}
315
				}
316
				return 0;
317
			});
318
		}
319
		$files = array_values(array_slice($files, $query->getOffset(), $query->getLimit() > 0 ? $query->getLimit() : null));
320
321
		return array_map(function (FileInfo $file) {
322
			return $this->createNode($file->getPath(), $file);
323
		}, $files);
324
	}
325
326
	private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, string $trimRoot, ICacheEntry $cacheEntry): FileInfo {
327
		$trimLength = strlen($trimRoot);
328
		$cacheEntry['internalPath'] = $cacheEntry['path'];
329
		$cacheEntry['path'] = $appendRoot . substr($cacheEntry['path'], $trimLength);
330
		return new \OC\Files\FileInfo($this->path . '/' . $cacheEntry['path'], $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
331
	}
332
333
	/**
334
	 * search for files by mimetype
335
	 *
336
	 * @param string $mimetype
337
	 * @return Node[]
338
	 */
339
	public function searchByMime($mimetype) {
340
		if (strpos($mimetype, '/') === false) {
341
			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
342
		} else {
343
			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
344
		}
345
		return $this->search($query);
346
	}
347
348
	/**
349
	 * search for files by tag
350
	 *
351
	 * @param string|int $tag name or tag id
352
	 * @param string $userId owner of the tags
353
	 * @return Node[]
354
	 */
355
	public function searchByTag($tag, $userId) {
356
		$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
357
		return $this->search($query);
358
	}
359
360
	/**
361
	 * @param int $id
362
	 * @return \OC\Files\Node\Node[]
363
	 */
364
	public function getById($id) {
365
		$mountCache = $this->root->getUserMountCache();
366
		if (strpos($this->getPath(), '/', 1) > 0) {
367
			[, $user] = explode('/', $this->getPath());
368
		} else {
369
			$user = null;
370
		}
371
		$mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user);
372
		$mounts = $this->root->getMountsIn($this->path);
373
		$mounts[] = $this->root->getMount($this->path);
374
		/** @var IMountPoint[] $folderMounts */
375
		$folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) {
376
			return $mountPoint->getMountPoint();
377
		}, $mounts), $mounts);
378
379
		/** @var ICachedMountInfo[] $mountsContainingFile */
380
		$mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) {
381
			return isset($folderMounts[$cachedMountInfo->getMountPoint()]);
382
		}));
383
384
		if (count($mountsContainingFile) === 0) {
385
			if ($user === $this->getAppDataDirectoryName()) {
386
				return $this->getByIdInRootMount((int)$id);
387
			}
388
			return [];
389
		}
390
391
		$nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($folderMounts, $id) {
392
			$mount = $folderMounts[$cachedMountInfo->getMountPoint()];
393
			$cacheEntry = $mount->getStorage()->getCache()->get((int)$id);
394
			if (!$cacheEntry) {
395
				return null;
396
			}
397
398
			// cache jails will hide the "true" internal path
399
			$internalPath = ltrim($cachedMountInfo->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/');
400
			$pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath()));
401
			$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
402
			$absolutePath = rtrim($cachedMountInfo->getMountPoint() . $pathRelativeToMount, '/');
403
			return $this->root->createNode($absolutePath, new \OC\Files\FileInfo(
404
				$absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
405
				\OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
406
			));
407
		}, $mountsContainingFile);
408
409
		$nodes = array_filter($nodes);
410
411
		return array_filter($nodes, function (Node $node) {
412
			return $this->getRelativePath($node->getPath());
413
		});
414
	}
415
416
	protected function getAppDataDirectoryName(): string {
417
		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
418
		return 'appdata_' . $instanceId;
419
	}
420
421
	/**
422
	 * In case the path we are currently in is inside the appdata_* folder,
423
	 * the original getById method does not work, because it can only look inside
424
	 * the user's mount points. But the user has no mount point for the root storage.
425
	 *
426
	 * So in that case we directly check the mount of the root if it contains
427
	 * the id. If it does we check if the path is inside the path we are working
428
	 * in.
429
	 *
430
	 * @param int $id
431
	 * @return array
432
	 */
433
	protected function getByIdInRootMount(int $id): array {
434
		$mount = $this->root->getMount('');
435
		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
436
		if (!$cacheEntry) {
437
			return [];
438
		}
439
440
		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
441
		$currentPath = rtrim($this->path, '/') . '/';
442
443
		if (strpos($absolutePath, $currentPath) !== 0) {
444
			return [];
445
		}
446
447
		return [$this->root->createNode(
448
			$absolutePath, new \OC\Files\FileInfo(
449
			$absolutePath,
450
			$mount->getStorage(),
451
			$cacheEntry->getPath(),
452
			$cacheEntry,
453
			$mount
454
		))];
455
	}
456
457
	public function getFreeSpace() {
458
		return $this->view->free_space($this->path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->view->free_space($this->path) also could return the type boolean which is incompatible with the return type mandated by OCP\Files\Folder::getFreeSpace() of integer.
Loading history...
459
	}
460
461
	public function delete() {
462
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
463
			$this->sendHooks(['preDelete']);
464
			$fileInfo = $this->getFileInfo();
465
			$this->view->rmdir($this->path);
466
			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
467
			$this->sendHooks(['postDelete'], [$nonExisting]);
468
			$this->exists = false;
0 ignored issues
show
Bug Best Practice introduced by
The property exists does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
469
		} else {
470
			throw new NotPermittedException('No delete permission for path');
471
		}
472
	}
473
474
	/**
475
	 * Add a suffix to the name in case the file exists
476
	 *
477
	 * @param string $name
478
	 * @return string
479
	 * @throws NotPermittedException
480
	 */
481
	public function getNonExistingName($name) {
482
		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
483
		return trim($this->getRelativePath($uniqueName), '/');
484
	}
485
486
	/**
487
	 * @param int $limit
488
	 * @param int $offset
489
	 * @return \OCP\Files\Node[]
490
	 */
491
	public function getRecent($limit, $offset = 0) {
492
		$mimetypeLoader = \OC::$server->getMimeTypeLoader();
493
		$mounts = $this->root->getMountsIn($this->path);
494
		$mounts[] = $this->getMountPoint();
495
496
		$mounts = array_filter($mounts, function (IMountPoint $mount) {
497
			return $mount->getStorage();
498
		});
499
		$storageIds = array_map(function (IMountPoint $mount) {
500
			return $mount->getStorage()->getCache()->getNumericStorageId();
501
		}, $mounts);
502
		/** @var IMountPoint[] $mountMap */
503
		$mountMap = array_combine($storageIds, $mounts);
504
		$folderMimetype = $mimetypeLoader->getId(FileInfo::MIMETYPE_FOLDER);
505
506
		/*
507
		 * Construct an array of the storage id with their prefix path
508
		 * This helps us to filter in the final query
509
		 */
510
		$filters = array_map(function (IMountPoint $mount) {
511
			$storage = $mount->getStorage();
512
513
			$storageId = $storage->getCache()->getNumericStorageId();
514
			$prefix = '';
515
516
			if ($storage->instanceOfStorage(Jail::class)) {
517
				$prefix = $storage->getUnJailedPath('');
0 ignored issues
show
Bug introduced by
The method getUnJailedPath() does not exist on OC\Files\Storage\Storage. It seems like you code against a sub-type of OC\Files\Storage\Storage such as OC\Files\Storage\Wrapper\Wrapper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

517
				/** @scrutinizer ignore-call */ 
518
    $prefix = $storage->getUnJailedPath('');
Loading history...
518
			}
519
520
			return [
521
				'storageId' => $storageId,
522
				'pathPrefix' => $prefix,
523
			];
524
		}, $mounts);
525
526
		// Search in batches of 500 entries
527
		$searchLimit = 500;
528
		$results = [];
529
		$searchResultCount = 0;
530
		$count = 0;
531
		do {
532
			$searchResult = $this->recentSearch($searchLimit, $offset, $folderMimetype, $filters);
533
534
			// Exit condition if there are no more results
535
			if (count($searchResult) === 0) {
536
				break;
537
			}
538
539
			$searchResultCount += count($searchResult);
540
541
			$parseResult = $this->recentParse($searchResult, $mountMap, $mimetypeLoader);
542
543
			foreach ($parseResult as $result) {
544
				$results[] = $result;
545
			}
546
547
			$offset += $searchLimit;
548
			$count++;
549
		} while (count($results) < $limit && ($searchResultCount < (3 * $limit) || $count < 5));
550
551
		return array_slice($results, 0, $limit);
552
	}
553
554
	private function recentSearch($limit, $offset, $folderMimetype, $filters) {
555
		$dbconn = \OC::$server->getDatabaseConnection();
556
		$builder = $dbconn->getQueryBuilder();
557
		$query = $builder
558
			->select('f.*')
559
			->from('filecache', 'f');
560
561
		/*
562
		 * Here is where we construct the filtering.
563
		 * Note that this is expensive filtering as it is a lot of like queries.
564
		 * However the alternative is we do this filtering and parsing later in php with the risk of looping endlessly
565
		 */
566
		$storageFilters = $builder->expr()->orX();
567
		foreach ($filters as $filter) {
568
			$storageFilter = $builder->expr()->andX(
569
				$builder->expr()->eq('f.storage', $builder->createNamedParameter($filter['storageId']))
570
			);
571
572
			if ($filter['pathPrefix'] !== '') {
573
				$storageFilter->add(
574
					$builder->expr()->like('f.path', $builder->createNamedParameter($dbconn->escapeLikeParameter($filter['pathPrefix']) . '/%'))
575
				);
576
			}
577
578
			$storageFilters->add($storageFilter);
579
		}
580
581
		$query->andWhere($storageFilters);
582
583
		$query->andWhere($builder->expr()->orX(
584
		// handle non empty folders separate
585
			$builder->expr()->neq('f.mimetype', $builder->createNamedParameter($folderMimetype, IQueryBuilder::PARAM_INT)),
586
			$builder->expr()->eq('f.size', new Literal(0))
587
		))
588
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_versions/%')))
589
			->andWhere($builder->expr()->notLike('f.path', $builder->createNamedParameter('files_trashbin/%')))
590
			->orderBy('f.mtime', 'DESC')
591
			->setMaxResults($limit)
592
			->setFirstResult($offset);
593
594
		$result = $query->execute();
595
		$rows = $result->fetchAll();
596
		$result->closeCursor();
597
598
		return $rows;
599
	}
600
601
	private function recentParse($result, $mountMap, $mimetypeLoader) {
602
		$files = array_filter(array_map(function (array $entry) use ($mountMap, $mimetypeLoader) {
603
			$mount = $mountMap[$entry['storage']];
604
			$entry['internalPath'] = $entry['path'];
605
			$entry['mimetype'] = $mimetypeLoader->getMimetypeById($entry['mimetype']);
606
			$entry['mimepart'] = $mimetypeLoader->getMimetypeById($entry['mimepart']);
607
			$path = $this->getAbsolutePath($mount, $entry['path']);
608
			if (is_null($path)) {
609
				return null;
610
			}
611
			$fileInfo = new \OC\Files\FileInfo($path, $mount->getStorage(), $entry['internalPath'], $entry, $mount);
612
			return $this->root->createNode($fileInfo->getPath(), $fileInfo);
613
		}, $result));
614
615
		return array_values(array_filter($files, function (Node $node) {
616
			$cacheEntry = $node->getMountPoint()->getStorage()->getCache()->get($node->getId());
617
			if (!$cacheEntry) {
618
				return false;
619
			}
620
			$relative = $this->getRelativePath($node->getPath());
621
			return $relative !== null && $relative !== '/'
622
				&& ($cacheEntry->getPermissions() & \OCP\Constants::PERMISSION_READ) === \OCP\Constants::PERMISSION_READ;
623
		}));
624
	}
625
626
	private function getAbsolutePath(IMountPoint $mount, $path) {
627
		$storage = $mount->getStorage();
628
		if ($storage->instanceOfStorage('\OC\Files\Storage\Wrapper\Jail')) {
629
			if ($storage->instanceOfStorage(SharedStorage::class)) {
630
				$storage->getSourceStorage();
0 ignored issues
show
Bug introduced by
The method getSourceStorage() does not exist on OC\Files\Storage\Storage. It seems like you code against a sub-type of OC\Files\Storage\Storage such as OC\Files\Storage\Wrapper\Wrapper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

630
				$storage->/** @scrutinizer ignore-call */ 
631
              getSourceStorage();
Loading history...
631
			}
632
			/** @var \OC\Files\Storage\Wrapper\Jail $storage */
633
			$jailRoot = $storage->getUnjailedPath('');
634
			$rootLength = strlen($jailRoot) + 1;
635
			if ($path === $jailRoot) {
636
				return $mount->getMountPoint();
637
			} elseif (substr($path, 0, $rootLength) === $jailRoot . '/') {
638
				return $mount->getMountPoint() . substr($path, $rootLength);
639
			} else {
640
				return null;
641
			}
642
		} else {
643
			return $mount->getMountPoint() . $path;
644
		}
645
	}
646
}
647