Passed
Push — master ( e17684...b6c034 )
by Blizzz
35:05 queued 17:14
created

Folder::getNonExistingName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2022 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Georg Ehrke <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Julius Härtl <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
namespace OC\Files\Node;
33
34
use OC\Files\Cache\QuerySearchHelper;
35
use OC\Files\Search\SearchBinaryOperator;
36
use OC\Files\Search\SearchComparison;
37
use OC\Files\Search\SearchOrder;
38
use OC\Files\Search\SearchQuery;
39
use OC\Files\Utils\PathHelper;
40
use OCP\Files\Cache\ICacheEntry;
41
use OCP\Files\FileInfo;
42
use OCP\Files\Mount\IMountPoint;
43
use OCP\Files\NotFoundException;
44
use OCP\Files\NotPermittedException;
45
use OCP\Files\Search\ISearchBinaryOperator;
46
use OCP\Files\Search\ISearchComparison;
47
use OCP\Files\Search\ISearchOperator;
48
use OCP\Files\Search\ISearchOrder;
49
use OCP\Files\Search\ISearchQuery;
50
use OCP\IUserManager;
51
52
class Folder extends Node implements \OCP\Files\Folder {
53
	/**
54
	 * Creates a Folder that represents a non-existing path
55
	 *
56
	 * @param string $path path
57
	 * @return NonExistingFolder non-existing node
58
	 */
59
	protected function createNonExistingNode($path) {
60
		return new NonExistingFolder($this->root, $this->view, $path);
61
	}
62
63
	/**
64
	 * @param string $path path relative to the folder
65
	 * @return string
66
	 * @throws \OCP\Files\NotPermittedException
67
	 */
68
	public function getFullPath($path) {
69
		$path = $this->normalizePath($path);
70
		if (!$this->isValidPath($path)) {
71
			throw new NotPermittedException('Invalid path "' . $path . '"');
72
		}
73
		return $this->path . $path;
74
	}
75
76
	/**
77
	 * @param string $path
78
	 * @return string|null
79
	 */
80
	public function getRelativePath($path) {
81
		return PathHelper::getRelativePath($this->getPath(), $path);
82
	}
83
84
	/**
85
	 * check if a node is a (grand-)child of the folder
86
	 *
87
	 * @param \OC\Files\Node\Node $node
88
	 * @return bool
89
	 */
90
	public function isSubNode($node) {
91
		return strpos($node->getPath(), $this->path . '/') === 0;
92
	}
93
94
	/**
95
	 * get the content of this directory
96
	 *
97
	 * @return Node[]
98
	 * @throws \OCP\Files\NotFoundException
99
	 */
100
	public function getDirectoryListing() {
101
		$folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
102
103
		return array_map(function (FileInfo $info) {
104
			if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
105
				return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
106
			} else {
107
				return new File($this->root, $this->view, $info->getPath(), $info, $this);
108
			}
109
		}, $folderContent);
110
	}
111
112
	/**
113
	 * @param string $path
114
	 * @param FileInfo $info
115
	 * @return File|Folder
116
	 */
117
	protected function createNode($path, FileInfo $info = null, bool $infoHasSubMountsIncluded = true) {
118
		if (is_null($info)) {
119
			$isDir = $this->view->is_dir($path);
120
		} else {
121
			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
122
		}
123
		$parent = dirname($path) === $this->getPath() ? $this : null;
124
		if ($isDir) {
125
			return new Folder($this->root, $this->view, $path, $info, $parent, $infoHasSubMountsIncluded);
126
		} else {
127
			return new File($this->root, $this->view, $path, $info, $parent);
128
		}
129
	}
130
131
	/**
132
	 * Get the node at $path
133
	 *
134
	 * @param string $path
135
	 * @return \OC\Files\Node\Node
136
	 * @throws \OCP\Files\NotFoundException
137
	 */
138
	public function get($path) {
139
		return $this->root->get($this->getFullPath($path));
140
	}
141
142
	/**
143
	 * @param string $path
144
	 * @return bool
145
	 */
146
	public function nodeExists($path) {
147
		try {
148
			$this->get($path);
149
			return true;
150
		} catch (NotFoundException $e) {
151
			return false;
152
		}
153
	}
154
155
	/**
156
	 * @param string $path
157
	 * @return \OC\Files\Node\Folder
158
	 * @throws \OCP\Files\NotPermittedException
159
	 */
