Passed
Push — master ( 645109...008e6d )
by Christoph
12:14 queued 12s
created
apps/dav/lib/Connector/Sabre/File.php 2 patches
Indentation   +601 added lines, -601 removed lines patch added patch discarded remove patch
@@ -71,605 +71,605 @@
 block discarded – undo
71 71
 
72 72
 class File extends Node implements IFile {
73 73
 
74
-	protected $request;
75
-
76
-	/**
77
-	 * Sets up the node, expects a full path name
78
-	 *
79
-	 * @param \OC\Files\View $view
80
-	 * @param \OCP\Files\FileInfo $info
81
-	 * @param \OCP\Share\IManager $shareManager
82
-	 * @param \OC\AppFramework\Http\Request $request
83
-	 */
84
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
85
-		parent::__construct($view, $info, $shareManager);
86
-
87
-		if (isset($request)) {
88
-			$this->request = $request;
89
-		} else {
90
-			$this->request = \OC::$server->getRequest();
91
-		}
92
-	}
93
-
94
-	/**
95
-	 * Updates the data
96
-	 *
97
-	 * The data argument is a readable stream resource.
98
-	 *
99
-	 * After a successful put operation, you may choose to return an ETag. The
100
-	 * etag must always be surrounded by double-quotes. These quotes must
101
-	 * appear in the actual string you're returning.
102
-	 *
103
-	 * Clients may use the ETag from a PUT request to later on make sure that
104
-	 * when they update the file, the contents haven't changed in the mean
105
-	 * time.
106
-	 *
107
-	 * If you don't plan to store the file byte-by-byte, and you return a
108
-	 * different object on a subsequent GET you are strongly recommended to not
109
-	 * return an ETag, and just return null.
110
-	 *
111
-	 * @param resource $data
112
-	 *
113
-	 * @throws Forbidden
114
-	 * @throws UnsupportedMediaType
115
-	 * @throws BadRequest
116
-	 * @throws Exception
117
-	 * @throws EntityTooLarge
118
-	 * @throws ServiceUnavailable
119
-	 * @throws FileLocked
120
-	 * @return string|null
121
-	 */
122
-	public function put($data) {
123
-		try {
124
-			$exists = $this->fileView->file_exists($this->path);
125
-			if ($this->info && $exists && !$this->info->isUpdateable()) {
126
-				throw new Forbidden();
127
-			}
128
-		} catch (StorageNotAvailableException $e) {
129
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
130
-		}
131
-
132
-		// verify path of the target
133
-		$this->verifyPath();
134
-
135
-		// chunked handling
136
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
137
-			try {
138
-				return $this->createFileChunked($data);
139
-			} catch (\Exception $e) {
140
-				$this->convertToSabreException($e);
141
-			}
142
-		}
143
-
144
-		/** @var Storage $partStorage */
145
-		list($partStorage) = $this->fileView->resolvePath($this->path);
146
-		$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
147
-
148
-		$view = \OC\Files\Filesystem::getView();
149
-
150
-		if ($needsPartFile) {
151
-			// mark file as partial while uploading (ignored by the scanner)
152
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
153
-
154
-			if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
155
-				$needsPartFile = false;
156
-			}
157
-		}
158
-		if (!$needsPartFile) {
159
-			// upload file directly as the final path
160
-			$partFilePath = $this->path;
161
-
162
-			if ($view && !$this->emitPreHooks($exists)) {
163
-				throw new Exception('Could not write to final file, canceled by hook');
164
-			}
165
-		}
166
-
167
-		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
168
-		/** @var \OC\Files\Storage\Storage $partStorage */
169
-		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
170
-		/** @var \OC\Files\Storage\Storage $storage */
171
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
172
-		try {
173
-			if (!$needsPartFile) {
174
-				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
175
-			}
176
-
177
-			if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
178
-
179
-				if (!is_resource($data)) {
180
-					$tmpData = fopen('php://temp', 'r+');
181
-					if ($data !== null) {
182
-						fwrite($tmpData, $data);
183
-						rewind($tmpData);
184
-					}
185
-					$data = $tmpData;
186
-				}
187
-
188
-				$isEOF = false;
189
-				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
190
-					$isEOF = feof($stream);
191
-				});
192
-
193
-				$count = $partStorage->writeStream($internalPartPath, $wrappedData);
194
-				$result = $count > 0;
195
-
196
-				if ($result === false) {
197
-					$result = $isEOF;
198
-					if (is_resource($wrappedData)) {
199
-						$result = feof($wrappedData);
200
-					}
201
-				}
202
-
203
-			} else {
204
-				$target = $partStorage->fopen($internalPartPath, 'wb');
205
-				if ($target === false) {
206
-					\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
207
-					// because we have no clue about the cause we can only throw back a 500/Internal Server Error
208
-					throw new Exception('Could not write file contents');
209
-				}
210
-				list($count, $result) = \OC_Helper::streamCopy($data, $target);
211
-				fclose($target);
212
-			}
213
-
214
-			if ($result === false) {
215
-				$expected = -1;
216
-				if (isset($_SERVER['CONTENT_LENGTH'])) {
217
-					$expected = $_SERVER['CONTENT_LENGTH'];
218
-				}
219
-				if ($expected !== "0") {
220
-					throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
221
-				}
222
-			}
223
-
224
-			// if content length is sent by client:
225
-			// double check if the file was fully received
226
-			// compare expected and actual size
227
-			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
228
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
229
-				if ($count !== $expected) {
230
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
231
-				}
232
-			}
233
-
234
-		} catch (\Exception $e) {
235
-			$context = [];
236
-
237
-			if ($e instanceof LockedException) {
238
-				$context['level'] = ILogger::DEBUG;
239
-			}
240
-
241
-			\OC::$server->getLogger()->logException($e, $context);
242
-			if ($needsPartFile) {
243
-				$partStorage->unlink($internalPartPath);
244
-			}
245
-			$this->convertToSabreException($e);
246
-		}
247
-
248
-		try {
249
-			if ($needsPartFile) {
250
-				if ($view && !$this->emitPreHooks($exists)) {
251
-					$partStorage->unlink($internalPartPath);
252
-					throw new Exception('Could not rename part file to final file, canceled by hook');
253
-				}
254
-				try {
255
-					$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
256
-				} catch (LockedException $e) {
257
-					// during very large uploads, the shared lock we got at the start might have been expired
258
-					// meaning that the above lock can fail not just only because somebody else got a shared lock
259
-					// or because there is no existing shared lock to make exclusive
260
-					//
261
-					// Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
262
-					// lock this will still fail, if our original shared lock expired the new lock will be successful and
263
-					// the entire operation will be safe
264
-
265
-					try {
266
-						$this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
267
-					} catch (LockedException $ex) {
268
-						if ($needsPartFile) {
269
-							$partStorage->unlink($internalPartPath);
270
-						}
271
-						throw new FileLocked($e->getMessage(), $e->getCode(), $e);
272
-					}
273
-				}
274
-
275
-				// rename to correct path
276
-				try {
277
-					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
278
-					$fileExists = $storage->file_exists($internalPath);
279
-					if ($renameOkay === false || $fileExists === false) {
280
-						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
281
-						throw new Exception('Could not rename part file to final file');
282
-					}
283
-				} catch (ForbiddenException $ex) {
284
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
285
-				} catch (\Exception $e) {
286
-					$partStorage->unlink($internalPartPath);
287
-					$this->convertToSabreException($e);
288
-				}
289
-			}
290
-
291
-			// since we skipped the view we need to scan and emit the hooks ourselves
292
-			$storage->getUpdater()->update($internalPath);
293
-
294
-			try {
295
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
296
-			} catch (LockedException $e) {
297
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
298
-			}
299
-
300
-			// allow sync clients to send the mtime along in a header
301
-			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
302
-				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
303
-				if ($this->fileView->touch($this->path, $mtime)) {
304
-					$this->header('X-OC-MTime: accepted');
305
-				}
306
-			}
307
-
308
-			$fileInfoUpdate = [
309
-				'upload_time' => time()
310
-			];
311
-
312
-			// allow sync clients to send the creation time along in a header
313
-			if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
314
-				$ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
315
-				$fileInfoUpdate['creation_time'] = $ctime;
316
-				$this->header('X-OC-CTime: accepted');
317
-			}
318
-
319
-			$this->fileView->putFileInfo($this->path, $fileInfoUpdate);
320
-
321
-			if ($view) {
322
-				$this->emitPostHooks($exists);
323
-			}
324
-
325
-			$this->refreshInfo();
326
-
327
-			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
328
-				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
329
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
330
-				$this->refreshInfo();
331
-			} else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
332
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
333
-				$this->refreshInfo();
334
-			}
335
-
336
-		} catch (StorageNotAvailableException $e) {
337
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
338
-		}
339
-
340
-		return '"' . $this->info->getEtag() . '"';
341
-	}
342
-
343
-	private function getPartFileBasePath($path) {
344
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
345
-		if ($partFileInStorage) {
346
-			return $path;
347
-		} else {
348
-			return md5($path); // will place it in the root of the view with a unique name
349
-		}
350
-	}
351
-
352
-	/**
353
-	 * @param string $path
354
-	 */
355
-	private function emitPreHooks($exists, $path = null) {
356
-		if (is_null($path)) {
357
-			$path = $this->path;
358
-		}
359
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
360
-		$run = true;
361
-
362
-		if (!$exists) {
363
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
364
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
365
-				\OC\Files\Filesystem::signal_param_run => &$run,
366
-			]);
367
-		} else {
368
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
369
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
370
-				\OC\Files\Filesystem::signal_param_run => &$run,
371
-			]);
372
-		}
373
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
374
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
375
-			\OC\Files\Filesystem::signal_param_run => &$run,
376
-		]);
377
-		return $run;
378
-	}
379
-
380
-	/**
381
-	 * @param string $path
382
-	 */
383
-	private function emitPostHooks($exists, $path = null) {
384
-		if (is_null($path)) {
385
-			$path = $this->path;
386
-		}
387
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
388
-		if (!$exists) {
389
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
390
-				\OC\Files\Filesystem::signal_param_path => $hookPath
391
-			]);
392
-		} else {
393
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
394
-				\OC\Files\Filesystem::signal_param_path => $hookPath
395
-			]);
396
-		}
397
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
398
-			\OC\Files\Filesystem::signal_param_path => $hookPath
399
-		]);
400
-	}
401
-
402
-	/**
403
-	 * Returns the data
404
-	 *
405
-	 * @return resource
406
-	 * @throws Forbidden
407
-	 * @throws ServiceUnavailable
408
-	 */
409
-	public function get() {
410
-		//throw exception if encryption is disabled but files are still encrypted
411
-		try {
412
-			if (!$this->info->isReadable()) {
413
-				// do a if the file did not exist
414
-				throw new NotFound();
415
-			}
416
-			try {
417
-				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
418
-			} catch (\Exception $e) {
419
-				$this->convertToSabreException($e);
420
-			}
421
-			if ($res === false) {
422
-				throw new ServiceUnavailable("Could not open file");
423
-			}
424
-			return $res;
425
-		} catch (GenericEncryptionException $e) {
426
-			// returning 503 will allow retry of the operation at a later point in time
427
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
428
-		} catch (StorageNotAvailableException $e) {
429
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
430
-		} catch (ForbiddenException $ex) {
431
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
432
-		} catch (LockedException $e) {
433
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
434
-		}
435
-	}
436
-
437
-	/**
438
-	 * Delete the current file
439
-	 *
440
-	 * @throws Forbidden
441
-	 * @throws ServiceUnavailable
442
-	 */
443
-	public function delete() {
444
-		if (!$this->info->isDeletable()) {
445
-			throw new Forbidden();
446
-		}
447
-
448
-		try {
449
-			if (!$this->fileView->unlink($this->path)) {
450
-				// assume it wasn't possible to delete due to permissions
451
-				throw new Forbidden();
452
-			}
453
-		} catch (StorageNotAvailableException $e) {
454
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
455
-		} catch (ForbiddenException $ex) {
456
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
457
-		} catch (LockedException $e) {
458
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
459
-		}
460
-	}
461
-
462
-	/**
463
-	 * Returns the mime-type for a file
464
-	 *
465
-	 * If null is returned, we'll assume application/octet-stream
466
-	 *
467
-	 * @return string
468
-	 */
469
-	public function getContentType() {
470
-		$mimeType = $this->info->getMimetype();
471
-
472
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
473
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
474
-			return $mimeType;
475
-		}
476
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
477
-	}
478
-
479
-	/**
480
-	 * @return array|false
481
-	 */
482
-	public function getDirectDownload() {
483
-		if (\OCP\App::isEnabled('encryption')) {
484
-			return [];
485
-		}
486
-		/** @var \OCP\Files\Storage $storage */
487
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
488
-		if (is_null($storage)) {
489
-			return [];
490
-		}
491
-
492
-		return $storage->getDirectDownload($internalPath);
493
-	}
494
-
495
-	/**
496
-	 * @param resource $data
497
-	 * @return null|string
498
-	 * @throws Exception
499
-	 * @throws BadRequest
500
-	 * @throws NotImplemented
501
-	 * @throws ServiceUnavailable
502
-	 */
503
-	private function createFileChunked($data) {
504
-		list($path, $name) = \Sabre\Uri\split($this->path);
505
-
506
-		$info = \OC_FileChunking::decodeName($name);
507
-		if (empty($info)) {
508
-			throw new NotImplemented('Invalid chunk name');
509
-		}
510
-
511
-		$chunk_handler = new \OC_FileChunking($info);
512
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
513
-
514
-		//detect aborted upload
515
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
516
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
517
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
518
-				if ($bytesWritten !== $expected) {
519
-					$chunk_handler->remove($info['index']);
520
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
521
-				}
522
-			}
523
-		}
524
-
525
-		if ($chunk_handler->isComplete()) {
526
-			/** @var Storage $storage */
527
-			list($storage,) = $this->fileView->resolvePath($path);
528
-			$needsPartFile = $storage->needsPartFile();
529
-			$partFile = null;
530
-
531
-			$targetPath = $path . '/' . $info['name'];
532
-			/** @var \OC\Files\Storage\Storage $targetStorage */
533
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
534
-
535
-			$exists = $this->fileView->file_exists($targetPath);
536
-
537
-			try {
538
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
539
-
540
-				$this->emitPreHooks($exists, $targetPath);
541
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
542
-				/** @var \OC\Files\Storage\Storage $targetStorage */
543
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
544
-
545
-				if ($needsPartFile) {
546
-					// we first assembly the target file as a part file
547
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
548
-					/** @var \OC\Files\Storage\Storage $targetStorage */
549
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
550
-
551
-
552
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
553
-
554
-					// here is the final atomic rename
555
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
556
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
557
-					if ($renameOkay === false || $fileExists === false) {
558
-						\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
559
-						// only delete if an error occurred and the target file was already created
560
-						if ($fileExists) {
561
-							// set to null to avoid double-deletion when handling exception
562
-							// stray part file
563
-							$partFile = null;
564
-							$targetStorage->unlink($targetInternalPath);
565
-						}
566
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
567
-						throw new Exception('Could not rename part file assembled from chunks');
568
-					}
569
-				} else {
570
-					// assemble directly into the final file
571
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
572
-				}
573
-
574
-				// allow sync clients to send the mtime along in a header
575
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
576
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
577
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
578
-						$this->header('X-OC-MTime: accepted');
579
-					}
580
-				}
581
-
582
-				// since we skipped the view we need to scan and emit the hooks ourselves
583
-				$targetStorage->getUpdater()->update($targetInternalPath);
584
-
585
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
586
-
587
-				$this->emitPostHooks($exists, $targetPath);
588
-
589
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
590
-				$info = $this->fileView->getFileInfo($targetPath);
591
-
592
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
593
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
594
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
595
-				} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
596
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
597
-				}
598
-
599
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
600
-
601
-				return $info->getEtag();
602
-			} catch (\Exception $e) {
603
-				if ($partFile !== null) {
604
-					$targetStorage->unlink($targetInternalPath);
605
-				}
606
-				$this->convertToSabreException($e);
607
-			}
608
-		}
609
-
610
-		return null;
611
-	}
612
-
613
-	/**
614
-	 * Convert the given exception to a SabreException instance
615
-	 *
616
-	 * @param \Exception $e
617
-	 *
618
-	 * @throws \Sabre\DAV\Exception
619
-	 */
620
-	private function convertToSabreException(\Exception $e) {
621
-		if ($e instanceof \Sabre\DAV\Exception) {
622
-			throw $e;
623
-		}
624
-		if ($e instanceof NotPermittedException) {
625
-			// a more general case - due to whatever reason the content could not be written
626
-			throw new Forbidden($e->getMessage(), 0, $e);
627
-		}
628
-		if ($e instanceof ForbiddenException) {
629
-			// the path for the file was forbidden
630
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
631
-		}
632
-		if ($e instanceof EntityTooLargeException) {
633
-			// the file is too big to be stored
634
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
635
-		}
636
-		if ($e instanceof InvalidContentException) {
637
-			// the file content is not permitted
638
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
639
-		}
640
-		if ($e instanceof InvalidPathException) {
641
-			// the path for the file was not valid
642
-			// TODO: find proper http status code for this case
643
-			throw new Forbidden($e->getMessage(), 0, $e);
644
-		}
645
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
646
-			// the file is currently being written to by another process
647
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
648
-		}
649
-		if ($e instanceof GenericEncryptionException) {
650
-			// returning 503 will allow retry of the operation at a later point in time
651
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
652
-		}
653
-		if ($e instanceof StorageNotAvailableException) {
654
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
655
-		}
656
-		if ($e instanceof NotFoundException) {
657
-			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
658
-		}
659
-
660
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
661
-	}
662
-
663
-	/**
664
-	 * Get the checksum for this file
665
-	 *
666
-	 * @return string
667
-	 */
668
-	public function getChecksum() {
669
-		return $this->info->getChecksum();
670
-	}
671
-
672
-	protected function header($string) {
673
-		\header($string);
674
-	}
74
+    protected $request;
75
+
76
+    /**
77
+     * Sets up the node, expects a full path name
78
+     *
79
+     * @param \OC\Files\View $view
80
+     * @param \OCP\Files\FileInfo $info
81
+     * @param \OCP\Share\IManager $shareManager
82
+     * @param \OC\AppFramework\Http\Request $request
83
+     */
84
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
85
+        parent::__construct($view, $info, $shareManager);
86
+
87
+        if (isset($request)) {
88
+            $this->request = $request;
89
+        } else {
90
+            $this->request = \OC::$server->getRequest();
91
+        }
92
+    }
93
+
94
+    /**
95
+     * Updates the data
96
+     *
97
+     * The data argument is a readable stream resource.
98
+     *
99
+     * After a successful put operation, you may choose to return an ETag. The
100
+     * etag must always be surrounded by double-quotes. These quotes must
101
+     * appear in the actual string you're returning.
102
+     *
103
+     * Clients may use the ETag from a PUT request to later on make sure that
104
+     * when they update the file, the contents haven't changed in the mean
105
+     * time.
106
+     *
107
+     * If you don't plan to store the file byte-by-byte, and you return a
108
+     * different object on a subsequent GET you are strongly recommended to not
109
+     * return an ETag, and just return null.
110
+     *
111
+     * @param resource $data
112
+     *
113
+     * @throws Forbidden
114
+     * @throws UnsupportedMediaType
115
+     * @throws BadRequest
116
+     * @throws Exception
117
+     * @throws EntityTooLarge
118
+     * @throws ServiceUnavailable
119
+     * @throws FileLocked
120
+     * @return string|null
121
+     */
122
+    public function put($data) {
123
+        try {
124
+            $exists = $this->fileView->file_exists($this->path);
125
+            if ($this->info && $exists && !$this->info->isUpdateable()) {
126
+                throw new Forbidden();
127
+            }
128
+        } catch (StorageNotAvailableException $e) {
129
+            throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
130
+        }
131
+
132
+        // verify path of the target
133
+        $this->verifyPath();
134
+
135
+        // chunked handling
136
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
137
+            try {
138
+                return $this->createFileChunked($data);
139
+            } catch (\Exception $e) {
140
+                $this->convertToSabreException($e);
141
+            }
142
+        }
143
+
144
+        /** @var Storage $partStorage */
145
+        list($partStorage) = $this->fileView->resolvePath($this->path);
146
+        $needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
147
+
148
+        $view = \OC\Files\Filesystem::getView();
149
+
150
+        if ($needsPartFile) {
151
+            // mark file as partial while uploading (ignored by the scanner)
152
+            $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
153
+
154
+            if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
155
+                $needsPartFile = false;
156
+            }
157
+        }
158
+        if (!$needsPartFile) {
159
+            // upload file directly as the final path
160
+            $partFilePath = $this->path;
161
+
162
+            if ($view && !$this->emitPreHooks($exists)) {
163
+                throw new Exception('Could not write to final file, canceled by hook');
164
+            }
165
+        }
166
+
167
+        // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
168
+        /** @var \OC\Files\Storage\Storage $partStorage */
169
+        list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
170
+        /** @var \OC\Files\Storage\Storage $storage */
171
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
172
+        try {
173
+            if (!$needsPartFile) {
174
+                $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
175
+            }
176
+
177
+            if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
178
+
179
+                if (!is_resource($data)) {
180
+                    $tmpData = fopen('php://temp', 'r+');
181
+                    if ($data !== null) {
182
+                        fwrite($tmpData, $data);
183
+                        rewind($tmpData);
184
+                    }
185
+                    $data = $tmpData;
186
+                }
187
+
188
+                $isEOF = false;
189
+                $wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
190
+                    $isEOF = feof($stream);
191
+                });
192
+
193
+                $count = $partStorage->writeStream($internalPartPath, $wrappedData);
194
+                $result = $count > 0;
195
+
196
+                if ($result === false) {
197
+                    $result = $isEOF;
198
+                    if (is_resource($wrappedData)) {
199
+                        $result = feof($wrappedData);
200
+                    }
201
+                }
202
+
203
+            } else {
204
+                $target = $partStorage->fopen($internalPartPath, 'wb');
205
+                if ($target === false) {
206
+                    \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
207
+                    // because we have no clue about the cause we can only throw back a 500/Internal Server Error
208
+                    throw new Exception('Could not write file contents');
209
+                }
210
+                list($count, $result) = \OC_Helper::streamCopy($data, $target);
211
+                fclose($target);
212
+            }
213
+
214
+            if ($result === false) {
215
+                $expected = -1;
216
+                if (isset($_SERVER['CONTENT_LENGTH'])) {
217
+                    $expected = $_SERVER['CONTENT_LENGTH'];
218
+                }
219
+                if ($expected !== "0") {
220
+                    throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
221
+                }
222
+            }
223
+
224
+            // if content length is sent by client:
225
+            // double check if the file was fully received
226
+            // compare expected and actual size
227
+            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
228
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
229
+                if ($count !== $expected) {
230
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
231
+                }
232
+            }
233
+
234
+        } catch (\Exception $e) {
235
+            $context = [];
236
+
237
+            if ($e instanceof LockedException) {
238
+                $context['level'] = ILogger::DEBUG;
239
+            }
240
+
241
+            \OC::$server->getLogger()->logException($e, $context);
242
+            if ($needsPartFile) {
243
+                $partStorage->unlink($internalPartPath);
244
+            }
245
+            $this->convertToSabreException($e);
246
+        }
247
+
248
+        try {
249
+            if ($needsPartFile) {
250
+                if ($view && !$this->emitPreHooks($exists)) {
251
+                    $partStorage->unlink($internalPartPath);
252
+                    throw new Exception('Could not rename part file to final file, canceled by hook');
253
+                }
254
+                try {
255
+                    $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
256
+                } catch (LockedException $e) {
257
+                    // during very large uploads, the shared lock we got at the start might have been expired
258
+                    // meaning that the above lock can fail not just only because somebody else got a shared lock
259
+                    // or because there is no existing shared lock to make exclusive
260
+                    //
261
+                    // Thus we try to get a new exclusive lock, if the original lock failed because of a different shared
262
+                    // lock this will still fail, if our original shared lock expired the new lock will be successful and
263
+                    // the entire operation will be safe
264
+
265
+                    try {
266
+                        $this->acquireLock(ILockingProvider::LOCK_EXCLUSIVE);
267
+                    } catch (LockedException $ex) {
268
+                        if ($needsPartFile) {
269
+                            $partStorage->unlink($internalPartPath);
270
+                        }
271
+                        throw new FileLocked($e->getMessage(), $e->getCode(), $e);
272
+                    }
273
+                }
274
+
275
+                // rename to correct path
276
+                try {
277
+                    $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
278
+                    $fileExists = $storage->file_exists($internalPath);
279
+                    if ($renameOkay === false || $fileExists === false) {
280
+                        \OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
281
+                        throw new Exception('Could not rename part file to final file');
282
+                    }
283
+                } catch (ForbiddenException $ex) {
284
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
285
+                } catch (\Exception $e) {
286
+                    $partStorage->unlink($internalPartPath);
287
+                    $this->convertToSabreException($e);
288
+                }
289
+            }
290
+
291
+            // since we skipped the view we need to scan and emit the hooks ourselves
292
+            $storage->getUpdater()->update($internalPath);
293
+
294
+            try {
295
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
296
+            } catch (LockedException $e) {
297
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
298
+            }
299
+
300
+            // allow sync clients to send the mtime along in a header
301
+            if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
302
+                $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
303
+                if ($this->fileView->touch($this->path, $mtime)) {
304
+                    $this->header('X-OC-MTime: accepted');
305
+                }
306
+            }
307
+
308
+            $fileInfoUpdate = [
309
+                'upload_time' => time()
310
+            ];
311
+
312
+            // allow sync clients to send the creation time along in a header
313
+            if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
314
+                $ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
315
+                $fileInfoUpdate['creation_time'] = $ctime;
316
+                $this->header('X-OC-CTime: accepted');
317
+            }
318
+
319
+            $this->fileView->putFileInfo($this->path, $fileInfoUpdate);
320
+
321
+            if ($view) {
322
+                $this->emitPostHooks($exists);
323
+            }
324
+
325
+            $this->refreshInfo();
326
+
327
+            if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
328
+                $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
329
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
330
+                $this->refreshInfo();
331
+            } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
332
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
333
+                $this->refreshInfo();
334
+            }
335
+
336
+        } catch (StorageNotAvailableException $e) {
337
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
338
+        }
339
+
340
+        return '"' . $this->info->getEtag() . '"';
341
+    }
342
+
343
+    private function getPartFileBasePath($path) {
344
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
345
+        if ($partFileInStorage) {
346
+            return $path;
347
+        } else {
348
+            return md5($path); // will place it in the root of the view with a unique name
349
+        }
350
+    }
351
+
352
+    /**
353
+     * @param string $path
354
+     */
355
+    private function emitPreHooks($exists, $path = null) {
356
+        if (is_null($path)) {
357
+            $path = $this->path;
358
+        }
359
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
360
+        $run = true;
361
+
362
+        if (!$exists) {
363
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
364
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
365
+                \OC\Files\Filesystem::signal_param_run => &$run,
366
+            ]);
367
+        } else {
368
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
369
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
370
+                \OC\Files\Filesystem::signal_param_run => &$run,
371
+            ]);
372
+        }
373
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
374
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
375
+            \OC\Files\Filesystem::signal_param_run => &$run,
376
+        ]);
377
+        return $run;
378
+    }
379
+
380
+    /**
381
+     * @param string $path
382
+     */
383
+    private function emitPostHooks($exists, $path = null) {
384
+        if (is_null($path)) {
385
+            $path = $this->path;
386
+        }
387
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
388
+        if (!$exists) {
389
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
390
+                \OC\Files\Filesystem::signal_param_path => $hookPath
391
+            ]);
392
+        } else {
393
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
394
+                \OC\Files\Filesystem::signal_param_path => $hookPath
395
+            ]);
396
+        }
397
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
398
+            \OC\Files\Filesystem::signal_param_path => $hookPath
399
+        ]);
400
+    }
401
+
402
+    /**
403
+     * Returns the data
404
+     *
405
+     * @return resource
406
+     * @throws Forbidden
407
+     * @throws ServiceUnavailable
408
+     */
409
+    public function get() {
410
+        //throw exception if encryption is disabled but files are still encrypted
411
+        try {
412
+            if (!$this->info->isReadable()) {
413
+                // do a if the file did not exist
414
+                throw new NotFound();
415
+            }
416
+            try {
417
+                $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
418
+            } catch (\Exception $e) {
419
+                $this->convertToSabreException($e);
420
+            }
421
+            if ($res === false) {
422
+                throw new ServiceUnavailable("Could not open file");
423
+            }
424
+            return $res;
425
+        } catch (GenericEncryptionException $e) {
426
+            // returning 503 will allow retry of the operation at a later point in time
427
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
428
+        } catch (StorageNotAvailableException $e) {
429
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
430
+        } catch (ForbiddenException $ex) {
431
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
432
+        } catch (LockedException $e) {
433
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
434
+        }
435
+    }
436
+
437
+    /**
438
+     * Delete the current file
439
+     *
440
+     * @throws Forbidden
441
+     * @throws ServiceUnavailable
442
+     */
443
+    public function delete() {
444
+        if (!$this->info->isDeletable()) {
445
+            throw new Forbidden();
446
+        }
447
+
448
+        try {
449
+            if (!$this->fileView->unlink($this->path)) {
450
+                // assume it wasn't possible to delete due to permissions
451
+                throw new Forbidden();
452
+            }
453
+        } catch (StorageNotAvailableException $e) {
454
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
455
+        } catch (ForbiddenException $ex) {
456
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
457
+        } catch (LockedException $e) {
458
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
459
+        }
460
+    }
461
+
462
+    /**
463
+     * Returns the mime-type for a file
464
+     *
465
+     * If null is returned, we'll assume application/octet-stream
466
+     *
467
+     * @return string
468
+     */
469
+    public function getContentType() {
470
+        $mimeType = $this->info->getMimetype();
471
+
472
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
473
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
474
+            return $mimeType;
475
+        }
476
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
477
+    }
478
+
479
+    /**
480
+     * @return array|false
481
+     */
482
+    public function getDirectDownload() {
483
+        if (\OCP\App::isEnabled('encryption')) {
484
+            return [];
485
+        }
486
+        /** @var \OCP\Files\Storage $storage */
487
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
488
+        if (is_null($storage)) {
489
+            return [];
490
+        }
491
+
492
+        return $storage->getDirectDownload($internalPath);
493
+    }
494
+
495
+    /**
496
+     * @param resource $data
497
+     * @return null|string
498
+     * @throws Exception
499
+     * @throws BadRequest
500
+     * @throws NotImplemented
501
+     * @throws ServiceUnavailable
502
+     */
503
+    private function createFileChunked($data) {
504
+        list($path, $name) = \Sabre\Uri\split($this->path);
505
+
506
+        $info = \OC_FileChunking::decodeName($name);
507
+        if (empty($info)) {
508
+            throw new NotImplemented('Invalid chunk name');
509
+        }
510
+
511
+        $chunk_handler = new \OC_FileChunking($info);
512
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
513
+
514
+        //detect aborted upload
515
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
516
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
517
+                $expected = (int)$_SERVER['CONTENT_LENGTH'];
518
+                if ($bytesWritten !== $expected) {
519
+                    $chunk_handler->remove($info['index']);
520
+                    throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
521
+                }
522
+            }
523
+        }
524
+
525
+        if ($chunk_handler->isComplete()) {
526
+            /** @var Storage $storage */
527
+            list($storage,) = $this->fileView->resolvePath($path);
528
+            $needsPartFile = $storage->needsPartFile();
529
+            $partFile = null;
530
+
531
+            $targetPath = $path . '/' . $info['name'];
532
+            /** @var \OC\Files\Storage\Storage $targetStorage */
533
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
534
+
535
+            $exists = $this->fileView->file_exists($targetPath);
536
+
537
+            try {
538
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
539
+
540
+                $this->emitPreHooks($exists, $targetPath);
541
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
542
+                /** @var \OC\Files\Storage\Storage $targetStorage */
543
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
544
+
545
+                if ($needsPartFile) {
546
+                    // we first assembly the target file as a part file
547
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
548
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
549
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
550
+
551
+
552
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
553
+
554
+                    // here is the final atomic rename
555
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
556
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
557
+                    if ($renameOkay === false || $fileExists === false) {
558
+                        \OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
559
+                        // only delete if an error occurred and the target file was already created
560
+                        if ($fileExists) {
561
+                            // set to null to avoid double-deletion when handling exception
562
+                            // stray part file
563
+                            $partFile = null;
564
+                            $targetStorage->unlink($targetInternalPath);
565
+                        }
566
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
567
+                        throw new Exception('Could not rename part file assembled from chunks');
568
+                    }
569
+                } else {
570
+                    // assemble directly into the final file
571
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
572
+                }
573
+
574
+                // allow sync clients to send the mtime along in a header
575
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
576
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
577
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
578
+                        $this->header('X-OC-MTime: accepted');
579
+                    }
580
+                }
581
+
582
+                // since we skipped the view we need to scan and emit the hooks ourselves
583
+                $targetStorage->getUpdater()->update($targetInternalPath);
584
+
585
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
586
+
587
+                $this->emitPostHooks($exists, $targetPath);
588
+
589
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
590
+                $info = $this->fileView->getFileInfo($targetPath);
591
+
592
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
593
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
594
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
595
+                } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
596
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
597
+                }
598
+
599
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
600
+
601
+                return $info->getEtag();
602
+            } catch (\Exception $e) {
603
+                if ($partFile !== null) {
604
+                    $targetStorage->unlink($targetInternalPath);
605
+                }
606
+                $this->convertToSabreException($e);
607
+            }
608
+        }
609
+
610
+        return null;
611
+    }
612
+
613
+    /**
614
+     * Convert the given exception to a SabreException instance
615
+     *
616
+     * @param \Exception $e
617
+     *
618
+     * @throws \Sabre\DAV\Exception
619
+     */
620
+    private function convertToSabreException(\Exception $e) {
621
+        if ($e instanceof \Sabre\DAV\Exception) {
622
+            throw $e;
623
+        }
624
+        if ($e instanceof NotPermittedException) {
625
+            // a more general case - due to whatever reason the content could not be written
626
+            throw new Forbidden($e->getMessage(), 0, $e);
627
+        }
628
+        if ($e instanceof ForbiddenException) {
629
+            // the path for the file was forbidden
630
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
631
+        }
632
+        if ($e instanceof EntityTooLargeException) {
633
+            // the file is too big to be stored
634
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
635
+        }
636
+        if ($e instanceof InvalidContentException) {
637
+            // the file content is not permitted
638
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
639
+        }
640
+        if ($e instanceof InvalidPathException) {
641
+            // the path for the file was not valid
642
+            // TODO: find proper http status code for this case
643
+            throw new Forbidden($e->getMessage(), 0, $e);
644
+        }
645
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
646
+            // the file is currently being written to by another process
647
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
648
+        }
649
+        if ($e instanceof GenericEncryptionException) {
650
+            // returning 503 will allow retry of the operation at a later point in time
651
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
652
+        }
653
+        if ($e instanceof StorageNotAvailableException) {
654
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
655
+        }
656
+        if ($e instanceof NotFoundException) {
657
+            throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
658
+        }
659
+
660
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
661
+    }
662
+
663
+    /**
664
+     * Get the checksum for this file
665
+     *
666
+     * @return string
667
+     */
668
+    public function getChecksum() {
669
+        return $this->info->getChecksum();
670
+    }
671
+
672
+    protected function header($string) {
673
+        \header($string);
674
+    }
675 675
 }
