Passed
Push — master ( f0dd71...c56a27 )
by Christoph
11:49 queued 12s
created
apps/dav/lib/BackgroundJob/UploadCleanup.php 2 patches
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -38,53 +38,53 @@
 block discarded – undo
38 38
 
39 39
 class UploadCleanup extends TimedJob {
40 40
 
41
-	/** @var IRootFolder */
42
-	private $rootFolder;
43
-
44
-	/** @var IJobList */
45
-	private $jobList;
46
-
47
-	public function __construct(ITimeFactory $time, IRootFolder $rootFolder, IJobList $jobList) {
48
-		parent::__construct($time);
49
-		$this->rootFolder = $rootFolder;
50
-		$this->jobList = $jobList;
51
-
52
-		// Run once a day
53
-		$this->setInterval(60*60*24);
54
-	}
55
-
56
-	protected function run($argument) {
57
-		$uid = $argument['uid'];
58
-		$folder = $argument['folder'];
59
-
60
-		try {
61
-			$userFolder = $this->rootFolder->getUserFolder($uid);
62
-			$userRoot = $userFolder->getParent();
63
-			/** @var Folder $uploads */
64
-			$uploads = $userRoot->get('uploads');
65
-			/** @var Folder $uploadFolder */
66
-			$uploadFolder = $uploads->get($folder);
67
-		} catch (NotFoundException|NoUserException $e) {
68
-			$this->jobList->remove(self::class, $argument);
69
-			return;
70
-		}
71
-
72
-		$files = $uploadFolder->getDirectoryListing();
73
-
74
-		// Remove if all files have an mtime of more than a day
75
-		$time = $this->time->getTime() - 60 * 60 * 24;
76
-
77
-		// The folder has to be more than a day old
78
-		$initial = $uploadFolder->getMTime() < $time;
79
-
80
-		$expire = array_reduce($files, function (bool $carry, File $file) use ($time) {
81
-			return $carry && $file->getMTime() < $time;
82
-		}, $initial);
83
-
84
-		if ($expire) {
85
-			$uploadFolder->delete();
86
-			$this->jobList->remove(self::class, $argument);
87
-		}
88
-	}
41
+    /** @var IRootFolder */
42
+    private $rootFolder;
43
+
44
+    /** @var IJobList */
45
+    private $jobList;
46
+
47
+    public function __construct(ITimeFactory $time, IRootFolder $rootFolder, IJobList $jobList) {
48
+        parent::__construct($time);
49
+        $this->rootFolder = $rootFolder;
50
+        $this->jobList = $jobList;
51
+
52
+        // Run once a day
53
+        $this->setInterval(60*60*24);
54
+    }
55
+
56
+    protected function run($argument) {
57
+        $uid = $argument['uid'];
58
+        $folder = $argument['folder'];
59
+
60
+        try {
61
+            $userFolder = $this->rootFolder->getUserFolder($uid);
62
+            $userRoot = $userFolder->getParent();
63
+            /** @var Folder $uploads */
64
+            $uploads = $userRoot->get('uploads');
65
+            /** @var Folder $uploadFolder */
66
+            $uploadFolder = $uploads->get($folder);
67
+        } catch (NotFoundException|NoUserException $e) {
68
+            $this->jobList->remove(self::class, $argument);
69
+            return;
70
+        }
71
+
72
+        $files = $uploadFolder->getDirectoryListing();
73
+
74
+        // Remove if all files have an mtime of more than a day
75
+        $time = $this->time->getTime() - 60 * 60 * 24;
76
+
77
+        // The folder has to be more than a day old
78
+        $initial = $uploadFolder->getMTime() < $time;
79
+
80
+        $expire = array_reduce($files, function (bool $carry, File $file) use ($time) {
81
+            return $carry && $file->getMTime() < $time;
82
+        }, $initial);
83
+
84
+        if ($expire) {
85
+            $uploadFolder->delete();
86
+            $this->jobList->remove(self::class, $argument);
87
+        }
88
+    }
89 89
 
90 90
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -50,7 +50,7 @@  discard block
 block discarded – undo
50 50
 		$this->jobList = $jobList;
51 51
 
52 52
 		// Run once a day
53
-		$this->setInterval(60*60*24);
53
+		$this->setInterval(60 * 60 * 24);
54 54
 	}
55 55
 
56 56
 	protected function run($argument) {
@@ -64,7 +64,7 @@  discard block
 block discarded – undo
64 64
 			$uploads = $userRoot->get('uploads');
65 65
 			/** @var Folder $uploadFolder */
66 66
 			$uploadFolder = $uploads->get($folder);
67
-		} catch (NotFoundException|NoUserException $e) {
67
+		} catch (NotFoundException | NoUserException $e) {
68 68
 			$this->jobList->remove(self::class, $argument);
69 69
 			return;
70 70
 		}
@@ -77,7 +77,7 @@  discard block
 block discarded – undo
77 77
 		// The folder has to be more than a day old
78 78
 		$initial = $uploadFolder->getMTime() < $time;
79 79
 