160
	public function newFolder($path) {
161
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
162
			$fullPath = $this->getFullPath($path);
163
			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
164
			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
165
			if (!$this->view->mkdir($fullPath)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->view->mkdir($fullPath) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
166
				throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
167
			}
168
			$parent = dirname($fullPath) === $this->getPath() ? $this : null;
169
			$node = new Folder($this->root, $this->view, $fullPath, null, $parent);
170
			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
171
			return $node;
172
		} else {
173
			throw new NotPermittedException('No create permission for folder "' . $path . '"');
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 (empty($path)) {
185
			throw new NotPermittedException('Could not create as provided path is empty');
186
		}
187
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
188
			$fullPath = $this->getFullPath($path);
189
			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
190
			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
191
			if ($content !== null) {
192
				$result = $this->view->file_put_contents($fullPath, $content);
193
			} else {
194
				$result = $this->view->touch($fullPath);
195
			}
196
			if ($result === false) {
197
				throw new NotPermittedException('Could not create path "' . $fullPath . '"');
198
			}
199
			$node = new File($this->root, $this->view, $fullPath, null, $this);
200
			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
201
			return $node;
202
		}
203
		throw new NotPermittedException('No create permission for path "' . $path . '"');
204
	}
205
206
	private function queryFromOperator(ISearchOperator $operator, string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery {
207
		if ($uid === null) {
208
			$user = null;
209
		} else {
210
			/** @var IUserManager $userManager */
211
			$userManager = \OC::$server->query(IUserManager::class);
212
			$user = $userManager->get($uid);
213
		}
214
		return new SearchQuery($operator, $limit, $offset, [], $user);
215
	}
216
217
	/**
218
	 * search for files with the name matching $query
219
	 *
220
	 * @param string|ISearchQuery $query
221
	 * @return \OC\Files\Node\Node[]
222
	 */
223
	public function search($query) {
224
		if (is_string($query)) {
225
			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
226
		}
227
228
		// search is handled by a single query covering all caches that this folder contains
229
		// this is done by collect
230
231
		$limitToHome = $query->limitToHome();
232
		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
233
			throw new \InvalidArgumentException('searching by owner is only allowed in the users home folder');
234
		}
235
236
		/** @var QuerySearchHelper $searchHelper */
237
		$searchHelper = \OC::$server->get(QuerySearchHelper::class);
238
		[$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome);
239
		$resultsPerCache = $searchHelper->searchInCaches($query, $caches);
240
241
		// loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all
242
		$files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) {
243
			$mount = $mountByMountPoint[$relativeMountPoint];
244
			return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
245
				return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
246
			}, $results);
247
		}, array_values($resultsPerCache), array_keys($resultsPerCache)));
248
249
		// don't include this folder in the results
250
		$files = array_filter($files, function (FileInfo $file) {
251
			return $file->getPath() !== $this->getPath();
252
		});
253
254
		// since results were returned per-cache, they are no longer fully sorted
255
		$order = $query->getOrder();
256
		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...
257
			usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
258
				foreach ($order as $orderField) {
259
					$cmp = $orderField->sortFileInfo($a, $b);
260
					if ($cmp !== 0) {
261
						return $cmp;
262
					}
263
				}
264
				return 0;
265
			});
266
		}
267
268
		return array_map(function (FileInfo $file) {
269
			return $this->createNode($file->getPath(), $file);
270
		}, $files);
271
	}
272
273
	private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
274
		$cacheEntry['internalPath'] = $cacheEntry['path'];
275
		$cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
276
		$subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
277
		return new \OC\Files\FileInfo($this->path . $subPath, $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
278
	}
279
280
	/**
281
	 * search for files by mimetype
282
	 *
283
	 * @param string $mimetype
284
	 * @return Node[]
285
	 */
286
	public function searchByMime($mimetype) {
287
		if (strpos($mimetype, '/') === false) {
288
			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
289
		} else {
290
			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
291
		}
292
		return $this->search($query);
293
	}
294
295
	/**
296
	 * search for files by tag
297
	 *
298
	 * @param string|int $tag name or tag id
299
	 * @param string $userId owner of the tags
300
	 * @return Node[]
301
	 */