Please login to merge, or discard this patch.
Spacing   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -126,7 +126,7 @@  discard block
 block discarded – undo
126 126
 				throw new Forbidden();
127 127
 			}
128 128
 		} catch (StorageNotAvailableException $e) {
129
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
129
+			throw new ServiceUnavailable("File is not updatable: ".$e->getMessage());
130 130
 		}
131 131
 
132 132
 		// verify path of the target
@@ -149,7 +149,7 @@  discard block
 block discarded – undo
149 149
 
150 150
 		if ($needsPartFile) {
151 151
 			// mark file as partial while uploading (ignored by the scanner)
152
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
152
+			$partFilePath = $this->getPartFileBasePath($this->path).'.ocTransferId'.rand().'.part';
153 153
 
154 154
 			if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
155 155
 				$needsPartFile = false;
@@ -186,7 +186,7 @@  discard block
 block discarded – undo
186 186
 				}
187 187
 
188 188
 				$isEOF = false;
189
-				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
189
+				$wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function($stream) use (&$isEOF) {
190 190
 					$isEOF = feof($stream);
191 191
 				});
192 192
 
@@ -217,7 +217,7 @@  discard block
 block discarded – undo
217 217
 					$expected = $_SERVER['CONTENT_LENGTH'];
218 218
 				}
219 219
 				if ($expected !== "0") {
220
-					throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
220
+					throw new Exception('Error while copying file to target location (copied bytes: '.$count.', expected filesize: '.$expected.' )');
221 221
 				}
222 222
 			}
223 223
 
@@ -225,9 +225,9 @@  discard block
 block discarded – undo
225 225
 			// double check if the file was fully received
226 226
 			// compare expected and actual size
227 227
 			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
228
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
228
+				$expected = (int) $_SERVER['CONTENT_LENGTH'];
229 229
 				if ($count !== $expected) {
230
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $count . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
230
+					throw new BadRequest('Expected filesize of '.$expected.' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) '.$count.' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
231 231
 				}
232 232
 			}
233 233
 
@@ -277,7 +277,7 @@  discard block
 block discarded – undo
277 277
 					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
278 278
 					$fileExists = $storage->file_exists($internalPath);
279 279
 					if ($renameOkay === false || $fileExists === false) {
280
-						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
280
+						\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: '.($renameOkay ? 'true' : 'false').', $fileExists: '.($fileExists ? 'true' : 'false').')', ['app' => 'webdav']);
281 281
 						throw new Exception('Could not rename part file to final file');
282 282
 					}
283 283
 				} catch (ForbiddenException $ex) {
@@ -334,10 +334,10 @@  discard block
 block discarded – undo
334 334
 			}
335 335
 
336 336
 		} catch (StorageNotAvailableException $e) {
337
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage(), 0, $e);
337
+			throw new ServiceUnavailable("Failed to check file size: ".$e->getMessage(), 0, $e);
338 338
 		}
339 339
 
340
-		return '"' . $this->info->getEtag() . '"';
340
+		return '"'.$this->info->getEtag().'"';
341 341
 	}
342 342
 
343 343
 	private function getPartFileBasePath($path) {
@@ -424,9 +424,9 @@  discard block
 block discarded – undo
424 424
 			return $res;
425 425
 		} catch (GenericEncryptionException $e) {
426 426
 			// returning 503 will allow retry of the operation at a later point in time
427
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
427
+			throw new ServiceUnavailable("Encryption not ready: ".$e->getMessage());
428 428
 		} catch (StorageNotAvailableException $e) {
429
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
429
+			throw new ServiceUnavailable("Failed to open file: ".$e->getMessage());
430 430
 		} catch (ForbiddenException $ex) {
431 431
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
432 432
 		} catch (LockedException $e) {
@@ -451,7 +451,7 @@  discard block
 block discarded – undo
451 451
 				throw new Forbidden();
452 452
 			}