80
-		$expire = array_reduce($files, function (bool $carry, File $file) use ($time) {
80
+		$expire = array_reduce($files, function(bool $carry, File $file) use ($time) {
81 81
 			return $carry && $file->getMTime() < $time;
82 82
 		}, $initial);
83 83
 
Please login to merge, or discard this patch.
apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php 2 patches
Indentation   +400 added lines, -400 removed lines patch added patch discarded remove patch
@@ -36,404 +36,404 @@
 block discarded – undo
36 36
 
37 37
 class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
38 38
 
39
-	/** @var IResourceManager */
40
-	private $resourceManager;
41
-
42
-	/** @var IRoomManager */
43
-	private $roomManager;
44
-
45
-	/** @var IDBConnection */
46
-	private $dbConnection;
47
-
48
-	/** @var CalDavBackend */
49
-	private $calDavBackend;
50
-
51
-	/**
52
-	 * UpdateCalendarResourcesRoomsBackgroundJob constructor.
53
-	 *
54
-	 * @param IResourceManager $resourceManager
55
-	 * @param IRoomManager $roomManager
56
-	 * @param IDBConnection $dbConnection
57
-	 * @param CalDavBackend $calDavBackend
58
-	 */
59
-	public function __construct(IResourceManager $resourceManager,
60
-								IRoomManager $roomManager,
61
-								IDBConnection $dbConnection,
62
-								CalDavBackend $calDavBackend) {
63
-		$this->resourceManager = $resourceManager;
64
-		$this->roomManager = $roomManager;
65
-		$this->dbConnection = $dbConnection;
66
-		$this->calDavBackend = $calDavBackend;
67
-
68
-		// run once an hour
69
-		$this->setInterval(60 * 60);
70
-	}
71
-
72
-	/**
73
-	 * @param $argument
74
-	 */
75
-	public function run($argument):void {
76
-		$this->runForBackend(
77
-			$this->resourceManager,
78
-			'calendar_resources',
79
-			'calendar_resources_md',
80
-			'resource_id',
81
-			'principals/calendar-resources'
82
-		);
83
-		$this->runForBackend(
84
-			$this->roomManager,
85
-			'calendar_rooms',
86
-			'calendar_rooms_md',
87
-			'room_id',
88
-			'principals/calendar-rooms'
89
-		);
90
-	}
91
-
92
-	/**
93
-	 * Run background-job for one specific backendManager
94
-	 * either ResourceManager or RoomManager
95
-	 *
96
-	 * @param IResourceManager|IRoomManager $backendManager
97
-	 * @param string $dbTable
98
-	 * @param string $dbTableMetadata
99
-	 * @param string $foreignKey
100
-	 * @param string $principalPrefix
101
-	 */
102
-	private function runForBackend($backendManager,
103
-								   string $dbTable,
104
-								   string $dbTableMetadata,
105
-								   string $foreignKey,
106
-								   string $principalPrefix):void {
107
-		$backends = $backendManager->getBackends();
108
-
109
-		foreach($backends as $backend) {
110
-			$backendId = $backend->getBackendIdentifier();
111
-
112
-			try {
113
-				if ($backend instanceof IResourceBackend) {
114
-					$list = $backend->listAllResources();
115
-				} else {
116
-					$list = $backend->listAllRooms();
117
-				}
118
-			} catch(BackendTemporarilyUnavailableException $ex) {
119
-				continue;
120
-			}
121
-
122
-			$cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
123
-			$newIds = array_diff($list, $cachedList);
124
-			$deletedIds = array_diff($cachedList, $list);
125
-			$editedIds = array_intersect($list, $cachedList);
126
-
127
-			foreach($newIds as $newId) {
128
-				try {
129
-					if ($backend instanceof IResourceBackend) {
130
-						$resource = $backend->getResource($newId);
131
-					} else {
132
-						$resource = $backend->getRoom($newId);
133
-					}
134
-
135
-					$metadata = [];
136
-					if ($resource instanceof IMetadataProvider) {
137
-						$metadata = $this->getAllMetadataOfBackend($resource);
138
-					}
139
-				} catch(BackendTemporarilyUnavailableException $ex) {
140
-					continue;
141
-				}
142
-
143
-				$id = $this->addToCache($dbTable, $backendId, $resource);
144
-				$this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
145
-				// we don't create the calendar here, it is created lazily
146
-				// when an event is actually scheduled with this resource / room
147
-			}
148
-
149
-			foreach($deletedIds as $deletedId) {
150
-				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
151
-				$this->deleteFromCache($dbTable, $id);
152
-				$this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
153
-
154
-				$principalName = implode('-', [$backendId, $deletedId]);
155
-				$this->deleteCalendarDataForResource($principalPrefix, $principalName);
156
-			}
157
-
158
-			foreach($editedIds as $editedId) {
159
-				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
160
-
161
-				try {
162
-					if ($backend instanceof IResourceBackend) {
163
-						$resource = $backend->getResource($editedId);
164
-					} else {
165
-						$resource = $backend->getRoom($editedId);
166
-					}
167
-
168
-					$metadata = [];
169
-					if ($resource instanceof IMetadataProvider) {
170
-						$metadata = $this->getAllMetadataOfBackend($resource);
171
-					}
172
-				} catch(BackendTemporarilyUnavailableException $ex) {
173
-					continue;
174
-				}
175
-
176
-				$this->updateCache($dbTable, $id, $resource);
177
-
178
-				if ($resource instanceof IMetadataProvider) {
179
-					$cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
180
-					$this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
181
-				}
182
-			}
183
-		}
184
-	}
185
-
186
-	/**
187
-	 * add entry to cache that exists remotely but not yet in cache
188
-	 *
189
-	 * @param string $table
190
-	 * @param string $backendId
191
-	 * @param IResource|IRoom $remote
192
-	 * @return int Insert id
193
-	 */
194
-	private function addToCache(string $table,
195
-								string $backendId,
196
-								$remote):int {
197
-		$query = $this->dbConnection->getQueryBuilder();
198
-		$query->insert($table)
199
-			->values([
200
-				'backend_id' => $query->createNamedParameter($backendId),
201
-				'resource_id' => $query->createNamedParameter($remote->getId()),
202
-				'email' => $query->createNamedParameter($remote->getEMail()),
203
-				'displayname' => $query->createNamedParameter($remote->getDisplayName()),
204
-				'group_restrictions' => $query->createNamedParameter(
205
-					$this->serializeGroupRestrictions(
206
-						$remote->getGroupRestrictions()
207
-					))
208
-			])
209
-			->execute();
210
-		return $query->getLastInsertId();
211
-	}
212
-
213
-	/**
214
-	 * @param string $table
215
-	 * @param string $foreignKey
216
-	 * @param int $foreignId
217
-	 * @param array $metadata
218
-	 */
219
-	private function addMetadataToCache(string $table,
220
-										string $foreignKey,
221
-										int $foreignId,
222
-										array $metadata):void {
223
-		foreach($metadata as $key => $value) {
224
-			$query = $this->dbConnection->getQueryBuilder();
225
-			$query->insert($table)
226
-				->values([
227
-					$foreignKey => $query->createNamedParameter($foreignId),
228
-					'key' => $query->createNamedParameter($key),
229
-					'value' => $query->createNamedParameter($value),
230
-				])
231
-				->execute();
232
-		}
233
-	}
234
-
235
-	/**
236
-	 * delete entry from cache that does not exist anymore remotely
237
-	 *
238
-	 * @param string $table
239
-	 * @param int $id
240
-	 */
241
-	private function deleteFromCache(string $table,
242
-									 int $id):void {
243
-		$query = $this->dbConnection->getQueryBuilder();
244
-		$query->delete($table)
245
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)))
246
-			->execute();
247
-	}
248
-
249
-	/**
250
-	 * @param string $table
251
-	 * @param string $foreignKey
252
-	 * @param int $id
253
-	 */
254
-	private function deleteMetadataFromCache(string $table,
255
-											 string $foreignKey,
256
-											 int $id):void {
257
-		$query = $this->dbConnection->getQueryBuilder();
258
-		$query->delete($table)
259
-			->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
260
-			->execute();
261
-	}
262
-
263
-	/**
264
-	 * update an existing entry in cache
265
-	 *
266
-	 * @param string $table
267
-	 * @param int $id
268
-	 * @param IResource|IRoom $remote
269
-	 */
270
-	private function updateCache(string $table,
271
-								 int $id,
272
-								 $remote):void {
273
-		$query = $this->dbConnection->getQueryBuilder();
274
-		$query->update($table)
275
-			->set('email', $query->createNamedParameter($remote->getEMail()))
276
-			->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
277
-			->set('group_restrictions', $query->createNamedParameter(
278
-				$this->serializeGroupRestrictions(
279
-					$remote->getGroupRestrictions()
280
-				)))
281
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)))
282
-			->execute();
283
-	}
284
-
285
-	/**
286
-	 * @param string $dbTable
287
-	 * @param string $foreignKey
288
-	 * @param int $id
289
-	 * @param array $metadata
290
-	 * @param array $cachedMetadata
291
-	 */
292
-	private function updateMetadataCache(string $dbTable,
293
-										 string $foreignKey,
294
-										 int $id,
295
-										 array $metadata,
296
-										 array $cachedMetadata):void {
297
-		$newMetadata = array_diff_key($metadata, $cachedMetadata);
298
-		$deletedMetadata = array_diff_key($cachedMetadata, $metadata);
299
-
300
-		foreach ($newMetadata as $key => $value) {
301
-			$query = $this->dbConnection->getQueryBuilder();
302
-			$query->insert($dbTable)
303
-				->values([
304
-					$foreignKey => $query->createNamedParameter($id),
305
-					'key' => $query->createNamedParameter($key),
306
-					'value' => $query->createNamedParameter($value),
307
-				])
308
-				->execute();
309
-		}
310
-
311
-		foreach($deletedMetadata as $key => $value) {
312
-			$query = $this->dbConnection->getQueryBuilder();
313
-			$query->delete($dbTable)
314
-				->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
315
-				->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
316
-				->execute();
317
-		}
318
-
319
-		$existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
320
-		foreach($existingKeys as $existingKey) {
321
-			if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
322
-				$query = $this->dbConnection->getQueryBuilder();
323
-				$query->update($dbTable)
324
-					->set('value', $query->createNamedParameter($metadata[$existingKey]))
325
-					->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
326
-					->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
327
-					->execute();
328
-			}
329
-		}
330
-	}
331
-
332
-	/**
333
-	 * serialize array of group restrictions to store them in database
334
-	 *
335
-	 * @param array $groups
336
-	 * @return string
337
-	 */
338
-	private function serializeGroupRestrictions(array $groups):string {
339
-		return \json_encode($groups);
340
-	}
341
-
342
-	/**
343
-	 * Gets all metadata of a backend
344
-	 *
345
-	 * @param IResource|IRoom $resource
346
-	 * @return array
347
-	 */
348
-	private function getAllMetadataOfBackend($resource):array {
349
-		if (!($resource instanceof IMetadataProvider)) {
350
-			return [];
351
-		}
352
-
353
-		$keys = $resource->getAllAvailableMetadataKeys();
354
-		$metadata = [];
355
-		foreach($keys as $key) {
356
-			$metadata[$key] = $resource->getMetadataForKey($key);
357
-		}
358
-
359
-		return $metadata;
360
-	}
361
-
362
-	/**
363
-	 * @param string $table
364
-	 * @param string $foreignKey
365
-	 * @param int $id
366
-	 * @return array
367
-	 */
368
-	private function getAllMetadataOfCache(string $table,
369
-										   string $foreignKey,
370
-										   int $id):array {
371
-		$query = $this->dbConnection->getQueryBuilder();
372
-		$query->select(['key', 'value'])
373
-			->from($table)
374
-			->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
375
-		$stmt = $query->execute();
376
-		$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
377
-
378
-		$metadata = [];
379
-		foreach($rows as $row) {
380
-			$metadata[$row['key']] = $row['value'];
381
-		}
382
-
383
-		return $metadata;
384
-	}
385
-
386
-	/**
387
-	 * Gets all cached rooms / resources by backend
388
-	 *
389
-	 * @param $tableName
390
-	 * @param $backendId
391
-	 * @return array
392
-	 */
393
-	private function getAllCachedByBackend(string $tableName,
394
-										   string $backendId):array {
395
-		$query = $this->dbConnection->getQueryBuilder();
396
-		$query->select('resource_id')
397
-			->from($tableName)
398
-			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
399
-		$stmt = $query->execute();
400
-
401
-		return array_map(function ($row) {
402
-			return $row['resource_id'];
403
-		}, $stmt->fetchAll(\PDO::FETCH_NAMED));
404
-	}
405
-
406
-	/**
407
-	 * @param $principalPrefix
408
-	 * @param $principalUri
409
-	 */
410
-	private function deleteCalendarDataForResource(string $principalPrefix,
411
-												   string $principalUri):void {
412
-		$calendar = $this->calDavBackend->getCalendarByUri(
413
-			implode('/', [$principalPrefix, $principalUri]),
414
-			CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
415
-
416
-		if ($calendar !== null) {
417
-			$this->calDavBackend->deleteCalendar($calendar['id']);
418
-		}
419
-	}
420
-
421
-	/**
422
-	 * @param $table
423
-	 * @param $backendId
424
-	 * @param $resourceId
425
-	 * @return int
426
-	 */
427
-	private function getIdForBackendAndResource(string $table,
428
-												string $backendId,
429
-												string $resourceId):int {
430
-		$query = $this->dbConnection->getQueryBuilder();
431
-		$query->select('id')
432
-			->from($table)
433
-			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
434
-			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
435
-		$stmt = $query->execute();
436
-
437
-		return $stmt->fetch(\PDO::FETCH_NAMED)['id'];
438
-	}
39
+    /** @var IResourceManager */
40
+    private $resourceManager;
41
+
42
+    /** @var IRoomManager */
43
+    private $roomManager;
44
+
45
+    /** @var IDBConnection */
46
+    private $dbConnection;
47
+
48
+    /** @var CalDavBackend */
49
+    private $calDavBackend;
50
+
51
+    /**
52
+     * UpdateCalendarResourcesRoomsBackgroundJob constructor.
53
+     *
54
+     * @param IResourceManager $resourceManager
55
+     * @param IRoomManager $roomManager
56
+     * @param IDBConnection $dbConnection
57
+     * @param CalDavBackend $calDavBackend
58
+     */
59
+    public function __construct(IResourceManager $resourceManager,
60
+                                IRoomManager $roomManager,
61
+                                IDBConnection $dbConnection,
62
+                                CalDavBackend $calDavBackend) {
63
+        $this->resourceManager = $resourceManager;
64
+        $this->roomManager = $roomManager;
65
+        $this->dbConnection = $dbConnection;
66
+        $this->calDavBackend = $calDavBackend;
67
+
68
+        // run once an hour
69
+        $this->setInterval(60 * 60);
70
+    }
71
+
72
+    /**
73
+     * @param $argument
74
+     */
75
+    public function run($argument):void {
76
+        $this->runForBackend(
77
+            $this->resourceManager,
78
+            'calendar_resources',
79
+            'calendar_resources_md',
80
+            'resource_id',
81
+            'principals/calendar-resources'
82
+        );
83
+        $this->runForBackend(
84
+            $this->roomManager,
85
+            'calendar_rooms',
86
+            'calendar_rooms_md',
87
+            'room_id',
88
+            'principals/calendar-rooms'
89
+        );
90
+    }
91
+
92
+    /**
93
+     * Run background-job for one specific backendManager
94
+     * either ResourceManager or RoomManager
95
+     *
96
+     * @param IResourceManager|IRoomManager $backendManager
97
+     * @param string $dbTable
98
+     * @param string $dbTableMetadata
99
+     * @param string $foreignKey
100
+     * @param string $principalPrefix
101
+     */
102
+    private function runForBackend($backendManager,
103
+                                    string $dbTable,
104
+                                    string $dbTableMetadata,
105
+                                    string $foreignKey,
106
+                                    string $principalPrefix):void {
107
+        $backends = $backendManager->getBackends();
108
+
109
+        foreach($backends as $backend) {
110
+            $backendId = $backend->getBackendIdentifier();
111
+
112
+            try {
113
+                if ($backend instanceof IResourceBackend) {
114
+                    $list = $backend->listAllResources();
115
+                } else {
116
+                    $list = $backend->listAllRooms();
117
+                }
118
+            } catch(BackendTemporarilyUnavailableException $ex) {
119
+                continue;
120
+            }
121
+
122
+            $cachedList = $this->getAllCachedByBackend($dbTable, $backendId);
123
+            $newIds = array_diff($list, $cachedList);
124
+            $deletedIds = array_diff($cachedList, $list);
125
+            $editedIds = array_intersect($list, $cachedList);
126
+
127
+            foreach($newIds as $newId) {
128
+                try {
129
+                    if ($backend instanceof IResourceBackend) {
130
+                        $resource = $backend->getResource($newId);
131
+                    } else {
132
+                        $resource = $backend->getRoom($newId);
133
+                    }
134
+
135
+                    $metadata = [];
136
+                    if ($resource instanceof IMetadataProvider) {
137
+                        $metadata = $this->getAllMetadataOfBackend($resource);
138
+                    }
139
+                } catch(BackendTemporarilyUnavailableException $ex) {
140
+                    continue;
141
+                }
142
+
143
+                $id = $this->addToCache($dbTable, $backendId, $resource);
144
+                $this->addMetadataToCache($dbTableMetadata, $foreignKey, $id, $metadata);
145
+                // we don't create the calendar here, it is created lazily
146
+                // when an event is actually scheduled with this resource / room
147
+            }
148
+
149
+            foreach($deletedIds as $deletedId) {
150
+                $id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
151
+                $this->deleteFromCache($dbTable, $id);
152
+                $this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
153
+
154
+                $principalName = implode('-', [$backendId, $deletedId]);
155
+                $this->deleteCalendarDataForResource($principalPrefix, $principalName);
156
+            }
157
+
158
+            foreach($editedIds as $editedId) {
159
+                $id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
160
+
161
+                try {
162
+                    if ($backend instanceof IResourceBackend) {
163
+                        $resource = $backend->getResource($editedId);
164
+                    } else {
165
+                        $resource = $backend->getRoom($editedId);
166
+                    }
167
+
168
+                    $metadata = [];
169
+                    if ($resource instanceof IMetadataProvider) {
170
+                        $metadata = $this->getAllMetadataOfBackend($resource);
171
+                    }
172
+                } catch(BackendTemporarilyUnavailableException $ex) {
173
+                    continue;
174
+                }
175
+
176
+                $this->updateCache($dbTable, $id, $resource);
177
+
178
+                if ($resource instanceof IMetadataProvider) {
179
+                    $cachedMetadata = $this->getAllMetadataOfCache($dbTableMetadata, $foreignKey, $id);
180
+                    $this->updateMetadataCache($dbTableMetadata, $foreignKey, $id, $metadata, $cachedMetadata);
181
+                }
182
+            }
183
+        }
184
+    }
185
+
186
+    /**
187
+     * add entry to cache that exists remotely but not yet in cache
188
+     *
189
+     * @param string $table
190
+     * @param string $backendId
191
+     * @param IResource|IRoom $remote
192
+     * @return int Insert id
193
+     */
194
+    private function addToCache(string $table,
195
+                                string $backendId,
196
+                                $remote):int {
197
+        $query = $this->dbConnection->getQueryBuilder();
198
+        $query->insert($table)
199
+            ->values([
200
+                'backend_id' => $query->createNamedParameter($backendId),
201
+                'resource_id' => $query->createNamedParameter($remote->getId()),
202
+                'email' => $query->createNamedParameter($remote->getEMail()),
203
+                'displayname' => $query->createNamedParameter($remote->getDisplayName()),
204
+                'group_restrictions' => $query->createNamedParameter(
205
+                    $this->serializeGroupRestrictions(
206
+                        $remote->getGroupRestrictions()
207
+                    ))
208
+            ])
209
+            ->execute();
210
+        return $query->getLastInsertId();
211
+    }
212
+
213
+    /**
214
+     * @param string $table
215
+     * @param string $foreignKey
216
+     * @param int $foreignId
217
+     * @param array $metadata
218
+     */
219
+    private function addMetadataToCache(string $table,
220
+                                        string $foreignKey,
221
+                                        int $foreignId,
222
+                                        array $metadata):void {
223
+        foreach($metadata as $key => $value) {
224
+            $query = $this->dbConnection->getQueryBuilder();
225
+            $query->insert($table)
226
+                ->values([
227
+                    $foreignKey => $query->createNamedParameter($foreignId),
228
+                    'key' => $query->createNamedParameter($key),
229
+                    'value' => $query->createNamedParameter($value),
230
+                ])
231
+                ->execute();
232
+        }
233
+    }
234
+
235
+    /**
236
+     * delete entry from cache that does not exist anymore remotely
237
+     *
238
+     * @param string $table
239
+     * @param int $id
240
+     */
241
+    private function deleteFromCache(string $table,
242
+                                        int $id):void {
243
+        $query = $this->dbConnection->getQueryBuilder();
244
+        $query->delete($table)
245
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
246
+            ->execute();
247
+    }
248
+
249
+    /**
250
+     * @param string $table
251
+     * @param string $foreignKey
252
+     * @param int $id
253
+     */
254
+    private function deleteMetadataFromCache(string $table,
255
+                                                string $foreignKey,
256
+                                                int $id):void {
257
+        $query = $this->dbConnection->getQueryBuilder();
258
+        $query->delete($table)
259
+            ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
260
+            ->execute();
261
+    }
262
+
263
+    /**
264
+     * update an existing entry in cache
265
+     *
266
+     * @param string $table
267
+     * @param int $id
268
+     * @param IResource|IRoom $remote
269
+     */
270
+    private function updateCache(string $table,
271
+                                    int $id,
272
+                                    $remote):void {
273
+        $query = $this->dbConnection->getQueryBuilder();
274
+        $query->update($table)
275
+            ->set('email', $query->createNamedParameter($remote->getEMail()))
276
+            ->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
277
+            ->set('group_restrictions', $query->createNamedParameter(
278
+                $this->serializeGroupRestrictions(
279
+                    $remote->getGroupRestrictions()
280
+                )))
281
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)))
282
+            ->execute();
283
+    }
284
+
285
+    /**
286
+     * @param string $dbTable
287
+     * @param string $foreignKey
288
+     * @param int $id
289
+     * @param array $metadata
290
+     * @param array $cachedMetadata
291
+     */
292
+    private function updateMetadataCache(string $dbTable,
293
+                                            string $foreignKey,
294
+                                            int $id,
295
+                                            array $metadata,
296
+                                            array $cachedMetadata):void {
297
+        $newMetadata = array_diff_key($metadata, $cachedMetadata);
298
+        $deletedMetadata = array_diff_key($cachedMetadata, $metadata);
299
+
300
+        foreach ($newMetadata as $key => $value) {
301
+            $query = $this->dbConnection->getQueryBuilder();
302
+            $query->insert($dbTable)
303
+                ->values([
304
+                    $foreignKey => $query->createNamedParameter($id),
305
+                    'key' => $query->createNamedParameter($key),
306
+                    'value' => $query->createNamedParameter($value),
307
+                ])
308
+                ->execute();
309
+        }
310
+
311
+        foreach($deletedMetadata as $key => $value) {
312
+            $query = $this->dbConnection->getQueryBuilder();
313
+            $query->delete($dbTable)
314
+                ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
315
+                ->andWhere($query->expr()->eq('key', $query->createNamedParameter($key)))
316
+                ->execute();
317
+        }
318
+
319
+        $existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
320
+        foreach($existingKeys as $existingKey) {
321
+            if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
322
+                $query = $this->dbConnection->getQueryBuilder();
323
+                $query->update($dbTable)
324
+                    ->set('value', $query->createNamedParameter($metadata[$existingKey]))
325
+                    ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
326
+                    ->andWhere($query->expr()->eq('key', $query->createNamedParameter($existingKey)))
327
+                    ->execute();
328
+            }
329
+        }
330
+    }
331
+
332
+    /**
333
+     * serialize array of group restrictions to store them in database
334
+     *
335
+     * @param array $groups
336
+     * @return string
337
+     */
338
+    private function serializeGroupRestrictions(array $groups):string {
339
+        return \json_encode($groups);
340
+    }
341
+
342
+    /**
343
+     * Gets all metadata of a backend
344
+     *
345
+     * @param IResource|IRoom $resource
346
+     * @return array
347
+     */
348
+    private function getAllMetadataOfBackend($resource):array {
349
+        if (!($resource instanceof IMetadataProvider)) {
350
+            return [];
351
+        }
352
+
353
+        $keys = $resource->getAllAvailableMetadataKeys();
354
+        $metadata = [];
355
+        foreach($keys as $key) {
356
+            $metadata[$key] = $resource->getMetadataForKey($key);
357
+        }
358
+
359
+        return $metadata;
360
+    }
361
+
362
+    /**
363
+     * @param string $table
364
+     * @param string $foreignKey
365
+     * @param int $id
366
+     * @return array
367
+     */
368
+    private function getAllMetadataOfCache(string $table,
369
+                                            string $foreignKey,
370
+                                            int $id):array {
371
+        $query = $this->dbConnection->getQueryBuilder();
372
+        $query->select(['key', 'value'])
373
+            ->from($table)
374
+            ->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)));
375
+        $stmt = $query->execute();
376
+        $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
377
+
378
+        $metadata = [];
379
+        foreach($rows as $row) {
380
+            $metadata[$row['key']] = $row['value'];
381
+        }
382
+
383
+        return $metadata;
384
+    }
385
+
386
+    /**
387
+     * Gets all cached rooms / resources by backend
388
+     *
389
+     * @param $tableName
390
+     * @param $backendId
391
+     * @return array
392
+     */
393
+    private function getAllCachedByBackend(string $tableName,
394
+                                            string $backendId):array {
395
+        $query = $this->dbConnection->getQueryBuilder();
396
+        $query->select('resource_id')
397
+            ->from($tableName)
398
+            ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
399
+        $stmt = $query->execute();
400
+
401
+        return array_map(function ($row) {
402
+            return $row['resource_id'];
403
+        }, $stmt->fetchAll(\PDO::FETCH_NAMED));
404
+    }
405
+
406
+    /**
407
+     * @param $principalPrefix
408
+     * @param $principalUri
409
+     */
410
+    private function deleteCalendarDataForResource(string $principalPrefix,
411
+                                                    string $principalUri):void {
412
+        $calendar = $this->calDavBackend->getCalendarByUri(
413
+            implode('/', [$principalPrefix, $principalUri]),
414
+            CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
415
+
416
+        if ($calendar !== null) {
417
+            $this->calDavBackend->deleteCalendar($calendar['id']);
418
+        }
419
+    }
420
+
421
+    /**
422
+     * @param $table
423
+     * @param $backendId
424
+     * @param $resourceId
425
+     * @return int
426
+     */
427
+    private function getIdForBackendAndResource(string $table,
428
+                                                string $backendId,
429
+                                                string $resourceId):int {
430
+        $query = $this->dbConnection->getQueryBuilder();
431
+        $query->select('id')
432
+            ->from($table)
433
+            ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
434
+            ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
435
+        $stmt = $query->execute();
436
+
437
+        return $stmt->fetch(\PDO::FETCH_NAMED)['id'];
438
+    }
439 439
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -106,7 +106,7 @@  discard block
 block discarded – undo
106 106
 								   string $principalPrefix):void {
107 107
 		$backends = $backendManager->getBackends();
108 108
 
109
-		foreach($backends as $backend) {
109
+		foreach ($backends as $backend) {
110 110
 			$backendId = $backend->getBackendIdentifier();
111 111
 
112 112
 			try {
@@ -115,7 +115,7 @@  discard block
 block discarded – undo
115 115
 				} else {
116 116
 					$list = $backend->listAllRooms();
117 117
 				}
118
-			} catch(BackendTemporarilyUnavailableException $ex) {
118
+			} catch (BackendTemporarilyUnavailableException $ex) {
119 119
 				continue;
120 120
 			}
121 121
 
@@ -124,7 +124,7 @@  discard block
 block discarded – undo
124 124
 			$deletedIds = array_diff($cachedList, $list);
125 125
 			$editedIds = array_intersect($list, $cachedList);
126 126
 
127
-			foreach($newIds as $newId) {
127
+			foreach ($newIds as $newId) {
128 128
 				try {
129 129
 					if ($backend instanceof IResourceBackend) {
130 130
 						$resource = $backend->getResource($newId);
@@ -136,7 +136,7 @@  discard block
 block discarded – undo
136 136
 					if ($resource instanceof IMetadataProvider) {
137 137
 						$metadata = $this->getAllMetadataOfBackend($resource);
138 138
 					}
139
-				} catch(BackendTemporarilyUnavailableException $ex) {
139
+				} catch (BackendTemporarilyUnavailableException $ex) {
140 140
 					continue;
141 141
 				}
142 142
 
@@ -146,7 +146,7 @@  discard block
 block discarded – undo
146 146
 				// when an event is actually scheduled with this resource / room
147 147
 			}
148 148
 
149
-			foreach($deletedIds as $deletedId) {
149
+			foreach ($deletedIds as $deletedId) {
150 150
 				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $deletedId);
151 151
 				$this->deleteFromCache($dbTable, $id);
152 152
 				$this->deleteMetadataFromCache($dbTableMetadata, $foreignKey, $id);
@@ -155,7 +155,7 @@  discard block
 block discarded – undo
155 155
 				$this->deleteCalendarDataForResource($principalPrefix, $principalName);
156 156
 			}
157 157
 
158
-			foreach($editedIds as $editedId) {
158
+			foreach ($editedIds as $editedId) {
159 159
 				$id = $this->getIdForBackendAndResource($dbTable, $backendId, $editedId);
160 160
 
161 161
 				try {
@@ -169,7 +169,7 @@  discard block
 block discarded – undo
169 169
 					if ($resource instanceof IMetadataProvider) {
170 170
 						$metadata = $this->getAllMetadataOfBackend($resource);
171 171
 					}
172
-				} catch(BackendTemporarilyUnavailableException $ex) {
172
+				} catch (BackendTemporarilyUnavailableException $ex) {
173 173
 					continue;
174 174
 				}
175 175
 
@@ -220,7 +220,7 @@  discard block
 block discarded – undo
220 220
 										string $foreignKey,
221 221
 										int $foreignId,
222 222
 										array $metadata):void {
223
-		foreach($metadata as $key => $value) {
223
+		foreach ($metadata as $key => $value) {
224 224
 			$query = $this->dbConnection->getQueryBuilder();
225 225
 			$query->insert($table)
226 226
 				->values([
@@ -308,7 +308,7 @@  discard block
 block discarded – undo
308 308
 				->execute();
309 309
 		}
310 310
 
311
-		foreach($deletedMetadata as $key => $value) {
311
+		foreach ($deletedMetadata as $key => $value) {
312 312
 			$query = $this->dbConnection->getQueryBuilder();
313 313
 			$query->delete($dbTable)
314 314
 				->where($query->expr()->eq($foreignKey, $query->createNamedParameter($id)))
@@ -317,7 +317,7 @@  discard block
 block discarded – undo
317 317
 		}
318 318
 
319 319
 		$existingKeys = array_keys(array_intersect_key($metadata, $cachedMetadata));
320
-		foreach($existingKeys as $existingKey) {
320
+		foreach ($existingKeys as $existingKey) {
321 321
 			if ($metadata[$existingKey] !== $cachedMetadata[$existingKey]) {
322 322
 				$query = $this->dbConnection->getQueryBuilder();
323 323
 				$query->update($dbTable)
@@ -352,7 +352,7 @@  discard block
 block discarded – undo
352 352
 
353 353
 		$keys = $resource->getAllAvailableMetadataKeys();
354 354
 		$metadata = [];
355
-		foreach($keys as $key) {
355
+		foreach ($keys as $key) {
356 356
 			$metadata[$key] = $resource->getMetadataForKey($key);
357 357
 		}
358 358
 
@@ -376,7 +376,7 @@  discard block
 block discarded – undo
376 376
 		$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
377 377
 
378 378
 		$metadata = [];
379
-		foreach($rows as $row) {
379
+		foreach ($rows as $row) {
380 380
 			$metadata[$row['key']] = $row['value'];
381 381
 		}
382 382
 
@@ -398,7 +398,7 @@  discard block
 block discarded – undo
398 398
 			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)));
399 399
 		$stmt = $query->execute();
400 400
 
401
-		return array_map(function ($row) {
401
+		return array_map(function($row) {
402 402
 			return $row['resource_id'];
403 403
 		}, $stmt->fetchAll(\PDO::FETCH_NAMED));
404 404
 	}
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/UserAddressBooks.php 2 patches
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -41,84 +41,84 @@
 block discarded – undo
41 41
 
42 42
 class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome {
43 43
 
44
-	/** @var IL10N */
45
-	protected $l10n;
46
-
47
-	/** @var IConfig */
48
-	protected $config;
49
-
50
-	/** @var PluginManager */
51
-	private $pluginManager;
52
-
53
-	public function __construct(Backend\BackendInterface $carddavBackend,
54
-								string $principalUri,
55
-								PluginManager $pluginManager) {
56
-		parent::__construct($carddavBackend, $principalUri);
57
-		$this->pluginManager = $pluginManager;
58
-	}
59
-
60
-	/**
61
-	 * Returns a list of address books
62
-	 *
63
-	 * @return IAddressBook[]
64
-	 */
65
-	function getChildren() {
66
-		if ($this->l10n === null) {
67
-			$this->l10n = \OC::$server->getL10N('dav');
68
-		}
69
-		if ($this->config === null) {
70
-			$this->config = \OC::$server->getConfig();
71
-		}
72
-
73
-		$addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
74
-		/** @var IAddressBook[] $objects */
75
-		$objects = array_map(function (array $addressBook) {
76
-			if ($addressBook['principaluri'] === 'principals/system/system') {
77
-				return new SystemAddressbook($this->carddavBackend, $addressBook, $this->l10n, $this->config);
78
-			}
79
-
80
-			return new AddressBook($this->carddavBackend, $addressBook, $this->l10n);
81
-		}, $addressBooks);
82
-		/** @var IAddressBook[][] $objectsFromPlugins */
83
-		$objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array {
84
-			return $plugin->fetchAllForAddressBookHome($this->principalUri);
85
-		}, $this->pluginManager->getAddressBookPlugins());
86
-
87
-		return array_merge($objects, ...$objectsFromPlugins);
88
-	}
89
-
90
-	public function createExtendedCollection($name, MkCol $mkCol) {
91
-		if (ExternalAddressBook::doesViolateReservedName($name)) {
92
-			throw new MethodNotAllowed('The resource you tried to create has a reserved name');
93
-		}
94
-
95
-		parent::createExtendedCollection($name, $mkCol);
96
-	}
97
-
98
-	/**
99
-	 * Returns a list of ACE's for this node.
100
-	 *
101
-	 * Each ACE has the following properties:
102
-	 *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
103
-	 *     currently the only supported privileges
104
-	 *   * 'principal', a url to the principal who owns the node
105
-	 *   * 'protected' (optional), indicating that this ACE is not allowed to
106
-	 *      be updated.
107
-	 *
108
-	 * @return array
109
-	 */
110
-	function getACL() {
111
-
112
-		$acl = parent::getACL();
113
-		if ($this->principalUri === 'principals/system/system') {
114
-			$acl[] = [
115
-				'privilege' => '{DAV:}read',
116
-				'principal' => '{DAV:}authenticated',
117
-				'protected' => true,
118
-			];
119
-		}
120
-
121
-		return $acl;
122
-	}
44
+    /** @var IL10N */
45
+    protected $l10n;
46
+
47
+    /** @var IConfig */
48
+    protected $config;
49
+
50
+    /** @var PluginManager */
51
+    private $pluginManager;
52
+
53
+    public function __construct(Backend\BackendInterface $carddavBackend,
54
+                                string $principalUri,
55
+                                PluginManager $pluginManager) {
56
+        parent::__construct($carddavBackend, $principalUri);
57
+        $this->pluginManager = $pluginManager;
58
+    }
59
+
60
+    /**
61
+     * Returns a list of address books
62
+     *
63
+     * @return IAddressBook[]
64
+     */
65
+    function getChildren() {
66
+        if ($this->l10n === null) {
67
+            $this->l10n = \OC::$server->getL10N('dav');
68
+        }
69
+        if ($this->config === null) {
70
+            $this->config = \OC::$server->getConfig();
71
+        }
72
+
73
+        $addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
74
+        /** @var IAddressBook[] $objects */
75
+        $objects = array_map(function (array $addressBook) {
76
+            if ($addressBook['principaluri'] === 'principals/system/system') {
77
+                return new SystemAddressbook($this->carddavBackend, $addressBook, $this->l10n, $this->config);
78
+            }
79
+
80
+            return new AddressBook($this->carddavBackend, $addressBook, $this->l10n);
81
+        }, $addressBooks);
82
+        /** @var IAddressBook[][] $objectsFromPlugins */
83
+        $objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array {
84
+            return $plugin->fetchAllForAddressBookHome($this->principalUri);
85
+        }, $this->pluginManager->getAddressBookPlugins());
86
+
87
+        return array_merge($objects, ...$objectsFromPlugins);
88
+    }
89
+
90
+    public function createExtendedCollection($name, MkCol $mkCol) {
91
+        if (ExternalAddressBook::doesViolateReservedName($name)) {
92
+            throw new MethodNotAllowed('The resource you tried to create has a reserved name');
93
+        }
94
+
95
+        parent::createExtendedCollection($name, $mkCol);
96
+    }
97
+
98
+    /**
99
+     * Returns a list of ACE's for this node.
100
+     *
101
+     * Each ACE has the following properties:
102
+     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
103
+     *     currently the only supported privileges
104
+     *   * 'principal', a url to the principal who owns the node
105
+     *   * 'protected' (optional), indicating that this ACE is not allowed to
106
+     *      be updated.
107
+     *
108
+     * @return array
109
+     */
110
+    function getACL() {
111
+
112
+        $acl = parent::getACL();
113
+        if ($this->principalUri === 'principals/system/system') {
114
+            $acl[] = [
115
+                'privilege' => '{DAV:}read',
116
+                'principal' => '{DAV:}authenticated',
117
+                'protected' => true,
118
+            ];
119
+        }
120
+
121
+        return $acl;
122
+    }
123 123
 
124 124
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -72,7 +72,7 @@  discard block
 block discarded – undo
72 72
 
73 73
 		$addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
74 74
 		/** @var IAddressBook[] $objects */
75
-		$objects = array_map(function (array $addressBook) {
75
+		$objects = array_map(function(array $addressBook) {
76 76
 			if ($addressBook['principaluri'] === 'principals/system/system') {
77 77
 				return new SystemAddressbook($this->carddavBackend, $addressBook, $this->l10n, $this->config);
78 78
 			}
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 			return new AddressBook($this->carddavBackend, $addressBook, $this->l10n);
81 81
 		}, $addressBooks);
82 82
 		/** @var IAddressBook[][] $objectsFromPlugins */
83
-		$objectsFromPlugins = array_map(function (IAddressBookProvider $plugin): array {
83
+		$objectsFromPlugins = array_map(function(IAddressBookProvider $plugin): array {
84 84
 			return $plugin->fetchAllForAddressBookHome($this->principalUri);
85 85
 		}, $this->pluginManager->getAddressBookPlugins());
86 86
 
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/dav/lib/CardDAV/SyncService.php 2 patches
Indentation   +298 added lines, -298 removed lines patch added patch discarded remove patch
@@ -42,304 +42,304 @@
 block discarded – undo
42 42
 
43 43
 class SyncService {
44 44
 
45
-	/** @var CardDavBackend */
46
-	private $backend;
47
-
48
-	/** @var IUserManager */
49
-	private $userManager;
50
-
51
-	/** @var ILogger */
52
-	private $logger;
53
-
54
-	/** @var array */
55
-	private $localSystemAddressBook;
56
-
57
-	/** @var AccountManager */
58
-	private $accountManager;
59
-
60
-	/** @var string */
61
-	protected $certPath;
62
-
63
-	/**
64
-	 * SyncService constructor.
65
-	 *
66
-	 * @param CardDavBackend $backend
67
-	 * @param IUserManager $userManager
68
-	 * @param ILogger $logger
69
-	 * @param AccountManager $accountManager
70
-	 */
71
-	public function __construct(CardDavBackend $backend, IUserManager $userManager, ILogger $logger, AccountManager $accountManager) {
72
-		$this->backend = $backend;
73
-		$this->userManager = $userManager;
74
-		$this->logger = $logger;
75
-		$this->accountManager = $accountManager;
76
-		$this->certPath = '';
77
-	}
78
-
79
-	/**
80
-	 * @param string $url
81
-	 * @param string $userName
82
-	 * @param string $addressBookUrl
83
-	 * @param string $sharedSecret
84
-	 * @param string $syncToken
85
-	 * @param int $targetBookId
86
-	 * @param string $targetPrincipal
87
-	 * @param array $targetProperties
88
-	 * @return string
89
-	 * @throws \Exception
90
-	 */
91
-	public function syncRemoteAddressBook($url, $userName, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetProperties) {
92
-		// 1. create addressbook
93
-		$book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookId, $targetProperties);
94
-		$addressBookId = $book['id'];
95
-
96
-		// 2. query changes
97
-		try {
98
-			$response = $this->requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken);
99
-		} catch (ClientHttpException $ex) {
100
-			if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
101
-				// remote server revoked access to the address book, remove it
102
-				$this->backend->deleteAddressBook($addressBookId);
103
-				$this->logger->info('Authorization failed, remove address book: ' . $url, ['app' => 'dav']);
104
-				throw $ex;
105
-			}
106
-		}
107
-
108
-		// 3. apply changes
109
-		// TODO: use multi-get for download
110
-		foreach ($response['response'] as $resource => $status) {
111
-			$cardUri = basename($resource);
112
-			if (isset($status[200])) {
113
-				$vCard = $this->download($url, $userName, $sharedSecret, $resource);
114
-				$existingCard = $this->backend->getCard($addressBookId, $cardUri);
115
-				if ($existingCard === false) {
116
-					$this->backend->createCard($addressBookId, $cardUri, $vCard['body']);
117
-				} else {
118
-					$this->backend->updateCard($addressBookId, $cardUri, $vCard['body']);
119
-				}
120
-			} else {
121
-				$this->backend->deleteCard($addressBookId, $cardUri);
122
-			}
123
-		}
124
-
125
-		return $response['token'];
126
-	}
127
-
128
-	/**
129
-	 * @param string $principal
130
-	 * @param string $id
131
-	 * @param array $properties
132
-	 * @return array|null
133
-	 * @throws \Sabre\DAV\Exception\BadRequest
134
-	 */
135
-	public function ensureSystemAddressBookExists($principal, $id, $properties) {
136
-		$book = $this->backend->getAddressBooksByUri($principal, $id);
137
-		if (!is_null($book)) {
138
-			return $book;
139
-		}
140
-		$this->backend->createAddressBook($principal, $id, $properties);
141
-
142
-		return $this->backend->getAddressBooksByUri($principal, $id);
143
-	}
144
-
145
-	/**
146
-	 * Check if there is a valid certPath we should use
147
-	 *
148
-	 * @return string
149
-	 */
150
-	protected function getCertPath() {
151
-
152
-		// we already have a valid certPath
153
-		if ($this->certPath !== '') {
154
-			return $this->certPath;
155
-		}
156
-
157
-		/** @var ICertificateManager $certManager */
158
-		$certManager = \OC::$server->getCertificateManager(null);
159
-		$certPath = $certManager->getAbsoluteBundlePath();
160
-		if (file_exists($certPath)) {
161
-			$this->certPath = $certPath;
162
-		}
163
-
164
-		return $this->certPath;
165
-	}
166
-
167
-	/**
168
-	 * @param string $url
169
-	 * @param string $userName
170
-	 * @param string $addressBookUrl
171
-	 * @param string $sharedSecret
172
-	 * @return Client
173
-	 */
174
-	protected function getClient($url, $userName, $sharedSecret) {
175
-		$settings = [
176
-			'baseUri' => $url . '/',
177
-			'userName' => $userName,
178
-			'password' => $sharedSecret,
179
-		];
180
-		$client = new Client($settings);
181
-		$certPath = $this->getCertPath();
182
-		$client->setThrowExceptions(true);
183
-
184
-		if ($certPath !== '' && strpos($url, 'http://') !== 0) {
185
-			$client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
186
-		}
187
-
188
-		return $client;
189
-	}
190
-
191
-	/**
192
-	 * @param string $url
193
-	 * @param string $userName
194
-	 * @param string $addressBookUrl
195
-	 * @param string $sharedSecret
196
-	 * @param string $syncToken
197
-	 * @return array
198
-	 */
199
-	 protected function requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken) {
200
-		 $client = $this->getClient($url, $userName, $sharedSecret);
201
-
202
-		 $body = $this->buildSyncCollectionRequestBody($syncToken);
203
-
204
-		 $response = $client->request('REPORT', $addressBookUrl, $body, [
205
-		 	'Content-Type' => 'application/xml'
206
-		 ]);
207
-
208
-		 return $this->parseMultiStatus($response['body']);
209
-	 }
210
-
211
-	/**
212
-	 * @param string $url
213
-	 * @param string $userName
214
-	 * @param string $sharedSecret
215
-	 * @param string $resourcePath
216
-	 * @return array
217
-	 */
218
-	protected function download($url, $userName, $sharedSecret, $resourcePath) {
219
-		$client = $this->getClient($url, $userName, $sharedSecret);
220
-		return $client->request('GET', $resourcePath);
221
-	}
222
-
223
-	/**
224
-	 * @param string|null $syncToken
225
-	 * @return string
226
-	 */
227
-	private function buildSyncCollectionRequestBody($syncToken) {
228
-
229
-		$dom = new \DOMDocument('1.0', 'UTF-8');
230
-		$dom->formatOutput = true;
231
-		$root = $dom->createElementNS('DAV:', 'd:sync-collection');
232
-		$sync = $dom->createElement('d:sync-token', $syncToken);
233
-		$prop = $dom->createElement('d:prop');
234
-		$cont = $dom->createElement('d:getcontenttype');
235
-		$etag = $dom->createElement('d:getetag');
236
-
237
-		$prop->appendChild($cont);
238
-		$prop->appendChild($etag);
239
-		$root->appendChild($sync);
240
-		$root->appendChild($prop);
241
-		$dom->appendChild($root);
242
-		return $dom->saveXML();
243
-	}
244
-
245
-	/**
246
-	 * @param string $body
247
-	 * @return array
248
-	 * @throws \Sabre\Xml\ParseException
249
-	 */
250
-	private function parseMultiStatus($body) {
251
-		$xml = new Service();
252
-
253
-		/** @var MultiStatus $multiStatus */
254
-		$multiStatus = $xml->expect('{DAV:}multistatus', $body);
255
-
256
-		$result = [];
257
-		foreach ($multiStatus->getResponses() as $response) {
258
-			$result[$response->getHref()] = $response->getResponseProperties();
259
-		}
260
-
261
-		return ['response' => $result, 'token' => $multiStatus->getSyncToken()];
262
-	}
263
-
264
-	/**
265
-	 * @param IUser $user
266
-	 */
267
-	public function updateUser(IUser $user) {
268
-		$systemAddressBook = $this->getLocalSystemAddressBook();
269
-		$addressBookId = $systemAddressBook['id'];
270
-		$converter = new Converter($this->accountManager);
271
-		$name = $user->getBackendClassName();
272
-		$userId = $user->getUID();
273
-
274
-		$cardId = "$name:$userId.vcf";
275
-		$card = $this->backend->getCard($addressBookId, $cardId);
276
-		if ($user->isEnabled()) {
277
-			if ($card === false) {
278
-				$vCard = $converter->createCardFromUser($user);
279
-				if ($vCard !== null) {
280
-					$this->backend->createCard($addressBookId, $cardId, $vCard->serialize());
281
-				}
282
-			} else {
283
-				$vCard = $converter->createCardFromUser($user);
284
-				if (is_null($vCard)) {
285
-					$this->backend->deleteCard($addressBookId, $cardId);
286
-				} else {
287
-					$this->backend->updateCard($addressBookId, $cardId, $vCard->serialize());
288
-				}
289
-			}
290
-		} else {
291
-			$this->backend->deleteCard($addressBookId, $cardId);
292
-		}
293
-	}
294
-
295
-	/**
296
-	 * @param IUser|string $userOrCardId
297
-	 */
298
-	public function deleteUser($userOrCardId) {
299
-		$systemAddressBook = $this->getLocalSystemAddressBook();
300
-		if ($userOrCardId instanceof IUser){
301
-			$name = $userOrCardId->getBackendClassName();
302
-			$userId = $userOrCardId->getUID();
303
-
304
-			$userOrCardId = "$name:$userId.vcf";
305
-		}
306
-		$this->backend->deleteCard($systemAddressBook['id'], $userOrCardId);
307
-	}
308
-
309
-	/**
310
-	 * @return array|null
311
-	 */
312
-	public function getLocalSystemAddressBook() {
313
-		if (is_null($this->localSystemAddressBook)) {
314
-			$systemPrincipal = "principals/system/system";
315
-			$this->localSystemAddressBook = $this->ensureSystemAddressBookExists($systemPrincipal, 'system', [
316
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance'
317
-			]);
318
-		}
319
-
320
-		return $this->localSystemAddressBook;
321
-	}
322
-
323
-	public function syncInstance(\Closure $progressCallback = null) {
324
-		$systemAddressBook = $this->getLocalSystemAddressBook();
325
-		$this->userManager->callForSeenUsers(function ($user) use ($systemAddressBook, $progressCallback) {
326
-			$this->updateUser($user);
327
-			if (!is_null($progressCallback)) {
328
-				$progressCallback();
329
-			}
330
-		});
331
-
332
-		// remove no longer existing
333
-		$allCards = $this->backend->getCards($systemAddressBook['id']);
334
-		foreach($allCards as $card) {
335
-			$vCard = Reader::read($card['carddata']);
336
-			$uid = $vCard->UID->getValue();
337
-			// load backend and see if user exists
338
-			if (!$this->userManager->userExists($uid)) {
339
-				$this->deleteUser($card['uri']);
340
-			}
341
-		}
342
-	}
45
+    /** @var CardDavBackend */
46
+    private $backend;
47
+
48
+    /** @var IUserManager */
49
+    private $userManager;
50
+
51
+    /** @var ILogger */
52
+    private $logger;
53
+
54
+    /** @var array */
55
+    private $localSystemAddressBook;
56
+
57
+    /** @var AccountManager */
58
+    private $accountManager;
59
+
60
+    /** @var string */
61
+    protected $certPath;
62
+
63
+    /**
64
+     * SyncService constructor.
65
+     *
66
+     * @param CardDavBackend $backend
67
+     * @param IUserManager $userManager
68
+     * @param ILogger $logger
69
+     * @param AccountManager $accountManager
70
+     */
71
+    public function __construct(CardDavBackend $backend, IUserManager $userManager, ILogger $logger, AccountManager $accountManager) {
72
+        $this->backend = $backend;
73
+        $this->userManager = $userManager;
74
+        $this->logger = $logger;
75
+        $this->accountManager = $accountManager;
76
+        $this->certPath = '';
77
+    }
78
+
79
+    /**
80
+     * @param string $url
81
+     * @param string $userName
82
+     * @param string $addressBookUrl
83
+     * @param string $sharedSecret
84
+     * @param string $syncToken
85
+     * @param int $targetBookId
86
+     * @param string $targetPrincipal
87
+     * @param array $targetProperties
88
+     * @return string
89
+     * @throws \Exception
90
+     */
91
+    public function syncRemoteAddressBook($url, $userName, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetProperties) {
92
+        // 1. create addressbook
93
+        $book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookId, $targetProperties);
94
+        $addressBookId = $book['id'];
95
+
96
+        // 2. query changes
97
+        try {
98
+            $response = $this->requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken);
99
+        } catch (ClientHttpException $ex) {
100
+            if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
101
+                // remote server revoked access to the address book, remove it
102
+                $this->backend->deleteAddressBook($addressBookId);
103
+                $this->logger->info('Authorization failed, remove address book: ' . $url, ['app' => 'dav']);
104
+                throw $ex;
105
+            }
106
+        }
107
+
108
+        // 3. apply changes
109
+        // TODO: use multi-get for download
110
+        foreach ($response['response'] as $resource => $status) {
111
+            $cardUri = basename($resource);
112
+            if (isset($status[200])) {
113
+                $vCard = $this->download($url, $userName, $sharedSecret, $resource);
114
+                $existingCard = $this->backend->getCard($addressBookId, $cardUri);
115
+                if ($existingCard === false) {
116
+                    $this->backend->createCard($addressBookId, $cardUri, $vCard['body']);
117
+                } else {
118
+                    $this->backend->updateCard($addressBookId, $cardUri, $vCard['body']);
119
+                }
120
+            } else {
121
+                $this->backend->deleteCard($addressBookId, $cardUri);
122
+            }
123
+        }
124
+
125
+        return $response['token'];
126
+    }
127
+
128
+    /**
129
+     * @param string $principal
130
+     * @param string $id
131
+     * @param array $properties
132
+     * @return array|null
133
+     * @throws \Sabre\DAV\Exception\BadRequest
134
+     */
135
+    public function ensureSystemAddressBookExists($principal, $id, $properties) {
136
+        $book = $this->backend->getAddressBooksByUri($principal, $id);
137
+        if (!is_null($book)) {
138
+            return $book;
139
+        }
140
+        $this->backend->createAddressBook($principal, $id, $properties);
141
+
142
+        return $this->backend->getAddressBooksByUri($principal, $id);
143
+    }
144
+
145
+    /**
146
+     * Check if there is a valid certPath we should use
147
+     *
148
+     * @return string
149
+     */
150
+    protected function getCertPath() {
151
+
152
+        // we already have a valid certPath
153
+        if ($this->certPath !== '') {
154
+            return $this->certPath;
155
+        }
156
+
157
+        /** @var ICertificateManager $certManager */
158
+        $certManager = \OC::$server->getCertificateManager(null);
159
+        $certPath = $certManager->getAbsoluteBundlePath();
160
+        if (file_exists($certPath)) {
161
+            $this->certPath = $certPath;
162
+        }
163
+
164
+        return $this->certPath;
165
+    }
166
+
167
+    /**
168
+     * @param string $url
169
+     * @param string $userName
170
+     * @param string $addressBookUrl
171
+     * @param string $sharedSecret
172
+     * @return Client
173
+     */
174
+    protected function getClient($url, $userName, $sharedSecret) {
175
+        $settings = [
176
+            'baseUri' => $url . '/',
177
+            'userName' => $userName,
178
+            'password' => $sharedSecret,
179
+        ];
180
+        $client = new Client($settings);
181
+        $certPath = $this->getCertPath();
182
+        $client->setThrowExceptions(true);
183
+
184
+        if ($certPath !== '' && strpos($url, 'http://') !== 0) {
185
+            $client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
186
+        }
187
+
188
+        return $client;
189
+    }
190
+
191
+    /**
192
+     * @param string $url
193
+     * @param string $userName
194
+     * @param string $addressBookUrl
195
+     * @param string $sharedSecret
196
+     * @param string $syncToken
197
+     * @return array
198
+     */
199
+        protected function requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken) {
200
+            $client = $this->getClient($url, $userName, $sharedSecret);
201
+
202
+            $body = $this->buildSyncCollectionRequestBody($syncToken);
203
+
204
+            $response = $client->request('REPORT', $addressBookUrl, $body, [
205
+                'Content-Type' => 'application/xml'
206
+            ]);
207
+
208
+            return $this->parseMultiStatus($response['body']);
209
+        }
210
+
211
+    /**
212
+     * @param string $url
213
+     * @param string $userName
214
+     * @param string $sharedSecret
215
+     * @param string $resourcePath
216
+     * @return array
217
+     */
218
+    protected function download($url, $userName, $sharedSecret, $resourcePath) {
219
+        $client = $this->getClient($url, $userName, $sharedSecret);
220
+        return $client->request('GET', $resourcePath);
221
+    }
222
+
223
+    /**
224
+     * @param string|null $syncToken
225
+     * @return string
226
+     */
227
+    private function buildSyncCollectionRequestBody($syncToken) {
228
+
229
+        $dom = new \DOMDocument('1.0', 'UTF-8');
230
+        $dom->formatOutput = true;
231
+        $root = $dom->createElementNS('DAV:', 'd:sync-collection');
232
+        $sync = $dom->createElement('d:sync-token', $syncToken);
233
+        $prop = $dom->createElement('d:prop');
234
+        $cont = $dom->createElement('d:getcontenttype');
235
+        $etag = $dom->createElement('d:getetag');
236
+
237
+        $prop->appendChild($cont);
238
+        $prop->appendChild($etag);
239
+        $root->appendChild($sync);
240
+        $root->appendChild($prop);
241
+        $dom->appendChild($root);
242
+        return $dom->saveXML();
243
+    }
244
+
245
+    /**
246
+     * @param string $body
247
+     * @return array
248
+     * @throws \Sabre\Xml\ParseException
249
+     */
250
+    private function parseMultiStatus($body) {
251
+        $xml = new Service();
252
+
253
+        /** @var MultiStatus $multiStatus */
254
+        $multiStatus = $xml->expect('{DAV:}multistatus', $body);
255
+
256
+        $result = [];
257
+        foreach ($multiStatus->getResponses() as $response) {
258
+            $result[$response->getHref()] = $response->getResponseProperties();
259
+        }
260
+
261
+        return ['response' => $result, 'token' => $multiStatus->getSyncToken()];
262
+    }
263
+
264
+    /**
265
+     * @param IUser $user
266
+     */
267
+    public function updateUser(IUser $user) {
268
+        $systemAddressBook = $this->getLocalSystemAddressBook();
269
+        $addressBookId = $systemAddressBook['id'];
270
+        $converter = new Converter($this->accountManager);
271
+        $name = $user->getBackendClassName();
272
+        $userId = $user->getUID();
273
+
274
+        $cardId = "$name:$userId.vcf";
275
+        $card = $this->backend->getCard($addressBookId, $cardId);
276
+        if ($user->isEnabled()) {
277
+            if ($card === false) {
278
+                $vCard = $converter->createCardFromUser($user);
279
+                if ($vCard !== null) {
280
+                    $this->backend->createCard($addressBookId, $cardId, $vCard->serialize());
281
+                }
282
+            } else {
283
+                $vCard = $converter->createCardFromUser($user);
284
+                if (is_null($vCard)) {
285
+                    $this->backend->deleteCard($addressBookId, $cardId);
286
+                } else {
287
+                    $this->backend->updateCard($addressBookId, $cardId, $vCard->serialize());
288
+                }
289
+            }
290
+        } else {
291
+            $this->backend->deleteCard($addressBookId, $cardId);
292
+        }
293
+    }
294
+
295
+    /**
296
+     * @param IUser|string $userOrCardId
297
+     */
298
+    public function deleteUser($userOrCardId) {
299
+        $systemAddressBook = $this->getLocalSystemAddressBook();
300
+        if ($userOrCardId instanceof IUser){
301
+            $name = $userOrCardId->getBackendClassName();
302
+            $userId = $userOrCardId->getUID();
303
+
304
+            $userOrCardId = "$name:$userId.vcf";
305
+        }
306
+        $this->backend->deleteCard($systemAddressBook['id'], $userOrCardId);
307
+    }
308
+
309
+    /**
310
+     * @return array|null
311
+     */
312
+    public function getLocalSystemAddressBook() {
313
+        if (is_null($this->localSystemAddressBook)) {
314
+            $systemPrincipal = "principals/system/system";
315
+            $this->localSystemAddressBook = $this->ensureSystemAddressBookExists($systemPrincipal, 'system', [
316
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance'
317
+            ]);
318
+        }
319
+
320
+        return $this->localSystemAddressBook;
321
+    }
322
+
323
+    public function syncInstance(\Closure $progressCallback = null) {
324
+        $systemAddressBook = $this->getLocalSystemAddressBook();
325
+        $this->userManager->callForSeenUsers(function ($user) use ($systemAddressBook, $progressCallback) {
326
+            $this->updateUser($user);
327
+            if (!is_null($progressCallback)) {
328
+                $progressCallback();
329
+            }
330
+        });
331
+
332
+        // remove no longer existing
333
+        $allCards = $this->backend->getCards($systemAddressBook['id']);
334
+        foreach($allCards as $card) {
335
+            $vCard = Reader::read($card['carddata']);
336
+            $uid = $vCard->UID->getValue();
337
+            // load backend and see if user exists
338
+            if (!$this->userManager->userExists($uid)) {
339
+                $this->deleteUser($card['uri']);
340
+            }
341
+        }
342
+    }
343 343
 
344 344
 
345 345
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -100,7 +100,7 @@  discard block
 block discarded – undo
100 100
 			if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
101 101
 				// remote server revoked access to the address book, remove it
102 102
 				$this->backend->deleteAddressBook($addressBookId);
103
-				$this->logger->info('Authorization failed, remove address book: ' . $url, ['app' => 'dav']);
103
+				$this->logger->info('Authorization failed, remove address book: '.$url, ['app' => 'dav']);
104 104
 				throw $ex;
105 105
 			}
106 106
 		}
@@ -173,7 +173,7 @@  discard block
 block discarded – undo
173 173
 	 */
174 174
 	protected function getClient($url, $userName, $sharedSecret) {
175 175
 		$settings = [
176
-			'baseUri' => $url . '/',
176
+			'baseUri' => $url.'/',
177 177
 			'userName' => $userName,
178 178
 			'password' => $sharedSecret,
179 179
 		];
@@ -297,7 +297,7 @@  discard block
 block discarded – undo
297 297
 	 */
298 298
 	public function deleteUser($userOrCardId) {
299 299
 		$systemAddressBook = $this->getLocalSystemAddressBook();
300
-		if ($userOrCardId instanceof IUser){
300
+		if ($userOrCardId instanceof IUser) {
301 301
 			$name = $userOrCardId->getBackendClassName();
302 302
 			$userId = $userOrCardId->getUID();
303 303
 
@@ -313,7 +313,7 @@  discard block
 block discarded – undo
313 313
 		if (is_null($this->localSystemAddressBook)) {
314 314
 			$systemPrincipal = "principals/system/system";
315 315
 			$this->localSystemAddressBook = $this->ensureSystemAddressBookExists($systemPrincipal, 'system', [
316
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance'
316
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => 'System addressbook which holds all users of this instance'
317 317
 			]);
318 318
 		}
319 319
 
@@ -322,7 +322,7 @@  discard block
 block discarded – undo
322 322
 
323 323
 	public function syncInstance(\Closure $progressCallback = null) {
324 324
 		$systemAddressBook = $this->getLocalSystemAddressBook();
325
-		$this->userManager->callForSeenUsers(function ($user) use ($systemAddressBook, $progressCallback) {
325
+		$this->userManager->callForSeenUsers(function($user) use ($systemAddressBook, $progressCallback) {
326 326
 			$this->updateUser($user);
327 327
 			if (!is_null($progressCallback)) {
328 328
 				$progressCallback();
@@ -331,7 +331,7 @@  discard block
 block discarded – undo
331 331
 
332 332
 		// remove no longer existing
333 333
 		$allCards = $this->backend->getCards($systemAddressBook['id']);
334
-		foreach($allCards as $card) {
334
+		foreach ($allCards as $card) {
335 335
 			$vCard = Reader::read($card['carddata']);
336 336
 			$uid = $vCard->UID->getValue();
337 337
 			// load backend and see if user exists
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/AddressBook.php 2 patches
Indentation   +189 added lines, -189 removed lines patch added patch discarded remove patch
@@ -42,193 +42,193 @@
 block discarded – undo
42 42
  */
43 43
 class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
44 44
 
45
-	/**
46
-	 * AddressBook constructor.
47
-	 *
48
-	 * @param BackendInterface $carddavBackend
49
-	 * @param array $addressBookInfo
50
-	 * @param IL10N $l10n
51
-	 */
52
-	public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n) {
53
-		parent::__construct($carddavBackend, $addressBookInfo);
54
-
55
-		if ($this->addressBookInfo['{DAV:}displayname'] === CardDavBackend::PERSONAL_ADDRESSBOOK_NAME &&
56
-			$this->getName() === CardDavBackend::PERSONAL_ADDRESSBOOK_URI) {
57
-			$this->addressBookInfo['{DAV:}displayname'] = $l10n->t('Contacts');
58
-		}
59
-	}
60
-
61
-	/**
62
-	 * Updates the list of shares.
63
-	 *
64
-	 * The first array is a list of people that are to be added to the
65
-	 * addressbook.
66
-	 *
67
-	 * Every element in the add array has the following properties:
68
-	 *   * href - A url. Usually a mailto: address
69
-	 *   * commonName - Usually a first and last name, or false
70
-	 *   * summary - A description of the share, can also be false
71
-	 *   * readOnly - A boolean value
72
-	 *
73
-	 * Every element in the remove array is just the address string.
74
-	 *
75
-	 * @param array $add
76
-	 * @param array $remove
77
-	 * @return void
78
-	 * @throws Forbidden
79
-	 */
80
-	public function updateShares(array $add, array $remove) {
81
-		if ($this->isShared()) {
82
-			throw new Forbidden();
83
-		}
84
-		$this->carddavBackend->updateShares($this, $add, $remove);
85
-	}
86
-
87
-	/**
88
-	 * Returns the list of people whom this addressbook is shared with.
89
-	 *
90
-	 * Every element in this array should have the following properties:
91
-	 *   * href - Often a mailto: address
92
-	 *   * commonName - Optional, for example a first + last name
93
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
94
-	 *   * readOnly - boolean
95
-	 *   * summary - Optional, a description for the share
96
-	 *
97
-	 * @return array
98
-	 */
99
-	public function getShares() {
100
-		if ($this->isShared()) {
101
-			return [];
102
-		}
103
-		return $this->carddavBackend->getShares($this->getResourceId());
104
-	}
105
-
106
-	public function getACL() {
107
-		$acl =  [
108
-			[
109
-				'privilege' => '{DAV:}read',
110
-				'principal' => $this->getOwner(),
111
-				'protected' => true,
112
-			],[
113
-				'privilege' => '{DAV:}write',
114
-				'principal' => $this->getOwner(),
115
-				'protected' => true,
116
-			]
117
-		];
118
-
119
-		if ($this->getOwner() === 'principals/system/system') {
120
-			$acl[] = [
121
-				'privilege' => '{DAV:}read',
122
-				'principal' => '{DAV:}authenticated',
123
-				'protected' => true,
124
-			];
125
-		}
126
-
127
-		if (!$this->isShared()) {
128
-			return $acl;
129
-		}
130
-
131
-		if ($this->getOwner() !== parent::getOwner()) {
132
-			$acl[] =  [
133
-				'privilege' => '{DAV:}read',
134
-				'principal' => parent::getOwner(),
135
-				'protected' => true,
136
-			];
137
-			if ($this->canWrite()) {
138
-				$acl[] = [
139
-					'privilege' => '{DAV:}write',
140
-					'principal' => parent::getOwner(),
141
-					'protected' => true,
142
-				];
143
-			}
144
-		}
145
-
146
-		$acl = $this->carddavBackend->applyShareAcl($this->getResourceId(), $acl);
147
-		$allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/system'];
148
-		return array_filter($acl, function ($rule) use ($allowedPrincipals) {
149
-			return \in_array($rule['principal'], $allowedPrincipals, true);
150
-		});
151
-	}
152
-
153
-	public function getChildACL() {
154
-		return $this->getACL();
155
-	}
156
-
157
-	public function getChild($name) {
158
-
159
-		$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
160
-		if (!$obj) {
161
-			throw new NotFound('Card not found');
162
-		}
163
-		$obj['acl'] = $this->getChildACL();
164
-		return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
165
-
166
-	}
167
-
168
-	/**
169
-	 * @return int
170
-	 */
171
-	public function getResourceId() {
172
-		return $this->addressBookInfo['id'];
173
-	}
174
-
175
-	public function getOwner() {
176
-		if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
177
-			return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
178
-		}
179
-		return parent::getOwner();
180
-	}
181
-
182
-	public function delete() {
183
-		if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
184
-			$principal = 'principal:' . parent::getOwner();
185
-			$shares = $this->carddavBackend->getShares($this->getResourceId());
186
-			$shares = array_filter($shares, function ($share) use ($principal) {
187
-				return $share['href'] === $principal;
188
-			});
189
-			if (empty($shares)) {
190
-				throw new Forbidden();
191
-			}
192
-
193
-			$this->carddavBackend->updateShares($this, [], [
194
-				$principal
195
-			]);
196
-			return;
197
-		}
198
-		parent::delete();
199
-	}
200
-
201
-	public function propPatch(PropPatch $propPatch) {
202
-		if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
203
-			throw new Forbidden();
204
-		}
205
-		parent::propPatch($propPatch);
206
-	}
207
-
208
-	public function getContactsGroups() {
209
-		return $this->carddavBackend->collectCardProperties($this->getResourceId(), 'CATEGORIES');
210
-	}
211
-
212
-	private function isShared() {
213
-		if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
214
-			return false;
215
-		}
216
-
217
-		return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'] !== $this->addressBookInfo['principaluri'];
218
-	}
219
-
220
-	private function canWrite() {
221
-		if (isset($this->addressBookInfo['{http://owncloud.org/ns}read-only'])) {
222
-			return !$this->addressBookInfo['{http://owncloud.org/ns}read-only'];
223
-		}
224
-		return true;
225
-	}
226
-
227
-	public function getChanges($syncToken, $syncLevel, $limit = null) {
228
-		if (!$syncToken && $limit) {
229
-			throw new UnsupportedLimitOnInitialSyncException();
230
-		}
231
-
232
-		return parent::getChanges($syncToken, $syncLevel, $limit);
233
-	}
45
+    /**
46
+     * AddressBook constructor.
47
+     *
48
+     * @param BackendInterface $carddavBackend
49
+     * @param array $addressBookInfo
50
+     * @param IL10N $l10n
51
+     */
52
+    public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n) {
53
+        parent::__construct($carddavBackend, $addressBookInfo);
54
+
55
+        if ($this->addressBookInfo['{DAV:}displayname'] === CardDavBackend::PERSONAL_ADDRESSBOOK_NAME &&
56
+            $this->getName() === CardDavBackend::PERSONAL_ADDRESSBOOK_URI) {
57
+            $this->addressBookInfo['{DAV:}displayname'] = $l10n->t('Contacts');
58
+        }
59
+    }
60
+
61
+    /**
62
+     * Updates the list of shares.
63
+     *
64
+     * The first array is a list of people that are to be added to the
65
+     * addressbook.
66
+     *
67
+     * Every element in the add array has the following properties:
68
+     *   * href - A url. Usually a mailto: address
69
+     *   * commonName - Usually a first and last name, or false
70
+     *   * summary - A description of the share, can also be false
71
+     *   * readOnly - A boolean value
72
+     *
73
+     * Every element in the remove array is just the address string.
74
+     *
75
+     * @param array $add
76
+     * @param array $remove
77
+     * @return void
78
+     * @throws Forbidden
79
+     */
80
+    public function updateShares(array $add, array $remove) {
81
+        if ($this->isShared()) {
82
+            throw new Forbidden();
83
+        }
84
+        $this->carddavBackend->updateShares($this, $add, $remove);
85
+    }
86
+
87
+    /**
88
+     * Returns the list of people whom this addressbook is shared with.
89
+     *
90
+     * Every element in this array should have the following properties:
91
+     *   * href - Often a mailto: address
92
+     *   * commonName - Optional, for example a first + last name
93
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
94
+     *   * readOnly - boolean
95
+     *   * summary - Optional, a description for the share
96
+     *
97
+     * @return array
98
+     */
99
+    public function getShares() {
100
+        if ($this->isShared()) {
101
+            return [];
102
+        }
103
+        return $this->carddavBackend->getShares($this->getResourceId());
104
+    }
105
+
106
+    public function getACL() {
107
+        $acl =  [
108
+            [
109
+                'privilege' => '{DAV:}read',
110
+                'principal' => $this->getOwner(),
111
+                'protected' => true,
112
+            ],[
113
+                'privilege' => '{DAV:}write',
114
+                'principal' => $this->getOwner(),
115
+                'protected' => true,
116
+            ]
117
+        ];
118
+
119
+        if ($this->getOwner() === 'principals/system/system') {
120
+            $acl[] = [
121
+                'privilege' => '{DAV:}read',
122
+                'principal' => '{DAV:}authenticated',
123
+                'protected' => true,
124
+            ];
125
+        }
126
+
127
+        if (!$this->isShared()) {
128
+            return $acl;
129
+        }
130
+
131
+        if ($this->getOwner() !== parent::getOwner()) {
132
+            $acl[] =  [
133
+                'privilege' => '{DAV:}read',
134
+                'principal' => parent::getOwner(),
135
+                'protected' => true,
136
+            ];
137
+            if ($this->canWrite()) {
138
+                $acl[] = [
139
+                    'privilege' => '{DAV:}write',
140
+                    'principal' => parent::getOwner(),
141
+                    'protected' => true,
142
+                ];
143
+            }
144
+        }
145
+
146
+        $acl = $this->carddavBackend->applyShareAcl($this->getResourceId(), $acl);
147
+        $allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/system'];
148
+        return array_filter($acl, function ($rule) use ($allowedPrincipals) {
149
+            return \in_array($rule['principal'], $allowedPrincipals, true);
150
+        });
151
+    }
152
+
153
+    public function getChildACL() {
154
+        return $this->getACL();
155
+    }
156
+
157
+    public function getChild($name) {
158
+
159
+        $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
160
+        if (!$obj) {
161
+            throw new NotFound('Card not found');
162
+        }
163
+        $obj['acl'] = $this->getChildACL();
164
+        return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
165
+
166
+    }
167
+
168
+    /**
169
+     * @return int
170
+     */
171
+    public function getResourceId() {
172
+        return $this->addressBookInfo['id'];
173
+    }
174
+
175
+    public function getOwner() {
176
+        if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
177
+            return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
178
+        }
179
+        return parent::getOwner();
180
+    }
181
+
182
+    public function delete() {
183
+        if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
184
+            $principal = 'principal:' . parent::getOwner();
185
+            $shares = $this->carddavBackend->getShares($this->getResourceId());
186
+            $shares = array_filter($shares, function ($share) use ($principal) {
187
+                return $share['href'] === $principal;
188
+            });
189
+            if (empty($shares)) {
190
+                throw new Forbidden();
191
+            }
192
+
193
+            $this->carddavBackend->updateShares($this, [], [
194
+                $principal
195
+            ]);
196
+            return;
197
+        }
198
+        parent::delete();
199
+    }
200
+
201
+    public function propPatch(PropPatch $propPatch) {
202
+        if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
203
+            throw new Forbidden();
204
+        }
205
+        parent::propPatch($propPatch);
206
+    }
207
+
208
+    public function getContactsGroups() {
209
+        return $this->carddavBackend->collectCardProperties($this->getResourceId(), 'CATEGORIES');
210
+    }
211
+
212
+    private function isShared() {
213
+        if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
214
+            return false;
215
+        }
216
+
217
+        return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'] !== $this->addressBookInfo['principaluri'];
218
+    }
219
+
220
+    private function canWrite() {
221
+        if (isset($this->addressBookInfo['{http://owncloud.org/ns}read-only'])) {
222
+            return !$this->addressBookInfo['{http://owncloud.org/ns}read-only'];
223
+        }
224
+        return true;
225
+    }
226
+
227
+    public function getChanges($syncToken, $syncLevel, $limit = null) {
228
+        if (!$syncToken && $limit) {
229
+            throw new UnsupportedLimitOnInitialSyncException();
230
+        }
231
+
232
+        return parent::getChanges($syncToken, $syncLevel, $limit);
233
+    }
234 234
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -104,12 +104,12 @@  discard block
 block discarded – undo
104 104
 	}
105 105
 
106 106
 	public function getACL() {
107
-		$acl =  [
107
+		$acl = [
108 108
 			[
109 109
 				'privilege' => '{DAV:}read',
110 110
 				'principal' => $this->getOwner(),
111 111
 				'protected' => true,
112
-			],[
112
+			], [
113 113
 				'privilege' => '{DAV:}write',
114 114
 				'principal' => $this->getOwner(),
115 115
 				'protected' => true,
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
 		}
130 130
 
131 131
 		if ($this->getOwner() !== parent::getOwner()) {
132
-			$acl[] =  [
132
+			$acl[] = [
133 133
 				'privilege' => '{DAV:}read',
134 134
 				'principal' => parent::getOwner(),
135 135
 				'protected' => true,
@@ -145,7 +145,7 @@  discard block
 block discarded – undo
145 145
 
146 146
 		$acl = $this->carddavBackend->applyShareAcl($this->getResourceId(), $acl);
147 147
 		$allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/system'];
148
-		return array_filter($acl, function ($rule) use ($allowedPrincipals) {
148
+		return array_filter($acl, function($rule) use ($allowedPrincipals) {
149 149
 			return \in_array($rule['principal'], $allowedPrincipals, true);
150 150
 		});
151 151
 	}
@@ -181,9 +181,9 @@  discard block
 block discarded – undo
181 181
 
182 182
 	public function delete() {
183 183
 		if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
184
-			$principal = 'principal:' . parent::getOwner();
184
+			$principal = 'principal:'.parent::getOwner();
185 185
 			$shares = $this->carddavBackend->getShares($this->getResourceId());
186
-			$shares = array_filter($shares, function ($share) use ($principal) {
186
+			$shares = array_filter($shares, function($share) use ($principal) {
187 187
 				return $share['href'] === $principal;
188 188
 			});
189 189
 			if (empty($shares)) {
Please login to merge, or discard this patch.
apps/dav/lib/SystemTag/SystemTagPlugin.php 2 patches
Indentation   +276 added lines, -276 removed lines patch added patch discarded remove patch
@@ -49,280 +49,280 @@
 block discarded – undo
49 49
  */
50 50
 class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
51 51
 
52
-	// namespace
53
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
54
-	const ID_PROPERTYNAME = '{http://owncloud.org/ns}id';
55
-	const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name';
56
-	const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible';
57
-	const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
58
-	const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
59
-	const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
60
-
61
-	/**
62
-	 * @var \Sabre\DAV\Server $server
63
-	 */
64
-	private $server;
65
-
66
-	/**
67
-	 * @var ISystemTagManager
68
-	 */
69
-	protected $tagManager;
70
-
71
-	/**
72
-	 * @var IUserSession
73
-	 */
74
-	protected $userSession;
75
-
76
-	/**
77
-	 * @var IGroupManager
78
-	 */
79
-	protected $groupManager;
80
-
81
-	/**
82
-	 * @param ISystemTagManager $tagManager tag manager
83
-	 * @param IGroupManager $groupManager
84
-	 * @param IUserSession $userSession
85
-	 */
86
-	public function __construct(ISystemTagManager $tagManager,
87
-								IGroupManager $groupManager,
88
-								IUserSession $userSession) {
89
-		$this->tagManager = $tagManager;
90
-		$this->userSession = $userSession;
91
-		$this->groupManager = $groupManager;
92
-	}
93
-
94
-	/**
95
-	 * This initializes the plugin.
96
-	 *
97
-	 * This function is called by \Sabre\DAV\Server, after
98
-	 * addPlugin is called.
99
-	 *
100
-	 * This method should set up the required event subscriptions.
101
-	 *
102
-	 * @param \Sabre\DAV\Server $server
103
-	 * @return void
104
-	 */
105
-	public function initialize(\Sabre\DAV\Server $server) {
106
-
107
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
108
-
109
-		$server->protectedProperties[] = self::ID_PROPERTYNAME;
110
-
111
-		$server->on('propFind', [$this, 'handleGetProperties']);
112
-		$server->on('propPatch', [$this, 'handleUpdateProperties']);
113
-		$server->on('method:POST', [$this, 'httpPost']);
114
-
115
-		$this->server = $server;
116
-	}
117
-
118
-	/**
119
-	 * POST operation on system tag collections
120
-	 *
121
-	 * @param RequestInterface $request request object
122
-	 * @param ResponseInterface $response response object
123
-	 * @return null|false
124
-	 */
125
-	public function httpPost(RequestInterface $request, ResponseInterface $response) {
126
-		$path = $request->getPath();
127
-
128
-		// Making sure the node exists
129
-		$node = $this->server->tree->getNodeForPath($path);
130
-		if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) {
131
-			$data = $request->getBodyAsString();
132
-
133
-			$tag = $this->createTag($data, $request->getHeader('Content-Type'));
134
-
135
-			if ($node instanceof SystemTagsObjectMappingCollection) {
136
-				// also add to collection
137
-				$node->createFile($tag->getId());
138
-				$url = $request->getBaseUrl() . 'systemtags/';
139
-			} else {
140
-				$url = $request->getUrl();
141
-			}
142
-
143
-			if ($url[strlen($url) - 1] !== '/') {
144
-				$url .= '/';
145
-			}
146
-
147
-			$response->setHeader('Content-Location', $url . $tag->getId());
148
-
149
-			// created
150
-			$response->setStatus(201);
151
-			return false;
152
-		}
153
-	}
154
-
155
-	/**
156
-	 * Creates a new tag
157
-	 *
158
-	 * @param string $data JSON encoded string containing the properties of the tag to create
159
-	 * @param string $contentType content type of the data
160
-	 * @return ISystemTag newly created system tag
161
-	 *
162
-	 * @throws BadRequest if a field was missing
163
-	 * @throws Conflict if a tag with the same properties already exists
164
-	 * @throws UnsupportedMediaType if the content type is not supported
165
-	 */
166
-	private function createTag($data, $contentType = 'application/json') {
167
-		if (explode(';', $contentType)[0] === 'application/json') {
168
-			$data = json_decode($data, true);
169
-		} else {
170
-			throw new UnsupportedMediaType();
171
-		}
172
-
173
-		if (!isset($data['name'])) {
174
-			throw new BadRequest('Missing "name" attribute');
175
-		}
176
-
177
-		$tagName = $data['name'];
178
-		$userVisible = true;
179
-		$userAssignable = true;
180
-
181
-		if (isset($data['userVisible'])) {
182
-			$userVisible = (bool)$data['userVisible'];
183
-		}
184
-
185
-		if (isset($data['userAssignable'])) {
186
-			$userAssignable = (bool)$data['userAssignable'];
187
-		}
188
-
189
-		$groups = [];
190
-		if (isset($data['groups'])) {
191
-			$groups = $data['groups'];
192
-			if (is_string($groups)) {
193
-				$groups = explode('|', $groups);
194
-			}
195
-		}
196
-
197
-		if($userVisible === false || $userAssignable === false || !empty($groups)) {
198
-			if(!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
199
-				throw new BadRequest('Not sufficient permissions');
200
-			}
201
-		}
202
-
203
-		try {
204
-			$tag = $this->tagManager->createTag($tagName, $userVisible, $userAssignable);
205
-			if (!empty($groups)) {
206
-				$this->tagManager->setTagGroups($tag, $groups);
207
-			}
208
-			return $tag;
209
-		} catch (TagAlreadyExistsException $e) {
210
-			throw new Conflict('Tag already exists', 0, $e);
211
-		}
212
-	}
213
-
214
-
215
-	/**
216
-	 * Retrieves system tag properties
217
-	 *
218
-	 * @param PropFind $propFind
219
-	 * @param \Sabre\DAV\INode $node
220
-	 */
221
-	public function handleGetProperties(
222
-		PropFind $propFind,
223
-		\Sabre\DAV\INode $node
224
-	) {
225
-		if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
226
-			return;
227
-		}
228
-
229
-		$propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
230
-			return $node->getSystemTag()->getId();
231
-		});
232
-
233
-		$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
234
-			return $node->getSystemTag()->getName();
235
-		});
236
-
237
-		$propFind->handle(self::USERVISIBLE_PROPERTYNAME, function () use ($node) {
238
-			return $node->getSystemTag()->isUserVisible() ? 'true' : 'false';
239
-		});
240
-
241
-		$propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function () use ($node) {
242
-			// this is the tag's inherent property "is user assignable"
243
-			return $node->getSystemTag()->isUserAssignable() ? 'true' : 'false';
244
-		});
245
-
246
-		$propFind->handle(self::CANASSIGN_PROPERTYNAME, function () use ($node) {
247
-			// this is the effective permission for the current user
248
-			return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false';
249
-		});
250
-
251
-		$propFind->handle(self::GROUPS_PROPERTYNAME, function () use ($node) {
252
-			if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
253
-				// property only available for admins
254
-				throw new Forbidden();
255
-			}
256
-			$groups = [];
257
-			// no need to retrieve groups for namespaces that don't qualify
258
-			if ($node->getSystemTag()->isUserVisible() && !$node->getSystemTag()->isUserAssignable()) {
259
-				$groups = $this->tagManager->getTagGroups($node->getSystemTag());
260
-			}
261
-			return implode('|', $groups);
262
-		});
263
-	}
264
-
265
-	/**
266
-	 * Updates tag attributes
267
-	 *
268
-	 * @param string $path
269
-	 * @param PropPatch $propPatch
270
-	 *
271
-	 * @return void
272
-	 */
273
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
274
-		$node = $this->server->tree->getNodeForPath($path);
275
-		if (!($node instanceof SystemTagNode)) {
276
-			return;
277
-		}
278
-
279
-		$propPatch->handle([
280
-			self::DISPLAYNAME_PROPERTYNAME,
281
-			self::USERVISIBLE_PROPERTYNAME,
282
-			self::USERASSIGNABLE_PROPERTYNAME,
283
-			self::GROUPS_PROPERTYNAME,
284
-		], function ($props) use ($node) {
285
-			$tag = $node->getSystemTag();
286
-			$name = $tag->getName();
287
-			$userVisible = $tag->isUserVisible();
288
-			$userAssignable = $tag->isUserAssignable();
289
-
290
-			$updateTag = false;
291
-
292
-			if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) {
293
-				$name = $props[self::DISPLAYNAME_PROPERTYNAME];
294
-				$updateTag = true;
295
-			}
296
-
297
-			if (isset($props[self::USERVISIBLE_PROPERTYNAME])) {
298
-				$propValue = $props[self::USERVISIBLE_PROPERTYNAME];
299
-				$userVisible = ($propValue !== 'false' && $propValue !== '0');
300
-				$updateTag = true;
301
-			}
302
-
303
-			if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) {
304
-				$propValue = $props[self::USERASSIGNABLE_PROPERTYNAME];
305
-				$userAssignable = ($propValue !== 'false' && $propValue !== '0');
306
-				$updateTag = true;
307
-			}
308
-
309
-			if (isset($props[self::GROUPS_PROPERTYNAME])) {
310
-				if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
311
-					// property only available for admins
312
-					throw new Forbidden();
313
-				}
314
-
315
-				$propValue = $props[self::GROUPS_PROPERTYNAME];
316
-				$groupIds = explode('|', $propValue);
317
-				$this->tagManager->setTagGroups($tag, $groupIds);
318
-			}
319
-
320
-			if ($updateTag) {
321
-				$node->update($name, $userVisible, $userAssignable);
322
-			}
323
-
324
-			return true;
325
-		});
326
-
327
-	}
52
+    // namespace
53
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
54
+    const ID_PROPERTYNAME = '{http://owncloud.org/ns}id';
55
+    const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name';
56
+    const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible';
57
+    const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
58
+    const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
59
+    const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
60
+
61
+    /**
62
+     * @var \Sabre\DAV\Server $server
63
+     */
64
+    private $server;
65
+
66
+    /**
67
+     * @var ISystemTagManager
68
+     */
69
+    protected $tagManager;
70
+
71
+    /**
72
+     * @var IUserSession
73
+     */
74
+    protected $userSession;
75
+
76
+    /**
77
+     * @var IGroupManager
78
+     */
79
+    protected $groupManager;
80
+
81
+    /**
82
+     * @param ISystemTagManager $tagManager tag manager
83
+     * @param IGroupManager $groupManager
84
+     * @param IUserSession $userSession
85
+     */
86
+    public function __construct(ISystemTagManager $tagManager,
87
+                                IGroupManager $groupManager,
88
+                                IUserSession $userSession) {
89
+        $this->tagManager = $tagManager;
90
+        $this->userSession = $userSession;
91
+        $this->groupManager = $groupManager;
92
+    }
93
+
94
+    /**
95
+     * This initializes the plugin.
96
+     *
97
+     * This function is called by \Sabre\DAV\Server, after
98
+     * addPlugin is called.
99
+     *
100
+     * This method should set up the required event subscriptions.
101
+     *
102
+     * @param \Sabre\DAV\Server $server
103
+     * @return void
104
+     */
105
+    public function initialize(\Sabre\DAV\Server $server) {
106
+
107
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
108
+
109
+        $server->protectedProperties[] = self::ID_PROPERTYNAME;
110
+
111
+        $server->on('propFind', [$this, 'handleGetProperties']);
112
+        $server->on('propPatch', [$this, 'handleUpdateProperties']);
113
+        $server->on('method:POST', [$this, 'httpPost']);
114
+
115
+        $this->server = $server;
116
+    }
117
+
118
+    /**
119
+     * POST operation on system tag collections
120
+     *
121
+     * @param RequestInterface $request request object
122
+     * @param ResponseInterface $response response object
123
+     * @return null|false
124
+     */
125
+    public function httpPost(RequestInterface $request, ResponseInterface $response) {
126
+        $path = $request->getPath();
127
+
128
+        // Making sure the node exists
129
+        $node = $this->server->tree->getNodeForPath($path);
130
+        if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) {
131
+            $data = $request->getBodyAsString();
132
+
133
+            $tag = $this->createTag($data, $request->getHeader('Content-Type'));
134
+
135
+            if ($node instanceof SystemTagsObjectMappingCollection) {
136
+                // also add to collection
137
+                $node->createFile($tag->getId());
138
+                $url = $request->getBaseUrl() . 'systemtags/';
139
+            } else {
140
+                $url = $request->getUrl();
141
+            }
142
+
143
+            if ($url[strlen($url) - 1] !== '/') {
144
+                $url .= '/';
145
+            }
146
+
147
+            $response->setHeader('Content-Location', $url . $tag->getId());
148
+
149
+            // created
150
+            $response->setStatus(201);
151
+            return false;
152
+        }
153
+    }
154
+
155
+    /**
156
+     * Creates a new tag
157
+     *
158
+     * @param string $data JSON encoded string containing the properties of the tag to create
159
+     * @param string $contentType content type of the data
160
+     * @return ISystemTag newly created system tag
161
+     *
162
+     * @throws BadRequest if a field was missing
163
+     * @throws Conflict if a tag with the same properties already exists
164
+     * @throws UnsupportedMediaType if the content type is not supported
165
+     */
166
+    private function createTag($data, $contentType = 'application/json') {
167
+        if (explode(';', $contentType)[0] === 'application/json') {
168
+            $data = json_decode($data, true);
169
+        } else {
170
+            throw new UnsupportedMediaType();
171
+        }
172
+
173
+        if (!isset($data['name'])) {
174
+            throw new BadRequest('Missing "name" attribute');
175
+        }
176
+
177
+        $tagName = $data['name'];
178
+        $userVisible = true;
179
+        $userAssignable = true;
180
+
181
+        if (isset($data['userVisible'])) {
182
+            $userVisible = (bool)$data['userVisible'];
183
+        }
184
+
185
+        if (isset($data['userAssignable'])) {
186
+            $userAssignable = (bool)$data['userAssignable'];
187
+        }
188
+
189
+        $groups = [];
190
+        if (isset($data['groups'])) {
191
+            $groups = $data['groups'];
192
+            if (is_string($groups)) {
193
+                $groups = explode('|', $groups);
194
+            }
195
+        }
196
+
197
+        if($userVisible === false || $userAssignable === false || !empty($groups)) {
198
+            if(!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
199
+                throw new BadRequest('Not sufficient permissions');
200
+            }
201
+        }
202
+
203
+        try {
204
+            $tag = $this->tagManager->createTag($tagName, $userVisible, $userAssignable);
205
+            if (!empty($groups)) {
206
+                $this->tagManager->setTagGroups($tag, $groups);
207
+            }
208
+            return $tag;
209
+        } catch (TagAlreadyExistsException $e) {
210
+            throw new Conflict('Tag already exists', 0, $e);
211
+        }
212
+    }
213
+
214
+
215
+    /**
216
+     * Retrieves system tag properties
217
+     *
218
+     * @param PropFind $propFind
219
+     * @param \Sabre\DAV\INode $node
220
+     */
221
+    public function handleGetProperties(
222
+        PropFind $propFind,
223
+        \Sabre\DAV\INode $node
224
+    ) {
225
+        if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
226
+            return;
227
+        }
228
+
229
+        $propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
230
+            return $node->getSystemTag()->getId();
231
+        });
232
+
233
+        $propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
234
+            return $node->getSystemTag()->getName();
235
+        });
236
+
237
+        $propFind->handle(self::USERVISIBLE_PROPERTYNAME, function () use ($node) {
238
+            return $node->getSystemTag()->isUserVisible() ? 'true' : 'false';
239
+        });
240
+
241
+        $propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function () use ($node) {
242
+            // this is the tag's inherent property "is user assignable"
243
+            return $node->getSystemTag()->isUserAssignable() ? 'true' : 'false';
244
+        });
245
+
246
+        $propFind->handle(self::CANASSIGN_PROPERTYNAME, function () use ($node) {
247
+            // this is the effective permission for the current user
248
+            return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false';
249
+        });
250
+
251
+        $propFind->handle(self::GROUPS_PROPERTYNAME, function () use ($node) {
252
+            if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
253
+                // property only available for admins
254
+                throw new Forbidden();
255
+            }
256
+            $groups = [];
257
+            // no need to retrieve groups for namespaces that don't qualify
258
+            if ($node->getSystemTag()->isUserVisible() && !$node->getSystemTag()->isUserAssignable()) {
259
+                $groups = $this->tagManager->getTagGroups($node->getSystemTag());
260
+            }
261
+            return implode('|', $groups);
262
+        });
263
+    }
264
+
265
+    /**
266
+     * Updates tag attributes
267
+     *
268
+     * @param string $path
269
+     * @param PropPatch $propPatch
270
+     *
271
+     * @return void
272
+     */
273
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
274
+        $node = $this->server->tree->getNodeForPath($path);
275
+        if (!($node instanceof SystemTagNode)) {
276
+            return;
277
+        }
278
+
279
+        $propPatch->handle([
280
+            self::DISPLAYNAME_PROPERTYNAME,
281
+            self::USERVISIBLE_PROPERTYNAME,
282
+            self::USERASSIGNABLE_PROPERTYNAME,
283
+            self::GROUPS_PROPERTYNAME,
284
+        ], function ($props) use ($node) {
285
+            $tag = $node->getSystemTag();
286
+            $name = $tag->getName();
287
+            $userVisible = $tag->isUserVisible();
288
+            $userAssignable = $tag->isUserAssignable();
289
+
290
+            $updateTag = false;
291
+
292
+            if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) {
293
+                $name = $props[self::DISPLAYNAME_PROPERTYNAME];
294
+                $updateTag = true;
295
+            }
296
+
297
+            if (isset($props[self::USERVISIBLE_PROPERTYNAME])) {
298
+                $propValue = $props[self::USERVISIBLE_PROPERTYNAME];
299
+                $userVisible = ($propValue !== 'false' && $propValue !== '0');
300
+                $updateTag = true;
301
+            }
302
+
303
+            if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) {
304
+                $propValue = $props[self::USERASSIGNABLE_PROPERTYNAME];
305
+                $userAssignable = ($propValue !== 'false' && $propValue !== '0');
306
+                $updateTag = true;
307
+            }
308
+
309
+            if (isset($props[self::GROUPS_PROPERTYNAME])) {
310
+                if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
311
+                    // property only available for admins
312
+                    throw new Forbidden();
313
+                }
314
+
315
+                $propValue = $props[self::GROUPS_PROPERTYNAME];
316
+                $groupIds = explode('|', $propValue);
317
+                $this->tagManager->setTagGroups($tag, $groupIds);
318
+            }
319
+
320
+            if ($updateTag) {
321
+                $node->update($name, $userVisible, $userAssignable);
322
+            }
323
+
324
+            return true;
325
+        });
326
+
327
+    }
328 328
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -135,7 +135,7 @@  discard block
 block discarded – undo