302
	public function searchByTag($tag, $userId) {
303
		$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
304
		return $this->search($query);
305
	}
306
307
	/**
308
	 * @param int $id
309
	 * @return \OC\Files\Node\Node[]
310
	 */
311
	public function getById($id) {
312
		return $this->root->getByIdInPath((int)$id, $this->getPath());
313
	}
314
315
	protected function getAppDataDirectoryName(): string {
316
		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
317
		return 'appdata_' . $instanceId;
318
	}
319
320
	/**
321
	 * In case the path we are currently in is inside the appdata_* folder,
322
	 * the original getById method does not work, because it can only look inside
323
	 * the user's mount points. But the user has no mount point for the root storage.
324
	 *
325
	 * So in that case we directly check the mount of the root if it contains
326
	 * the id. If it does we check if the path is inside the path we are working
327
	 * in.
328
	 *
329
	 * @param int $id
330
	 * @return array
331
	 */
332
	protected function getByIdInRootMount(int $id): array {
333
		$mount = $this->root->getMount('');
334
		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
335
		if (!$cacheEntry) {
336
			return [];
337
		}
338
339
		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
340
		$currentPath = rtrim($this->path, '/') . '/';
341
342
		if (strpos($absolutePath, $currentPath) !== 0) {
343
			return [];
344
		}
345
346
		return [$this->root->createNode(
347
			$absolutePath, new \OC\Files\FileInfo(
348
				$absolutePath,
349
				$mount->getStorage(),
350
				$cacheEntry->getPath(),
351
				$cacheEntry,
352
				$mount
353
			))];
354
	}
355
356
	public function getFreeSpace() {
357
		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...
358
	}
359
360
	public function delete() {
361
		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
362
			$this->sendHooks(['preDelete']);
363
			$fileInfo = $this->getFileInfo();
364
			$this->view->rmdir($this->path);
365
			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
366
			$this->sendHooks(['postDelete'], [$nonExisting]);
367
		} else {
368
			throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
369
		}
370
	}
371
372
	/**
373
	 * Add a suffix to the name in case the file exists
374
	 *
375
	 * @param string $name
376
	 * @return string
377
	 * @throws NotPermittedException
378
	 */
379
	public function getNonExistingName($name) {
380
		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
381
		return trim($this->getRelativePath($uniqueName), '/');
382
	}
383
384
	/**
385
	 * @param int $limit
386
	 * @param int $offset
387
	 * @return \OCP\Files\Node[]
388
	 */
389
	public function getRecent($limit, $offset = 0) {
390
		$filterOutNonEmptyFolder = new SearchBinaryOperator(
391
			// filter out non empty folders
392
			ISearchBinaryOperator::OPERATOR_OR,
393
			[
394
				new SearchBinaryOperator(
395
					ISearchBinaryOperator::OPERATOR_NOT,
396
					[
397
						new SearchComparison(
398
							ISearchComparison::COMPARE_EQUAL,
399
							'mimetype',
400
							FileInfo::MIMETYPE_FOLDER
401
						),
402
					]
403
				),
404
				new SearchComparison(
405
					ISearchComparison::COMPARE_EQUAL,
406
					'size',
407
					0
408
				),
409
			]
410
		);
411
412
		$filterNonRecentFiles = new SearchComparison(
413
			ISearchComparison::COMPARE_GREATER_THAN,
414
			'mtime',
415
			strtotime("-2 week")
416
		);
417
		if ($offset === 0 && $limit <= 100) {
418
			$query = new SearchQuery(
419
				new SearchBinaryOperator(
420
					ISearchBinaryOperator::OPERATOR_AND,
421
					[
422
						$filterOutNonEmptyFolder,
423
						$filterNonRecentFiles,
424
					],
425
				),
426
				$limit,
427
				$offset,
428
				[
429
					new SearchOrder(
430
						ISearchOrder::DIRECTION_DESCENDING,
431
						'mtime'
432
					),
433
				]
434
			);
435
		} else {
436
			$query = new SearchQuery(
437
				$filterOutNonEmptyFolder,
438
				$limit,
439
				$offset,
440
				[
441
					new SearchOrder(
442
						ISearchOrder::DIRECTION_DESCENDING,
443
						'mtime'
444
					),
445
				]
446
			);
447
		}
448
449
		return $this->search($query);
450
	}
451
}
452