453 453
 		} catch (StorageNotAvailableException $e) {
454
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
454
+			throw new ServiceUnavailable("Failed to unlink: ".$e->getMessage());
455 455
 		} catch (ForbiddenException $ex) {
456 456
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
457 457
 		} catch (LockedException $e) {
@@ -514,10 +514,10 @@  discard block
 block discarded – undo
514 514
 		//detect aborted upload
515 515
 		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
516 516
 			if (isset($_SERVER['CONTENT_LENGTH'])) {
517
-				$expected = (int)$_SERVER['CONTENT_LENGTH'];
517
+				$expected = (int) $_SERVER['CONTENT_LENGTH'];
518 518
 				if ($bytesWritten !== $expected) {
519 519
 					$chunk_handler->remove($info['index']);
520
-					throw new BadRequest('Expected filesize of ' . $expected . ' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) ' . $bytesWritten . ' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
520
+					throw new BadRequest('Expected filesize of '.$expected.' bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) '.$bytesWritten.' bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.');
521 521
 				}
522 522
 			}
523 523
 		}
@@ -528,7 +528,7 @@  discard block
 block discarded – undo
528 528
 			$needsPartFile = $storage->needsPartFile();
529 529
 			$partFile = null;
530 530
 
531
-			$targetPath = $path . '/' . $info['name'];
531
+			$targetPath = $path.'/'.$info['name'];
532 532
 			/** @var \OC\Files\Storage\Storage $targetStorage */
533 533
 			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
534 534
 
@@ -544,7 +544,7 @@  discard block
 block discarded – undo
544 544
 
545 545
 				if ($needsPartFile) {
546 546
 					// we first assembly the target file as a part file
547
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
547
+					$partFile = $this->getPartFileBasePath($path.'/'.$info['name']).'.ocTransferId'.$info['transferid'].'.part';
548 548
 					/** @var \OC\Files\Storage\Storage $targetStorage */
549 549
 					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
550 550
 
@@ -648,13 +648,13 @@  discard block
 block discarded – undo
648 648
 		}
649 649
 		if ($e instanceof GenericEncryptionException) {
650 650
 			// returning 503 will allow retry of the operation at a later point in time
651
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
651
+			throw new ServiceUnavailable('Encryption not ready: '.$e->getMessage(), 0, $e);
652 652
 		}
653 653
 		if ($e instanceof StorageNotAvailableException) {
654
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
654
+			throw new ServiceUnavailable('Failed to write file contents: '.$e->getMessage(), 0, $e);
655 655
 		}
656 656
 		if ($e instanceof NotFoundException) {
657
-			throw new NotFound('File not found: ' . $e->getMessage(), 0, $e);
657
+			throw new NotFound('File not found: '.$e->getMessage(), 0, $e);
658 658
 		}
659 659
 
660 660
 		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/CardDavBackend.php 2 patches
Indentation   +1118 added lines, -1118 removed lines patch added patch discarded remove patch
@@ -56,1122 +56,1122 @@
 block discarded – undo
56 56
 
57 57
 class CardDavBackend implements BackendInterface, SyncSupport {
58 58
 
59
-	const PERSONAL_ADDRESSBOOK_URI = 'contacts';
60
-	const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
61
-
62
-	/** @var Principal */
63
-	private $principalBackend;
64
-
65
-	/** @var string */
66
-	private $dbCardsTable = 'cards';
67
-
68
-	/** @var string */
69
-	private $dbCardsPropertiesTable = 'cards_properties';
70
-
71
-	/** @var IDBConnection */
72
-	private $db;
73
-
74
-	/** @var Backend */
75
-	private $sharingBackend;
76
-
77
-	/** @var array properties to index */
78
-	public static $indexProperties = [
79
-		'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
80
-		'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
81
-
82
-	/**
83
-	 * @var string[] Map of uid => display name
84
-	 */
85
-	protected $userDisplayNames;
86
-
87
-	/** @var IUserManager */
88
-	private $userManager;
89
-
90
-	/** @var EventDispatcherInterface */
91
-	private $dispatcher;
92
-
93
-	/**
94
-	 * CardDavBackend constructor.
95
-	 *
96
-	 * @param IDBConnection $db
97
-	 * @param Principal $principalBackend
98
-	 * @param IUserManager $userManager
99
-	 * @param IGroupManager $groupManager
100
-	 * @param EventDispatcherInterface $dispatcher
101
-	 */
102
-	public function __construct(IDBConnection $db,
103
-								Principal $principalBackend,
104
-								IUserManager $userManager,
105
-								IGroupManager $groupManager,
106
-								EventDispatcherInterface $dispatcher) {
107
-		$this->db = $db;
108
-		$this->principalBackend = $principalBackend;
109
-		$this->userManager = $userManager;
110
-		$this->dispatcher = $dispatcher;
111
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
112
-	}
113
-
114
-	/**
115
-	 * Return the number of address books for a principal
116
-	 *
117
-	 * @param $principalUri
118
-	 * @return int
119
-	 */
120
-	public function getAddressBooksForUserCount($principalUri) {
121
-		$principalUri = $this->convertPrincipal($principalUri, true);
122
-		$query = $this->db->getQueryBuilder();
123
-		$query->select($query->func()->count('*'))
124
-			->from('addressbooks')
125
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
126
-
127
-		return (int)$query->execute()->fetchColumn();
128
-	}
129
-
130
-	/**
131
-	 * Returns the list of address books for a specific user.
132
-	 *
133
-	 * Every addressbook should have the following properties:
134
-	 *   id - an arbitrary unique id
135
-	 *   uri - the 'basename' part of the url
136
-	 *   principaluri - Same as the passed parameter
137
-	 *
138
-	 * Any additional clark-notation property may be passed besides this. Some
139
-	 * common ones are :
140
-	 *   {DAV:}displayname
141
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
142
-	 *   {http://calendarserver.org/ns/}getctag
143
-	 *
144
-	 * @param string $principalUri
145
-	 * @return array
146
-	 */
147
-	function getAddressBooksForUser($principalUri) {
148
-		$principalUriOriginal = $principalUri;
149
-		$principalUri = $this->convertPrincipal($principalUri, true);
150
-		$query = $this->db->getQueryBuilder();
151
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
152
-			->from('addressbooks')
153
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
154
-
155
-		$addressBooks = [];
156
-
157
-		$result = $query->execute();
158
-		while($row = $result->fetch()) {
159
-			$addressBooks[$row['id']] = [
160
-				'id'  => $row['id'],
161
-				'uri' => $row['uri'],
162
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
163
-				'{DAV:}displayname' => $row['displayname'],
164
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
165
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
166
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
167
-			];
168
-
169
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
170
-		}
171
-		$result->closeCursor();
172
-
173
-		// query for shared addressbooks
174
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
175
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
176
-
177
-		$principals = array_map(function ($principal) {
178
-			return urldecode($principal);
179
-		}, $principals);
180
-		$principals[]= $principalUri;
181
-
182
-		$query = $this->db->getQueryBuilder();
183
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
184
-			->from('dav_shares', 's')
185
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
186
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
187
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
188
-			->setParameter('type', 'addressbook')
189
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
190
-			->execute();
191
-
192
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
193
-		while($row = $result->fetch()) {
194
-			if ($row['principaluri'] === $principalUri) {
195
-				continue;
196
-			}
197
-
198
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
199
-			if (isset($addressBooks[$row['id']])) {
200
-				if ($readOnly) {
201
-					// New share can not have more permissions then the old one.
202
-					continue;
203
-				}
204
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
205
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
206
-					// Old share is already read-write, no more permissions can be gained
207
-					continue;
208
-				}
209
-			}
210
-
211
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
212
-			$uri = $row['uri'] . '_shared_by_' . $name;
213
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
214
-
215
-			$addressBooks[$row['id']] = [
216
-				'id'  => $row['id'],
217
-				'uri' => $uri,
218
-				'principaluri' => $principalUriOriginal,
219
-				'{DAV:}displayname' => $displayName,
220
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
221
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
222
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
223
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
224
-				$readOnlyPropertyName => $readOnly,
225
-			];
226
-
227
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
228
-		}
229
-		$result->closeCursor();
230
-
231
-		return array_values($addressBooks);
232
-	}
233
-
234
-	public function getUsersOwnAddressBooks($principalUri) {
235
-		$principalUri = $this->convertPrincipal($principalUri, true);
236
-		$query = $this->db->getQueryBuilder();
237
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
238
-			  ->from('addressbooks')
239
-			  ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
240
-
241
-		$addressBooks = [];
242
-
243
-		$result = $query->execute();
244
-		while($row = $result->fetch()) {
245
-			$addressBooks[$row['id']] = [
246
-				'id'  => $row['id'],
247
-				'uri' => $row['uri'],
248
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
249
-				'{DAV:}displayname' => $row['displayname'],
250
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
251
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
252
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
253
-			];
254
-
255
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
256
-		}
257
-		$result->closeCursor();
258
-
259
-		return array_values($addressBooks);
260
-	}
261
-
262
-	private function getUserDisplayName($uid) {
263
-		if (!isset($this->userDisplayNames[$uid])) {
264
-			$user = $this->userManager->get($uid);
265
-
266
-			if ($user instanceof IUser) {
267
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
268
-			} else {
269
-				$this->userDisplayNames[$uid] = $uid;
270
-			}
271
-		}
272
-
273
-		return $this->userDisplayNames[$uid];
274
-	}
275
-
276
-	/**
277
-	 * @param int $addressBookId
278
-	 */
279
-	public function getAddressBookById($addressBookId) {
280
-		$query = $this->db->getQueryBuilder();
281
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
282
-			->from('addressbooks')
283
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
284
-			->execute();
285
-
286
-		$row = $result->fetch();
287
-		$result->closeCursor();
288
-		if ($row === false) {
289
-			return null;
290
-		}
291
-
292
-		$addressBook = [
293
-			'id'  => $row['id'],
294
-			'uri' => $row['uri'],
295
-			'principaluri' => $row['principaluri'],
296
-			'{DAV:}displayname' => $row['displayname'],
297
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
298
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
299
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
300
-		];
301
-
302
-		$this->addOwnerPrincipal($addressBook);
303
-
304
-		return $addressBook;
305
-	}
306
-
307
-	/**
308
-	 * @param $addressBookUri
309
-	 * @return array|null
310
-	 */
311
-	public function getAddressBooksByUri($principal, $addressBookUri) {
312
-		$query = $this->db->getQueryBuilder();
313
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
314
-			->from('addressbooks')
315
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
316
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
317
-			->setMaxResults(1)
318
-			->execute();
319
-
320
-		$row = $result->fetch();
321
-		$result->closeCursor();
322
-		if ($row === false) {
323
-			return null;
324
-		}
325
-
326
-		$addressBook = [
327
-			'id'  => $row['id'],
328
-			'uri' => $row['uri'],
329
-			'principaluri' => $row['principaluri'],
330
-			'{DAV:}displayname' => $row['displayname'],
331
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
332
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
333
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
334
-		];
335
-
336
-		$this->addOwnerPrincipal($addressBook);
337
-
338
-		return $addressBook;
339
-	}
340
-
341
-	/**
342
-	 * Updates properties for an address book.
343
-	 *
344
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
345
-	 * To do the actual updates, you must tell this object which properties
346
-	 * you're going to process with the handle() method.
347
-	 *
348
-	 * Calling the handle method is like telling the PropPatch object "I
349
-	 * promise I can handle updating this property".
350
-	 *
351
-	 * Read the PropPatch documentation for more info and examples.
352
-	 *
353
-	 * @param string $addressBookId
354
-	 * @param \Sabre\DAV\PropPatch $propPatch
355
-	 * @return void
356
-	 */
357
-	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
358
-		$supportedProperties = [
359
-			'{DAV:}displayname',
360
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
361
-		];
362
-
363
-		/**
364
-		 * @suppress SqlInjectionChecker
365
-		 */
366
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
367
-
368
-			$updates = [];
369
-			foreach($mutations as $property=>$newValue) {
370
-
371
-				switch($property) {
372
-					case '{DAV:}displayname':
373
-						$updates['displayname'] = $newValue;
374
-						break;
375
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
376
-						$updates['description'] = $newValue;
377
-						break;
378
-				}
379
-			}
380
-			$query = $this->db->getQueryBuilder();
381
-			$query->update('addressbooks');
382
-
383
-			foreach($updates as $key=>$value) {
384
-				$query->set($key, $query->createNamedParameter($value));
385
-			}
386
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
387
-			->execute();
388
-
389
-			$this->addChange($addressBookId, "", 2);
390
-
391
-			return true;
392
-
393
-		});
394
-	}
395
-
396
-	/**
397
-	 * Creates a new address book
398
-	 *
399
-	 * @param string $principalUri
400
-	 * @param string $url Just the 'basename' of the url.
401
-	 * @param array $properties
402
-	 * @return int
403
-	 * @throws BadRequest
404
-	 */
405
-	function createAddressBook($principalUri, $url, array $properties) {
406
-		$values = [
407
-			'displayname' => null,
408
-			'description' => null,
409
-			'principaluri' => $principalUri,
410
-			'uri' => $url,
411
-			'synctoken' => 1
412
-		];
413
-
414
-		foreach($properties as $property=>$newValue) {
415
-
416
-			switch($property) {
417
-				case '{DAV:}displayname':
418
-					$values['displayname'] = $newValue;
419
-					break;
420
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
421
-					$values['description'] = $newValue;
422
-					break;
423
-				default:
424
-					throw new BadRequest('Unknown property: ' . $property);
425
-			}
426
-
427
-		}
428
-
429
-		// Fallback to make sure the displayname is set. Some clients may refuse
430
-		// to work with addressbooks not having a displayname.
431
-		if(is_null($values['displayname'])) {
432
-			$values['displayname'] = $url;
433
-		}
434
-
435
-		$query = $this->db->getQueryBuilder();
436
-		$query->insert('addressbooks')
437
-			->values([
438
-				'uri' => $query->createParameter('uri'),
439
-				'displayname' => $query->createParameter('displayname'),
440
-				'description' => $query->createParameter('description'),
441
-				'principaluri' => $query->createParameter('principaluri'),
442
-				'synctoken' => $query->createParameter('synctoken'),
443
-			])
444
-			->setParameters($values)
445
-			->execute();
446
-
447
-		return $query->getLastInsertId();
448
-	}
449
-
450
-	/**
451
-	 * Deletes an entire addressbook and all its contents
452
-	 *
453
-	 * @param mixed $addressBookId
454
-	 * @return void
455
-	 */
456
-	function deleteAddressBook($addressBookId) {
457
-		$query = $this->db->getQueryBuilder();
458
-		$query->delete('cards')
459
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
460
-			->setParameter('addressbookid', $addressBookId)
461
-			->execute();
462
-
463
-		$query->delete('addressbookchanges')
464
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
465
-			->setParameter('addressbookid', $addressBookId)
466
-			->execute();
467
-
468
-		$query->delete('addressbooks')
469
-			->where($query->expr()->eq('id', $query->createParameter('id')))
470
-			->setParameter('id', $addressBookId)
471
-			->execute();
472
-
473
-		$this->sharingBackend->deleteAllShares($addressBookId);
474
-
475
-		$query->delete($this->dbCardsPropertiesTable)
476
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
477
-			->execute();
478
-
479
-	}
480
-
481
-	/**
482
-	 * Returns all cards for a specific addressbook id.
483
-	 *
484
-	 * This method should return the following properties for each card:
485
-	 *   * carddata - raw vcard data
486
-	 *   * uri - Some unique url
487
-	 *   * lastmodified - A unix timestamp
488
-	 *
489
-	 * It's recommended to also return the following properties:
490
-	 *   * etag - A unique etag. This must change every time the card changes.
491
-	 *   * size - The size of the card in bytes.
492
-	 *
493
-	 * If these last two properties are provided, less time will be spent
494
-	 * calculating them. If they are specified, you can also ommit carddata.
495
-	 * This may speed up certain requests, especially with large cards.
496
-	 *
497
-	 * @param mixed $addressBookId
498
-	 * @return array
499
-	 */
500
-	function getCards($addressBookId) {
501
-		$query = $this->db->getQueryBuilder();
502
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
503
-			->from('cards')
504
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
505
-
506
-		$cards = [];
507
-
508
-		$result = $query->execute();
509
-		while($row = $result->fetch()) {
510
-			$row['etag'] = '"' . $row['etag'] . '"';
511
-			$row['carddata'] = $this->readBlob($row['carddata']);
512
-			$cards[] = $row;
513
-		}
514
-		$result->closeCursor();
515
-
516
-		return $cards;
517
-	}
518
-
519
-	/**
520
-	 * Returns a specific card.
521
-	 *
522
-	 * The same set of properties must be returned as with getCards. The only
523
-	 * exception is that 'carddata' is absolutely required.
524
-	 *
525
-	 * If the card does not exist, you must return false.
526
-	 *
527
-	 * @param mixed $addressBookId
528
-	 * @param string $cardUri
529
-	 * @return array
530
-	 */
531
-	function getCard($addressBookId, $cardUri) {
532
-		$query = $this->db->getQueryBuilder();
533
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
534
-			->from('cards')
535
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
536
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
537
-			->setMaxResults(1);
538
-
539
-		$result = $query->execute();
540
-		$row = $result->fetch();
541
-		if (!$row) {
542
-			return false;
543
-		}
544
-		$row['etag'] = '"' . $row['etag'] . '"';
545
-		$row['carddata'] = $this->readBlob($row['carddata']);
546
-
547
-		return $row;
548
-	}
549
-
550
-	/**
551
-	 * Returns a list of cards.
552
-	 *
553
-	 * This method should work identical to getCard, but instead return all the
554
-	 * cards in the list as an array.
555
-	 *
556
-	 * If the backend supports this, it may allow for some speed-ups.
557
-	 *
558
-	 * @param mixed $addressBookId
559
-	 * @param string[] $uris
560
-	 * @return array
561
-	 */
562
-	function getMultipleCards($addressBookId, array $uris) {
563
-		if (empty($uris)) {
564
-			return [];
565
-		}
566
-
567
-		$chunks = array_chunk($uris, 100);
568
-		$cards = [];
569
-
570
-		$query = $this->db->getQueryBuilder();
571
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
572
-			->from('cards')
573
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
574
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
575
-
576
-		foreach ($chunks as $uris) {
577
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
578
-			$result = $query->execute();
579
-
580
-			while ($row = $result->fetch()) {
581
-				$row['etag'] = '"' . $row['etag'] . '"';
582
-				$row['carddata'] = $this->readBlob($row['carddata']);
583
-				$cards[] = $row;
584
-			}
585
-			$result->closeCursor();
586
-		}
587
-		return $cards;
588
-	}
589
-
590
-	/**
591
-	 * Creates a new card.
592
-	 *
593
-	 * The addressbook id will be passed as the first argument. This is the
594
-	 * same id as it is returned from the getAddressBooksForUser method.
595
-	 *
596
-	 * The cardUri is a base uri, and doesn't include the full path. The
597
-	 * cardData argument is the vcard body, and is passed as a string.
598
-	 *
599
-	 * It is possible to return an ETag from this method. This ETag is for the
600
-	 * newly created resource, and must be enclosed with double quotes (that
601
-	 * is, the string itself must contain the double quotes).
602
-	 *
603
-	 * You should only return the ETag if you store the carddata as-is. If a
604
-	 * subsequent GET request on the same card does not have the same body,
605
-	 * byte-by-byte and you did return an ETag here, clients tend to get
606
-	 * confused.
607
-	 *
608
-	 * If you don't return an ETag, you can just return null.
609
-	 *
610
-	 * @param mixed $addressBookId
611
-	 * @param string $cardUri
612
-	 * @param string $cardData
613
-	 * @return string
614
-	 */
615
-	function createCard($addressBookId, $cardUri, $cardData) {
616
-		$etag = md5($cardData);
617
-		$uid = $this->getUID($cardData);
618
-
619
-		$q = $this->db->getQueryBuilder();
620
-		$q->select('uid')
621
-			->from('cards')
622
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
623
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
624
-			->setMaxResults(1);
625
-		$result = $q->execute();
626
-		$count = (bool) $result->fetchColumn();
627
-		$result->closeCursor();
628
-		if ($count) {
629
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
630
-		}
631
-
632
-		$query = $this->db->getQueryBuilder();
633
-		$query->insert('cards')
634
-			->values([
635
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
636
-				'uri' => $query->createNamedParameter($cardUri),
637
-				'lastmodified' => $query->createNamedParameter(time()),
638
-				'addressbookid' => $query->createNamedParameter($addressBookId),
639
-				'size' => $query->createNamedParameter(strlen($cardData)),
640
-				'etag' => $query->createNamedParameter($etag),
641
-				'uid' => $query->createNamedParameter($uid),
642
-			])
643
-			->execute();
644
-
645
-		$this->addChange($addressBookId, $cardUri, 1);
646
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
647
-
648
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
649
-			new GenericEvent(null, [
650
-				'addressBookId' => $addressBookId,
651
-				'cardUri' => $cardUri,
652
-				'cardData' => $cardData]));
653
-
654
-		return '"' . $etag . '"';
655
-	}
656
-
657
-	/**
658
-	 * Updates a card.
659
-	 *
660
-	 * The addressbook id will be passed as the first argument. This is the
661
-	 * same id as it is returned from the getAddressBooksForUser method.
662
-	 *
663
-	 * The cardUri is a base uri, and doesn't include the full path. The
664
-	 * cardData argument is the vcard body, and is passed as a string.
665
-	 *
666
-	 * It is possible to return an ETag from this method. This ETag should
667
-	 * match that of the updated resource, and must be enclosed with double
668
-	 * quotes (that is: the string itself must contain the actual quotes).
669
-	 *
670
-	 * You should only return the ETag if you store the carddata as-is. If a
671
-	 * subsequent GET request on the same card does not have the same body,
672
-	 * byte-by-byte and you did return an ETag here, clients tend to get
673
-	 * confused.
674
-	 *
675
-	 * If you don't return an ETag, you can just return null.
676
-	 *
677
-	 * @param mixed $addressBookId
678
-	 * @param string $cardUri
679
-	 * @param string $cardData
680
-	 * @return string
681
-	 */
682
-	function updateCard($addressBookId, $cardUri, $cardData) {
683
-
684
-		$uid = $this->getUID($cardData);
685
-		$etag = md5($cardData);
686
-		$query = $this->db->getQueryBuilder();
687
-		$query->update('cards')
688
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
689
-			->set('lastmodified', $query->createNamedParameter(time()))
690
-			->set('size', $query->createNamedParameter(strlen($cardData)))
691
-			->set('etag', $query->createNamedParameter($etag))
692
-			->set('uid', $query->createNamedParameter($uid))
693
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
694
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
695
-			->execute();
696
-
697
-		$this->addChange($addressBookId, $cardUri, 2);
698
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
699
-
700
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
701
-			new GenericEvent(null, [
702
-				'addressBookId' => $addressBookId,
703
-				'cardUri' => $cardUri,
704
-				'cardData' => $cardData]));
705
-
706
-		return '"' . $etag . '"';
707
-	}
708
-
709
-	/**
710
-	 * Deletes a card
711
-	 *
712
-	 * @param mixed $addressBookId
713
-	 * @param string $cardUri
714
-	 * @return bool
715
-	 */
716
-	function deleteCard($addressBookId, $cardUri) {
717
-		try {
718
-			$cardId = $this->getCardId($addressBookId, $cardUri);
719
-		} catch (\InvalidArgumentException $e) {
720
-			$cardId = null;
721
-		}
722
-		$query = $this->db->getQueryBuilder();
723
-		$ret = $query->delete('cards')
724
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
725
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
726
-			->execute();
727
-
728
-		$this->addChange($addressBookId, $cardUri, 3);
729
-
730
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
731
-			new GenericEvent(null, [
732
-				'addressBookId' => $addressBookId,
733
-				'cardUri' => $cardUri]));
734
-
735
-		if ($ret === 1) {
736
-			if ($cardId !== null) {
737
-				$this->purgeProperties($addressBookId, $cardId);
738
-			}
739
-			return true;
740
-		}
741
-
742
-		return false;
743
-	}
744
-
745
-	/**
746
-	 * The getChanges method returns all the changes that have happened, since
747
-	 * the specified syncToken in the specified address book.
748
-	 *
749
-	 * This function should return an array, such as the following:
750
-	 *
751
-	 * [
752
-	 *   'syncToken' => 'The current synctoken',
753
-	 *   'added'   => [
754
-	 *      'new.txt',
755
-	 *   ],
756
-	 *   'modified'   => [
757
-	 *      'modified.txt',
758
-	 *   ],
759
-	 *   'deleted' => [
760
-	 *      'foo.php.bak',
761
-	 *      'old.txt'
762
-	 *   ]
763
-	 * ];
764
-	 *
765
-	 * The returned syncToken property should reflect the *current* syncToken
766
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
767
-	 * property. This is needed here too, to ensure the operation is atomic.
768
-	 *
769
-	 * If the $syncToken argument is specified as null, this is an initial
770
-	 * sync, and all members should be reported.
771
-	 *
772
-	 * The modified property is an array of nodenames that have changed since
773
-	 * the last token.
774
-	 *
775
-	 * The deleted property is an array with nodenames, that have been deleted
776
-	 * from collection.
777
-	 *
778
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
779
-	 * 1, you only have to report changes that happened only directly in
780
-	 * immediate descendants. If it's 2, it should also include changes from
781
-	 * the nodes below the child collections. (grandchildren)
782
-	 *
783
-	 * The $limit argument allows a client to specify how many results should
784
-	 * be returned at most. If the limit is not specified, it should be treated
785
-	 * as infinite.
786
-	 *
787
-	 * If the limit (infinite or not) is higher than you're willing to return,
788
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
789
-	 *
790
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
791
-	 * return null.
792
-	 *
793
-	 * The limit is 'suggestive'. You are free to ignore it.
794
-	 *
795
-	 * @param string $addressBookId
796
-	 * @param string $syncToken
797
-	 * @param int $syncLevel
798
-	 * @param int $limit
799
-	 * @return array
800
-	 */
801
-	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
802
-		// Current synctoken
803
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
804
-		$stmt->execute([ $addressBookId ]);
805
-		$currentToken = $stmt->fetchColumn(0);
806
-
807
-		if (is_null($currentToken)) return null;
808
-
809
-		$result = [
810
-			'syncToken' => $currentToken,
811
-			'added'     => [],
812
-			'modified'  => [],
813
-			'deleted'   => [],
814
-		];
815
-
816
-		if ($syncToken) {
817
-
818
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
819
-			if ($limit>0) {
820
-				$query .= " LIMIT " . (int)$limit;
821
-			}
822
-
823
-			// Fetching all changes
824
-			$stmt = $this->db->prepare($query);
825
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
826
-
827
-			$changes = [];
828
-
829
-			// This loop ensures that any duplicates are overwritten, only the
830
-			// last change on a node is relevant.
831
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
832
-
833
-				$changes[$row['uri']] = $row['operation'];
834
-
835
-			}
836
-
837
-			foreach($changes as $uri => $operation) {
838
-
839
-				switch($operation) {
840
-					case 1:
841
-						$result['added'][] = $uri;
842
-						break;
843
-					case 2:
844
-						$result['modified'][] = $uri;
845
-						break;
846
-					case 3:
847
-						$result['deleted'][] = $uri;
848
-						break;
849
-				}
850
-
851
-			}
852
-		} else {
853
-			// No synctoken supplied, this is the initial sync.
854
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
855
-			$stmt = $this->db->prepare($query);
856
-			$stmt->execute([$addressBookId]);
857
-
858
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
859
-		}
860
-		return $result;
861
-	}
862
-
863
-	/**
864
-	 * Adds a change record to the addressbookchanges table.
865
-	 *
866
-	 * @param mixed $addressBookId
867
-	 * @param string $objectUri
868
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
869
-	 * @return void
870
-	 */
871
-	protected function addChange($addressBookId, $objectUri, $operation) {
872
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
873
-		$stmt = $this->db->prepare($sql);
874
-		$stmt->execute([
875
-			$objectUri,
876
-			$addressBookId,
877
-			$operation,
878
-			$addressBookId
879
-		]);
880
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
881
-		$stmt->execute([
882
-			$addressBookId
883
-		]);
884
-	}
885
-
886
-	private function readBlob($cardData) {
887
-		if (is_resource($cardData)) {
888
-			return stream_get_contents($cardData);
889
-		}
890
-
891
-		return $cardData;
892
-	}
893
-
894
-	/**
895
-	 * @param IShareable $shareable
896
-	 * @param string[] $add
897
-	 * @param string[] $remove
898
-	 */
899
-	public function updateShares(IShareable $shareable, $add, $remove) {
900
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
901
-	}
902
-
903
-	/**
904
-	 * search contact
905
-	 *
906
-	 * @param int $addressBookId
907
-	 * @param string $pattern which should match within the $searchProperties
908
-	 * @param array $searchProperties defines the properties within the query pattern should match
909
-	 * @param array $options = array() to define the search behavior
910
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
911
-	 * @return array an array of contacts which are arrays of key-value-pairs
912
-	 */
913
-	public function search($addressBookId, $pattern, $searchProperties, $options = []) {
914
-		$query = $this->db->getQueryBuilder();
915
-		$query2 = $this->db->getQueryBuilder();
916
-
917
-		$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
918
-		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
919
-		$or = $query2->expr()->orX();
920
-		foreach ($searchProperties as $property) {
921
-			$or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
922
-		}
923
-		$query2->andWhere($or);
924
-
925
-		// No need for like when the pattern is empty
926
-		if ('' !== $pattern) {
927
-			if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
928
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
929
-			} else {
930
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
931
-			}
932
-		}
933
-
934
-		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
935
-			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
936
-
937
-		$result = $query->execute();
938
-		$cards = $result->fetchAll();
939
-
940
-		$result->closeCursor();
941
-
942
-		return array_map(function ($array) {
943
-			$array['carddata'] = $this->readBlob($array['carddata']);
944
-			return $array;
945
-		}, $cards);
946
-	}
947
-
948
-	/**
949
-	 * @param int $bookId
950
-	 * @param string $name
951
-	 * @return array
952
-	 */
953
-	public function collectCardProperties($bookId, $name) {
954
-		$query = $this->db->getQueryBuilder();
955
-		$result = $query->selectDistinct('value')
956
-			->from($this->dbCardsPropertiesTable)
957
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
958
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
959
-			->execute();
960
-
961
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
962
-		$result->closeCursor();
963
-
964
-		return $all;
965
-	}
966
-
967
-	/**
968
-	 * get URI from a given contact
969
-	 *
970
-	 * @param int $id
971
-	 * @return string
972
-	 */
973
-	public function getCardUri($id) {
974
-		$query = $this->db->getQueryBuilder();
975
-		$query->select('uri')->from($this->dbCardsTable)
976
-				->where($query->expr()->eq('id', $query->createParameter('id')))
977
-				->setParameter('id', $id);
978
-
979
-		$result = $query->execute();
980
-		$uri = $result->fetch();
981
-		$result->closeCursor();
982
-
983
-		if (!isset($uri['uri'])) {
984
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
985
-		}
986
-
987
-		return $uri['uri'];
988
-	}
989
-
990
-	/**
991
-	 * return contact with the given URI
992
-	 *
993
-	 * @param int $addressBookId
994
-	 * @param string $uri
995
-	 * @returns array
996
-	 */
997
-	public function getContact($addressBookId, $uri) {
998
-		$result = [];
999
-		$query = $this->db->getQueryBuilder();
1000
-		$query->select('*')->from($this->dbCardsTable)
1001
-				->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1002
-				->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1003
-		$queryResult = $query->execute();
1004
-		$contact = $queryResult->fetch();
1005
-		$queryResult->closeCursor();
1006
-
1007
-		if (is_array($contact)) {
1008
-			$result = $contact;
1009
-		}
1010
-
1011
-		return $result;
1012
-	}
1013
-
1014
-	/**
1015
-	 * Returns the list of people whom this address book is shared with.
1016
-	 *
1017
-	 * Every element in this array should have the following properties:
1018
-	 *   * href - Often a mailto: address
1019
-	 *   * commonName - Optional, for example a first + last name
1020
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1021
-	 *   * readOnly - boolean
1022
-	 *   * summary - Optional, a description for the share
1023
-	 *
1024
-	 * @return array
1025
-	 */
1026
-	public function getShares($addressBookId) {
1027
-		return $this->sharingBackend->getShares($addressBookId);
1028
-	}
1029
-
1030
-	/**
1031
-	 * update properties table
1032
-	 *
1033
-	 * @param int $addressBookId
1034
-	 * @param string $cardUri
1035
-	 * @param string $vCardSerialized
1036
-	 */
1037
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1038
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1039
-		$vCard = $this->readCard($vCardSerialized);
1040
-
1041
-		$this->purgeProperties($addressBookId, $cardId);
1042
-
1043
-		$query = $this->db->getQueryBuilder();
1044
-		$query->insert($this->dbCardsPropertiesTable)
1045
-			->values(
1046
-				[
1047
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1048
-					'cardid' => $query->createNamedParameter($cardId),
1049
-					'name' => $query->createParameter('name'),
1050
-					'value' => $query->createParameter('value'),
1051
-					'preferred' => $query->createParameter('preferred')
1052
-				]
1053
-			);
1054
-
1055
-		foreach ($vCard->children() as $property) {
1056
-			if(!in_array($property->name, self::$indexProperties)) {
1057
-				continue;
1058
-			}
1059
-			$preferred = 0;
1060
-			foreach($property->parameters as $parameter) {
1061
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1062
-					$preferred = 1;
1063
-					break;
1064
-				}
1065
-			}
1066
-			$query->setParameter('name', $property->name);
1067
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1068
-			$query->setParameter('preferred', $preferred);
1069
-			$query->execute();
1070
-		}
1071
-	}
1072
-
1073
-	/**
1074
-	 * read vCard data into a vCard object
1075
-	 *
1076
-	 * @param string $cardData
1077
-	 * @return VCard
1078
-	 */
1079
-	protected function readCard($cardData) {
1080
-		return  Reader::read($cardData);
1081
-	}
1082
-
1083
-	/**
1084
-	 * delete all properties from a given card
1085
-	 *
1086
-	 * @param int $addressBookId
1087
-	 * @param int $cardId
1088
-	 */
1089
-	protected function purgeProperties($addressBookId, $cardId) {
1090
-		$query = $this->db->getQueryBuilder();
1091
-		$query->delete($this->dbCardsPropertiesTable)
1092
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1093
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1094
-		$query->execute();
1095
-	}
1096
-
1097
-	/**
1098
-	 * get ID from a given contact
1099
-	 *
1100
-	 * @param int $addressBookId
1101
-	 * @param string $uri
1102
-	 * @return int
1103
-	 */
1104
-	protected function getCardId($addressBookId, $uri) {
1105
-		$query = $this->db->getQueryBuilder();
1106
-		$query->select('id')->from($this->dbCardsTable)
1107
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1108
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1109
-
1110
-		$result = $query->execute();
1111
-		$cardIds = $result->fetch();
1112
-		$result->closeCursor();
1113
-
1114
-		if (!isset($cardIds['id'])) {
1115
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1116
-		}
1117
-
1118
-		return (int)$cardIds['id'];
1119
-	}
1120
-
1121
-	/**
1122
-	 * For shared address books the sharee is set in the ACL of the address book
1123
-	 * @param $addressBookId
1124
-	 * @param $acl
1125
-	 * @return array
1126
-	 */
1127
-	public function applyShareAcl($addressBookId, $acl) {
1128
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1129
-	}
1130
-
1131
-	private function convertPrincipal($principalUri, $toV2) {
1132
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1133
-			list(, $name) = \Sabre\Uri\split($principalUri);
1134
-			if ($toV2 === true) {
1135
-				return "principals/users/$name";
1136
-			}
1137
-			return "principals/$name";
1138
-		}
1139
-		return $principalUri;
1140
-	}
1141
-
1142
-	private function addOwnerPrincipal(&$addressbookInfo) {
1143
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1144
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1145
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1146
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1147
-		} else {
1148
-			$uri = $addressbookInfo['principaluri'];
1149
-		}
1150
-
1151
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1152
-		if (isset($principalInformation['{DAV:}displayname'])) {
1153
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1154
-		}
1155
-	}
1156
-
1157
-	/**
1158
-	 * Extract UID from vcard
1159
-	 *
1160
-	 * @param string $cardData the vcard raw data
1161
-	 * @return string the uid
1162
-	 * @throws BadRequest if no UID is available
1163
-	 */
1164
-	private function getUID($cardData) {
1165
-		if ($cardData != '') {
1166
-			$vCard = Reader::read($cardData);
1167
-			if ($vCard->UID) {
1168
-				$uid = $vCard->UID->getValue();
1169
-				return $uid;
1170
-			}
1171
-			// should already be handled, but just in case
1172
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1173
-		}
1174
-		// should already be handled, but just in case
1175
-		throw new BadRequest('vCard can not be empty');
1176
-	}
59
+    const PERSONAL_ADDRESSBOOK_URI = 'contacts';
60
+    const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
61
+
62
+    /** @var Principal */
63
+    private $principalBackend;
64
+
65
+    /** @var string */
66
+    private $dbCardsTable = 'cards';
67
+
68
+    /** @var string */
69
+    private $dbCardsPropertiesTable = 'cards_properties';
70
+
71
+    /** @var IDBConnection */
72
+    private $db;
73
+
74
+    /** @var Backend */
75
+    private $sharingBackend;
76
+
77
+    /** @var array properties to index */
78
+    public static $indexProperties = [
79
+        'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
80
+        'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
81
+
82
+    /**
83
+     * @var string[] Map of uid => display name
84
+     */
85
+    protected $userDisplayNames;
86
+
87
+    /** @var IUserManager */
88
+    private $userManager;
89
+
90
+    /** @var EventDispatcherInterface */
91
+    private $dispatcher;
92
+
93
+    /**
94
+     * CardDavBackend constructor.
95
+     *
96
+     * @param IDBConnection $db
97
+     * @param Principal $principalBackend
98
+     * @param IUserManager $userManager
99
+     * @param IGroupManager $groupManager
100
+     * @param EventDispatcherInterface $dispatcher
101
+     */
102
+    public function __construct(IDBConnection $db,
103
+                                Principal $principalBackend,
104
+                                IUserManager $userManager,
105
+                                IGroupManager $groupManager,
106
+                                EventDispatcherInterface $dispatcher) {
107
+        $this->db = $db;
108
+        $this->principalBackend = $principalBackend;
109
+        $this->userManager = $userManager;
110
+        $this->dispatcher = $dispatcher;
111
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
112
+    }
113
+
114
+    /**
115
+     * Return the number of address books for a principal
116
+     *
117
+     * @param $principalUri
118
+     * @return int
119
+     */
120
+    public function getAddressBooksForUserCount($principalUri) {
121
+        $principalUri = $this->convertPrincipal($principalUri, true);
122
+        $query = $this->db->getQueryBuilder();
123
+        $query->select($query->func()->count('*'))
124
+            ->from('addressbooks')
125
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
126
+
127
+        return (int)$query->execute()->fetchColumn();
128
+    }
129
+
130
+    /**
131
+     * Returns the list of address books for a specific user.
132
+     *
133
+     * Every addressbook should have the following properties:
134
+     *   id - an arbitrary unique id
135
+     *   uri - the 'basename' part of the url
136
+     *   principaluri - Same as the passed parameter
137
+     *
138
+     * Any additional clark-notation property may be passed besides this. Some
139
+     * common ones are :
140
+     *   {DAV:}displayname
141
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
142
+     *   {http://calendarserver.org/ns/}getctag
143
+     *
144
+     * @param string $principalUri
145
+     * @return array
146
+     */
147
+    function getAddressBooksForUser($principalUri) {
148
+        $principalUriOriginal = $principalUri;
149
+        $principalUri = $this->convertPrincipal($principalUri, true);
150
+        $query = $this->db->getQueryBuilder();
151
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
152
+            ->from('addressbooks')
153
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
154
+
155
+        $addressBooks = [];
156
+
157
+        $result = $query->execute();
158
+        while($row = $result->fetch()) {
159
+            $addressBooks[$row['id']] = [
160
+                'id'  => $row['id'],
161
+                'uri' => $row['uri'],
162
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
163
+                '{DAV:}displayname' => $row['displayname'],
164
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
165
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
166
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
167
+            ];
168
+
169
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
170
+        }
171
+        $result->closeCursor();
172
+
173
+        // query for shared addressbooks
174
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
175
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
176
+
177
+        $principals = array_map(function ($principal) {
178
+            return urldecode($principal);
179
+        }, $principals);
180
+        $principals[]= $principalUri;
181
+
182
+        $query = $this->db->getQueryBuilder();
183
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
184
+            ->from('dav_shares', 's')
185
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
186
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
187
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
188
+            ->setParameter('type', 'addressbook')
189
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
190
+            ->execute();
191
+
192
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
193
+        while($row = $result->fetch()) {
194
+            if ($row['principaluri'] === $principalUri) {
195
+                continue;
196
+            }
197
+
198
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
199
+            if (isset($addressBooks[$row['id']])) {
200
+                if ($readOnly) {
201
+                    // New share can not have more permissions then the old one.
202
+                    continue;
203
+                }
204
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
205
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
206
+                    // Old share is already read-write, no more permissions can be gained
207
+                    continue;
208
+                }
209
+            }
210
+
211
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
212
+            $uri = $row['uri'] . '_shared_by_' . $name;
213
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
214
+
215
+            $addressBooks[$row['id']] = [
216
+                'id'  => $row['id'],
217
+                'uri' => $uri,
218
+                'principaluri' => $principalUriOriginal,
219
+                '{DAV:}displayname' => $displayName,
220
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
221
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
222
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
223
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
224
+                $readOnlyPropertyName => $readOnly,
225
+            ];
226
+
227
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
228
+        }
229
+        $result->closeCursor();
230
+
231
+        return array_values($addressBooks);
232
+    }
233
+
234
+    public function getUsersOwnAddressBooks($principalUri) {
235
+        $principalUri = $this->convertPrincipal($principalUri, true);
236
+        $query = $this->db->getQueryBuilder();
237
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
238
+                ->from('addressbooks')
239
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
240
+
241
+        $addressBooks = [];
242
+
243
+        $result = $query->execute();
244
+        while($row = $result->fetch()) {
245
+            $addressBooks[$row['id']] = [
246
+                'id'  => $row['id'],
247
+                'uri' => $row['uri'],
248
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
249
+                '{DAV:}displayname' => $row['displayname'],
250
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
251
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
252
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
253
+            ];
254
+
255
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
256
+        }
257
+        $result->closeCursor();
258
+
259
+        return array_values($addressBooks);
260
+    }
261
+
262
+    private function getUserDisplayName($uid) {
263
+        if (!isset($this->userDisplayNames[$uid])) {
264
+            $user = $this->userManager->get($uid);
265
+
266
+            if ($user instanceof IUser) {
267
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
268
+            } else {
269
+                $this->userDisplayNames[$uid] = $uid;
270
+            }
271
+        }
272
+
273
+        return $this->userDisplayNames[$uid];
274
+    }
275
+
276
+    /**
277
+     * @param int $addressBookId
278
+     */
279
+    public function getAddressBookById($addressBookId) {
280
+        $query = $this->db->getQueryBuilder();
281
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
282
+            ->from('addressbooks')
283
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
284
+            ->execute();
285
+
286
+        $row = $result->fetch();
287
+        $result->closeCursor();
288
+        if ($row === false) {
289
+            return null;
290
+        }
291
+
292
+        $addressBook = [
293
+            'id'  => $row['id'],
294
+            'uri' => $row['uri'],
295
+            'principaluri' => $row['principaluri'],
296
+            '{DAV:}displayname' => $row['displayname'],
297
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
298
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
299
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
300
+        ];
301
+
302
+        $this->addOwnerPrincipal($addressBook);
303
+
304
+        return $addressBook;
305
+    }
306
+
307
+    /**
308
+     * @param $addressBookUri
309
+     * @return array|null
310
+     */
311
+    public function getAddressBooksByUri($principal, $addressBookUri) {
312
+        $query = $this->db->getQueryBuilder();
313
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
314
+            ->from('addressbooks')
315
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
316
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
317
+            ->setMaxResults(1)
318
+            ->execute();
319
+
320
+        $row = $result->fetch();
321
+        $result->closeCursor();
322
+        if ($row === false) {
323
+            return null;
324
+        }
325
+
326
+        $addressBook = [
327
+            'id'  => $row['id'],
328
+            'uri' => $row['uri'],
329
+            'principaluri' => $row['principaluri'],
330
+            '{DAV:}displayname' => $row['displayname'],
331
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
332
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
333
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
334
+        ];
335
+
336
+        $this->addOwnerPrincipal($addressBook);
337
+
338
+        return $addressBook;
339
+    }
340
+
341
+    /**
342
+     * Updates properties for an address book.
343
+     *
344
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
345
+     * To do the actual updates, you must tell this object which properties
346
+     * you're going to process with the handle() method.
347
+     *
348
+     * Calling the handle method is like telling the PropPatch object "I
349
+     * promise I can handle updating this property".
350
+     *
351
+     * Read the PropPatch documentation for more info and examples.
352
+     *
353
+     * @param string $addressBookId
354
+     * @param \Sabre\DAV\PropPatch $propPatch
355
+     * @return void
356
+     */
357
+    function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
358
+        $supportedProperties = [
359
+            '{DAV:}displayname',
360
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
361
+        ];
362
+
363
+        /**
364
+         * @suppress SqlInjectionChecker
365
+         */
366
+        $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
367
+
368
+            $updates = [];
369
+            foreach($mutations as $property=>$newValue) {
370
+
371
+                switch($property) {
372
+                    case '{DAV:}displayname':
373
+                        $updates['displayname'] = $newValue;
374
+                        break;
375
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
376
+                        $updates['description'] = $newValue;
377
+                        break;
378
+                }
379
+            }
380
+            $query = $this->db->getQueryBuilder();
381
+            $query->update('addressbooks');
382
+
383
+            foreach($updates as $key=>$value) {
384
+                $query->set($key, $query->createNamedParameter($value));
385
+            }
386
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
387
+            ->execute();
388
+
389
+            $this->addChange($addressBookId, "", 2);
390
+
391
+            return true;
392
+
393
+        });
394
+    }
395
+
396
+    /**
397
+     * Creates a new address book
398
+     *
399
+     * @param string $principalUri
400
+     * @param string $url Just the 'basename' of the url.
401
+     * @param array $properties
402
+     * @return int
403
+     * @throws BadRequest
404
+     */
405
+    function createAddressBook($principalUri, $url, array $properties) {
406
+        $values = [
407
+            'displayname' => null,
408
+            'description' => null,
409
+            'principaluri' => $principalUri,
410
+            'uri' => $url,
411
+            'synctoken' => 1
412
+        ];
413
+
414
+        foreach($properties as $property=>$newValue) {
415
+
416
+            switch($property) {
417
+                case '{DAV:}displayname':
418
+                    $values['displayname'] = $newValue;
419
+                    break;
420
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
421
+                    $values['description'] = $newValue;
422
+                    break;
423
+                default:
424
+                    throw new BadRequest('Unknown property: ' . $property);
425
+            }
426
+
427
+        }
428
+
429
+        // Fallback to make sure the displayname is set. Some clients may refuse
430
+        // to work with addressbooks not having a displayname.
431
+        if(is_null($values['displayname'])) {
432
+            $values['displayname'] = $url;
433
+        }
434
+
435
+        $query = $this->db->getQueryBuilder();
436
+        $query->insert('addressbooks')
437
+            ->values([
438
+                'uri' => $query->createParameter('uri'),
439
+                'displayname' => $query->createParameter('displayname'),
440
+                'description' => $query->createParameter('description'),
441
+                'principaluri' => $query->createParameter('principaluri'),
442
+                'synctoken' => $query->createParameter('synctoken'),
443
+            ])
444
+            ->setParameters($values)
445
+            ->execute();
446
+
447
+        return $query->getLastInsertId();
448
+    }
449
+
450
+    /**
451
+     * Deletes an entire addressbook and all its contents
452
+     *
453
+     * @param mixed $addressBookId
454
+     * @return void
455
+     */
456
+    function deleteAddressBook($addressBookId) {
457
+        $query = $this->db->getQueryBuilder();
458
+        $query->delete('cards')
459
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
460
+            ->setParameter('addressbookid', $addressBookId)
461
+            ->execute();
462
+
463
+        $query->delete('addressbookchanges')
464
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
465
+            ->setParameter('addressbookid', $addressBookId)
466
+            ->execute();
467
+
468
+        $query->delete('addressbooks')
469
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
470
+            ->setParameter('id', $addressBookId)
471
+            ->execute();
472
+
473
+        $this->sharingBackend->deleteAllShares($addressBookId);
474
+
475
+        $query->delete($this->dbCardsPropertiesTable)
476
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
477
+            ->execute();
478
+
479
+    }
480
+
481
+    /**
482
+     * Returns all cards for a specific addressbook id.
483
+     *
484
+     * This method should return the following properties for each card:
485
+     *   * carddata - raw vcard data
486
+     *   * uri - Some unique url
487
+     *   * lastmodified - A unix timestamp
488
+     *
489
+     * It's recommended to also return the following properties:
490
+     *   * etag - A unique etag. This must change every time the card changes.
491
+     *   * size - The size of the card in bytes.
492
+     *
493
+     * If these last two properties are provided, less time will be spent
494
+     * calculating them. If they are specified, you can also ommit carddata.
495
+     * This may speed up certain requests, especially with large cards.
496
+     *
497
+     * @param mixed $addressBookId
498
+     * @return array
499
+     */
500
+    function getCards($addressBookId) {
501
+        $query = $this->db->getQueryBuilder();
502
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
503
+            ->from('cards')
504
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
505
+
506
+        $cards = [];
507
+
508
+        $result = $query->execute();
509
+        while($row = $result->fetch()) {
510
+            $row['etag'] = '"' . $row['etag'] . '"';
511
+            $row['carddata'] = $this->readBlob($row['carddata']);
512
+            $cards[] = $row;
513
+        }
514
+        $result->closeCursor();
515
+
516
+        return $cards;
517
+    }
518
+
519
+    /**
520
+     * Returns a specific card.
521
+     *
522
+     * The same set of properties must be returned as with getCards. The only
523
+     * exception is that 'carddata' is absolutely required.
524
+     *
525
+     * If the card does not exist, you must return false.
526
+     *
527
+     * @param mixed $addressBookId
528
+     * @param string $cardUri
529
+     * @return array
530
+     */
531
+    function getCard($addressBookId, $cardUri) {
532
+        $query = $this->db->getQueryBuilder();
533
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
534
+            ->from('cards')
535
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
536
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
537
+            ->setMaxResults(1);
538
+
539
+        $result = $query->execute();
540
+        $row = $result->fetch();
541
+        if (!$row) {
542
+            return false;
543
+        }
544
+        $row['etag'] = '"' . $row['etag'] . '"';
545
+        $row['carddata'] = $this->readBlob($row['carddata']);
546
+
547
+        return $row;
548
+    }
549
+
550
+    /**
551
+     * Returns a list of cards.
552
+     *
553
+     * This method should work identical to getCard, but instead return all the
554
+     * cards in the list as an array.
555
+     *
556
+     * If the backend supports this, it may allow for some speed-ups.
557
+     *
558
+     * @param mixed $addressBookId
559
+     * @param string[] $uris
560
+     * @return array
561
+     */
562
+    function getMultipleCards($addressBookId, array $uris) {
563
+        if (empty($uris)) {
564
+            return [];
565
+        }
566
+
567
+        $chunks = array_chunk($uris, 100);
568
+        $cards = [];
569
+
570
+        $query = $this->db->getQueryBuilder();
571
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
572
+            ->from('cards')
573
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
574
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
575
+
576
+        foreach ($chunks as $uris) {
577
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
578
+            $result = $query->execute();
579
+
580
+            while ($row = $result->fetch()) {
581
+                $row['etag'] = '"' . $row['etag'] . '"';
582
+                $row['carddata'] = $this->readBlob($row['carddata']);
583
+                $cards[] = $row;
584
+            }
585
+            $result->closeCursor();
586
+        }
587
+        return $cards;
588
+    }
589
+
590
+    /**
591
+     * Creates a new card.
592
+     *
593
+     * The addressbook id will be passed as the first argument. This is the
594
+     * same id as it is returned from the getAddressBooksForUser method.
595
+     *
596
+     * The cardUri is a base uri, and doesn't include the full path. The
597
+     * cardData argument is the vcard body, and is passed as a string.
598
+     *
599
+     * It is possible to return an ETag from this method. This ETag is for the
600
+     * newly created resource, and must be enclosed with double quotes (that
601
+     * is, the string itself must contain the double quotes).
602
+     *
603
+     * You should only return the ETag if you store the carddata as-is. If a
604
+     * subsequent GET request on the same card does not have the same body,
605
+     * byte-by-byte and you did return an ETag here, clients tend to get
606
+     * confused.
607
+     *
608
+     * If you don't return an ETag, you can just return null.
609
+     *
610
+     * @param mixed $addressBookId
611
+     * @param string $cardUri
612
+     * @param string $cardData
613
+     * @return string
614
+     */
615
+    function createCard($addressBookId, $cardUri, $cardData) {
616
+        $etag = md5($cardData);
617
+        $uid = $this->getUID($cardData);
618
+
619
+        $q = $this->db->getQueryBuilder();
620
+        $q->select('uid')
621
+            ->from('cards')
622
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
623
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
624
+            ->setMaxResults(1);
625
+        $result = $q->execute();
626
+        $count = (bool) $result->fetchColumn();
627
+        $result->closeCursor();
628
+        if ($count) {
629
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
630
+        }
631
+
632
+        $query = $this->db->getQueryBuilder();
633
+        $query->insert('cards')
634
+            ->values([
635
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
636
+                'uri' => $query->createNamedParameter($cardUri),
637
+                'lastmodified' => $query->createNamedParameter(time()),
638
+                'addressbookid' => $query->createNamedParameter($addressBookId),
639
+                'size' => $query->createNamedParameter(strlen($cardData)),
640
+                'etag' => $query->createNamedParameter($etag),
641
+                'uid' => $query->createNamedParameter($uid),
642
+            ])
643
+            ->execute();
644
+
645
+        $this->addChange($addressBookId, $cardUri, 1);
646
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
647
+
648
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
649
+            new GenericEvent(null, [
650
+                'addressBookId' => $addressBookId,
651
+                'cardUri' => $cardUri,
652
+                'cardData' => $cardData]));
653
+
654
+        return '"' . $etag . '"';
655
+    }
656
+
657
+    /**
658
+     * Updates a card.
659
+     *
660
+     * The addressbook id will be passed as the first argument. This is the
661
+     * same id as it is returned from the getAddressBooksForUser method.
662
+     *
663
+     * The cardUri is a base uri, and doesn't include the full path. The
664
+     * cardData argument is the vcard body, and is passed as a string.
665
+     *
666
+     * It is possible to return an ETag from this method. This ETag should
667
+     * match that of the updated resource, and must be enclosed with double
668
+     * quotes (that is: the string itself must contain the actual quotes).
669
+     *
670
+     * You should only return the ETag if you store the carddata as-is. If a
671
+     * subsequent GET request on the same card does not have the same body,
672
+     * byte-by-byte and you did return an ETag here, clients tend to get
673
+     * confused.
674
+     *
675
+     * If you don't return an ETag, you can just return null.
676
+     *
677
+     * @param mixed $addressBookId
678
+     * @param string $cardUri
679
+     * @param string $cardData
680
+     * @return string
681
+     */
682
+    function updateCard($addressBookId, $cardUri, $cardData) {
683
+
684
+        $uid = $this->getUID($cardData);
685
+        $etag = md5($cardData);
686
+        $query = $this->db->getQueryBuilder();
687
+        $query->update('cards')
688
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
689
+            ->set('lastmodified', $query->createNamedParameter(time()))
690
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
691
+            ->set('etag', $query->createNamedParameter($etag))
692
+            ->set('uid', $query->createNamedParameter($uid))
693
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
694
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
695
+            ->execute();
696
+
697
+        $this->addChange($addressBookId, $cardUri, 2);
698
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
699
+
700
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
701
+            new GenericEvent(null, [
702
+                'addressBookId' => $addressBookId,
703
+                'cardUri' => $cardUri,
704
+                'cardData' => $cardData]));
705
+
706
+        return '"' . $etag . '"';
707
+    }
708
+
709
+    /**
710
+     * Deletes a card
711
+     *
712
+     * @param mixed $addressBookId
713
+     * @param string $cardUri
714
+     * @return bool
715
+     */
716
+    function deleteCard($addressBookId, $cardUri) {
717
+        try {
718
+            $cardId = $this->getCardId($addressBookId, $cardUri);
719
+        } catch (\InvalidArgumentException $e) {
720
+            $cardId = null;
721
+        }
722
+        $query = $this->db->getQueryBuilder();
723
+        $ret = $query->delete('cards')
724
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
725
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
726
+            ->execute();
727
+
728
+        $this->addChange($addressBookId, $cardUri, 3);
729
+
730
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
731
+            new GenericEvent(null, [
732
+                'addressBookId' => $addressBookId,
733
+                'cardUri' => $cardUri]));
734
+
735
+        if ($ret === 1) {
736
+            if ($cardId !== null) {
737
+                $this->purgeProperties($addressBookId, $cardId);
738
+            }
739
+            return true;
740
+        }
741
+
742
+        return false;
743
+    }
744
+
745
+    /**
746
+     * The getChanges method returns all the changes that have happened, since
747
+     * the specified syncToken in the specified address book.
748
+     *
749
+     * This function should return an array, such as the following:
750
+     *
751
+     * [
752
+     *   'syncToken' => 'The current synctoken',
753
+     *   'added'   => [
754
+     *      'new.txt',
755
+     *   ],
756
+     *   'modified'   => [
757
+     *      'modified.txt',
758
+     *   ],
759
+     *   'deleted' => [
760
+     *      'foo.php.bak',
761
+     *      'old.txt'
762
+     *   ]
763
+     * ];
764
+     *
765
+     * The returned syncToken property should reflect the *current* syncToken
766
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
767
+     * property. This is needed here too, to ensure the operation is atomic.
768
+     *
769
+     * If the $syncToken argument is specified as null, this is an initial
770
+     * sync, and all members should be reported.
771
+     *
772
+     * The modified property is an array of nodenames that have changed since
773
+     * the last token.
774
+     *
775
+     * The deleted property is an array with nodenames, that have been deleted
776
+     * from collection.
777
+     *
778
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
779
+     * 1, you only have to report changes that happened only directly in
780
+     * immediate descendants. If it's 2, it should also include changes from
781
+     * the nodes below the child collections. (grandchildren)
782
+     *
783
+     * The $limit argument allows a client to specify how many results should
784
+     * be returned at most. If the limit is not specified, it should be treated
785
+     * as infinite.
786
+     *
787
+     * If the limit (infinite or not) is higher than you're willing to return,
788
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
789
+     *
790
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
791
+     * return null.
792
+     *
793
+     * The limit is 'suggestive'. You are free to ignore it.
794
+     *
795
+     * @param string $addressBookId
796
+     * @param string $syncToken
797
+     * @param int $syncLevel
798
+     * @param int $limit
799
+     * @return array
800
+     */
801
+    function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
802
+        // Current synctoken
803
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
804
+        $stmt->execute([ $addressBookId ]);
805
+        $currentToken = $stmt->fetchColumn(0);
806
+
807
+        if (is_null($currentToken)) return null;
808
+
809
+        $result = [
810
+            'syncToken' => $currentToken,
811
+            'added'     => [],
812
+            'modified'  => [],
813
+            'deleted'   => [],
814
+        ];
815
+
816
+        if ($syncToken) {
817
+
818
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
819
+            if ($limit>0) {
820
+                $query .= " LIMIT " . (int)$limit;
821
+            }
822
+
823
+            // Fetching all changes
824
+            $stmt = $this->db->prepare($query);
825
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
826
+
827
+            $changes = [];
828
+
829
+            // This loop ensures that any duplicates are overwritten, only the
830
+            // last change on a node is relevant.
831
+            while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
832
+
833
+                $changes[$row['uri']] = $row['operation'];
834
+
835
+            }
836
+
837
+            foreach($changes as $uri => $operation) {
838
+
839
+                switch($operation) {
840
+                    case 1:
841
+                        $result['added'][] = $uri;
842
+                        break;
843
+                    case 2:
844
+                        $result['modified'][] = $uri;
845
+                        break;
846
+                    case 3:
847
+                        $result['deleted'][] = $uri;
848
+                        break;
849
+                }
850
+
851
+            }
852
+        } else {
853
+            // No synctoken supplied, this is the initial sync.
854
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
855
+            $stmt = $this->db->prepare($query);
856
+            $stmt->execute([$addressBookId]);
857
+
858
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
859
+        }
860
+        return $result;
861
+    }
862
+
863
+    /**
864
+     * Adds a change record to the addressbookchanges table.
865
+     *
866
+     * @param mixed $addressBookId
867
+     * @param string $objectUri
868
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
869
+     * @return void
870
+     */
871
+    protected function addChange($addressBookId, $objectUri, $operation) {
872
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
873
+        $stmt = $this->db->prepare($sql);
874
+        $stmt->execute([
875
+            $objectUri,
876
+            $addressBookId,
877
+            $operation,
878
+            $addressBookId
879
+        ]);
880
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
881
+        $stmt->execute([
882
+            $addressBookId
883
+        ]);
884
+    }
885
+
886
+    private function readBlob($cardData) {
887
+        if (is_resource($cardData)) {
888
+            return stream_get_contents($cardData);
889
+        }
890
+
891
+        return $cardData;
892
+    }
893
+
894
+    /**
895
+     * @param IShareable $shareable
896
+     * @param string[] $add
897
+     * @param string[] $remove
898
+     */
899
+    public function updateShares(IShareable $shareable, $add, $remove) {
900
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
901
+    }
902
+
903
+    /**
904
+     * search contact
905
+     *
906
+     * @param int $addressBookId
907
+     * @param string $pattern which should match within the $searchProperties
908
+     * @param array $searchProperties defines the properties within the query pattern should match
909
+     * @param array $options = array() to define the search behavior
910
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
911
+     * @return array an array of contacts which are arrays of key-value-pairs
912
+     */
913
+    public function search($addressBookId, $pattern, $searchProperties, $options = []) {
914
+        $query = $this->db->getQueryBuilder();
915
+        $query2 = $this->db->getQueryBuilder();
916
+
917
+        $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
918
+        $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
919
+        $or = $query2->expr()->orX();
920
+        foreach ($searchProperties as $property) {
921
+            $or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
922
+        }
923
+        $query2->andWhere($or);
924
+
925
+        // No need for like when the pattern is empty
926
+        if ('' !== $pattern) {
927
+            if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
928
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
929
+            } else {
930
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
931
+            }
932
+        }
933
+
934
+        $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
935
+            ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
936
+
937
+        $result = $query->execute();
938
+        $cards = $result->fetchAll();
939
+
940
+        $result->closeCursor();
941
+
942
+        return array_map(function ($array) {
943
+            $array['carddata'] = $this->readBlob($array['carddata']);
944
+            return $array;
945
+        }, $cards);
946
+    }
947
+
948
+    /**
949
+     * @param int $bookId
950
+     * @param string $name
951
+     * @return array
952
+     */
953
+    public function collectCardProperties($bookId, $name) {
954
+        $query = $this->db->getQueryBuilder();
955
+        $result = $query->selectDistinct('value')
956
+            ->from($this->dbCardsPropertiesTable)
957
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
958
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
959
+            ->execute();
960
+
961
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
962
+        $result->closeCursor();
963
+
964
+        return $all;
965
+    }
966
+
967
+    /**
968
+     * get URI from a given contact
969
+     *
970
+     * @param int $id
971
+     * @return string
972
+     */
973
+    public function getCardUri($id) {
974
+        $query = $this->db->getQueryBuilder();
975
+        $query->select('uri')->from($this->dbCardsTable)
976
+                ->where($query->expr()->eq('id', $query->createParameter('id')))
977
+                ->setParameter('id', $id);
978
+
979
+        $result = $query->execute();
980
+        $uri = $result->fetch();
981
+        $result->closeCursor();
982
+
983
+        if (!isset($uri['uri'])) {
984
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
985
+        }
986
+
987
+        return $uri['uri'];
988
+    }
989
+
990
+    /**
991
+     * return contact with the given URI
992
+     *
993
+     * @param int $addressBookId
994
+     * @param string $uri
995
+     * @returns array
996
+     */
997
+    public function getContact($addressBookId, $uri) {
998
+        $result = [];
999
+        $query = $this->db->getQueryBuilder();
1000
+        $query->select('*')->from($this->dbCardsTable)
1001
+                ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1002
+                ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1003
+        $queryResult = $query->execute();
1004
+        $contact = $queryResult->fetch();
1005
+        $queryResult->closeCursor();
1006
+
1007
+        if (is_array($contact)) {
1008
+            $result = $contact;
1009
+        }
1010
+
1011
+        return $result;
1012
+    }
1013
+
1014
+    /**
1015
+     * Returns the list of people whom this address book is shared with.
1016
+     *
1017
+     * Every element in this array should have the following properties:
1018
+     *   * href - Often a mailto: address
1019
+     *   * commonName - Optional, for example a first + last name
1020
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1021
+     *   * readOnly - boolean
1022
+     *   * summary - Optional, a description for the share
1023
+     *
1024
+     * @return array
1025
+     */
1026
+    public function getShares($addressBookId) {
1027
+        return $this->sharingBackend->getShares($addressBookId);
1028
+    }
1029
+
1030
+    /**
1031
+     * update properties table
1032
+     *
1033
+     * @param int $addressBookId
1034
+     * @param string $cardUri
1035
+     * @param string $vCardSerialized
1036
+     */
1037
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1038
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1039
+        $vCard = $this->readCard($vCardSerialized);
1040
+
1041
+        $this->purgeProperties($addressBookId, $cardId);
1042
+
1043
+        $query = $this->db->getQueryBuilder();
1044
+        $query->insert($this->dbCardsPropertiesTable)
1045
+            ->values(
1046
+                [
1047
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1048
+                    'cardid' => $query->createNamedParameter($cardId),
1049
+                    'name' => $query->createParameter('name'),
1050
+                    'value' => $query->createParameter('value'),
1051
+                    'preferred' => $query->createParameter('preferred')
1052
+                ]
1053
+            );
1054
+
1055
+        foreach ($vCard->children() as $property) {
1056
+            if(!in_array($property->name, self::$indexProperties)) {
1057
+                continue;
1058
+            }
1059
+            $preferred = 0;
1060
+            foreach($property->parameters as $parameter) {
1061
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1062
+                    $preferred = 1;
1063
+                    break;
1064
+                }
1065
+            }
1066
+            $query->setParameter('name', $property->name);
1067
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1068
+            $query->setParameter('preferred', $preferred);
1069
+            $query->execute();
1070
+        }
1071
+    }
1072
+
1073
+    /**
1074
+     * read vCard data into a vCard object
1075
+     *
1076
+     * @param string $cardData
1077
+     * @return VCard
1078
+     */
1079
+    protected function readCard($cardData) {
1080
+        return  Reader::read($cardData);
1081
+    }
1082
+
1083
+    /**
1084
+     * delete all properties from a given card
1085
+     *
1086
+     * @param int $addressBookId
1087
+     * @param int $cardId
1088
+     */
1089
+    protected function purgeProperties($addressBookId, $cardId) {
1090
+        $query = $this->db->getQueryBuilder();
1091
+        $query->delete($this->dbCardsPropertiesTable)
1092
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1093
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1094
+        $query->execute();
1095
+    }
1096
+
1097
+    /**
1098
+     * get ID from a given contact
1099
+     *
1100
+     * @param int $addressBookId
1101
+     * @param string $uri
1102
+     * @return int
1103
+     */
1104
+    protected function getCardId($addressBookId, $uri) {
1105
+        $query = $this->db->getQueryBuilder();
1106
+        $query->select('id')->from($this->dbCardsTable)
1107
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1108
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1109
+
1110
+        $result = $query->execute();
1111
+        $cardIds = $result->fetch();
1112
+        $result->closeCursor();
1113
+
1114
+        if (!isset($cardIds['id'])) {
1115
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1116
+        }
1117
+
1118
+        return (int)$cardIds['id'];
1119
+    }
1120
+
1121
+    /**
1122
+     * For shared address books the sharee is set in the ACL of the address book
1123
+     * @param $addressBookId
1124
+     * @param $acl
1125
+     * @return array
1126
+     */
1127
+    public function applyShareAcl($addressBookId, $acl) {
1128
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1129
+    }
1130
+
1131
+    private function convertPrincipal($principalUri, $toV2) {
1132
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1133
+            list(, $name) = \Sabre\Uri\split($principalUri);
1134
+            if ($toV2 === true) {
1135
+                return "principals/users/$name";
1136
+            }
1137
+            return "principals/$name";
1138
+        }
1139
+        return $principalUri;
1140
+    }
1141
+
1142
+    private function addOwnerPrincipal(&$addressbookInfo) {
1143
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1144
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1145
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1146
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1147
+        } else {
1148
+            $uri = $addressbookInfo['principaluri'];
1149
+        }
1150
+
1151
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1152
+        if (isset($principalInformation['{DAV:}displayname'])) {
1153
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1154
+        }
1155
+    }
1156
+
1157
+    /**
1158
+     * Extract UID from vcard
1159
+     *
1160
+     * @param string $cardData the vcard raw data
1161
+     * @return string the uid
1162
+     * @throws BadRequest if no UID is available
1163
+     */
1164
+    private function getUID($cardData) {
1165
+        if ($cardData != '') {
1166
+            $vCard = Reader::read($cardData);
1167
+            if ($vCard->UID) {
1168
+                $uid = $vCard->UID->getValue();
1169
+                return $uid;
1170
+            }
1171
+            // should already be handled, but just in case
1172
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1173
+        }
1174
+        // should already be handled, but just in case
1175
+        throw new BadRequest('vCard can not be empty');
1176
+    }
1177 1177
 }