135 135
 			if ($node instanceof SystemTagsObjectMappingCollection) {
136 136
 				// also add to collection
137 137
 				$node->createFile($tag->getId());
138
-				$url = $request->getBaseUrl() . 'systemtags/';
138
+				$url = $request->getBaseUrl().'systemtags/';
139 139
 			} else {
140 140
 				$url = $request->getUrl();
141 141
 			}
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
 				$url .= '/';
145 145
 			}
146 146
 
147
-			$response->setHeader('Content-Location', $url . $tag->getId());
147
+			$response->setHeader('Content-Location', $url.$tag->getId());
148 148
 
149 149
 			// created
150 150
 			$response->setStatus(201);
@@ -179,11 +179,11 @@  discard block
 block discarded – undo
179 179
 		$userAssignable = true;
180 180
 
181 181
 		if (isset($data['userVisible'])) {
182
-			$userVisible = (bool)$data['userVisible'];
182
+			$userVisible = (bool) $data['userVisible'];
183 183
 		}
184 184
 
185 185
 		if (isset($data['userAssignable'])) {
186
-			$userAssignable = (bool)$data['userAssignable'];
186
+			$userAssignable = (bool) $data['userAssignable'];
187 187
 		}
188 188
 
189 189
 		$groups = [];
@@ -194,8 +194,8 @@  discard block
 block discarded – undo
