Passed
Push — master ( 8eb950...2e6e15 )
by John
17:12 queued 12s
created

ApiController::showHiddenFiles()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
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
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Daniel Kesselberg <[email protected]>
7
 * @author Felix Nüsse <[email protected]>
8
 * @author fnuesse <[email protected]>
9
 * @author fnuesse <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author John Molakvoæ <[email protected]>
12
 * @author Julius Härtl <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author Max Kovalenko <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Nina Pypchenko <[email protected]>
17
 * @author Richard Steinmetz <[email protected]>
18
 * @author Robin Appelman <[email protected]>
19
 * @author Roeland Jago Douma <[email protected]>
20
 * @author Tobias Kaminsky <[email protected]>
21
 * @author Vincent Petry <[email protected]>
22
 *
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program. If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
namespace OCA\Files\Controller;
39
40
use OC\Files\Node\Node;
41
use OCA\Files\Service\TagService;
42
use OCA\Files\Service\UserConfig;
43
use OCP\AppFramework\Controller;
44
use OCP\AppFramework\Http;
45
use OCP\AppFramework\Http\ContentSecurityPolicy;
46
use OCP\AppFramework\Http\DataResponse;
47
use OCP\AppFramework\Http\FileDisplayResponse;
48
use OCP\AppFramework\Http\JSONResponse;
49
use OCP\AppFramework\Http\Response;
50
use OCP\AppFramework\Http\StreamResponse;
51
use OCP\Files\File;
52
use OCP\Files\Folder;
53
use OCP\Files\NotFoundException;
54
use OCP\IConfig;
55
use OCP\IPreview;
56
use OCP\IRequest;
57
use OCP\IUserSession;
58
use OCP\Share\IManager;
59
use OCP\Share\IShare;
60
61
/**
62
 * Class ApiController
63
 *
64
 * @package OCA\Files\Controller
65
 */
66
class ApiController extends Controller {
67
	private TagService $tagService;
68
	private IManager $shareManager;
69
	private IPreview $previewManager;
70
	private IUserSession $userSession;
71
	private IConfig $config;
72
	private Folder $userFolder;
73
	private UserConfig $userConfig;
74
75
	/**
76
	 * @param string $appName
77
	 * @param IRequest $request
78
	 * @param IUserSession $userSession
79
	 * @param TagService $tagService
80
	 * @param IPreview $previewManager
81
	 * @param IManager $shareManager
82
	 * @param IConfig $config
83
	 * @param Folder $userFolder
84
	 */
85
	public function __construct($appName,
86
								IRequest $request,
87
								IUserSession $userSession,
88
								TagService $tagService,
89
								IPreview $previewManager,
90
								IManager $shareManager,
91
								IConfig $config,
92
								Folder $userFolder,
93
								UserConfig $userConfig) {
94
		parent::__construct($appName, $request);
95
		$this->userSession = $userSession;
96
		$this->tagService = $tagService;
97
		$this->previewManager = $previewManager;
98
		$this->shareManager = $shareManager;
99
		$this->config = $config;
100
		$this->userFolder = $userFolder;
101
		$this->userConfig = $userConfig;
102
	}
103
104
	/**
105
	 * Gets a thumbnail of the specified file
106
	 *
107
	 * @since API version 1.0
108
	 *
109
	 * @NoAdminRequired
110
	 * @NoCSRFRequired
111
	 * @StrictCookieRequired
112
	 *
113
	 * @param int $x
114
	 * @param int $y
115
	 * @param string $file URL-encoded filename
116
	 * @return DataResponse|FileDisplayResponse
117
	 */
118
	public function getThumbnail($x, $y, $file) {
119
		if ($x < 1 || $y < 1) {
120
			return new DataResponse(['message' => 'Requested size must be numeric and a positive value.'], Http::STATUS_BAD_REQUEST);
121
		}
122
123
		try {
124
			$file = $this->userFolder->get($file);
125
			if ($file instanceof Folder) {
126
				throw new NotFoundException();
127
			}
128
129
			/** @var File $file */
130
			$preview = $this->previewManager->getPreview($file, $x, $y, true);
131
132
			return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]);
133
		} catch (NotFoundException $e) {
134
			return new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND);
135
		} catch (\Exception $e) {
136
			return new DataResponse([], Http::STATUS_BAD_REQUEST);
137
		}
138
	}
139
140
	/**
141
	 * Updates the info of the specified file path
142
	 * The passed tags are absolute, which means they will
143
	 * replace the actual tag selection.
144
	 *
145
	 * @NoAdminRequired
146
	 *
147
	 * @param string $path path
148
	 * @param array|string $tags array of tags
149
	 * @return DataResponse
150
	 */