Please login to merge, or discard this patch.
Spacing   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -124,7 +124,7 @@  discard block
 block discarded – undo
124 124
 			->from('addressbooks')
125 125
 			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
126 126
 
127
-		return (int)$query->execute()->fetchColumn();
127
+		return (int) $query->execute()->fetchColumn();
128 128
 	}
129 129
 
130 130
 	/**
@@ -155,15 +155,15 @@  discard block
 block discarded – undo
155 155
 		$addressBooks = [];
156 156
 
157 157
 		$result = $query->execute();
158
-		while($row = $result->fetch()) {
158
+		while ($row = $result->fetch()) {
159 159
 			$addressBooks[$row['id']] = [
160 160
 				'id'  => $row['id'],
161 161
 				'uri' => $row['uri'],
162 162
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
163 163
 				'{DAV:}displayname' => $row['displayname'],
164
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
164
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
165 165
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
166
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
166
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
167 167
 			];
168 168
 
169 169
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -174,10 +174,10 @@  discard block
 block discarded – undo
174 174
 		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
175 175
 		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
176 176
 
177
-		$principals = array_map(function ($principal) {
177
+		$principals = array_map(function($principal) {
178 178
 			return urldecode($principal);
179 179
 		}, $principals);
180
-		$principals[]= $principalUri;
180
+		$principals[] = $principalUri;
181 181
 
182 182
 		$query = $this->db->getQueryBuilder();
183 183
 		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
@@ -189,8 +189,8 @@  discard block
 block discarded – undo
189 189
 			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
190 190
 			->execute();
191 191
 
192
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
193
-		while($row = $result->fetch()) {
192
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
193
+		while ($row = $result->fetch()) {
194 194
 			if ($row['principaluri'] === $principalUri) {
195 195
 				continue;
196 196
 			}
@@ -209,18 +209,18 @@  discard block
 block discarded – undo
209 209
 			}
210 210
 
211 211
 			list(, $name) = \Sabre\Uri\split($row['principaluri']);
212
-			$uri = $row['uri'] . '_shared_by_' . $name;
213
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
212
+			$uri = $row['uri'].'_shared_by_'.$name;
213
+			$displayName = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
214 214
 
215 215
 			$addressBooks[$row['id']] = [
216 216
 				'id'  => $row['id'],
217 217
 				'uri' => $uri,
218 218
 				'principaluri' => $principalUriOriginal,
219 219
 				'{DAV:}displayname' => $displayName,
220
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
220
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
221 221
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
222
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
223
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
222
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
223
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $row['principaluri'],
224 224
 				$readOnlyPropertyName => $readOnly,
225 225
 			];
226 226
 
@@ -241,15 +241,15 @@  discard block
 block discarded – undo
241 241
 		$addressBooks = [];
242 242
 
243 243
 		$result = $query->execute();
244
-		while($row = $result->fetch()) {
244
+		while ($row = $result->fetch()) {
245 245
 			$addressBooks[$row['id']] = [
246 246
 				'id'  => $row['id'],
247 247
 				'uri' => $row['uri'],
248 248
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
249 249
 				'{DAV:}displayname' => $row['displayname'],
250
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
250
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
251 251
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
252
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
252
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
253 253
 			];
254 254
 
255 255
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -294,9 +294,9 @@  discard block
 block discarded – undo
294 294
 			'uri' => $row['uri'],
295 295
 			'principaluri' => $row['principaluri'],
296 296
 			'{DAV:}displayname' => $row['displayname'],
297
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
297
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
298 298
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
299
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
299
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
300 300
 		];
301 301
 
302 302
 		$this->addOwnerPrincipal($addressBook);
@@ -328,9 +328,9 @@  discard block
 block discarded – undo
328 328
 			'uri' => $row['uri'],
329 329
 			'principaluri' => $row['principaluri'],
330 330
 			'{DAV:}displayname' => $row['displayname'],
331
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
331
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
332 332
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
333
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
333
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
334 334
 		];
335 335
 
336 336
 		$this->addOwnerPrincipal($addressBook);
@@ -357,22 +357,22 @@  discard block
 block discarded – undo
357 357
 	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
358 358
 		$supportedProperties = [
359 359
 			'{DAV:}displayname',
360
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
360
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description',
361 361
 		];
362 362
 
363 363
 		/**
364 364
 		 * @suppress SqlInjectionChecker
365 365
 		 */