194 194
 			}
195 195
 		}
196 196
 
197
-		if($userVisible === false || $userAssignable === false || !empty($groups)) {
198
-			if(!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
197
+		if ($userVisible === false || $userAssignable === false || !empty($groups)) {
198
+			if (!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
199 199
 				throw new BadRequest('Not sufficient permissions');
200 200
 			}
201 201
 		}
@@ -226,29 +226,29 @@  discard block
 block discarded – undo
226 226
 			return;
227 227
 		}
228 228
 
229
-		$propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
229
+		$propFind->handle(self::ID_PROPERTYNAME, function() use ($node) {
230 230
 			return $node->getSystemTag()->getId();
231 231
 		});
232 232
 
233
-		$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
233
+		$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function() use ($node) {
234 234
 			return $node->getSystemTag()->getName();
235 235
 		});
236 236
 
237
-		$propFind->handle(self::USERVISIBLE_PROPERTYNAME, function () use ($node) {
237
+		$propFind->handle(self::USERVISIBLE_PROPERTYNAME, function() use ($node) {
238 238
 			return $node->getSystemTag()->isUserVisible() ? 'true' : 'false';
239 239
 		});
240 240
 
241
-		$propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function () use ($node) {
241
+		$propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function() use ($node) {
242 242
 			// this is the tag's inherent property "is user assignable"
243 243
 			return $node->getSystemTag()->isUserAssignable() ? 'true' : 'false';
244 244
 		});
245 245
 
246
-		$propFind->handle(self::CANASSIGN_PROPERTYNAME, function () use ($node) {
246
+		$propFind->handle(self::CANASSIGN_PROPERTYNAME, function() use ($node) {
247 247
 			// this is the effective permission for the current user
248 248
 			return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false';
249 249
 		});
250 250
 
251
-		$propFind->handle(self::GROUPS_PROPERTYNAME, function () use ($node) {
251
+		$propFind->handle(self::GROUPS_PROPERTYNAME, function() use ($node) {
252 252
 			if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
253 253
 				// property only available for admins
254 254
 				throw new Forbidden();
@@ -281,7 +281,7 @@  discard block
 block discarded – undo
281 281
 			self::USERVISIBLE_PROPERTYNAME,
282 282
 			self::USERASSIGNABLE_PROPERTYNAME,
283 283
 			self::GROUPS_PROPERTYNAME,
284
-		], function ($props) use ($node) {
284
+		], function($props) use ($node) {
285 285
 			$tag = $node->getSystemTag();
286 286
 			$name = $tag->getName();
287 287
 			$userVisible = $tag->isUserVisible();
Please login to merge, or discard this patch.
apps/dav/lib/SystemTag/SystemTagsRelationsCollection.php 2 patches
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -37,59 +37,59 @@
 block discarded – undo
37 37
 
38 38
 class SystemTagsRelationsCollection extends SimpleCollection {
39 39
 
40
-	/**
41
-	 * SystemTagsRelationsCollection constructor.
42
-	 *
43
-	 * @param ISystemTagManager $tagManager
44
-	 * @param ISystemTagObjectMapper $tagMapper
45
-	 * @param IUserSession $userSession
46
-	 * @param IGroupManager $groupManager
47
-	 * @param EventDispatcherInterface $dispatcher
48
-	 */
49
-	public function __construct(
50
-		ISystemTagManager $tagManager,
51
-		ISystemTagObjectMapper $tagMapper,
52
-		IUserSession $userSession,
53
-		IGroupManager $groupManager,
54
-		EventDispatcherInterface $dispatcher
55
-	) {
56
-		$children = [
57
-			new SystemTagsObjectTypeCollection(
58
-				'files',
59
-				$tagManager,
60
-				$tagMapper,
61
-				$userSession,
62
-				$groupManager,
63
-				function ($name) {
64
-					$nodes = \OC::$server->getUserFolder()->getById((int)$name);
65
-					return !empty($nodes);
66
-				}
67
-			),
68
-		];
40
+    /**
41
+     * SystemTagsRelationsCollection constructor.
42
+     *
43
+     * @param ISystemTagManager $tagManager
44
+     * @param ISystemTagObjectMapper $tagMapper
45
+     * @param IUserSession $userSession
46
+     * @param IGroupManager $groupManager
47
+     * @param EventDispatcherInterface $dispatcher
48
+     */
49
+    public function __construct(
50
+        ISystemTagManager $tagManager,
51
+        ISystemTagObjectMapper $tagMapper,
52
+        IUserSession $userSession,
53
+        IGroupManager $groupManager,
54
+        EventDispatcherInterface $dispatcher
55
+    ) {
56
+        $children = [
57
+            new SystemTagsObjectTypeCollection(
58
+                'files',
59
+                $tagManager,
60
+                $tagMapper,
61
+                $userSession,
62
+                $groupManager,
63
+                function ($name) {
64
+                    $nodes = \OC::$server->getUserFolder()->getById((int)$name);
65
+                    return !empty($nodes);
66
+                }
67
+            ),
68
+        ];
69 69
 
70
-		$event = new SystemTagsEntityEvent(SystemTagsEntityEvent::EVENT_ENTITY);
71
-		$dispatcher->dispatch(SystemTagsEntityEvent::EVENT_ENTITY, $event);
70
+        $event = new SystemTagsEntityEvent(SystemTagsEntityEvent::EVENT_ENTITY);
71
+        $dispatcher->dispatch(SystemTagsEntityEvent::EVENT_ENTITY, $event);
72 72
 
73
-		foreach ($event->getEntityCollections() as $entity => $entityExistsFunction) {
74
-			$children[] = new SystemTagsObjectTypeCollection(
75
-				$entity,
76
-				$tagManager,
77
-				$tagMapper,
78
-				$userSession,
79
-				$groupManager,
80
-				$entityExistsFunction
81
-			);
82
-		}
73
+        foreach ($event->getEntityCollections() as $entity => $entityExistsFunction) {
74
+            $children[] = new SystemTagsObjectTypeCollection(
75
+                $entity,
76
+                $tagManager,
77
+                $tagMapper,
78
+                $userSession,
79
+                $groupManager,
80
+                $entityExistsFunction
81
+            );
82
+        }
83 83
 
84
-		parent::__construct('root', $children);
85
-	}
84
+        parent::__construct('root', $children);
85
+    }
86 86
 
87
-	function getName() {
88
-		return 'systemtags-relations';
89
-	}
87
+    function getName() {
88
+        return 'systemtags-relations';
89
+    }
90 90
 
91
-	function setName($name) {
92
-		throw new Forbidden('Permission denied to rename this collection');
93
-	}
91
+    function setName($name) {
92
+        throw new Forbidden('Permission denied to rename this collection');
93
+    }
94 94
 
95 95
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -60,8 +60,8 @@
 block discarded – undo
60 60
 				$tagMapper,
61 61
 				$userSession,
62 62
 				$groupManager,
63
-				function ($name) {
64
-					$nodes = \OC::$server->getUserFolder()->getById((int)$name);
63
+				function($name) {
64
+					$nodes = \OC::$server->getUserFolder()->getById((int) $name);
65 65
 					return !empty($nodes);
66 66
 				}
67 67
 			),
Please login to merge, or discard this patch.
apps/dav/lib/Controller/BirthdayCalendarController.php 2 patches
Indentation   +65 added lines, -65 removed lines patch added patch discarded remove patch
@@ -38,80 +38,80 @@
 block discarded – undo
38 38
 
39 39
 class BirthdayCalendarController extends Controller {
40 40
 
41
-	/**
42
-	 * @var IDBConnection
43
-	 */
44
-	protected $db;
41
+    /**
42
+     * @var IDBConnection
43
+     */
44
+    protected $db;
45 45
 
46
-	/**
47
-	 * @var IConfig
48
-	 */
49
-	protected $config;
46
+    /**
47
+     * @var IConfig
48
+     */
49
+    protected $config;
50 50
 
51
-	/**
52
-	 * @var IUserManager
53
-	 */
54
-	protected $userManager;
51
+    /**
52
+     * @var IUserManager
53
+     */
54
+    protected $userManager;
55 55
 
56
-	/**
57
-	 * @var CalDavBackend
58
-	 */
59
-	protected $caldavBackend;
56
+    /**
57
+     * @var CalDavBackend
58
+     */
59
+    protected $caldavBackend;
60 60
 
61
-	/**
62
-	 * @var IJobList
63
-	 */
64
-	protected $jobList;
61
+    /**
62
+     * @var IJobList
63
+     */
64
+    protected $jobList;
65 65
 
66
-	/**
67
-	 * BirthdayCalendar constructor.
68
-	 *
69
-	 * @param string $appName
70
-	 * @param IRequest $request
71
-	 * @param IDBConnection $db
72
-	 * @param IConfig $config
73
-	 * @param IJobList $jobList
74
-	 * @param IUserManager $userManager
75
-	 * @param CalDavBackend $calDavBackend
76
-	 */
77
-	public function __construct($appName, IRequest $request,
78
-								IDBConnection $db, IConfig $config,
79
-								IJobList $jobList,
80
-								IUserManager $userManager,
81
-								CalDavBackend $calDavBackend) {
82
-		parent::__construct($appName, $request);
83
-		$this->db = $db;
84
-		$this->config = $config;
85
-		$this->userManager = $userManager;
86
-		$this->jobList = $jobList;
87
-		$this->caldavBackend = $calDavBackend;
88
-	}
66
+    /**
67
+     * BirthdayCalendar constructor.
68
+     *
69
+     * @param string $appName
70
+     * @param IRequest $request
71
+     * @param IDBConnection $db
72
+     * @param IConfig $config
73
+     * @param IJobList $jobList
74
+     * @param IUserManager $userManager
75
+     * @param CalDavBackend $calDavBackend
76
+     */
77
+    public function __construct($appName, IRequest $request,
78
+                                IDBConnection $db, IConfig $config,
79
+                                IJobList $jobList,
80
+                                IUserManager $userManager,
81
+                                CalDavBackend $calDavBackend) {
82
+        parent::__construct($appName, $request);
83
+        $this->db = $db;
84
+        $this->config = $config;
85
+        $this->userManager = $userManager;
86
+        $this->jobList = $jobList;
87
+        $this->caldavBackend = $calDavBackend;
88
+    }
89 89
 
90
-	/**
91
-	 * @return Response
92
-	 */
93
-	public function enable() {
94
-		$this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'yes');
90
+    /**
91
+     * @return Response
92
+     */
93
+    public function enable() {
94
+        $this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'yes');
95 95
 
96
-		// add background job for each user
97
-		$this->userManager->callForSeenUsers(function (IUser $user) {
98
-			$this->jobList->add(GenerateBirthdayCalendarBackgroundJob::class, [
99
-				'userId' => $user->getUID(),
100
-			]);
101
-		});
96
+        // add background job for each user
97
+        $this->userManager->callForSeenUsers(function (IUser $user) {
98
+            $this->jobList->add(GenerateBirthdayCalendarBackgroundJob::class, [
99
+                'userId' => $user->getUID(),
100
+            ]);
101
+        });
102 102
 
103
-		return new JSONResponse([]);
104
-	}
103
+        return new JSONResponse([]);
104
+    }
105 105
 
106
-	/**
107
-	 * @return Response
108
-	 */
109
-	public function disable() {
110
-		$this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'no');
106
+    /**
107
+     * @return Response
108
+     */
109
+    public function disable() {
110
+        $this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'no');
111 111
 
112
-		$this->jobList->remove(GenerateBirthdayCalendarBackgroundJob::class);
113
-		$this->caldavBackend->deleteAllBirthdayCalendars();
112
+        $this->jobList->remove(GenerateBirthdayCalendarBackgroundJob::class);
113
+        $this->caldavBackend->deleteAllBirthdayCalendars();
114 114
 
115
-		return new JSONResponse([]);
116
-	}
115
+        return new JSONResponse([]);
116
+    }
117 117
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -94,7 +94,7 @@
 block discarded – undo
94 94
 		$this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'yes');
95 95
 
96 96
 		// add background job for each user
97
-		$this->userManager->callForSeenUsers(function (IUser $user) {
97
+		$this->userManager->callForSeenUsers(function(IUser $user) {
98 98
 			$this->jobList->add(GenerateBirthdayCalendarBackgroundJob::class, [
99 99
 				'userId' => $user->getUID(),
100 100
 			]);
Please login to merge, or discard this patch.