151
	public function updateFileTags($path, $tags = null) {
152
		$result = [];
153
		// if tags specified or empty array, update tags
154
		if (!is_null($tags)) {
155
			try {
156
				$this->tagService->updateFileTags($path, $tags);
157
			} catch (\OCP\Files\NotFoundException $e) {
158
				return new DataResponse([
159
					'message' => $e->getMessage()
160
				], Http::STATUS_NOT_FOUND);
161
			} catch (\OCP\Files\StorageNotAvailableException $e) {
162
				return new DataResponse([
163
					'message' => $e->getMessage()
164
				], Http::STATUS_SERVICE_UNAVAILABLE);
165
			} catch (\Exception $e) {
166
				return new DataResponse([
167
					'message' => $e->getMessage()
168
				], Http::STATUS_NOT_FOUND);
169
			}
170
			$result['tags'] = $tags;
171
		}
172
		return new DataResponse($result);
173
	}
174
175
	/**
176
	 * @param \OCP\Files\Node[] $nodes
177
	 * @return array
178
	 */
179
	private function formatNodes(array $nodes) {
180
		$shareTypesForNodes = $this->getShareTypesForNodes($nodes);
181
		return array_values(array_map(function (Node $node) use ($shareTypesForNodes) {
182
			$shareTypes = $shareTypesForNodes[$node->getId()] ?? [];
183
			$file = \OCA\Files\Helper::formatFileInfo($node->getFileInfo());
184
			$file['hasPreview'] = $this->previewManager->isAvailable($node);
185
			$parts = explode('/', dirname($node->getPath()), 4);
186
			if (isset($parts[3])) {
187
				$file['path'] = '/' . $parts[3];
188
			} else {
189
				$file['path'] = '/';
190
			}
191
			if (!empty($shareTypes)) {
192
				$file['shareTypes'] = $shareTypes;
193
			}
194
			return $file;
195
		}, $nodes));
196
	}
197
198
	/**
199
	 * Get the share types for each node
200
	 *
201
	 * @param \OCP\Files\Node[] $nodes
202
	 * @return array<int, int[]> list of share types for each fileid
203
	 */
204
	private function getShareTypesForNodes(array $nodes): array {
205
		$userId = $this->userSession->getUser()->getUID();
206
		$requestedShareTypes = [
207
			IShare::TYPE_USER,
208
			IShare::TYPE_GROUP,
209
			IShare::TYPE_LINK,
210
			IShare::TYPE_REMOTE,
211
			IShare::TYPE_EMAIL,
212
			IShare::TYPE_ROOM,
213
			IShare::TYPE_DECK,
214
			IShare::TYPE_SCIENCEMESH,
215
		];
216
		$shareTypes = [];
217
218
		$nodeIds = array_map(function (Node $node) {
219
			return $node->getId();
220
		}, $nodes);
221
222
		foreach ($requestedShareTypes as $shareType) {
223
			$nodesLeft = array_combine($nodeIds, array_fill(0, count($nodeIds), true));
224
			$offset = 0;
225
226
			// fetch shares until we've either found shares for all nodes or there are no more shares left
227
			while (count($nodesLeft) > 0) {
228
				$shares = $this->shareManager->getSharesBy($userId, $shareType, null, false, 100, $offset);
229
				foreach ($shares as $share) {
230
					$fileId = $share->getNodeId();
231
					if (isset($nodesLeft[$fileId])) {
232
						if (!isset($shareTypes[$fileId])) {
233
							$shareTypes[$fileId] = [];
234
						}
235
						$shareTypes[$fileId][] = $shareType;
236
						unset($nodesLeft[$fileId]);
237
					}
238
				}
239
240
				if (count($shares) < 100) {
241
					break;
242
				} else {
243
					$offset += count($shares);
244
				}
245
			}
246
		}
247
		return $shareTypes;
248
	}
249
250
	/**
251
	 * Returns a list of recently modified files.
252
	 *
253
	 * @NoAdminRequired
254
	 *
255
	 * @return DataResponse
256
	 */
257
	public function getRecentFiles() {
258
		$nodes = $this->userFolder->getRecent(100);
259
		$files = $this->formatNodes($nodes);
260
		return new DataResponse(['files' => $files]);
261
	}
262
263
264
	/**
265
	 * Returns the current logged-in user's storage stats.
266
	 *
267
	 * @NoAdminRequired
268
	 *
269
	 * @param ?string $dir the directory to get the storage stats from
270
	 * @return JSONResponse
271
	 */
272
	public function getStorageStats($dir = '/'): JSONResponse {
273
		$storageInfo = \OC_Helper::getStorageInfo($dir ?: '/');
274
		return new JSONResponse(['message' => 'ok', 'data' => $storageInfo]);
275
	}
276
277
	/**
278
	 * Change the default sort mode
279
	 *
280
	 * @NoAdminRequired
281
	 *
282
	 * @param string $mode
283
	 * @param string $direction
284
	 * @return JSONResponse
285
	 * @throws \OCP\PreConditionNotMetException
286
	 */
287
	public function updateFileSorting($mode, string $direction = 'asc', string $view = 'files'): JSONResponse {
288
		$allowedDirection = ['asc', 'desc'];
289
		if (!in_array($direction, $allowedDirection)) {
290
			return  new JSONResponse(['message' => 'Invalid direction parameter'], Http::STATUS_UNPROCESSABLE_ENTITY);
291
		}
292
293
		$userId = $this->userSession->getUser()->getUID();
294
295
		$sortingJson = $this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}');