366
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
366
+		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
367 367
 
368 368
 			$updates = [];
369
-			foreach($mutations as $property=>$newValue) {
369
+			foreach ($mutations as $property=>$newValue) {
370 370
 
371
-				switch($property) {
371
+				switch ($property) {
372 372
 					case '{DAV:}displayname':
373 373
 						$updates['displayname'] = $newValue;
374 374
 						break;
375
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
375
+					case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
376 376
 						$updates['description'] = $newValue;
377 377
 						break;
378 378
 				}
@@ -380,7 +380,7 @@  discard block
 block discarded – undo
380 380
 			$query = $this->db->getQueryBuilder();
381 381
 			$query->update('addressbooks');
382 382
 
383
-			foreach($updates as $key=>$value) {
383
+			foreach ($updates as $key=>$value) {
384 384
 				$query->set($key, $query->createNamedParameter($value));
385 385
 			}
386 386
 			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
@@ -411,24 +411,24 @@  discard block
 block discarded – undo
411 411
 			'synctoken' => 1
412 412
 		];
413 413
 
414
-		foreach($properties as $property=>$newValue) {
414
+		foreach ($properties as $property=>$newValue) {
415 415
 
416
-			switch($property) {
416
+			switch ($property) {
417 417
 				case '{DAV:}displayname':
418 418
 					$values['displayname'] = $newValue;
419 419
 					break;
420
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
420
+				case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
421 421
 					$values['description'] = $newValue;
422 422
 					break;
423 423
 				default:
424
-					throw new BadRequest('Unknown property: ' . $property);
424
+					throw new BadRequest('Unknown property: '.$property);
425 425
 			}
426 426
 
427 427
 		}
428 428
 
429 429
 		// Fallback to make sure the displayname is set. Some clients may refuse
430 430
 		// to work with addressbooks not having a displayname.
431
-		if(is_null($values['displayname'])) {
431
+		if (is_null($values['displayname'])) {
432 432
 			$values['displayname'] = $url;
433 433
 		}
434 434
 
@@ -506,8 +506,8 @@  discard block
 block discarded – undo
506 506
 		$cards = [];
507 507
 
508 508
 		$result = $query->execute();
509
-		while($row = $result->fetch()) {
510
-			$row['etag'] = '"' . $row['etag'] . '"';
509
+		while ($row = $result->fetch()) {
510
+			$row['etag'] = '"'.$row['etag'].'"';
511 511
 			$row['carddata'] = $this->readBlob($row['carddata']);
512 512
 			$cards[] = $row;
513 513
 		}
@@ -541,7 +541,7 @@  discard block
 block discarded – undo
541 541
 		if (!$row) {
542 542
 			return false;
543 543
 		}
544
-		$row['etag'] = '"' . $row['etag'] . '"';
544
+		$row['etag'] = '"'.$row['etag'].'"';
545 545
 		$row['carddata'] = $this->readBlob($row['carddata']);
546 546
 
547 547
 		return $row;
@@ -578,7 +578,7 @@  discard block
 block discarded – undo
578 578
 			$result = $query->execute();
579 579
 
580 580
 			while ($row = $result->fetch()) {
581
-				$row['etag'] = '"' . $row['etag'] . '"';
581
+				$row['etag'] = '"'.$row['etag'].'"';
582 582
 				$row['carddata'] = $this->readBlob($row['carddata']);
583 583
 				$cards[] = $row;
584 584
 			}
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
 				'cardUri' => $cardUri,
652 652
 				'cardData' => $cardData]));
653 653
 
654
-		return '"' . $etag . '"';
654
+		return '"'.$etag.'"';
655 655
 	}
656 656
 
657 657
 	/**
@@ -703,7 +703,7 @@  discard block
 block discarded – undo
703 703
 				'cardUri' => $cardUri,
704 704
 				'cardData' => $cardData]));
705 705
 
706
-		return '"' . $etag . '"';
706
+		return '"'.$etag.'"';
707 707
 	}
708 708
 
709 709
 	/**
@@ -801,7 +801,7 @@  discard block
 block discarded – undo
801 801
 	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
802 802
 		// Current synctoken
803 803
 		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
804
-		$stmt->execute([ $addressBookId ]);
804
+		$stmt->execute([$addressBookId]);
805 805
 		$currentToken = $stmt->fetchColumn(0);
806 806
 
807 807
 		if (is_null($currentToken)) return null;
@@ -816,8 +816,8 @@  discard block
 block discarded – undo
816 816
 		if ($syncToken) {
817 817
 
818 818
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
819
-			if ($limit>0) {
820
-				$query .= " LIMIT " . (int)$limit;
819
+			if ($limit > 0) {
820
+				$query .= " LIMIT ".(int) $limit;
821 821
 			}
822 822
 
823 823
 			// Fetching all changes
@@ -828,15 +828,15 @@  discard block
 block discarded – undo
828 828
 
829 829
 			// This loop ensures that any duplicates are overwritten, only the
830 830
 			// last change on a node is relevant.
831
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
831
+			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
832 832
 
833 833
 				$changes[$row['uri']] = $row['operation'];
834 834
 
835 835
 			}
836 836
 
837
-			foreach($changes as $uri => $operation) {
837
+			foreach ($changes as $uri => $operation) {
838 838
 
839
-				switch($operation) {
839
+				switch ($operation) {
840 840
 					case 1:
841 841
 						$result['added'][] = $uri;
842 842
 						break;
@@ -924,10 +924,10 @@  discard block
 block discarded – undo
924 924
 
925 925
 		// No need for like when the pattern is empty
926 926
 		if ('' !== $pattern) {
927
-			if(\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
927
+			if (\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
928 928
 				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
929 929
 			} else {
930
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
930
+				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
931 931
 			}
932 932
 		}
933 933
 
@@ -939,7 +939,7 @@  discard block
 block discarded – undo
939 939
 
940 940
 		$result->closeCursor();
941 941
 
942
-		return array_map(function ($array) {
942
+		return array_map(function($array) {
943 943
 			$array['carddata'] = $this->readBlob($array['carddata']);
944 944
 			return $array;
945 945
 		}, $cards);
@@ -981,7 +981,7 @@  discard block
 block discarded – undo
981 981
 		$result->closeCursor();
982 982
 
983 983
 		if (!isset($uri['uri'])) {
984
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
984
+			throw new \InvalidArgumentException('Card does not exists: '.$id);
985 985
 		}
986 986
 
987 987
 		return $uri['uri'];
@@ -1053,11 +1053,11 @@  discard block
 block discarded – undo
1053 1053
 			);
1054 1054
 
1055 1055
 		foreach ($vCard->children() as $property) {
1056
-			if(!in_array($property->name, self::$indexProperties)) {
1056
+			if (!in_array($property->name, self::$indexProperties)) {
1057 1057
 				continue;
1058 1058
 			}
1059 1059
 			$preferred = 0;
1060
-			foreach($property->parameters as $parameter) {
1060
+			foreach ($property->parameters as $parameter) {
1061 1061
 				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1062 1062
 					$preferred = 1;
1063 1063
 					break;
@@ -1112,10 +1112,10 @@  discard block
 block discarded – undo
1112 1112
 		$result->closeCursor();
1113 1113
 
1114 1114
 		if (!isset($cardIds['id'])) {
1115
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1115
+			throw new \InvalidArgumentException('Card does not exists: '.$uri);
1116 1116
 		}
1117 1117
 
1118
-		return (int)$cardIds['id'];
1118
+		return (int) $cardIds['id'];
1119 1119
 	}
1120 1120
 
1121 1121
 	/**
@@ -1140,8 +1140,8 @@  discard block
 block discarded – undo
1140 1140
 	}
1141 1141
 
1142 1142
 	private function addOwnerPrincipal(&$addressbookInfo) {
1143
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1144
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1143
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1144
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1145 1145
 		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1146 1146
 			$uri = $addressbookInfo[$ownerPrincipalKey];
1147 1147
 		} else {
Please login to merge, or discard this patch.
apps/cloud_federation_api/lib/Controller/RequestHandlerController.php 1 patch
Indentation   +244 added lines, -244 removed lines patch added patch discarded remove patch
@@ -53,249 +53,249 @@
 block discarded – undo
53 53
  */
54 54
 class RequestHandlerController extends Controller {
55 55
 
56
-	/** @var ILogger */
57
-	private $logger;
58
-
59
-	/** @var IUserManager */
60
-	private $userManager;
61
-
62
-	/** @var IGroupManager */
63
-	private $groupManager;
64
-
65
-	/** @var IURLGenerator */
66
-	private $urlGenerator;
67
-
68
-	/** @var ICloudFederationProviderManager */
69
-	private $cloudFederationProviderManager;
70
-
71
-	/** @var Config */
72
-	private $config;
73
-
74
-	/** @var ICloudFederationFactory */
75
-	private $factory;
76
-
77
-	/** @var ICloudIdManager */
78
-	private $cloudIdManager;
79
-
80
-	public function __construct($appName,
81
-								IRequest $request,
82
-								ILogger $logger,
83
-								IUserManager $userManager,
84
-								IGroupManager $groupManager,
85
-								IURLGenerator $urlGenerator,
86
-								ICloudFederationProviderManager $cloudFederationProviderManager,
87
-								Config $config,
88
-								ICloudFederationFactory $factory,
89
-								ICloudIdManager $cloudIdManager
90
-	) {
91
-		parent::__construct($appName, $request);
92
-
93
-		$this->logger = $logger;
94
-		$this->userManager = $userManager;
95
-		$this->groupManager = $groupManager;
96
-		$this->urlGenerator = $urlGenerator;
97
-		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
98
-		$this->config = $config;
99
-		$this->factory = $factory;
100
-		$this->cloudIdManager = $cloudIdManager;
101
-	}
102
-
103
-	/**
104
-	 * add share
105
-	 *
106
-	 * @NoCSRFRequired
107
-	 * @PublicPage
108
-	 * @BruteForceProtection(action=receiveFederatedShare)
109
-	 *
110
-	 * @param string $shareWith
111
-	 * @param string $name resource name (e.g. document.odt)
112
-	 * @param string $description share description (optional)
113
-	 * @param string $providerId resource UID on the provider side
114
-	 * @param string $owner provider specific UID of the user who owns the resource
115
-	 * @param string $ownerDisplayName display name of the user who shared the item
116
-	 * @param string $sharedBy provider specific UID of the user who shared the resource
117
-	 * @param string $sharedByDisplayName display name of the user who shared the resource
118
-	 * @param array $protocol (e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]])
119
-	 * @param string $shareType ('group' or 'user' share)
120
-	 * @param $resourceType ('file', 'calendar',...)
121
-	 * @return Http\DataResponse|JSONResponse
122
-	 *
123
-	 * Example: curl -H "Content-Type: application/json" -X POST -d '{"shareWith":"admin1@serve1","name":"welcome server2.txt","description":"desc","providerId":"2","owner":"admin2@http://localhost/server2","ownerDisplayName":"admin2 display","shareType":"user","resourceType":"file","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' http://localhost/server/index.php/ocm/shares
124
-	 */
125
-	public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
126
-
127
-		// check if all required parameters are set
128
-		if ($shareWith === null ||
129
-			$name === null ||
130
-			$providerId === null ||
131
-			$owner === null ||
132
-			$resourceType === null ||
133
-			$shareType === null ||
134
-			!is_array($protocol) ||
135
-			!isset($protocol['name']) ||
136
-			!isset($protocol['options']) ||
137
-			!is_array($protocol['options']) ||
138
-			!isset($protocol['options']['sharedSecret'])
139
-		) {
140
-			return new JSONResponse(
141
-				['message' => 'Missing arguments'],
142
-				Http::STATUS_BAD_REQUEST
143
-			);
144
-		}
145
-
146
-		$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
147
-		if (!in_array($shareType, $supportedShareTypes)) {
148
-			return new JSONResponse(
149
-				['message' => 'Share type "' . $shareType . '" not implemented'],
150
-				Http::STATUS_NOT_IMPLEMENTED
151
-			);
152
-		}
153
-
154
-		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
155
-		$shareWith = $cloudId->getUser();
156
-
157
-		if ($shareType === 'user') {
158
-			$shareWith = $this->mapUid($shareWith);
159
-
160
-			if (!$this->userManager->userExists($shareWith)) {
161
-				return new JSONResponse(
162
-					['message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
163
-					Http::STATUS_BAD_REQUEST
164
-				);
165
-			}
166
-		}
167
-
168
-		if ($shareType === 'group') {
169
-			if(!$this->groupManager->groupExists($shareWith)) {
170
-				return new JSONResponse(
171
-					['message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
172
-					Http::STATUS_BAD_REQUEST
173
-				);
174
-			}
175
-		}
176
-
177
-		// if no explicit display name is given, we use the uid as display name
178
-		$ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
179
-		$sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
180
-
181
-		// sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
182
-		if ($sharedBy === null) {
183
-			$sharedBy = $owner;
184
-			$sharedByDisplayName = $ownerDisplayName;
185
-		}
186
-
187
-		try {
188
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
189
-			$share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
190
-			$share->setProtocol($protocol);
191
-			$provider->shareReceived($share);
192
-		} catch (ProviderDoesNotExistsException $e) {
193
-			return new JSONResponse(
194
-				['message' => $e->getMessage()],
195
-				Http::STATUS_NOT_IMPLEMENTED
196
-			);
197
-		} catch (ProviderCouldNotAddShareException $e) {
198
-			return new JSONResponse(
199
-				['message' => $e->getMessage()],
200
-				$e->getCode()
201
-			);
202
-		} catch (\Exception $e) {
203
-			return new JSONResponse(
204
-				['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
205
-				Http::STATUS_BAD_REQUEST
206
-			);
207
-		}
208
-
209
-		$user = $this->userManager->get($shareWith);
210
-		$recipientDisplayName = '';
211
-		if($user) {
212
-			$recipientDisplayName = $user->getDisplayName();
213
-		}
214
-
215
-		return new JSONResponse(
216
-			['recipientDisplayName' => $recipientDisplayName],
217
-			Http::STATUS_CREATED);
218
-
219
-	}
220
-
221
-	/**
222
-	 * receive notification about existing share
223
-	 *
224
-	 * @NoCSRFRequired
225
-	 * @PublicPage
226
-	 * @BruteForceProtection(action=receiveFederatedShareNotification)
227
-	 *
228
-	 * @param string $notificationType (notification type, e.g. SHARE_ACCEPTED)
229
-	 * @param string $resourceType (calendar, file, contact,...)
230
-	 * @param string $providerId id of the share
231
-	 * @param array $notification the actual payload of the notification
232
-	 * @return JSONResponse
233
-	 */
234
-	public function receiveNotification($notificationType, $resourceType, $providerId, array $notification) {
235
-
236
-		// check if all required parameters are set
237
-		if ($notificationType === null ||
238
-			$resourceType === null ||
239
-			$providerId === null ||
240
-			!is_array($notification)
241
-		) {
242
-			return new JSONResponse(
243
-				['message' => 'Missing arguments'],
244
-				Http::STATUS_BAD_REQUEST
245
-			);
246
-		}
247
-
248
-		try {
249
-			$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
250
-			$result = $provider->notificationReceived($notificationType, $providerId, $notification);
251
-		} catch (ProviderDoesNotExistsException $e) {
252
-			return new JSONResponse(
253
-				['message' => $e->getMessage()],
254
-				Http::STATUS_BAD_REQUEST
255
-			);
256
-		} catch (ShareNotFound $e) {
257
-			return new JSONResponse(
258
-				['message' => $e->getMessage()],
259
-				Http::STATUS_BAD_REQUEST
260
-			);
261
-		} catch (ActionNotSupportedException $e) {
262
-			return new JSONResponse(
263
-				['message' => $e->getMessage()],
264
-				Http::STATUS_NOT_IMPLEMENTED
265
-			);
266
-		} catch (BadRequestException $e) {
267
-			return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
268
-		} catch (AuthenticationFailedException $e) {
269
-			return new JSONResponse(["message" => "RESOURCE_NOT_FOUND"], Http::STATUS_FORBIDDEN);
270
-		}
271
-		catch (\Exception $e) {
272
-			return new JSONResponse(
273
-				['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
274
-				Http::STATUS_BAD_REQUEST
275
-			);
276
-		}
277
-
278
-		return new JSONResponse($result,Http::STATUS_CREATED);
279
-
280
-	}
281
-
282
-	/**
283
-	 * map login name to internal LDAP UID if a LDAP backend is in use
284
-	 *
285
-	 * @param string $uid
286
-	 * @return string mixed
287
-	 */
288
-	private function mapUid($uid) {
289
-		// FIXME this should be a method in the user management instead
290
-		$this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
291
-		\OCP\Util::emitHook(
292
-			'\OCA\Files_Sharing\API\Server2Server',
293
-			'preLoginNameUsedAsUserName',
294
-			['uid' => &$uid]
295
-		);
296
-		$this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
297
-
298
-		return $uid;
299
-	}
56
+    /** @var ILogger */
57
+    private $logger;
58
+
59
+    /** @var IUserManager */
60
+    private $userManager;
61
+
62
+    /** @var IGroupManager */
63
+    private $groupManager;
64
+
65
+    /** @var IURLGenerator */
66
+    private $urlGenerator;
67
+
68
+    /** @var ICloudFederationProviderManager */
69
+    private $cloudFederationProviderManager;
70
+
71
+    /** @var Config */
72
+    private $config;
73
+
74
+    /** @var ICloudFederationFactory */
75
+    private $factory;
76
+
77
+    /** @var ICloudIdManager */
78
+    private $cloudIdManager;
79
+
80
+    public function __construct($appName,
81
+                                IRequest $request,
82
+                                ILogger $logger,
83
+                                IUserManager $userManager,
84
+                                IGroupManager $groupManager,
85
+                                IURLGenerator $urlGenerator,
86
+                                ICloudFederationProviderManager $cloudFederationProviderManager,
87
+                                Config $config,
88
+                                ICloudFederationFactory $factory,
89
+                                ICloudIdManager $cloudIdManager
90
+    ) {
91
+        parent::__construct($appName, $request);
92
+
93
+        $this->logger = $logger;
94
+        $this->userManager = $userManager;
95
+        $this->groupManager = $groupManager;
96
+        $this->urlGenerator = $urlGenerator;
97
+        $this->cloudFederationProviderManager = $cloudFederationProviderManager;
98
+        $this->config = $config;
99
+        $this->factory = $factory;
100
+        $this->cloudIdManager = $cloudIdManager;
101
+    }
102
+
103
+    /**
104
+     * add share
105
+     *
106
+     * @NoCSRFRequired
107
+     * @PublicPage
108
+     * @BruteForceProtection(action=receiveFederatedShare)
109
+     *
110
+     * @param string $shareWith
111
+     * @param string $name resource name (e.g. document.odt)
112
+     * @param string $description share description (optional)
113
+     * @param string $providerId resource UID on the provider side
114
+     * @param string $owner provider specific UID of the user who owns the resource
115
+     * @param string $ownerDisplayName display name of the user who shared the item
116
+     * @param string $sharedBy provider specific UID of the user who shared the resource
117
+     * @param string $sharedByDisplayName display name of the user who shared the resource
118
+     * @param array $protocol (e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]])
119
+     * @param string $shareType ('group' or 'user' share)
120
+     * @param $resourceType ('file', 'calendar',...)
121
+     * @return Http\DataResponse|JSONResponse
122
+     *
123
+     * Example: curl -H "Content-Type: application/json" -X POST -d '{"shareWith":"admin1@serve1","name":"welcome server2.txt","description":"desc","providerId":"2","owner":"admin2@http://localhost/server2","ownerDisplayName":"admin2 display","shareType":"user","resourceType":"file","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' http://localhost/server/index.php/ocm/shares
124
+     */
125
+    public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
126
+
127
+        // check if all required parameters are set
128
+        if ($shareWith === null ||
129
+            $name === null ||
130
+            $providerId === null ||
131
+            $owner === null ||
132
+            $resourceType === null ||
133
+            $shareType === null ||
134
+            !is_array($protocol) ||
135
+            !isset($protocol['name']) ||
136
+            !isset($protocol['options']) ||
137
+            !is_array($protocol['options']) ||
138
+            !isset($protocol['options']['sharedSecret'])
139
+        ) {
140
+            return new JSONResponse(
141
+                ['message' => 'Missing arguments'],
142
+                Http::STATUS_BAD_REQUEST
143
+            );
144
+        }
145
+
146
+        $supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
147
+        if (!in_array($shareType, $supportedShareTypes)) {
148
+            return new JSONResponse(
149
+                ['message' => 'Share type "' . $shareType . '" not implemented'],
150
+                Http::STATUS_NOT_IMPLEMENTED
151
+            );
152
+        }
153
+
154
+        $cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
155
+        $shareWith = $cloudId->getUser();
156
+
157
+        if ($shareType === 'user') {
158
+            $shareWith = $this->mapUid($shareWith);
159
+
160
+            if (!$this->userManager->userExists($shareWith)) {
161
+                return new JSONResponse(
162
+                    ['message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
163
+                    Http::STATUS_BAD_REQUEST
164
+                );
165
+            }
166
+        }
167
+
168
+        if ($shareType === 'group') {
169
+            if(!$this->groupManager->groupExists($shareWith)) {
170
+                return new JSONResponse(
171
+                    ['message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
172
+                    Http::STATUS_BAD_REQUEST
173
+                );
174
+            }
175
+        }
176
+
177
+        // if no explicit display name is given, we use the uid as display name
178
+        $ownerDisplayName = $ownerDisplayName === null ? $owner : $ownerDisplayName;
179
+        $sharedByDisplayName = $sharedByDisplayName === null ? $sharedBy : $sharedByDisplayName;
180
+
181
+        // sharedBy* parameter is optional, if nothing is set we assume that it is the same user as the owner
182
+        if ($sharedBy === null) {
183
+            $sharedBy = $owner;
184
+            $sharedByDisplayName = $ownerDisplayName;
185
+        }
186
+
187
+        try {
188
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
189
+            $share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
190
+            $share->setProtocol($protocol);
191
+            $provider->shareReceived($share);
192
+        } catch (ProviderDoesNotExistsException $e) {
193
+            return new JSONResponse(
194
+                ['message' => $e->getMessage()],
195
+                Http::STATUS_NOT_IMPLEMENTED
196
+            );
197
+        } catch (ProviderCouldNotAddShareException $e) {
198
+            return new JSONResponse(
199
+                ['message' => $e->getMessage()],
200
+                $e->getCode()
201
+            );
202
+        } catch (\Exception $e) {
203
+            return new JSONResponse(
204
+                ['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
205
+                Http::STATUS_BAD_REQUEST
206
+            );
207
+        }
208
+
209
+        $user = $this->userManager->get($shareWith);
210
+        $recipientDisplayName = '';
211
+        if($user) {
212
+            $recipientDisplayName = $user->getDisplayName();
213
+        }
214
+
215
+        return new JSONResponse(
216
+            ['recipientDisplayName' => $recipientDisplayName],
217
+            Http::STATUS_CREATED);
218
+
219
+    }
220
+
221
+    /**
222
+     * receive notification about existing share
223
+     *
224
+     * @NoCSRFRequired
225
+     * @PublicPage
226
+     * @BruteForceProtection(action=receiveFederatedShareNotification)
227
+     *
228
+     * @param string $notificationType (notification type, e.g. SHARE_ACCEPTED)
229
+     * @param string $resourceType (calendar, file, contact,...)
230
+     * @param string $providerId id of the share
231
+     * @param array $notification the actual payload of the notification
232
+     * @return JSONResponse
233
+     */
234
+    public function receiveNotification($notificationType, $resourceType, $providerId, array $notification) {
235
+
236
+        // check if all required parameters are set
237
+        if ($notificationType === null ||
238
+            $resourceType === null ||
239
+            $providerId === null ||
240
+            !is_array($notification)
241
+        ) {
242
+            return new JSONResponse(
243
+                ['message' => 'Missing arguments'],
244
+                Http::STATUS_BAD_REQUEST
245
+            );
246
+        }
247
+
248
+        try {
249
+            $provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
250
+            $result = $provider->notificationReceived($notificationType, $providerId, $notification);
251
+        } catch (ProviderDoesNotExistsException $e) {
252
+            return new JSONResponse(
253
+                ['message' => $e->getMessage()],
254
+                Http::STATUS_BAD_REQUEST
255
+            );
256
+        } catch (ShareNotFound $e) {
257
+            return new JSONResponse(
258
+                ['message' => $e->getMessage()],
259
+                Http::STATUS_BAD_REQUEST
260
+            );
261
+        } catch (ActionNotSupportedException $e) {
262
+            return new JSONResponse(
263
+                ['message' => $e->getMessage()],
264
+                Http::STATUS_NOT_IMPLEMENTED
265
+            );
266
+        } catch (BadRequestException $e) {
267
+            return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
268
+        } catch (AuthenticationFailedException $e) {
269
+            return new JSONResponse(["message" => "RESOURCE_NOT_FOUND"], Http::STATUS_FORBIDDEN);
270
+        }
271
+        catch (\Exception $e) {
272
+            return new JSONResponse(
273
+                ['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
274
+                Http::STATUS_BAD_REQUEST
275
+            );
276
+        }
277
+
278
+        return new JSONResponse($result,Http::STATUS_CREATED);
279
+
280
+    }
281
+
282
+    /**
283
+     * map login name to internal LDAP UID if a LDAP backend is in use
284
+     *
285
+     * @param string $uid
286
+     * @return string mixed
287
+     */
288
+    private function mapUid($uid) {
289
+        // FIXME this should be a method in the user management instead
290
+        $this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
291
+        \OCP\Util::emitHook(
292
+            '\OCA\Files_Sharing\API\Server2Server',
293
+            'preLoginNameUsedAsUserName',
294
+            ['uid' => &$uid]
295
+        );
296
+        $this->logger->debug('shareWith after, ' . $uid, ['app' => $this->appName]);
297
+
298
+        return $uid;
299
+    }
300 300
 
301 301
 }
Please login to merge, or discard this patch.
apps/files/templates/list.php 1 patch
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -12,10 +12,10 @@  discard block
 block discarded – undo
12 12
 	*/ ?>
13 13
 	<input type="hidden" name="permissions" value="" id="permissions">
14 14
 	<input type="hidden" id="free_space" value="<?php isset($_['freeSpace']) ? p($_['freeSpace']) : '' ?>">
15
-	<?php if(isset($_['dirToken'])):?>
15
+	<?php if (isset($_['dirToken'])):?>
16 16
 	<input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
17 17
 	<input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" />
18
-	<?php endif;?>
18
+	<?php endif; ?>
19 19
 	<input type="hidden" class="max_human_file_size"
20 20
 		   value="(max <?php isset($_['uploadMaxHumanFilesize']) ? p($_['uploadMaxHumanFilesize']) : ''; ?>)">
21 21
 </div>
@@ -77,6 +77,6 @@  discard block
 block discarded – undo
77 77
 <div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! -->
78 78
 <div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>">
79 79
 	<p>
80
-	<?php p($l->t('The files you are trying to upload exceed the maximum size for file uploads on this server.'));?>
80
+	<?php p($l->t('The files you are trying to upload exceed the maximum size for file uploads on this server.')); ?>
81 81
 	</p>
82 82
 </div>
Please login to merge, or discard this patch.
apps/files/lib/Command/Scan.php 2 patches
Indentation   +270 added lines, -270 removed lines patch added patch discarded remove patch
@@ -51,275 +51,275 @@
 block discarded – undo
51 51
 
52 52
 class Scan extends Base {
53 53
 
54
-	/** @var IUserManager $userManager */
55
-	private $userManager;
56
-	/** @var float */
57
-	protected $execTime = 0;
58
-	/** @var int */
59
-	protected $foldersCounter = 0;
60
-	/** @var int */
61
-	protected $filesCounter = 0;
62
-
63
-	public function __construct(IUserManager $userManager) {
64
-		$this->userManager = $userManager;
65
-		parent::__construct();
66
-	}
67
-
68
-	protected function configure() {
69
-		parent::configure();
70
-
71
-		$this
72
-			->setName('files:scan')
73
-			->setDescription('rescan filesystem')
74
-			->addArgument(
75
-				'user_id',
76
-				InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
77
-				'will rescan all files of the given user(s)'
78
-			)
79
-			->addOption(
80
-				'path',
81
-				'p',
82
-				InputArgument::OPTIONAL,
83
-				'limit rescan to this path, eg. --path="/alice/files/Music", the user_id is determined by the path and the user_id parameter and --all are ignored'
84
-			)
85
-			->addOption(
86
-				'all',
87
-				null,
88
-				InputOption::VALUE_NONE,
89
-				'will rescan all files of all known users'
90
-			)->addOption(
91
-				'unscanned',
92
-				null,
93
-				InputOption::VALUE_NONE,
94
-				'only scan files which are marked as not fully scanned'
95
-			)->addOption(
96
-				'shallow',
97
-				null,
98
-				InputOption::VALUE_NONE,
99
-				'do not scan folders recursively'
100
-			)->addOption(
101
-				'home-only',
102
-				null,
103
-				InputOption::VALUE_NONE,
104
-				'only scan the home storage, ignoring any mounted external storage or share'
105
-			);
106
-	}
107
-
108
-	public function checkScanWarning($fullPath, OutputInterface $output) {
109
-		$normalizedPath = basename(\OC\Files\Filesystem::normalizePath($fullPath));
110
-		$path = basename($fullPath);
111
-
112
-		if ($normalizedPath !== $path) {
113
-			$output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>');
114
-		}
115
-	}
116
-
117
-	protected function scanFiles($user, $path, OutputInterface $output, $backgroundScan = false, $recursive = true, $homeOnly = false) {
118
-		$connection = $this->reconnectToDatabase($output);
119
-		$scanner = new \OC\Files\Utils\Scanner($user, $connection, \OC::$server->query(IEventDispatcher::class), \OC::$server->getLogger());
120
-
121
-		# check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
122
-
123
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
124
-			$output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
125
-			++$this->filesCounter;
126
-			$this->abortIfInterrupted();
127
-		});
128
-
129
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
130
-			$output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
131
-			++$this->foldersCounter;
132
-			$this->abortIfInterrupted();
133
-		});
134
-
135
-		$scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) {
136
-			$output->writeln('Error while scanning, storage not available (' . $e->getMessage() . ')', OutputInterface::VERBOSITY_VERBOSE);
137
-		});
138
-
139
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
140
-			$this->checkScanWarning($path, $output);
141
-		});
142
-
143
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
144
-			$this->checkScanWarning($path, $output);
145
-		});
146
-
147
-		try {
148
-			if ($backgroundScan) {
149
-				$scanner->backgroundScan($path);
150
-			} else {
151
-				$scanner->scan($path, $recursive, $homeOnly ? [$this, 'filterHomeMount'] : null);
152
-			}
153
-		} catch (ForbiddenException $e) {
154
-			$output->writeln("<error>Home storage for user $user not writable</error>");
155
-			$output->writeln('Make sure you\'re running the scan command only as the user the web server runs as');
156
-		} catch (InterruptedException $e) {
157
-			# exit the function if ctrl-c has been pressed
158
-			$output->writeln('Interrupted by user');
159
-		} catch (NotFoundException $e) {
160
-			$output->writeln('<error>Path not found: ' . $e->getMessage() . '</error>');
161
-		} catch (\Exception $e) {
162
-			$output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>');
163
-			$output->writeln('<error>' . $e->getTraceAsString() . '</error>');
164
-		}
165
-	}
166
-
167
-	public function filterHomeMount(IMountPoint $mountPoint) {
168
-		// any mountpoint inside '/$user/files/'
169
-		return substr_count($mountPoint->getMountPoint(), '/') <= 3;
170
-	}
171
-
172
-	protected function execute(InputInterface $input, OutputInterface $output) {
173
-		$inputPath = $input->getOption('path');
174
-		if ($inputPath) {
175
-			$inputPath = '/' . trim($inputPath, '/');
176
-			list(, $user,) = explode('/', $inputPath, 3);
177
-			$users = [$user];
178
-		} else if ($input->getOption('all')) {
179
-			$users = $this->userManager->search('');
180
-		} else {
181
-			$users = $input->getArgument('user_id');
182
-		}
183
-
184
-		# restrict the verbosity level to VERBOSITY_VERBOSE
185
-		if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) {
186
-			$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
187
-		}
188
-
189
-		# check quantity of users to be process and show it on the command line
190
-		$users_total = count($users);
191
-		if ($users_total === 0) {
192
-			$output->writeln('<error>Please specify the user id to scan, --all to scan for all users or --path=...</error>');
193
-			return;
194
-		}
195
-
196
-		$this->initTools();
197
-
198
-		$user_count = 0;
199
-		foreach ($users as $user) {
200
-			if (is_object($user)) {
201
-				$user = $user->getUID();
202
-			}
203
-			$path = $inputPath ? $inputPath : '/' . $user;
204
-			++$user_count;
205
-			if ($this->userManager->userExists($user)) {
206
-				$output->writeln("Starting scan for user $user_count out of $users_total ($user)");
207
-				$this->scanFiles($user, $path, $output, $input->getOption('unscanned'), !$input->getOption('shallow'), $input->getOption('home-only'));
208
-				$output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
209
-			} else {
210
-				$output->writeln("<error>Unknown user $user_count $user</error>");
211
-				$output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
212
-			}
213
-
214
-			try {
215
-				$this->abortIfInterrupted();
216
-			} catch (InterruptedException $e) {
217
-				break;
218
-			}
219
-		}
220
-
221
-		$this->presentStats($output);
222
-	}
223
-
224
-	/**
225
-	 * Initialises some useful tools for the Command
226
-	 */
227
-	protected function initTools() {
228
-		// Start the timer
229
-		$this->execTime = -microtime(true);
230
-		// Convert PHP errors to exceptions
231
-		set_error_handler([$this, 'exceptionErrorHandler'], E_ALL);
232
-	}
233
-
234
-	/**
235
-	 * Processes PHP errors as exceptions in order to be able to keep track of problems
236
-	 *
237
-	 * @see https://secure.php.net/manual/en/function.set-error-handler.php
238
-	 *
239
-	 * @param int $severity the level of the error raised
240
-	 * @param string $message
241
-	 * @param string $file the filename that the error was raised in
242
-	 * @param int $line the line number the error was raised
243
-	 *
244
-	 * @throws \ErrorException
245
-	 */
246
-	public function exceptionErrorHandler($severity, $message, $file, $line) {
247
-		if (!(error_reporting() & $severity)) {
248
-			// This error code is not included in error_reporting
249
-			return;
250
-		}
251
-		throw new \ErrorException($message, 0, $severity, $file, $line);
252
-	}
253
-
254
-	/**
255
-	 * @param OutputInterface $output
256
-	 */
257
-	protected function presentStats(OutputInterface $output) {
258
-		// Stop the timer
259
-		$this->execTime += microtime(true);
260
-
261
-		$headers = [
262
-			'Folders', 'Files', 'Elapsed time'
263
-		];
264
-
265
-		$this->showSummary($headers, null, $output);
266
-	}
267
-
268
-	/**
269
-	 * Shows a summary of operations
270
-	 *
271
-	 * @param string[] $headers
272
-	 * @param string[] $rows
273
-	 * @param OutputInterface $output
274
-	 */
275
-	protected function showSummary($headers, $rows, OutputInterface $output) {
276
-		$niceDate = $this->formatExecTime();
277
-		if (!$rows) {
278
-			$rows = [
279
-				$this->foldersCounter,
280
-				$this->filesCounter,
281
-				$niceDate,
282
-			];
283
-		}
284
-		$table = new Table($output);
285
-		$table
286
-			->setHeaders($headers)
287
-			->setRows([$rows]);
288
-		$table->render();
289
-	}
290
-
291
-
292
-	/**
293
-	 * Formats microtime into a human readable format
294
-	 *
295
-	 * @return string
296
-	 */
297
-	protected function formatExecTime() {
298
-		$secs = round($this->execTime);
299
-		# convert seconds into HH:MM:SS form
300
-		return sprintf('%02d:%02d:%02d', ($secs/3600), ($secs/60%60), $secs%60);
301
-	}
302
-
303
-	/**
304
-	 * @return \OCP\IDBConnection
305
-	 */
306
-	protected function reconnectToDatabase(OutputInterface $output) {
307
-		/** @var Connection | IDBConnection $connection */
308
-		$connection = \OC::$server->getDatabaseConnection();
309
-		try {
310
-			$connection->close();
311
-		} catch (\Exception $ex) {
312
-			$output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
313
-		}
314
-		while (!$connection->isConnected()) {
315
-			try {
316
-				$connection->connect();
317
-			} catch (\Exception $ex) {
318
-				$output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
319
-				sleep(60);
320
-			}
321
-		}
322
-		return $connection;
323
-	}
54
+    /** @var IUserManager $userManager */
55
+    private $userManager;
56
+    /** @var float */
57
+    protected $execTime = 0;
58
+    /** @var int */
59
+    protected $foldersCounter = 0;
60
+    /** @var int */
61
+    protected $filesCounter = 0;
62
+
63
+    public function __construct(IUserManager $userManager) {
64
+        $this->userManager = $userManager;
65
+        parent::__construct();
66
+    }
67
+
68
+    protected function configure() {
69
+        parent::configure();
70
+
71
+        $this
72
+            ->setName('files:scan')
73
+            ->setDescription('rescan filesystem')
74
+            ->addArgument(
75
+                'user_id',
76
+                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
77
+                'will rescan all files of the given user(s)'
78
+            )
79
+            ->addOption(
80
+                'path',
81
+                'p',
82
+                InputArgument::OPTIONAL,
83
+                'limit rescan to this path, eg. --path="/alice/files/Music", the user_id is determined by the path and the user_id parameter and --all are ignored'
84
+            )
85
+            ->addOption(
86
+                'all',
87
+                null,
88
+                InputOption::VALUE_NONE,
89
+                'will rescan all files of all known users'
90
+            )->addOption(
91
+                'unscanned',
92
+                null,
93
+                InputOption::VALUE_NONE,
94
+                'only scan files which are marked as not fully scanned'
95
+            )->addOption(
96
+                'shallow',
97
+                null,
98
+                InputOption::VALUE_NONE,
99
+                'do not scan folders recursively'
100
+            )->addOption(
101
+                'home-only',
102
+                null,
103
+                InputOption::VALUE_NONE,
104
+                'only scan the home storage, ignoring any mounted external storage or share'
105
+            );
106
+    }
107
+
108
+    public function checkScanWarning($fullPath, OutputInterface $output) {
109
+        $normalizedPath = basename(\OC\Files\Filesystem::normalizePath($fullPath));
110
+        $path = basename($fullPath);
111
+
112
+        if ($normalizedPath !== $path) {
113
+            $output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>');
114
+        }
115
+    }
116
+
117
+    protected function scanFiles($user, $path, OutputInterface $output, $backgroundScan = false, $recursive = true, $homeOnly = false) {
118
+        $connection = $this->reconnectToDatabase($output);
119
+        $scanner = new \OC\Files\Utils\Scanner($user, $connection, \OC::$server->query(IEventDispatcher::class), \OC::$server->getLogger());
120
+
121
+        # check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
122
+
123
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
124
+            $output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
125
+            ++$this->filesCounter;
126
+            $this->abortIfInterrupted();
127
+        });
128
+
129
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
130
+            $output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
131
+            ++$this->foldersCounter;
132
+            $this->abortIfInterrupted();
133
+        });
134
+
135
+        $scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) {
136
+            $output->writeln('Error while scanning, storage not available (' . $e->getMessage() . ')', OutputInterface::VERBOSITY_VERBOSE);
137
+        });
138
+
139
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
140
+            $this->checkScanWarning($path, $output);
141
+        });
142
+
143
+        $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
144
+            $this->checkScanWarning($path, $output);
145
+        });
146
+
147
+        try {
148
+            if ($backgroundScan) {
149
+                $scanner->backgroundScan($path);
150
+            } else {
151
+                $scanner->scan($path, $recursive, $homeOnly ? [$this, 'filterHomeMount'] : null);
152
+            }
153
+        } catch (ForbiddenException $e) {
154
+            $output->writeln("<error>Home storage for user $user not writable</error>");
155
+            $output->writeln('Make sure you\'re running the scan command only as the user the web server runs as');
156
+        } catch (InterruptedException $e) {
157
+            # exit the function if ctrl-c has been pressed
158
+            $output->writeln('Interrupted by user');
159
+        } catch (NotFoundException $e) {
160
+            $output->writeln('<error>Path not found: ' . $e->getMessage() . '</error>');
161
+        } catch (\Exception $e) {
162
+            $output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>');
163
+            $output->writeln('<error>' . $e->getTraceAsString() . '</error>');
164
+        }
165
+    }
166
+
167
+    public function filterHomeMount(IMountPoint $mountPoint) {
168
+        // any mountpoint inside '/$user/files/'
169
+        return substr_count($mountPoint->getMountPoint(), '/') <= 3;
170
+    }
171
+
172
+    protected function execute(InputInterface $input, OutputInterface $output) {
173
+        $inputPath = $input->getOption('path');
174
+        if ($inputPath) {
175
+            $inputPath = '/' . trim($inputPath, '/');
176
+            list(, $user,) = explode('/', $inputPath, 3);
177
+            $users = [$user];
178
+        } else if ($input->getOption('all')) {
179
+            $users = $this->userManager->search('');
180
+        } else {
181
+            $users = $input->getArgument('user_id');
182
+        }
183
+
184
+        # restrict the verbosity level to VERBOSITY_VERBOSE
185
+        if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) {
186
+            $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
187
+        }
188
+
189
+        # check quantity of users to be process and show it on the command line
190
+        $users_total = count($users);
191
+        if ($users_total === 0) {
192
+            $output->writeln('<error>Please specify the user id to scan, --all to scan for all users or --path=...</error>');
193
+            return;
194
+        }
195
+
196
+        $this->initTools();
197
+
198
+        $user_count = 0;
199
+        foreach ($users as $user) {
200
+            if (is_object($user)) {
201
+                $user = $user->getUID();
202
+            }
203
+            $path = $inputPath ? $inputPath : '/' . $user;
204
+            ++$user_count;
205
+            if ($this->userManager->userExists($user)) {
206
+                $output->writeln("Starting scan for user $user_count out of $users_total ($user)");
207
+                $this->scanFiles($user, $path, $output, $input->getOption('unscanned'), !$input->getOption('shallow'), $input->getOption('home-only'));
208
+                $output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
209
+            } else {
210
+                $output->writeln("<error>Unknown user $user_count $user</error>");
211
+                $output->writeln('', OutputInterface::VERBOSITY_VERBOSE);
212
+            }
213
+
214
+            try {
215
+                $this->abortIfInterrupted();
216
+            } catch (InterruptedException $e) {
217
+                break;
218
+            }
219
+        }
220
+
221
+        $this->presentStats($output);
222
+    }
223
+
224
+    /**
225
+     * Initialises some useful tools for the Command
226
+     */
227
+    protected function initTools() {
228
+        // Start the timer
229
+        $this->execTime = -microtime(true);
230
+        // Convert PHP errors to exceptions
231
+        set_error_handler([$this, 'exceptionErrorHandler'], E_ALL);
232
+    }
233
+
234
+    /**
235
+     * Processes PHP errors as exceptions in order to be able to keep track of problems
236
+     *
237
+     * @see https://secure.php.net/manual/en/function.set-error-handler.php
238
+     *
239
+     * @param int $severity the level of the error raised
240
+     * @param string $message
241
+     * @param string $file the filename that the error was raised in
242
+     * @param int $line the line number the error was raised
243
+     *
244
+     * @throws \ErrorException
245
+     */
246
+    public function exceptionErrorHandler($severity, $message, $file, $line) {
247
+        if (!(error_reporting() & $severity)) {
248
+            // This error code is not included in error_reporting
249
+            return;
250
+        }
251
+        throw new \ErrorException($message, 0, $severity, $file, $line);
252
+    }
253
+
254
+    /**
255
+     * @param OutputInterface $output
256
+     */
257
+    protected function presentStats(OutputInterface $output) {
258
+        // Stop the timer
259
+        $this->execTime += microtime(true);
260
+
261
+        $headers = [
262
+            'Folders', 'Files', 'Elapsed time'
263
+        ];
264
+
265
+        $this->showSummary($headers, null, $output);
266
+    }
267
+
268
+    /**
269
+     * Shows a summary of operations
270
+     *
271
+     * @param string[] $headers
272
+     * @param string[] $rows
273
+     * @param OutputInterface $output
274
+     */
275
+    protected function showSummary($headers, $rows, OutputInterface $output) {
276
+        $niceDate = $this->formatExecTime();
277
+        if (!$rows) {
278
+            $rows = [
279
+                $this->foldersCounter,
280
+                $this->filesCounter,
281
+                $niceDate,
282
+            ];
283
+        }
284
+        $table = new Table($output);
285
+        $table
286
+            ->setHeaders($headers)
287
+            ->setRows([$rows]);
288
+        $table->render();
289
+    }
290
+
291
+
292
+    /**
293
+     * Formats microtime into a human readable format
294
+     *
295
+     * @return string
296
+     */
297
+    protected function formatExecTime() {
298
+        $secs = round($this->execTime);
299
+        # convert seconds into HH:MM:SS form
300
+        return sprintf('%02d:%02d:%02d', ($secs/3600), ($secs/60%60), $secs%60);
301
+    }
302
+
303
+    /**
304
+     * @return \OCP\IDBConnection
305
+     */
306
+    protected function reconnectToDatabase(OutputInterface $output) {
307
+        /** @var Connection | IDBConnection $connection */
308
+        $connection = \OC::$server->getDatabaseConnection();
309
+        try {
310
+            $connection->close();
311
+        } catch (\Exception $ex) {
312
+            $output->writeln("<info>Error while disconnecting from database: {$ex->getMessage()}</info>");
313
+        }
314
+        while (!$connection->isConnected()) {
315
+            try {
316
+                $connection->connect();
317
+            } catch (\Exception $ex) {
318
+                $output->writeln("<info>Error while re-connecting to database: {$ex->getMessage()}</info>");
319
+                sleep(60);
320
+            }
321
+        }
322
+        return $connection;
323
+    }
324 324
 
325 325
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -110,7 +110,7 @@  discard block
 block discarded – undo
110 110
 		$path = basename($fullPath);
111 111
 
112 112
 		if ($normalizedPath !== $path) {
113
-			$output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>');
113
+			$output->writeln("\t<error>Entry \"".$fullPath.'" will not be accessible due to incompatible encoding</error>');
114 114
 		}
115 115
 	}
116 116
 
@@ -120,27 +120,27 @@  discard block
 block discarded – undo
120 120
 
121 121
 		# check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
122 122
 
123
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
123
+		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function($path) use ($output) {
124 124
 			$output->writeln("\tFile\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
125 125
 			++$this->filesCounter;
126 126
 			$this->abortIfInterrupted();
127 127
 		});
128 128
 
129
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
129
+		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function($path) use ($output) {
130 130
 			$output->writeln("\tFolder\t<info>$path</info>", OutputInterface::VERBOSITY_VERBOSE);
131 131
 			++$this->foldersCounter;
132 132
 			$this->abortIfInterrupted();
133 133
 		});
134 134
 
135
-		$scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) {
136
-			$output->writeln('Error while scanning, storage not available (' . $e->getMessage() . ')', OutputInterface::VERBOSITY_VERBOSE);
135
+		$scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function(StorageNotAvailableException $e) use ($output) {
136
+			$output->writeln('Error while scanning, storage not available ('.$e->getMessage().')', OutputInterface::VERBOSITY_VERBOSE);
137 137
 		});