296
		$sortingConfig = json_decode($sortingJson, true) ?: [];
297
		$sortingConfig[$view] = [
298
			'mode' => $mode,
299
			'direction' => $direction,
300
		];
301
302
		$this->config->setUserValue($userId, 'files', 'files_sorting_configs', json_encode($sortingConfig));
303
		return new JSONResponse([
304
			'message' => 'ok',
305
			'data' => $sortingConfig,
306
		]);
307
	}
308
309
	/**
310
	 * Toggle default files user config
311
	 *
312
	 * @NoAdminRequired
313
	 *
314
	 * @param string $key
315
	 * @param string|bool $value
316
	 * @return JSONResponse
317
	 */
318
	public function setConfig(string $key, $value): JSONResponse {
319
		try {
320
			$this->userConfig->setConfig($key, (string)$value);
321
		} catch (\InvalidArgumentException $e) {
322
			return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
323
		}
324
325
		return new JSONResponse(['message' => 'ok', 'data' => ['key' => $key, 'value' => $value]]);
326
	}
327
328
329
	/**
330
	 * Get the user config
331
	 *
332
	 * @NoAdminRequired
333
	 *
334
	 * @return JSONResponse
335
	 */
336
	public function getConfigs(): JSONResponse {
337
		return new JSONResponse(['message' => 'ok', 'data' => $this->userConfig->getConfigs()]);
338
	}
339
340
	/**
341
	 * Toggle default for showing/hiding hidden files
342
	 *
343
	 * @NoAdminRequired
344
	 *
345
	 * @param bool $value
346
	 * @return Response
347
	 * @throws \OCP\PreConditionNotMetException
348
	 */
349
	public function showHiddenFiles(bool $value): Response {
350
		$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', $value ? '1' : '0');
351
		return new Response();
352
	}
353
354
	/**
355
	 * Toggle default for cropping preview images
356
	 *
357
	 * @NoAdminRequired
358
	 *
359
	 * @param bool $value
360
	 * @return Response
361
	 * @throws \OCP\PreConditionNotMetException
362
	 */
363
	public function cropImagePreviews(bool $value): Response {
364
		$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'crop_image_previews', $value ? '1' : '0');
365
		return new Response();
366
	}
367
368
	/**
369
	 * Toggle default for files grid view
370
	 *
371
	 * @NoAdminRequired
372
	 *
373
	 * @param bool $show
374
	 * @return Response
375
	 * @throws \OCP\PreConditionNotMetException
376
	 */
377
	public function showGridView(bool $show): Response {
378
		$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_grid', $show ? '1' : '0');
379
		return new Response();
380
	}
381
382
	/**
383
	 * Get default settings for the grid view
384
	 *
385
	 * @NoAdminRequired
386
	 */
387
	public function getGridView() {
388
		$status = $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_grid', '0') === '1';
389
		return new JSONResponse(['gridview' => $status]);
390
	}
391
392
	/**
393
	 * Toggle default for showing/hiding xxx folder
394
	 *
395
	 * @NoAdminRequired
396
	 *
397
	 * @param int $show
398
	 * @param string $key the key of the folder
399
	 *
400
	 * @return Response
401
	 * @throws \OCP\PreConditionNotMetException
402
	 */
403
	public function toggleShowFolder(int $show, string $key): Response {
404
		if ($show !== 0 && $show !== 1) {
405
			return new DataResponse([
406
				'message' => 'Invalid show value. Only 0 and 1 are allowed.'
407
			], Http::STATUS_BAD_REQUEST);
408
		}
409
410
		$userId = $this->userSession->getUser()->getUID();
411
412
		// Set the new value and return it
413
		// Using a prefix prevents the user from setting arbitrary keys
414
		$this->config->setUserValue($userId, 'files', 'show_' . $key, (string)$show);
415
		return new JSONResponse([$key => $show]);
416
	}
417
418
	/**
419
	 * Get sorting-order for custom sorting
420
	 *
421
	 * @NoAdminRequired
422
	 *
423
	 * @param string $folderpath
424
	 * @return string
425
	 * @throws \OCP\Files\NotFoundException
426
	 */
427
	public function getNodeType($folderpath) {
428
		$node = $this->userFolder->get($folderpath);
429
		return $node->getType();
430
	}
431
	
432
	/**
433
	 * @NoAdminRequired
434
	 * @NoCSRFRequired
435
	 */
436
	public function serviceWorker(): StreamResponse {
437
		$response = new StreamResponse(__DIR__ . '/../../../../dist/preview-service-worker.js');
438
		$response->setHeaders([
439
			'Content-Type' => 'application/javascript',
440
			'Service-Worker-Allowed' => '/'
441
		]);
442
		$policy = new ContentSecurityPolicy();
443
		$policy->addAllowedWorkerSrcDomain("'self'");
444
		$policy->addAllowedScriptDomain("'self'");
445
		$policy->addAllowedConnectDomain("'self'");
446
		$response->setContentSecurityPolicy($policy);
447
		return $response;
448
	}
449
}
450