138 138
 
139
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
139
+		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function($path) use ($output) {
140 140
 			$this->checkScanWarning($path, $output);
141 141
 		});
142 142
 
143
-		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
143
+		$scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function($path) use ($output) {
144 144
 			$this->checkScanWarning($path, $output);
145 145
 		});
146 146
 
@@ -157,10 +157,10 @@  discard block
 block discarded – undo
157 157
 			# exit the function if ctrl-c has been pressed
158 158
 			$output->writeln('Interrupted by user');
159 159
 		} catch (NotFoundException $e) {
160
-			$output->writeln('<error>Path not found: ' . $e->getMessage() . '</error>');
160
+			$output->writeln('<error>Path not found: '.$e->getMessage().'</error>');
161 161
 		} catch (\Exception $e) {
162
-			$output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>');
163
-			$output->writeln('<error>' . $e->getTraceAsString() . '</error>');
162
+			$output->writeln('<error>Exception during scan: '.$e->getMessage().'</error>');
163
+			$output->writeln('<error>'.$e->getTraceAsString().'</error>');
164 164
 		}
165 165
 	}
166 166
 
@@ -172,7 +172,7 @@  discard block
 block discarded – undo
172 172
 	protected function execute(InputInterface $input, OutputInterface $output) {
173 173
 		$inputPath = $input->getOption('path');
174 174
 		if ($inputPath) {
175
-			$inputPath = '/' . trim($inputPath, '/');
175
+			$inputPath = '/'.trim($inputPath, '/');
176 176
 			list(, $user,) = explode('/', $inputPath, 3);
177 177
 			$users = [$user];
178 178
 		} else if ($input->getOption('all')) {
@@ -200,7 +200,7 @@  discard block
 block discarded – undo
200 200
 			if (is_object($user)) {
201 201
 				$user = $user->getUID();
202 202
 			}
203
-			$path = $inputPath ? $inputPath : '/' . $user;
203
+			$path = $inputPath ? $inputPath : '/'.$user;
204 204
 			++$user_count;
205 205
 			if ($this->userManager->userExists($user)) {
206 206
 				$output->writeln("Starting scan for user $user_count out of $users_total ($user)");
@@ -297,7 +297,7 @@  discard block
 block discarded – undo
297 297
 	protected function formatExecTime() {
298 298
 		$secs = round($this->execTime);
299 299
 		# convert seconds into HH:MM:SS form
300
-		return sprintf('%02d:%02d:%02d', ($secs/3600), ($secs/60%60), $secs%60);
300
+		return sprintf('%02d:%02d:%02d', ($secs / 3600), ($secs / 60 % 60), $secs % 60);
301 301
 	}
302 302
 
303 303
 	/**
Please login to merge, or discard this patch.
apps/oauth2/lib/Controller/OauthApiController.php 1 patch
Indentation   +133 added lines, -133 removed lines patch added patch discarded remove patch
@@ -44,137 +44,137 @@
 block discarded – undo
44 44
 use OCP\Security\ISecureRandom;
45 45
 
46 46
 class OauthApiController extends Controller {
47
-	/** @var AccessTokenMapper */
48
-	private $accessTokenMapper;
49
-	/** @var ClientMapper */
50
-	private $clientMapper;
51
-	/** @var ICrypto */
52
-	private $crypto;
53
-	/** @var TokenProvider */
54
-	private $tokenProvider;
55
-	/** @var ISecureRandom */
56
-	private $secureRandom;
57
-	/** @var ITimeFactory */
58
-	private $time;
59
-	/** @var Throttler */
60
-	private $throttler;
61
-
62
-	public function __construct(string $appName,
63
-								IRequest $request,
64
-								ICrypto $crypto,
65
-								AccessTokenMapper $accessTokenMapper,
66
-								ClientMapper $clientMapper,
67
-								TokenProvider $tokenProvider,
68
-								ISecureRandom $secureRandom,
69
-								ITimeFactory $time,
70
-								Throttler $throttler) {
71
-		parent::__construct($appName, $request);
72
-		$this->crypto = $crypto;
73
-		$this->accessTokenMapper = $accessTokenMapper;
74
-		$this->clientMapper = $clientMapper;
75
-		$this->tokenProvider = $tokenProvider;
76
-		$this->secureRandom = $secureRandom;
77
-		$this->time = $time;
78
-		$this->throttler = $throttler;
79
-	}
80
-
81
-	/**
82
-	 * @PublicPage
83
-	 * @NoCSRFRequired
84
-	 *
85
-	 * @param string $grant_type
86
-	 * @param string $code
87
-	 * @param string $refresh_token
88
-	 * @param string $client_id
89
-	 * @param string $client_secret
90
-	 * @return JSONResponse
91
-	 */
92
-	public function getToken($grant_type, $code, $refresh_token, $client_id, $client_secret): JSONResponse {
93
-
94
-		// We only handle two types
95
-		if ($grant_type !== 'authorization_code' && $grant_type !== 'refresh_token') {
96
-			return new JSONResponse([
97
-				'error' => 'invalid_grant',
98
-			], Http::STATUS_BAD_REQUEST);
99
-		}
100
-
101
-		// We handle the initial and refresh tokens the same way
102
-		if ($grant_type === 'refresh_token') {
103
-			$code = $refresh_token;
104
-		}
105
-
106
-		try {
107
-			$accessToken = $this->accessTokenMapper->getByCode($code);
108
-		} catch (AccessTokenNotFoundException $e) {
109
-			return new JSONResponse([
110
-				'error' => 'invalid_request',
111
-			], Http::STATUS_BAD_REQUEST);
112
-		}
113
-
114
-		try {
115
-			$client = $this->clientMapper->getByUid($accessToken->getClientId());
116
-		} catch (ClientNotFoundException $e) {
117
-			return new JSONResponse([
118
-				'error' => 'invalid_request',
119
-			], Http::STATUS_BAD_REQUEST);
120
-		}
121
-
122
-		if (isset($this->request->server['PHP_AUTH_USER'])) {
123
-			$client_id = $this->request->server['PHP_AUTH_USER'];
124
-			$client_secret = $this->request->server['PHP_AUTH_PW'];
125
-		}
126
-
127
-		// The client id and secret must match. Else we don't provide an access token!
128
-		if ($client->getClientIdentifier() !== $client_id || $client->getSecret() !== $client_secret) {
129
-			return new JSONResponse([
130
-				'error' => 'invalid_client',
131
-			], Http::STATUS_BAD_REQUEST);
132
-		}
133
-
134
-		$decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
135
-
136
-		// Obtain the appToken assoicated
137
-		try {
138
-			$appToken = $this->tokenProvider->getTokenById($accessToken->getTokenId());
139
-		} catch (ExpiredTokenException $e) {
140
-			$appToken = $e->getToken();
141
-		} catch (InvalidTokenException $e) {
142
-			//We can't do anything...
143
-			$this->accessTokenMapper->delete($accessToken);
144
-			return new JSONResponse([
145
-				'error' => 'invalid_request',
146
-			], Http::STATUS_BAD_REQUEST);
147
-		}
148
-
149
-		// Rotate the apptoken (so the old one becomes invalid basically)
150
-		$newToken = $this->secureRandom->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
151
-
152
-		$appToken = $this->tokenProvider->rotate(
153
-			$appToken,
154
-			$decryptedToken,
155
-			$newToken
156
-		);
157
-
158
-		// Expiration is in 1 hour again
159
-		$appToken->setExpires($this->time->getTime() + 3600);
160
-		$this->tokenProvider->updateToken($appToken);
161
-
162
-		// Generate a new refresh token and encrypt the new apptoken in the DB
163
-		$newCode = $this->secureRandom->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
164
-		$accessToken->setHashedCode(hash('sha512', $newCode));
165
-		$accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
166
-		$this->accessTokenMapper->update($accessToken);
167
-
168
-		$this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
169
-
170
-		return new JSONResponse(
171
-			[
172
-				'access_token' => $newToken,
173
-				'token_type' => 'Bearer',
174
-				'expires_in' => 3600,
175
-				'refresh_token' => $newCode,
176
-				'user_id' => $appToken->getUID(),
177
-			]
178
-		);
179
-	}
47
+    /** @var AccessTokenMapper */
48
+    private $accessTokenMapper;
49
+    /** @var ClientMapper */
50
+    private $clientMapper;
51
+    /** @var ICrypto */
52
+    private $crypto;
53
+    /** @var TokenProvider */
54
+    private $tokenProvider;
55
+    /** @var ISecureRandom */
56
+    private $secureRandom;
57
+    /** @var ITimeFactory */
58
+    private $time;
59
+    /** @var Throttler */
60
+    private $throttler;
61
+
62
+    public function __construct(string $appName,
63
+                                IRequest $request,
64
+                                ICrypto $crypto,
65
+                                AccessTokenMapper $accessTokenMapper,
66
+                                ClientMapper $clientMapper,
67
+                                TokenProvider $tokenProvider,
68
+                                ISecureRandom $secureRandom,
69
+                                ITimeFactory $time,
70
+                                Throttler $throttler) {
71
+        parent::__construct($appName, $request);
72
+        $this->crypto = $crypto;
73
+        $this->accessTokenMapper = $accessTokenMapper;
74
+        $this->clientMapper = $clientMapper;
75
+        $this->tokenProvider = $tokenProvider;
76
+        $this->secureRandom = $secureRandom;
77
+        $this->time = $time;
78
+        $this->throttler = $throttler;
79
+    }
80
+
81
+    /**
82
+     * @PublicPage
83
+     * @NoCSRFRequired
84
+     *
85
+     * @param string $grant_type
86
+     * @param string $code
87
+     * @param string $refresh_token
88
+     * @param string $client_id
89
+     * @param string $client_secret
90
+     * @return JSONResponse
91
+     */
92
+    public function getToken($grant_type, $code, $refresh_token, $client_id, $client_secret): JSONResponse {
93
+
94
+        // We only handle two types
95
+        if ($grant_type !== 'authorization_code' && $grant_type !== 'refresh_token') {
96
+            return new JSONResponse([
97
+                'error' => 'invalid_grant',
98
+            ], Http::STATUS_BAD_REQUEST);
99
+        }
100
+
101
+        // We handle the initial and refresh tokens the same way
102
+        if ($grant_type === 'refresh_token') {
103
+            $code = $refresh_token;
104
+        }
105
+
106
+        try {
107
+            $accessToken = $this->accessTokenMapper->getByCode($code);
108
+        } catch (AccessTokenNotFoundException $e) {
109
+            return new JSONResponse([
110
+                'error' => 'invalid_request',
111
+            ], Http::STATUS_BAD_REQUEST);
112
+        }
113
+
114
+        try {
115
+            $client = $this->clientMapper->getByUid($accessToken->getClientId());
116
+        } catch (ClientNotFoundException $e) {
117
+            return new JSONResponse([
118
+                'error' => 'invalid_request',
119
+            ], Http::STATUS_BAD_REQUEST);
120
+        }
121
+
122
+        if (isset($this->request->server['PHP_AUTH_USER'])) {
123
+            $client_id = $this->request->server['PHP_AUTH_USER'];
124
+            $client_secret = $this->request->server['PHP_AUTH_PW'];
125
+        }
126
+
127
+        // The client id and secret must match. Else we don't provide an access token!
128
+        if ($client->getClientIdentifier() !== $client_id || $client->getSecret() !== $client_secret) {
129
+            return new JSONResponse([
130
+                'error' => 'invalid_client',
131
+            ], Http::STATUS_BAD_REQUEST);
132
+        }
133
+
134
+        $decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
135
+
136
+        // Obtain the appToken assoicated
137
+        try {
138
+            $appToken = $this->tokenProvider->getTokenById($accessToken->getTokenId());
139
+        } catch (ExpiredTokenException $e) {
140
+            $appToken = $e->getToken();
141
+        } catch (InvalidTokenException $e) {
142
+            //We can't do anything...
143
+            $this->accessTokenMapper->delete($accessToken);
144
+            return new JSONResponse([
145
+                'error' => 'invalid_request',
146
+            ], Http::STATUS_BAD_REQUEST);
147
+        }
148
+
149
+        // Rotate the apptoken (so the old one becomes invalid basically)
150
+        $newToken = $this->secureRandom->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
151
+
152
+        $appToken = $this->tokenProvider->rotate(
153
+            $appToken,
154
+            $decryptedToken,
155
+            $newToken
156
+        );
157
+
158
+        // Expiration is in 1 hour again
159
+        $appToken->setExpires($this->time->getTime() + 3600);
160
+        $this->tokenProvider->updateToken($appToken);
161
+
162
+        // Generate a new refresh token and encrypt the new apptoken in the DB
163
+        $newCode = $this->secureRandom->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
164
+        $accessToken->setHashedCode(hash('sha512', $newCode));
165
+        $accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
166
+        $this->accessTokenMapper->update($accessToken);
167
+
168
+        $this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
169
+
170
+        return new JSONResponse(
171
+            [
172
+                'access_token' => $newToken,
173
+                'token_type' => 'Bearer',
174
+                'expires_in' => 3600,
175
+                'refresh_token' => $newCode,
176
+                'user_id' => $appToken->getUID(),
177
+            ]
178
+        );
179
+    }
180 180
 }
Please login to merge, or discard this patch.
apps/settings/templates/settings/admin/sharing.php 2 patches
Spacing   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -27,82 +27,82 @@  discard block
 block discarded – undo
27 27
 ?>
28 28
 
29 29
 <div class="section" id="shareAPI">
30
-	<h2><?php p($l->t('Sharing'));?></h2>
30
+	<h2><?php p($l->t('Sharing')); ?></h2>
31 31
 	<a target="_blank" rel="noreferrer noopener" class="icon-info"
32
-	   title="<?php p($l->t('Open documentation'));?>"
32
+	   title="<?php p($l->t('Open documentation')); ?>"
33 33
 	   href="<?php p(link_to_docs('admin-sharing')); ?>"></a>
34
-        <p class="settings-hint"><?php p($l->t('As admin you can fine-tune the sharing behavior. Please see the documentation for more information.'));?></p>
34
+        <p class="settings-hint"><?php p($l->t('As admin you can fine-tune the sharing behavior. Please see the documentation for more information.')); ?></p>
35 35
 	<p id="enable">
36 36
 		<input type="checkbox" name="shareapi_enabled" id="shareAPIEnabled" class="checkbox"
37 37
 			   value="1" <?php if ($_['shareAPIEnabled'] === 'yes') print_unescaped('checked="checked"'); ?> />
38
-		<label for="shareAPIEnabled"><?php p($l->t('Allow apps to use the Share API'));?></label><br/>
38
+		<label for="shareAPIEnabled"><?php p($l->t('Allow apps to use the Share API')); ?></label><br/>
39 39
 	</p>
40 40
 
41 41
 	<p id="internalShareSettings" class="indent <?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
42 42
 		<input type="checkbox" name="shareapi_default_internal_expire_date" id="shareapiDefaultInternalExpireDate" class="checkbox"
43 43
 			   value="1" <?php if ($_['shareDefaultInternalExpireDateSet'] === 'yes') print_unescaped('checked="checked"'); ?> />
44
-		<label for="shareapiDefaultInternalExpireDate"><?php p($l->t('Set default expiration date for shares'));?></label><br/>
44
+		<label for="shareapiDefaultInternalExpireDate"><?php p($l->t('Set default expiration date for shares')); ?></label><br/>
45 45
 	</p>
46
-	<p id="setDefaultInternalExpireDate" class="double-indent <?php if ($_['shareDefaultInternalExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') p('hidden');?>">
46
+	<p id="setDefaultInternalExpireDate" class="double-indent <?php if ($_['shareDefaultInternalExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
47 47
 		<?php p($l->t('Expire after ')); ?>
48 48
 		<input type="text" name='shareapi_internal_expire_after_n_days' id="shareapiInternalExpireAfterNDays" placeholder="<?php p('7')?>"
49 49
 			   value='<?php p($_['shareInternalExpireAfterNDays']) ?>' />
50 50
 		<?php p($l->t('days')); ?>
51 51
 		<input type="checkbox" name="shareapi_internal_enforce_expire_date" id="shareapiInternalEnforceExpireDate" class="checkbox"
52 52
 			   value="1" <?php if ($_['shareInternalEnforceExpireDate'] === 'yes') print_unescaped('checked="checked"'); ?> />
53
-		<label for="shareapiInternalEnforceExpireDate"><?php p($l->t('Enforce expiration date'));?></label><br/>
53
+		<label for="shareapiInternalEnforceExpireDate"><?php p($l->t('Enforce expiration date')); ?></label><br/>
54 54
 	</p>
55 55
 
56
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
56
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
57 57
 		<input type="checkbox" name="shareapi_allow_links" id="allowLinks" class="checkbox"
58 58
 			   value="1" <?php if ($_['allowLinks'] === 'yes') print_unescaped('checked="checked"'); ?> />
59
-		<label for="allowLinks"><?php p($l->t('Allow users to share via link'));?></label><br/>
59
+		<label for="allowLinks"><?php p($l->t('Allow users to share via link')); ?></label><br/>
60 60
 	</p>
61 61
 
62 62
 	<p id="publicLinkSettings" class="indent <?php if ($_['allowLinks'] !== 'yes' || $_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
63 63
 		<input type="checkbox" name="shareapi_allow_public_upload" id="allowPublicUpload" class="checkbox"
64 64
 			   value="1" <?php if ($_['allowPublicUpload'] == 'yes') print_unescaped('checked="checked"'); ?> />
65
-		<label for="allowPublicUpload"><?php p($l->t('Allow public uploads'));?></label><br/>
65
+		<label for="allowPublicUpload"><?php p($l->t('Allow public uploads')); ?></label><br/>
66 66
 		<input type="checkbox" name="shareapi_enable_link_password_by_default" id="enableLinkPasswordByDefault" class="checkbox"
67 67
 			   value="1" <?php if ($_['enableLinkPasswordByDefault'] === 'yes') print_unescaped('checked="checked"'); ?> />
68
-		<label for="enableLinkPasswordByDefault"><?php p($l->t('Always ask for a password'));?></label><br/>
68
+		<label for="enableLinkPasswordByDefault"><?php p($l->t('Always ask for a password')); ?></label><br/>
69 69
 		<input type="checkbox" name="shareapi_enforce_links_password" id="enforceLinkPassword" class="checkbox"
70 70
 			   value="1" <?php if ($_['enforceLinkPassword']) print_unescaped('checked="checked"'); ?> />
71
-		<label for="enforceLinkPassword"><?php p($l->t('Enforce password protection'));?></label><br/>
71
+		<label for="enforceLinkPassword"><?php p($l->t('Enforce password protection')); ?></label><br/>
72 72
 
73 73
 		<input type="checkbox" name="shareapi_default_expire_date" id="shareapiDefaultExpireDate" class="checkbox"
74 74
 			   value="1" <?php if ($_['shareDefaultExpireDateSet'] === 'yes') print_unescaped('checked="checked"'); ?> />
75
-		<label for="shareapiDefaultExpireDate"><?php p($l->t('Set default expiration date for link shares'));?></label><br/>
75
+		<label for="shareapiDefaultExpireDate"><?php p($l->t('Set default expiration date for link shares')); ?></label><br/>
76 76
 
77 77
 	</p>
78
-	<p id="setDefaultExpireDate" class="double-indent <?php if ($_['allowLinks'] !== 'yes' || $_['shareDefaultExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') p('hidden');?>">
78
+	<p id="setDefaultExpireDate" class="double-indent <?php if ($_['allowLinks'] !== 'yes' || $_['shareDefaultExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
79 79
 		<?php p($l->t('Expire after ')); ?>
80 80
 		<input type="text" name='shareapi_expire_after_n_days' id="shareapiExpireAfterNDays" placeholder="<?php p('7')?>"
81 81
 			   value='<?php p($_['shareExpireAfterNDays']) ?>' />
82 82
 		<?php p($l->t('days')); ?>
83 83
 		<input type="checkbox" name="shareapi_enforce_expire_date" id="shareapiEnforceExpireDate" class="checkbox"
84 84
 			   value="1" <?php if ($_['shareEnforceExpireDate'] === 'yes') print_unescaped('checked="checked"'); ?> />
85
-		<label for="shareapiEnforceExpireDate"><?php p($l->t('Enforce expiration date'));?></label><br/>
85
+		<label for="shareapiEnforceExpireDate"><?php p($l->t('Enforce expiration date')); ?></label><br/>
86 86
 	</p>
87
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
87
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
88 88
 		<input type="checkbox" name="shareapi_allow_resharing" id="allowResharing" class="checkbox"
89 89
 			   value="1" <?php if ($_['allowResharing'] === 'yes') print_unescaped('checked="checked"'); ?> />
90
-		<label for="allowResharing"><?php p($l->t('Allow resharing'));?></label><br/>
90
+		<label for="allowResharing"><?php p($l->t('Allow resharing')); ?></label><br/>
91 91
 	</p>
92
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
92
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
93 93
 		<input type="checkbox" name="shareapi_allow_group_sharing" id="allowGroupSharing" class="checkbox"
94 94
 			   value="1" <?php if ($_['allowGroupSharing'] === 'yes') print_unescaped('checked="checked"'); ?> />
95
-		<label for="allowGroupSharing"><?php p($l->t('Allow sharing with groups'));?></label><br />
95
+		<label for="allowGroupSharing"><?php p($l->t('Allow sharing with groups')); ?></label><br />
96 96
 	</p>
97
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
97
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
98 98
 		<input type="checkbox" name="shareapi_only_share_with_group_members" id="onlyShareWithGroupMembers" class="checkbox"
99 99
 			   value="1" <?php if ($_['onlyShareWithGroupMembers']) print_unescaped('checked="checked"'); ?> />
100
-		<label for="onlyShareWithGroupMembers"><?php p($l->t('Restrict users to only share with users in their groups'));?></label><br/>
100
+		<label for="onlyShareWithGroupMembers"><?php p($l->t('Restrict users to only share with users in their groups')); ?></label><br/>
101 101
 	</p>
102
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
102
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
103 103
 		<input type="checkbox" name="shareapi_exclude_groups" id="shareapiExcludeGroups" class="checkbox"
104 104
 			   value="1" <?php if ($_['shareExcludeGroups']) print_unescaped('checked="checked"'); ?> />
105
-		<label for="shareapiExcludeGroups"><?php p($l->t('Exclude groups from sharing'));?></label><br/>
105
+		<label for="shareapiExcludeGroups"><?php p($l->t('Exclude groups from sharing')); ?></label><br/>
106 106
 	</p>
107 107
 	<p id="selectExcludedGroups" class="indent <?php if (!$_['shareExcludeGroups'] || $_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
108 108
 		<input name="shareapi_exclude_groups_list" type="hidden" id="excludedGroups" value="<?php p($_['shareExcludedGroupsList']) ?>" style="width: 400px" class="noJSAutoUpdate"/>
@@ -110,35 +110,35 @@  discard block
 block discarded – undo
110 110
 		<em><?php p($l->t('These groups will still be able to receive shares, but not to initiate them.')); ?></em>
111 111
 	</p>
112 112
 
113
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
113
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
114 114
 		<input type="checkbox" name="shareapi_allow_share_dialog_user_enumeration" value="1" id="shareapi_allow_share_dialog_user_enumeration" class="checkbox"
115 115
 			<?php if ($_['allowShareDialogUserEnumeration'] === 'yes') print_unescaped('checked="checked"'); ?> />
116
-		<label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog. If this is disabled the full username or email address needs to be entered.'));?></label><br />
116
+		<label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog. If this is disabled the full username or email address needs to be entered.')); ?></label><br />
117 117
 	</p>
118 118
 
119
-	<p id="shareapi_restrict_user_enumeration_to_group_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') p('hidden');?>">
119
+	<p id="shareapi_restrict_user_enumeration_to_group_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') p('hidden'); ?>">
120 120
 		<input type="checkbox" name="shareapi_restrict_user_enumeration_to_group" value="1" id="shareapi_restrict_user_enumeration_to_group" class="checkbox"
121 121
 			<?php if ($_['restrictUserEnumerationToGroup'] === 'yes') print_unescaped('checked="checked"'); ?> />
122
-		<label for="shareapi_restrict_user_enumeration_to_group"><?php p($l->t('Restrict username autocompletion to users within the same groups'));?></label><br />
122
+		<label for="shareapi_restrict_user_enumeration_to_group"><?php p($l->t('Restrict username autocompletion to users within the same groups')); ?></label><br />
123 123
 	</p>
124 124
 
125 125
 	<p>
126 126
 		<input type="checkbox" id="publicShareDisclaimer" class="checkbox noJSAutoUpdate"
127 127
 			<?php if ($_['publicShareDisclaimerText'] !== null) print_unescaped('checked="checked"'); ?> />
128
-		<label for="publicShareDisclaimer"><?php p($l->t('Show disclaimer text on the public link upload page. (Only shown when the file list is hidden.)'));?></label>
128
+		<label for="publicShareDisclaimer"><?php p($l->t('Show disclaimer text on the public link upload page. (Only shown when the file list is hidden.)')); ?></label>
129 129
 		<span id="publicShareDisclaimerStatus" class="msg" style="display:none"></span>
130 130
 		<br/>
131 131
 		<textarea placeholder="<?php p($l->t('This text will be shown on the public link upload page when the file list is hidden.')) ?>" id="publicShareDisclaimerText" <?php if ($_['publicShareDisclaimerText'] === null) { print_unescaped('class="hidden"'); } ?>><?php p($_['publicShareDisclaimerText']) ?></textarea>
132 132
 	</p>
133 133
 
134
-	<h3><?php p($l->t('Default share permissions'));?></h3>
134
+	<h3><?php p($l->t('Default share permissions')); ?></h3>
135 135
 	<input type="hidden" name="shareapi_default_permissions" id="shareApiDefaultPermissions" class="checkbox"
136 136
 		   value="<?php p($_['shareApiDefaultPermissions']) ?>" />
137 137
 	<p id="shareApiDefaultPermissionsSection" class="indent <?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
138 138
 		<?php foreach ($_['shareApiDefaultPermissionsCheckboxes'] as $perm): ?>
139 139
 			<input type="checkbox" name="shareapi_default_permission_<?php p($perm['id']) ?>" id="shareapi_default_permission_<?php p($perm['id']) ?>"
140 140
 				   class="noautosave checkbox" value="<?php p($perm['value']) ?>" <?php if (($_['shareApiDefaultPermissions'] & $perm['value']) !== 0) print_unescaped('checked="checked"'); ?> />
141
-			<label for="shareapi_default_permission_<?php p($perm['id']) ?>"><?php p($perm['label']);?></label>
141
+			<label for="shareapi_default_permission_<?php p($perm['id']) ?>"><?php p($perm['label']); ?></label>
142 142
 		<?php endforeach ?>
143 143
 	</p>
144 144
 </div>
Please login to merge, or discard this patch.
Braces   +120 added lines, -30 removed lines patch added patch discarded remove patch
@@ -34,97 +34,181 @@  discard block
 block discarded – undo
34 34
         <p class="settings-hint"><?php p($l->t('As admin you can fine-tune the sharing behavior. Please see the documentation for more information.'));?></p>
35 35
 	<p id="enable">
36 36
 		<input type="checkbox" name="shareapi_enabled" id="shareAPIEnabled" class="checkbox"
37
-			   value="1" <?php if ($_['shareAPIEnabled'] === 'yes') print_unescaped('checked="checked"'); ?> />
37
+			   value="1" <?php if ($_['shareAPIEnabled'] === 'yes') {
38
+    print_unescaped('checked="checked"');
39
+}
40
+?> />
38 41
 		<label for="shareAPIEnabled"><?php p($l->t('Allow apps to use the Share API'));?></label><br/>
39 42
 	</p>
40 43
 
41
-	<p id="internalShareSettings" class="indent <?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
44
+	<p id="internalShareSettings" class="indent <?php if ($_['shareAPIEnabled'] === 'no') {
45
+    p('hidden');
46
+}
47
+?>">
42 48
 		<input type="checkbox" name="shareapi_default_internal_expire_date" id="shareapiDefaultInternalExpireDate" class="checkbox"
43
-			   value="1" <?php if ($_['shareDefaultInternalExpireDateSet'] === 'yes') print_unescaped('checked="checked"'); ?> />
49
+			   value="1" <?php if ($_['shareDefaultInternalExpireDateSet'] === 'yes') {
50
+    print_unescaped('checked="checked"');
51
+}
52
+?> />
44 53
 		<label for="shareapiDefaultInternalExpireDate"><?php p($l->t('Set default expiration date for shares'));?></label><br/>
45 54
 	</p>
46
-	<p id="setDefaultInternalExpireDate" class="double-indent <?php if ($_['shareDefaultInternalExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') p('hidden');?>">
55
+	<p id="setDefaultInternalExpireDate" class="double-indent <?php if ($_['shareDefaultInternalExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') {
56
+    p('hidden');
57
+}
58
+?>">
47 59
 		<?php p($l->t('Expire after ')); ?>
48 60
 		<input type="text" name='shareapi_internal_expire_after_n_days' id="shareapiInternalExpireAfterNDays" placeholder="<?php p('7')?>"
49 61
 			   value='<?php p($_['shareInternalExpireAfterNDays']) ?>' />
50 62
 		<?php p($l->t('days')); ?>
51 63
 		<input type="checkbox" name="shareapi_internal_enforce_expire_date" id="shareapiInternalEnforceExpireDate" class="checkbox"
52
-			   value="1" <?php if ($_['shareInternalEnforceExpireDate'] === 'yes') print_unescaped('checked="checked"'); ?> />
64
+			   value="1" <?php if ($_['shareInternalEnforceExpireDate'] === 'yes') {
65
+    print_unescaped('checked="checked"');
66
+}
67
+?> />
53 68
 		<label for="shareapiInternalEnforceExpireDate"><?php p($l->t('Enforce expiration date'));?></label><br/>
54 69
 	</p>
55 70
 
56
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
71
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') {
72
+    p('hidden');
73
+}
74
+?>">
57 75
 		<input type="checkbox" name="shareapi_allow_links" id="allowLinks" class="checkbox"
58
-			   value="1" <?php if ($_['allowLinks'] === 'yes') print_unescaped('checked="checked"'); ?> />
76
+			   value="1" <?php if ($_['allowLinks'] === 'yes') {
77
+    print_unescaped('checked="checked"');
78
+}
79
+?> />
59 80
 		<label for="allowLinks"><?php p($l->t('Allow users to share via link'));?></label><br/>
60 81
 	</p>
61 82
 
62
-	<p id="publicLinkSettings" class="indent <?php if ($_['allowLinks'] !== 'yes' || $_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
83
+	<p id="publicLinkSettings" class="indent <?php if ($_['allowLinks'] !== 'yes' || $_['shareAPIEnabled'] === 'no') {
84
+    p('hidden');
85
+}
86
+?>">
63 87
 		<input type="checkbox" name="shareapi_allow_public_upload" id="allowPublicUpload" class="checkbox"
64
-			   value="1" <?php if ($_['allowPublicUpload'] == 'yes') print_unescaped('checked="checked"'); ?> />
88
+			   value="1" <?php if ($_['allowPublicUpload'] == 'yes') {
89
+    print_unescaped('checked="checked"');
90
+}
91
+?> />
65 92
 		<label for="allowPublicUpload"><?php p($l->t('Allow public uploads'));?></label><br/>
66 93
 		<input type="checkbox" name="shareapi_enable_link_password_by_default" id="enableLinkPasswordByDefault" class="checkbox"
67
-			   value="1" <?php if ($_['enableLinkPasswordByDefault'] === 'yes') print_unescaped('checked="checked"'); ?> />
94
+			   value="1" <?php if ($_['enableLinkPasswordByDefault'] === 'yes') {
95
+    print_unescaped('checked="checked"');
96
+}
97
+?> />
68 98
 		<label for="enableLinkPasswordByDefault"><?php p($l->t('Always ask for a password'));?></label><br/>
69 99
 		<input type="checkbox" name="shareapi_enforce_links_password" id="enforceLinkPassword" class="checkbox"
70
-			   value="1" <?php if ($_['enforceLinkPassword']) print_unescaped('checked="checked"'); ?> />
100
+			   value="1" <?php if ($_['enforceLinkPassword']) {
101
+    print_unescaped('checked="checked"');
102
+}
103
+?> />
71 104
 		<label for="enforceLinkPassword"><?php p($l->t('Enforce password protection'));?></label><br/>
72 105
 
73 106
 		<input type="checkbox" name="shareapi_default_expire_date" id="shareapiDefaultExpireDate" class="checkbox"
74
-			   value="1" <?php if ($_['shareDefaultExpireDateSet'] === 'yes') print_unescaped('checked="checked"'); ?> />
107
+			   value="1" <?php if ($_['shareDefaultExpireDateSet'] === 'yes') {
108
+    print_unescaped('checked="checked"');
109
+}
110
+?> />
75 111
 		<label for="shareapiDefaultExpireDate"><?php p($l->t('Set default expiration date for link shares'));?></label><br/>
76 112
 
77 113
 	</p>
78
-	<p id="setDefaultExpireDate" class="double-indent <?php if ($_['allowLinks'] !== 'yes' || $_['shareDefaultExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') p('hidden');?>">
114
+	<p id="setDefaultExpireDate" class="double-indent <?php if ($_['allowLinks'] !== 'yes' || $_['shareDefaultExpireDateSet'] === 'no' || $_['shareAPIEnabled'] === 'no') {
115
+    p('hidden');
116
+}
117
+?>">
79 118
 		<?php p($l->t('Expire after ')); ?>
80 119
 		<input type="text" name='shareapi_expire_after_n_days' id="shareapiExpireAfterNDays" placeholder="<?php p('7')?>"
81 120
 			   value='<?php p($_['shareExpireAfterNDays']) ?>' />
82 121
 		<?php p($l->t('days')); ?>
83 122
 		<input type="checkbox" name="shareapi_enforce_expire_date" id="shareapiEnforceExpireDate" class="checkbox"
84
-			   value="1" <?php if ($_['shareEnforceExpireDate'] === 'yes') print_unescaped('checked="checked"'); ?> />
123
+			   value="1" <?php if ($_['shareEnforceExpireDate'] === 'yes') {
124
+    print_unescaped('checked="checked"');
125
+}
126
+?> />
85 127
 		<label for="shareapiEnforceExpireDate"><?php p($l->t('Enforce expiration date'));?></label><br/>
86 128
 	</p>
87
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
129
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') {
130
+    p('hidden');
131
+}
132
+?>">
88 133
 		<input type="checkbox" name="shareapi_allow_resharing" id="allowResharing" class="checkbox"
89
-			   value="1" <?php if ($_['allowResharing'] === 'yes') print_unescaped('checked="checked"'); ?> />
134
+			   value="1" <?php if ($_['allowResharing'] === 'yes') {
135
+    print_unescaped('checked="checked"');
136
+}
137
+?> />
90 138
 		<label for="allowResharing"><?php p($l->t('Allow resharing'));?></label><br/>
91 139
 	</p>
92
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
140
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') {
141
+    p('hidden');
142
+}
143
+?>">
93 144
 		<input type="checkbox" name="shareapi_allow_group_sharing" id="allowGroupSharing" class="checkbox"
94
-			   value="1" <?php if ($_['allowGroupSharing'] === 'yes') print_unescaped('checked="checked"'); ?> />
145
+			   value="1" <?php if ($_['allowGroupSharing'] === 'yes') {
146
+    print_unescaped('checked="checked"');
147
+}
148
+?> />
95 149
 		<label for="allowGroupSharing"><?php p($l->t('Allow sharing with groups'));?></label><br />
96 150
 	</p>
97
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
151
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') {
152
+    p('hidden');
153
+}
154
+?>">
98 155
 		<input type="checkbox" name="shareapi_only_share_with_group_members" id="onlyShareWithGroupMembers" class="checkbox"
99
-			   value="1" <?php if ($_['onlyShareWithGroupMembers']) print_unescaped('checked="checked"'); ?> />
156
+			   value="1" <?php if ($_['onlyShareWithGroupMembers']) {
157
+    print_unescaped('checked="checked"');
158
+}
159
+?> />
100 160
 		<label for="onlyShareWithGroupMembers"><?php p($l->t('Restrict users to only share with users in their groups'));?></label><br/>
101 161
 	</p>
102
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
162
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') {
163
+    p('hidden');
164
+}
165
+?>">
103 166
 		<input type="checkbox" name="shareapi_exclude_groups" id="shareapiExcludeGroups" class="checkbox"
104
-			   value="1" <?php if ($_['shareExcludeGroups']) print_unescaped('checked="checked"'); ?> />
167
+			   value="1" <?php if ($_['shareExcludeGroups']) {
168
+    print_unescaped('checked="checked"');
169
+}
170
+?> />
105 171
 		<label for="shareapiExcludeGroups"><?php p($l->t('Exclude groups from sharing'));?></label><br/>
106 172
 	</p>
107
-	<p id="selectExcludedGroups" class="indent <?php if (!$_['shareExcludeGroups'] || $_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
173
+	<p id="selectExcludedGroups" class="indent <?php if (!$_['shareExcludeGroups'] || $_['shareAPIEnabled'] === 'no') {
174
+    p('hidden');
175
+}
176
+?>">
108 177
 		<input name="shareapi_exclude_groups_list" type="hidden" id="excludedGroups" value="<?php p($_['shareExcludedGroupsList']) ?>" style="width: 400px" class="noJSAutoUpdate"/>
109 178
 		<br />
110 179
 		<em><?php p($l->t('These groups will still be able to receive shares, but not to initiate them.')); ?></em>
111 180
 	</p>
112 181
 
113
-	<p class="<?php if ($_['shareAPIEnabled'] === 'no') p('hidden');?>">
182
+	<p class="<?php if ($_['shareAPIEnabled'] === 'no') {
183
+    p('hidden');
184
+}
185
+?>">
114 186
 		<input type="checkbox" name="shareapi_allow_share_dialog_user_enumeration" value="1" id="shareapi_allow_share_dialog_user_enumeration" class="checkbox"
115
-			<?php if ($_['allowShareDialogUserEnumeration'] === 'yes') print_unescaped('checked="checked"'); ?> />
187
+			<?php if ($_['allowShareDialogUserEnumeration'] === 'yes') {
188
+    print_unescaped('checked="checked"');
189
+}
190
+?> />
116 191
 		<label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog. If this is disabled the full username or email address needs to be entered.'));?></label><br />
117 192
 	</p>
118 193
 
119
-	<p id="shareapi_restrict_user_enumeration_to_group_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') p('hidden');?>">
194
+	<p id="shareapi_restrict_user_enumeration_to_group_setting" class="indent <?php if ($_['shareAPIEnabled'] === 'no' || $_['allowShareDialogUserEnumeration'] === 'no') {
195
+    p('hidden');
196
+}
197
+?>">
120 198
 		<input type="checkbox" name="shareapi_restrict_user_enumeration_to_group" value="1" id="shareapi_restrict_user_enumeration_to_group" class="checkbox"
121
-			<?php if ($_['restrictUserEnumerationToGroup'] === 'yes') print_unescaped('checked="checked"'); ?> />
199
+			<?php if ($_['restrictUserEnumerationToGroup'] === 'yes') {
200
+    print_unescaped('checked="checked"');
201
+}
202
+?> />
122 203
 		<label for="shareapi_restrict_user_enumeration_to_group"><?php p($l->t('Restrict username autocompletion to users within the same groups'));?></label><br />
123 204
 	</p>
124 205
 
125 206
 	<p>
126 207
 		<input type="checkbox" id="publicShareDisclaimer" class="checkbox noJSAutoUpdate"
127
-			<?php if ($_['publicShareDisclaimerText'] !== null) print_unescaped('checked="checked"'); ?> />
208
+			<?php if ($_['publicShareDisclaimerText'] !== null) {
209
+    print_unescaped('checked="checked"');
210
+}
211
+?> />
128 212
 		<label for="publicShareDisclaimer"><?php p($l->t('Show disclaimer text on the public link upload page. (Only shown when the file list is hidden.)'));?></label>
129 213
 		<span id="publicShareDisclaimerStatus" class="msg" style="display:none"></span>
130 214
 		<br/>
@@ -134,10 +218,16 @@  discard block
 block discarded – undo
134 218
 	<h3><?php p($l->t('Default share permissions'));?></h3>
135 219
 	<input type="hidden" name="shareapi_default_permissions" id="shareApiDefaultPermissions" class="checkbox"
136 220
 		   value="<?php p($_['shareApiDefaultPermissions']) ?>" />
137
-	<p id="shareApiDefaultPermissionsSection" class="indent <?php if ($_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
221
+	<p id="shareApiDefaultPermissionsSection" class="indent <?php if ($_['shareAPIEnabled'] === 'no') {
222
+    p('hidden');
223
+}
224
+?>">
138 225
 		<?php foreach ($_['shareApiDefaultPermissionsCheckboxes'] as $perm): ?>
139 226
 			<input type="checkbox" name="shareapi_default_permission_<?php p($perm['id']) ?>" id="shareapi_default_permission_<?php p($perm['id']) ?>"
140
-				   class="noautosave checkbox" value="<?php p($perm['value']) ?>" <?php if (($_['shareApiDefaultPermissions'] & $perm['value']) !== 0) print_unescaped('checked="checked"'); ?> />
227
+				   class="noautosave checkbox" value="<?php p($perm['value']) ?>" <?php if (($_['shareApiDefaultPermissions'] & $perm['value']) !== 0) {
228
+    print_unescaped('checked="checked"');
229
+}
230
+?> />
141 231
 			<label for="shareapi_default_permission_<?php p($perm['id']) ?>"><?php p($perm['label']);?></label>
142 232
 		<?php endforeach ?>
143 233
 	</p>
Please login to merge, or discard this patch.
apps/settings/templates/settings/admin/overview.php 1 patch
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -28,24 +28,24 @@  discard block
 block discarded – undo
28 28
 ?>
29 29
 
30 30
 <div id="security-warning" class="section">
31
-	<h2 class="inlineblock"><?php p($l->t('Security & setup warnings'));?></h2>
32
-	<a target="_blank" rel="noreferrer" class="icon-info" title="<?php p($l->t('Open documentation'));?>" href="<?php p(link_to_docs('admin-warnings')); ?>"></a>
33
-	<p class="settings-hint"><?php p($l->t('It\'s important for the security and performance of your instance that everything is configured correctly. To help you with that we are doing some automatic checks. Please see the linked documentation for more information.'));?></p>
31
+	<h2 class="inlineblock"><?php p($l->t('Security & setup warnings')); ?></h2>
32
+	<a target="_blank" rel="noreferrer" class="icon-info" title="<?php p($l->t('Open documentation')); ?>" href="<?php p(link_to_docs('admin-warnings')); ?>"></a>
33
+	<p class="settings-hint"><?php p($l->t('It\'s important for the security and performance of your instance that everything is configured correctly. To help you with that we are doing some automatic checks. Please see the linked documentation for more information.')); ?></p>
34 34
 
35 35
 	<div id="security-warning-state-ok" class="hidden">
36
-		<span class="icon icon-checkmark-white"></span><span class="message"><?php p($l->t('All checks passed.'));?></span>
36
+		<span class="icon icon-checkmark-white"></span><span class="message"><?php p($l->t('All checks passed.')); ?></span>
37 37
 	</div>
38 38
 	<div id="security-warning-state-failure" class="hidden">
39
-		<span class="icon icon-close-white"></span><span class="message"><?php p($l->t('There are some errors regarding your setup.'));?></span>
39
+		<span class="icon icon-close-white"></span><span class="message"><?php p($l->t('There are some errors regarding your setup.')); ?></span>
40 40
 	</div>
41 41
 	<div id="security-warning-state-warning" class="hidden">
42
-		<span class="icon icon-error-white"></span><span class="message"><?php p($l->t('There are some warnings regarding your setup.'));?></span>
42
+		<span class="icon icon-error-white"></span><span class="message"><?php p($l->t('There are some warnings regarding your setup.')); ?></span>
43 43
 	</div>
44 44
 	<div id="security-warning-state-loading">
45
-		<span class="icon loading"></span><span class="message"><?php p($l->t('Checking for system and security issues.'));?></span>
45
+		<span class="icon loading"></span><span class="message"><?php p($l->t('Checking for system and security issues.')); ?></span>
46 46
 	</div>
47 47
 
48
-	<div id="postsetupchecks" data-check-wellknown="<?php if($_['checkForWorkingWellKnownSetup']) { p('true'); } else { p('false'); } ?>">
48
+	<div id="postsetupchecks" data-check-wellknown="<?php if ($_['checkForWorkingWellKnownSetup']) { p('true'); } else { p('false'); } ?>">
49 49
 		<ul class="errors hidden"></ul>
50 50
 		<ul class="warnings hidden"></ul>
51 51
 		<ul class="info hidden"></ul>
@@ -55,13 +55,13 @@  discard block
 block discarded – undo
55 55
 	</p>
56 56
 
57 57
 	<p class="extra-top-margin">
58
-		<?php print_unescaped($l->t('Check the security of your Nextcloud over <a target="_blank" rel="noreferrer noopener" href="%s">our security scan ↗</a>.', ['https://scan.nextcloud.com']));?>
58
+		<?php print_unescaped($l->t('Check the security of your Nextcloud over <a target="_blank" rel="noreferrer noopener" href="%s">our security scan ↗</a>.', ['https://scan.nextcloud.com'])); ?>
59 59
 	</p>
60 60
 
61 61
 </div>
62 62
 
63 63
 <div id="version" class="section">
64 64
 	<!-- should be the last part, so Updater can follow if enabled (it has no heading therefore). -->
65
-	<h2><?php p($l->t('Version'));?></h2>
65
+	<h2><?php p($l->t('Version')); ?></h2>
66 66
 	<p><strong><a href="<?php print_unescaped($theme->getBaseUrl()); ?>" rel="noreferrer noopener" target="_blank"><?php p($theme->getTitle()); ?></a> <?php p(OC_Util::getHumanVersion()) ?></strong></p>
67 67
 </div>
Please login to merge, or discard this patch.
apps/settings/templates/settings/frame.php 1 patch
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -22,7 +22,7 @@  discard block
 block discarded – undo
22 22
  */
23 23
 
24 24
 style('settings', 'settings');
25
-script('settings', [ 'settings', 'admin', 'log', 'certificates']);
25
+script('settings', ['settings', 'admin', 'log', 'certificates']);
26 26
 script('core', 'setupchecks');
27 27
 script('files', 'jquery.fileupload');
28 28
 
@@ -30,14 +30,14 @@  discard block
 block discarded – undo
30 30
 
31 31
 <div id="app-navigation">
32 32
 	<ul>
33
-		<?php if(!empty($_['forms']['admin'])) { ?>
33
+		<?php if (!empty($_['forms']['admin'])) { ?>
34 34
 			<li class="app-navigation-caption"><?php p($l->t('Personal')); ?></li>
35 35
 		<?php
36 36
 		}
37
-		foreach($_['forms']['personal'] as $form) {
37
+		foreach ($_['forms']['personal'] as $form) {
38 38
 			if (isset($form['anchor'])) {
39 39
 				$anchor = \OC::$server->getURLGenerator()->linkToRoute('settings.PersonalSettings.index', ['section' => $form['anchor']]);
40
-				$class = 'nav-icon-' . $form['anchor'];
40
+				$class = 'nav-icon-'.$form['anchor'];
41 41
 				$sectionName = $form['section-name'];
42 42
 				$active = $form['active'] ? ' class="active"' : '';
43 43
 				?>
@@ -57,16 +57,16 @@  discard block
 block discarded – undo
57 57
 		?>
58 58
 
59 59
 		<?php
60
-		if(!empty($_['forms']['admin'])) {
60
+		if (!empty($_['forms']['admin'])) {
61 61
 			?>
62 62
 			<li class="app-navigation-caption"><?php p($l->t('Administration')); ?></li>
63 63
 			<?php
64 64
 		}
65
-		foreach($_['forms']['admin'] as $form) {
65
+		foreach ($_['forms']['admin'] as $form) {
66 66
 			if (isset($form['anchor'])) {
67 67
 
68 68
 				$anchor = \OC::$server->getURLGenerator()->linkToRoute('settings.AdminSettings.index', ['section' => $form['anchor']]);
69
-				$class = 'nav-icon-' . $form['anchor'];
69
+				$class = 'nav-icon-'.$form['anchor'];
70 70
 				$sectionName = $form['section-name'];
71 71
 				$active = $form['active'] ? ' class="active"' : '';
72 72
 		?>
Please login to merge, or discard this patch.