Completed
Push — master ( 1fd7ff...09d5b6 )
by Morris
35:50 queued 16:04
created
apps/dav/lib/Migration/Version1006Date20180619154313.php 1 patch
Indentation   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -11,61 +11,61 @@
 block discarded – undo
11 11
  */
12 12
 class Version1006Date20180619154313 extends SimpleMigrationStep {
13 13
 
14
-	/**
15
-	 * @param IOutput $output
16
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
17
-	 * @param array $options
18
-	 * @return null|ISchemaWrapper
19
-	 * @since 13.0.0
20
-	 */
21
-	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
22
-		/** @var ISchemaWrapper $schema */
23
-		$schema = $schemaClosure();
14
+    /**
15
+     * @param IOutput $output
16
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
17
+     * @param array $options
18
+     * @return null|ISchemaWrapper
19
+     * @since 13.0.0
20
+     */
21
+    public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
22
+        /** @var ISchemaWrapper $schema */
23
+        $schema = $schemaClosure();
24 24
 
25
-		if (!$schema->hasTable('calendar_invitations')) {
26
-			$table = $schema->createTable('calendar_invitations');
25
+        if (!$schema->hasTable('calendar_invitations')) {
26
+            $table = $schema->createTable('calendar_invitations');
27 27
 
28
-			$table->addColumn('id', Type::BIGINT, [
29
-				'autoincrement' => true,
30
-				'notnull' => true,
31
-				'length' => 11,
32
-				'unsigned' => true,
33
-			]);
34
-			$table->addColumn('uid', Type::STRING, [
35
-				'notnull' => true,
36
-				'length' => 255,
37
-			]);
38
-			$table->addColumn('recurrenceid', Type::STRING, [
39
-				'notnull' => false,
40
-				'length' => 255,
41
-			]);
42
-			$table->addColumn('attendee', Type::STRING, [
43
-				'notnull' => true,
44
-				'length' => 255,
45
-			]);
46
-			$table->addColumn('organizer', Type::STRING, [
47
-				'notnull' => true,
48
-				'length' => 255,
49
-			]);
50
-			$table->addColumn('sequence', Type::BIGINT, [
51
-				'notnull' => false,
52
-				'length' => 11,
53
-				'unsigned' => true,
54
-			]);
55
-			$table->addColumn('token', Type::STRING, [
56
-				'notnull' => true,
57
-				'length' => 60,
58
-			]);
59
-			$table->addColumn('expiration', Type::BIGINT, [
60
-				'notnull' => true,
61
-				'length' => 11,
62
-				'unsigned' => true,
63
-			]);
28
+            $table->addColumn('id', Type::BIGINT, [
29
+                'autoincrement' => true,
30
+                'notnull' => true,
31
+                'length' => 11,
32
+                'unsigned' => true,
33
+            ]);
34
+            $table->addColumn('uid', Type::STRING, [
35
+                'notnull' => true,
36
+                'length' => 255,
37
+            ]);
38
+            $table->addColumn('recurrenceid', Type::STRING, [
39
+                'notnull' => false,
40
+                'length' => 255,
41
+            ]);
42
+            $table->addColumn('attendee', Type::STRING, [
43
+                'notnull' => true,
44
+                'length' => 255,
45
+            ]);
46
+            $table->addColumn('organizer', Type::STRING, [
47
+                'notnull' => true,
48
+                'length' => 255,
49
+            ]);
50
+            $table->addColumn('sequence', Type::BIGINT, [
51
+                'notnull' => false,
52
+                'length' => 11,
53
+                'unsigned' => true,
54
+            ]);
55
+            $table->addColumn('token', Type::STRING, [
56
+                'notnull' => true,
57
+                'length' => 60,
58
+            ]);
59
+            $table->addColumn('expiration', Type::BIGINT, [
60
+                'notnull' => true,
61
+                'length' => 11,
62
+                'unsigned' => true,
63
+            ]);
64 64
 
65
-			$table->setPrimaryKey(['id']);
66
-			$table->addIndex(['token'], 'calendar_invitation_tokens');
65
+            $table->setPrimaryKey(['id']);
66
+            $table->addIndex(['token'], 'calendar_invitation_tokens');
67 67
 
68
-			return $schema;
69
-		}
70
-	}
68
+            return $schema;
69
+        }
70
+    }
71 71
 }
Please login to merge, or discard this patch.
apps/dav/lib/Migration/Version1005Date20180530124431.php 1 patch
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -29,56 +29,56 @@
 block discarded – undo
29 29
 
30 30
 class Version1005Date20180530124431 extends SimpleMigrationStep {
31 31
 
32
-	/**
33
-	 * @param IOutput $output
34
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
35
-	 * @param array $options
36
-	 * @return null|ISchemaWrapper
37
-	 * @since 13.0.0
38
-	 */
39
-	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
40
-		/** @var ISchemaWrapper $schema */
41
-		$schema = $schemaClosure();
32
+    /**
33
+     * @param IOutput $output
34
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
35
+     * @param array $options
36
+     * @return null|ISchemaWrapper
37
+     * @since 13.0.0
38
+     */
39
+    public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
40
+        /** @var ISchemaWrapper $schema */
41
+        $schema = $schemaClosure();
42 42
 
43
-		$types = ['resources', 'rooms'];
44
-		foreach($types as $type) {
45
-			if (!$schema->hasTable('calendar_' . $type)) {
46
-				$table = $schema->createTable('calendar_' . $type);
43
+        $types = ['resources', 'rooms'];
44
+        foreach($types as $type) {
45
+            if (!$schema->hasTable('calendar_' . $type)) {
46
+                $table = $schema->createTable('calendar_' . $type);
47 47
 
48
-				$table->addColumn('id', Type::BIGINT, [
49
-					'autoincrement' => true,
50
-					'notnull' => true,
51
-					'length' => 11,
52
-					'unsigned' => true,
53
-				]);
54
-				$table->addColumn('backend_id', Type::STRING, [
55
-					'notnull' => false,
56
-					'length' => 64,
57
-				]);
58
-				$table->addColumn('resource_id', Type::STRING, [
59
-					'notnull' => false,
60
-					'length' => 64,
61
-				]);
62
-				$table->addColumn('email', Type::STRING, [
63
-					'notnull' => false,
64
-					'length' => 255,
65
-				]);
66
-				$table->addColumn('displayname', Type::STRING, [
67
-					'notnull' => false,
68
-					'length' => 255,
69
-				]);
70
-				$table->addColumn('group_restrictions', Type::STRING, [
71
-					'notnull' => false,
72
-					'length' => 4000,
73
-				]);
48
+                $table->addColumn('id', Type::BIGINT, [
49
+                    'autoincrement' => true,
50
+                    'notnull' => true,
51
+                    'length' => 11,
52
+                    'unsigned' => true,
53
+                ]);
54
+                $table->addColumn('backend_id', Type::STRING, [
55
+                    'notnull' => false,
56
+                    'length' => 64,
57
+                ]);
58
+                $table->addColumn('resource_id', Type::STRING, [
59
+                    'notnull' => false,
60
+                    'length' => 64,
61
+                ]);
62
+                $table->addColumn('email', Type::STRING, [
63
+                    'notnull' => false,
64
+                    'length' => 255,
65
+                ]);
66
+                $table->addColumn('displayname', Type::STRING, [
67
+                    'notnull' => false,
68
+                    'length' => 255,
69
+                ]);
70
+                $table->addColumn('group_restrictions', Type::STRING, [
71
+                    'notnull' => false,
72
+                    'length' => 4000,
73
+                ]);
74 74
 
75
-				$table->setPrimaryKey(['id']);
76
-				$table->addIndex(['backend_id', 'resource_id'], 'calendar_' . $type . '_bkdrsc');
77
-				$table->addIndex(['email'], 'calendar_' . $type . '_email');
78
-				$table->addIndex(['displayname'], 'calendar_' . $type . '_name');
79
-			}
80
-		}
75
+                $table->setPrimaryKey(['id']);
76
+                $table->addIndex(['backend_id', 'resource_id'], 'calendar_' . $type . '_bkdrsc');
77
+                $table->addIndex(['email'], 'calendar_' . $type . '_email');
78
+                $table->addIndex(['displayname'], 'calendar_' . $type . '_name');
79
+            }
80
+        }
81 81
 
82
-		return $schema;
83
-	}
82
+        return $schema;
83
+    }
84 84
 }
Please login to merge, or discard this patch.
apps/dav/lib/BackgroundJob/CleanupInvitationTokenJob.php 1 patch
Indentation   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -30,24 +30,24 @@
 block discarded – undo
30 30
 
31 31
 class CleanupInvitationTokenJob extends TimedJob {
32 32
 
33
-	/** @var IDBConnection  */
34
-	private $db;
35
-
36
-	/** @var ITimeFactory */
37
-	private $timeFactory;
38
-
39
-	public function __construct(IDBConnection $db, ITimeFactory $timeFactory) {
40
-		$this->db = $db;
41
-		$this->timeFactory = $timeFactory;
42
-
43
-		$this->setInterval(60 * 60 * 24);
44
-	}
45
-
46
-	public function run($argument) {
47
-		$query = $this->db->getQueryBuilder();
48
-		$query->delete('calendar_invitations')
49
-			->where($query->expr()->lt('expiration',
50
-				$query->createNamedParameter($this->timeFactory->getTime())))
51
-			->execute();
52
-	}
33
+    /** @var IDBConnection  */
34
+    private $db;
35
+
36
+    /** @var ITimeFactory */
37
+    private $timeFactory;
38
+
39
+    public function __construct(IDBConnection $db, ITimeFactory $timeFactory) {
40
+        $this->db = $db;
41
+        $this->timeFactory = $timeFactory;
42
+
43
+        $this->setInterval(60 * 60 * 24);
44
+    }
45
+
46
+    public function run($argument) {
47
+        $query = $this->db->getQueryBuilder();
48
+        $query->delete('calendar_invitations')
49
+            ->where($query->expr()->lt('expiration',
50
+                $query->createNamedParameter($this->timeFactory->getTime())))
51
+            ->execute();
52
+    }
53 53
 }
Please login to merge, or discard this patch.
apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php 1 patch
Indentation   +300 added lines, -300 removed lines patch added patch discarded remove patch
@@ -34,304 +34,304 @@
 block discarded – undo
34 34
 
35 35
 class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
36 36
 
37
-	/** @var IResourceManager */
38
-	private $resourceManager;
39
-
40
-	/** @var IRoomManager */
41
-	private $roomManager;
42
-
43
-	/** @var IDBConnection */
44
-	private $db;
45
-
46
-	/** @var CalDavBackend */
47
-	private $calDavBackend;
48
-
49
-	/** @var string */
50
-	private $resourceDbTable;
51
-
52
-	/** @var string */
53
-	private $resourcePrincipalUri;
54
-
55
-	/** @var string */
56
-	private $roomDbTable;
57
-
58
-	/** @var string */
59
-	private $roomPrincipalUri;
60
-
61
-	/**
62
-	 * UpdateCalendarResourcesRoomsBackgroundJob constructor.
63
-	 *
64
-	 * @param IResourceManager $resourceManager
65
-	 * @param IRoomManager $roomManager
66
-	 * @param IDBConnection $dbConnection
67
-	 * @param CalDavBackend $calDavBackend
68
-	 */
69
-	public function __construct(IResourceManager $resourceManager, IRoomManager $roomManager,
70
-								IDBConnection $dbConnection, CalDavBackend $calDavBackend) {
71
-		$this->resourceManager = $resourceManager;
72
-		$this->roomManager = $roomManager;
73
-		$this->db = $dbConnection;
74
-		$this->calDavBackend = $calDavBackend;
75
-		$this->resourceDbTable = 'calendar_resources';
76
-		$this->resourcePrincipalUri = 'principals/calendar-resources';
77
-		$this->roomDbTable = 'calendar_rooms';
78
-		$this->roomPrincipalUri = 'principals/calendar-rooms';
79
-
80
-		// run once an hour
81
-		$this->setInterval(60 * 60);
82
-	}
83
-
84
-	/**
85
-	 * @param $argument
86
-	 */
87
-	public function run($argument) {
88
-		$this->runResources();
89
-		$this->runRooms();
90
-	}
91
-
92
-	/**
93
-	 * run timed job for resources
94
-	 */
95
-	private function runResources() {
96
-		$resourceBackends = $this->resourceManager->getBackends();
97
-		$cachedResources = $this->getCached($this->resourceDbTable);
98
-		$cachedResourceIds = $this->getCachedResourceIds($cachedResources);
99
-
100
-		$remoteResourceIds = [];
101
-		foreach($resourceBackends as $resourceBackend) {
102
-			try {
103
-				$remoteResourceIds[$resourceBackend->getBackendIdentifier()] =
104
-					$resourceBackend->listAllResources();
105
-			} catch(BackendTemporarilyUnavailableException $ex) {
106
-				// If the backend is temporarily unavailable
107
-				// ignore this backend in this execution
108
-				unset($cachedResourceIds[$resourceBackend->getBackendIdentifier()]);
109
-			}
110
-		}
111
-
112
-		$sortedResources = $this->sortByNewDeletedExisting($cachedResourceIds, $remoteResourceIds);
113
-
114
-		foreach($sortedResources['new'] as $backendId => $newResources) {
115
-			foreach ($newResources as $newResource) {
116
-				$resource = $this->resourceManager->getBackend($backendId)
117
-					->getResource($newResource);
118
-				$this->addToCache($this->resourceDbTable, $resource);
119
-			}
120
-		}
121
-		foreach($sortedResources['deleted'] as $backendId => $deletedResources) {
122
-			foreach ($deletedResources as $deletedResource) {
123
-				$this->deleteFromCache($this->resourceDbTable,
124
-					$this->resourcePrincipalUri, $backendId, $deletedResource);
125
-			}
126
-		}
127
-		foreach($sortedResources['edited'] as $backendId => $editedResources) {
128
-			foreach ($editedResources as $editedResource) {
129
-				$resource = $this->resourceManager->getBackend($backendId)
130
-					->getResource($editedResource);
131
-				$this->updateCache($this->resourceDbTable, $resource);
132
-			}
133
-		}
134
-	}
135
-
136
-	/**
137
-	 * run timed job for rooms
138
-	 */
139
-	private function runRooms() {
140
-		$roomBackends = $this->roomManager->getBackends();
141
-		$cachedRooms = $this->getCached($this->roomDbTable);
142
-		$cachedRoomIds = $this->getCachedRoomIds($cachedRooms);
143
-
144
-		$remoteRoomIds = [];
145
-		foreach($roomBackends as $roomBackend) {
146
-			try {
147
-				$remoteRoomIds[$roomBackend->getBackendIdentifier()] =
148
-					$roomBackend->listAllRooms();
149
-			} catch(BackendTemporarilyUnavailableException $ex) {
150
-				// If the backend is temporarily unavailable
151
-				// ignore this backend in this execution
152
-				unset($cachedRoomIds[$roomBackend->getBackendIdentifier()]);
153
-			}
154
-		}
155
-
156
-		$sortedRooms = $this->sortByNewDeletedExisting($cachedRoomIds, $remoteRoomIds);
157
-
158
-		foreach($sortedRooms['new'] as $backendId => $newRooms) {
159
-			foreach ($newRooms as $newRoom) {
160
-				$resource = $this->roomManager->getBackend($backendId)
161
-					->getRoom($newRoom);
162
-				$this->addToCache($this->roomDbTable, $resource);
163
-			}
164
-		}
165
-		foreach($sortedRooms['deleted'] as $backendId => $deletedRooms) {
166
-			foreach ($deletedRooms as $deletedRoom) {
167
-				$this->deleteFromCache($this->roomDbTable,
168
-					$this->roomPrincipalUri, $backendId, $deletedRoom);
169
-			}
170
-		}
171
-		foreach($sortedRooms['edited'] as $backendId => $editedRooms) {
172
-			foreach ($editedRooms as $editedRoom) {
173
-				$resource = $this->roomManager->getBackend($backendId)
174
-					->getRoom($editedRoom);
175
-				$this->updateCache($this->roomDbTable, $resource);
176
-			}
177
-		}
178
-	}
179
-
180
-	/**
181
-	 * get cached db rows for resources / rooms
182
-	 * @param string $tableName
183
-	 * @return array
184
-	 */
185
-	private function getCached($tableName):array {
186
-		$query = $this->db->getQueryBuilder();
187
-		$query->select('*')->from($tableName);
188
-
189
-		$rows = [];
190
-		$stmt = $query->execute();
191
-		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
192
-			$rows[] = $row;
193
-		}
194
-
195
-		return $rows;
196
-	}
197
-
198
-	/**
199
-	 * @param array $cachedResources
200
-	 * @return array
201
-	 */
202
-	private function getCachedResourceIds(array $cachedResources):array {
203
-		$cachedResourceIds = [];
204
-		foreach ($cachedResources as $cachedResource) {
205
-			if (!isset($cachedResourceIds[$cachedResource['backend_id']])) {
206
-				$cachedResourceIds[$cachedResource['backend_id']] = [];
207
-			}
208
-
209
-			$cachedResourceIds[$cachedResource['backend_id']][] =
210
-				$cachedResource['resource_id'];
211
-		}
212
-
213
-		return $cachedResourceIds;
214
-	}
215
-
216
-	/**
217
-	 * @param array $cachedRooms
218
-	 * @return array
219
-	 */
220
-	private function getCachedRoomIds(array $cachedRooms):array {
221
-		$cachedRoomIds = [];
222
-		foreach ($cachedRooms as $cachedRoom) {
223
-			if (!isset($cachedRoomIds[$cachedRoom['backend_id']])) {
224
-				$cachedRoomIds[$cachedRoom['backend_id']] = [];
225
-			}
226
-
227
-			$cachedRoomIds[$cachedRoom['backend_id']][] =
228
-				$cachedRoom['resource_id'];
229
-		}
230
-
231
-		return $cachedRoomIds;
232
-	}
233
-
234
-	/**
235
-	 * sort list of ids by whether they appear only in the backend /
236
-	 * only in the cache / in both
237
-	 *
238
-	 * @param array $cached
239
-	 * @param array $remote
240
-	 * @return array
241
-	 */
242
-	private function sortByNewDeletedExisting(array $cached, array $remote):array {
243
-		$sorted = [
244
-			'new' => [],
245
-			'deleted' => [],
246
-			'edited' => [],
247
-		];
248
-
249
-		$backendIds = array_merge(array_keys($cached), array_keys($remote));
250
-		foreach($backendIds as $backendId) {
251
-			if (!isset($cached[$backendId])) {
252
-				$sorted['new'][$backendId] = $remote[$backendId];
253
-			} elseif (!isset($remote[$backendId])) {
254
-				$sorted['deleted'][$backendId] = $cached[$backendId];
255
-			} else {
256
-				$sorted['new'][$backendId] = array_diff($remote[$backendId], $cached[$backendId]);
257
-				$sorted['deleted'][$backendId] = array_diff($cached[$backendId], $remote[$backendId]);
258
-				$sorted['edited'][$backendId] = array_intersect($remote[$backendId], $cached[$backendId]);
259
-			}
260
-		}
261
-
262
-		return $sorted;
263
-	}
264
-
265
-	/**
266
-	 * add entry to cache that exists remotely but not yet in cache
267
-	 *
268
-	 * @param string $table
269
-	 * @param IResource|IRoom $remote
270
-	 */
271
-	private function addToCache($table, $remote) {
272
-		$query = $this->db->getQueryBuilder();
273
-		$query->insert($table)
274
-			->values([
275
-				'backend_id' => $query->createNamedParameter($remote->getBackend()->getBackendIdentifier()),
276
-				'resource_id' => $query->createNamedParameter($remote->getId()),
277
-				'email' => $query->createNamedParameter($remote->getEMail()),
278
-				'displayname' => $query->createNamedParameter($remote->getDisplayName()),
279
-				'group_restrictions' => $query->createNamedParameter(
280
-					$this->serializeGroupRestrictions(
281
-						$remote->getGroupRestrictions()
282
-					))
283
-			])
284
-			->execute();
285
-	}
286
-
287
-	/**
288
-	 * delete entry from cache that does not exist anymore remotely
289
-	 *
290
-	 * @param string $table
291
-	 * @param string $principalUri
292
-	 * @param string $backendId
293
-	 * @param string $resourceId
294
-	 */
295
-	private function deleteFromCache($table, $principalUri, $backendId, $resourceId) {
296
-		$query = $this->db->getQueryBuilder();
297
-		$query->delete($table)
298
-			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
299
-			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)))
300
-			->execute();
301
-
302
-		$calendar = $this->calDavBackend->getCalendarByUri($principalUri, implode('-', [$backendId, $resourceId]));
303
-		if ($calendar !== null) {
304
-			$this->calDavBackend->deleteCalendar($calendar['id']);
305
-		}
306
-	}
307
-
308
-	/**
309
-	 * update an existing entry in cache
310
-	 *
311
-	 * @param string $table
312
-	 * @param IResource|IRoom $remote
313
-	 */
314
-	private function updateCache($table, $remote) {
315
-		$query = $this->db->getQueryBuilder();
316
-		$query->update($table)
317
-			->set('email', $query->createNamedParameter($remote->getEMail()))
318
-			->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
319
-			->set('group_restrictions', $query->createNamedParameter(
320
-				$this->serializeGroupRestrictions(
321
-					$remote->getGroupRestrictions()
322
-				)))
323
-			->where($query->expr()->eq('backend_id', $query->createNamedParameter($remote->getBackend()->getBackendIdentifier())))
324
-			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($remote->getId())))
325
-			->execute();
326
-	}
327
-
328
-	/**
329
-	 * serialize array of group restrictions to store them in database
330
-	 *
331
-	 * @param array $groups
332
-	 * @return string
333
-	 */
334
-	private function serializeGroupRestrictions(array $groups):string {
335
-		return \json_encode($groups);
336
-	}
37
+    /** @var IResourceManager */
38
+    private $resourceManager;
39
+
40
+    /** @var IRoomManager */
41
+    private $roomManager;
42
+
43
+    /** @var IDBConnection */
44
+    private $db;
45
+
46
+    /** @var CalDavBackend */
47
+    private $calDavBackend;
48
+
49
+    /** @var string */
50
+    private $resourceDbTable;
51
+
52
+    /** @var string */
53
+    private $resourcePrincipalUri;
54
+
55
+    /** @var string */
56
+    private $roomDbTable;
57
+
58
+    /** @var string */
59
+    private $roomPrincipalUri;
60
+
61
+    /**
62
+     * UpdateCalendarResourcesRoomsBackgroundJob constructor.
63
+     *
64
+     * @param IResourceManager $resourceManager
65
+     * @param IRoomManager $roomManager
66
+     * @param IDBConnection $dbConnection
67
+     * @param CalDavBackend $calDavBackend
68
+     */
69
+    public function __construct(IResourceManager $resourceManager, IRoomManager $roomManager,
70
+                                IDBConnection $dbConnection, CalDavBackend $calDavBackend) {
71
+        $this->resourceManager = $resourceManager;
72
+        $this->roomManager = $roomManager;
73
+        $this->db = $dbConnection;
74
+        $this->calDavBackend = $calDavBackend;
75
+        $this->resourceDbTable = 'calendar_resources';
76
+        $this->resourcePrincipalUri = 'principals/calendar-resources';
77
+        $this->roomDbTable = 'calendar_rooms';
78
+        $this->roomPrincipalUri = 'principals/calendar-rooms';
79
+
80
+        // run once an hour
81
+        $this->setInterval(60 * 60);
82
+    }
83
+
84
+    /**
85
+     * @param $argument
86
+     */
87
+    public function run($argument) {
88
+        $this->runResources();
89
+        $this->runRooms();
90
+    }
91
+
92
+    /**
93
+     * run timed job for resources
94
+     */
95
+    private function runResources() {
96
+        $resourceBackends = $this->resourceManager->getBackends();
97
+        $cachedResources = $this->getCached($this->resourceDbTable);
98
+        $cachedResourceIds = $this->getCachedResourceIds($cachedResources);
99
+
100
+        $remoteResourceIds = [];
101
+        foreach($resourceBackends as $resourceBackend) {
102
+            try {
103
+                $remoteResourceIds[$resourceBackend->getBackendIdentifier()] =
104
+                    $resourceBackend->listAllResources();
105
+            } catch(BackendTemporarilyUnavailableException $ex) {
106
+                // If the backend is temporarily unavailable
107
+                // ignore this backend in this execution
108
+                unset($cachedResourceIds[$resourceBackend->getBackendIdentifier()]);
109
+            }
110
+        }
111
+
112
+        $sortedResources = $this->sortByNewDeletedExisting($cachedResourceIds, $remoteResourceIds);
113
+
114
+        foreach($sortedResources['new'] as $backendId => $newResources) {
115
+            foreach ($newResources as $newResource) {
116
+                $resource = $this->resourceManager->getBackend($backendId)
117
+                    ->getResource($newResource);
118
+                $this->addToCache($this->resourceDbTable, $resource);
119
+            }
120
+        }
121
+        foreach($sortedResources['deleted'] as $backendId => $deletedResources) {
122
+            foreach ($deletedResources as $deletedResource) {
123
+                $this->deleteFromCache($this->resourceDbTable,
124
+                    $this->resourcePrincipalUri, $backendId, $deletedResource);
125
+            }
126
+        }
127
+        foreach($sortedResources['edited'] as $backendId => $editedResources) {
128
+            foreach ($editedResources as $editedResource) {
129
+                $resource = $this->resourceManager->getBackend($backendId)
130
+                    ->getResource($editedResource);
131
+                $this->updateCache($this->resourceDbTable, $resource);
132
+            }
133
+        }
134
+    }
135
+
136
+    /**
137
+     * run timed job for rooms
138
+     */
139
+    private function runRooms() {
140
+        $roomBackends = $this->roomManager->getBackends();
141
+        $cachedRooms = $this->getCached($this->roomDbTable);
142
+        $cachedRoomIds = $this->getCachedRoomIds($cachedRooms);
143
+
144
+        $remoteRoomIds = [];
145
+        foreach($roomBackends as $roomBackend) {
146
+            try {
147
+                $remoteRoomIds[$roomBackend->getBackendIdentifier()] =
148
+                    $roomBackend->listAllRooms();
149
+            } catch(BackendTemporarilyUnavailableException $ex) {
150
+                // If the backend is temporarily unavailable
151
+                // ignore this backend in this execution
152
+                unset($cachedRoomIds[$roomBackend->getBackendIdentifier()]);
153
+            }
154
+        }
155
+
156
+        $sortedRooms = $this->sortByNewDeletedExisting($cachedRoomIds, $remoteRoomIds);
157
+
158
+        foreach($sortedRooms['new'] as $backendId => $newRooms) {
159
+            foreach ($newRooms as $newRoom) {
160
+                $resource = $this->roomManager->getBackend($backendId)
161
+                    ->getRoom($newRoom);
162
+                $this->addToCache($this->roomDbTable, $resource);
163
+            }
164
+        }
165
+        foreach($sortedRooms['deleted'] as $backendId => $deletedRooms) {
166
+            foreach ($deletedRooms as $deletedRoom) {
167
+                $this->deleteFromCache($this->roomDbTable,
168
+                    $this->roomPrincipalUri, $backendId, $deletedRoom);
169
+            }
170
+        }
171
+        foreach($sortedRooms['edited'] as $backendId => $editedRooms) {
172
+            foreach ($editedRooms as $editedRoom) {
173
+                $resource = $this->roomManager->getBackend($backendId)
174
+                    ->getRoom($editedRoom);
175
+                $this->updateCache($this->roomDbTable, $resource);
176
+            }
177
+        }
178
+    }
179
+
180
+    /**
181
+     * get cached db rows for resources / rooms
182
+     * @param string $tableName
183
+     * @return array
184
+     */
185
+    private function getCached($tableName):array {
186
+        $query = $this->db->getQueryBuilder();
187
+        $query->select('*')->from($tableName);
188
+
189
+        $rows = [];
190
+        $stmt = $query->execute();
191
+        while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
192
+            $rows[] = $row;
193
+        }
194
+
195
+        return $rows;
196
+    }
197
+
198
+    /**
199
+     * @param array $cachedResources
200
+     * @return array
201
+     */
202
+    private function getCachedResourceIds(array $cachedResources):array {
203
+        $cachedResourceIds = [];
204
+        foreach ($cachedResources as $cachedResource) {
205
+            if (!isset($cachedResourceIds[$cachedResource['backend_id']])) {
206
+                $cachedResourceIds[$cachedResource['backend_id']] = [];
207
+            }
208
+
209
+            $cachedResourceIds[$cachedResource['backend_id']][] =
210
+                $cachedResource['resource_id'];
211
+        }
212
+
213
+        return $cachedResourceIds;
214
+    }
215
+
216
+    /**
217
+     * @param array $cachedRooms
218
+     * @return array
219
+     */
220
+    private function getCachedRoomIds(array $cachedRooms):array {
221
+        $cachedRoomIds = [];
222
+        foreach ($cachedRooms as $cachedRoom) {
223
+            if (!isset($cachedRoomIds[$cachedRoom['backend_id']])) {
224
+                $cachedRoomIds[$cachedRoom['backend_id']] = [];
225
+            }
226
+
227
+            $cachedRoomIds[$cachedRoom['backend_id']][] =
228
+                $cachedRoom['resource_id'];
229
+        }
230
+
231
+        return $cachedRoomIds;
232
+    }
233
+
234
+    /**
235
+     * sort list of ids by whether they appear only in the backend /
236
+     * only in the cache / in both
237
+     *
238
+     * @param array $cached
239
+     * @param array $remote
240
+     * @return array
241
+     */
242
+    private function sortByNewDeletedExisting(array $cached, array $remote):array {
243
+        $sorted = [
244
+            'new' => [],
245
+            'deleted' => [],
246
+            'edited' => [],
247
+        ];
248
+
249
+        $backendIds = array_merge(array_keys($cached), array_keys($remote));
250
+        foreach($backendIds as $backendId) {
251
+            if (!isset($cached[$backendId])) {
252
+                $sorted['new'][$backendId] = $remote[$backendId];
253
+            } elseif (!isset($remote[$backendId])) {
254
+                $sorted['deleted'][$backendId] = $cached[$backendId];
255
+            } else {
256
+                $sorted['new'][$backendId] = array_diff($remote[$backendId], $cached[$backendId]);
257
+                $sorted['deleted'][$backendId] = array_diff($cached[$backendId], $remote[$backendId]);
258
+                $sorted['edited'][$backendId] = array_intersect($remote[$backendId], $cached[$backendId]);
259
+            }
260
+        }
261
+
262
+        return $sorted;
263
+    }
264
+
265
+    /**
266
+     * add entry to cache that exists remotely but not yet in cache
267
+     *
268
+     * @param string $table
269
+     * @param IResource|IRoom $remote
270
+     */
271
+    private function addToCache($table, $remote) {
272
+        $query = $this->db->getQueryBuilder();
273
+        $query->insert($table)
274
+            ->values([
275
+                'backend_id' => $query->createNamedParameter($remote->getBackend()->getBackendIdentifier()),
276
+                'resource_id' => $query->createNamedParameter($remote->getId()),
277
+                'email' => $query->createNamedParameter($remote->getEMail()),
278
+                'displayname' => $query->createNamedParameter($remote->getDisplayName()),
279
+                'group_restrictions' => $query->createNamedParameter(
280
+                    $this->serializeGroupRestrictions(
281
+                        $remote->getGroupRestrictions()
282
+                    ))
283
+            ])
284
+            ->execute();
285
+    }
286
+
287
+    /**
288
+     * delete entry from cache that does not exist anymore remotely
289
+     *
290
+     * @param string $table
291
+     * @param string $principalUri
292
+     * @param string $backendId
293
+     * @param string $resourceId
294
+     */
295
+    private function deleteFromCache($table, $principalUri, $backendId, $resourceId) {
296
+        $query = $this->db->getQueryBuilder();
297
+        $query->delete($table)
298
+            ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
299
+            ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)))
300
+            ->execute();
301
+
302
+        $calendar = $this->calDavBackend->getCalendarByUri($principalUri, implode('-', [$backendId, $resourceId]));
303
+        if ($calendar !== null) {
304
+            $this->calDavBackend->deleteCalendar($calendar['id']);
305
+        }
306
+    }
307
+
308
+    /**
309
+     * update an existing entry in cache
310
+     *
311
+     * @param string $table
312
+     * @param IResource|IRoom $remote
313
+     */
314
+    private function updateCache($table, $remote) {
315
+        $query = $this->db->getQueryBuilder();
316
+        $query->update($table)
317
+            ->set('email', $query->createNamedParameter($remote->getEMail()))
318
+            ->set('displayname', $query->createNamedParameter($remote->getDisplayName()))
319
+            ->set('group_restrictions', $query->createNamedParameter(
320
+                $this->serializeGroupRestrictions(
321
+                    $remote->getGroupRestrictions()
322
+                )))
323
+            ->where($query->expr()->eq('backend_id', $query->createNamedParameter($remote->getBackend()->getBackendIdentifier())))
324
+            ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($remote->getId())))
325
+            ->execute();
326
+    }
327
+
328
+    /**
329
+     * serialize array of group restrictions to store them in database
330
+     *
331
+     * @param array $groups
332
+     * @return string
333
+     */
334
+    private function serializeGroupRestrictions(array $groups):string {
335
+        return \json_encode($groups);
336
+    }
337 337
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Schedule/IMipPlugin.php 1 patch
Indentation   +489 added lines, -489 removed lines patch added patch discarded remove patch
@@ -60,494 +60,494 @@
 block discarded – undo
60 60
  */
61 61
 class IMipPlugin extends SabreIMipPlugin {
62 62
 
63
-	/** @var string */
64
-	private $userId;
65
-
66
-	/** @var IConfig */
67
-	private $config;
68
-
69
-	/** @var IMailer */
70
-	private $mailer;
71
-
72
-	/** @var ILogger */
73
-	private $logger;
74
-
75
-	/** @var ITimeFactory */
76
-	private $timeFactory;
77
-
78
-	/** @var L10NFactory */
79
-	private $l10nFactory;
80
-
81
-	/** @var IURLGenerator */
82
-	private $urlGenerator;
83
-
84
-	/** @var ISecureRandom */
85
-	private $random;
86
-
87
-	/** @var IDBConnection */
88
-	private $db;
89
-
90
-	/** @var Defaults */
91
-	private $defaults;
92
-
93
-	const MAX_DATE = '2038-01-01';
94
-
95
-	const METHOD_REQUEST = 'request';
96
-	const METHOD_REPLY = 'reply';
97
-	const METHOD_CANCEL = 'cancel';
98
-
99
-	/**
100
-	 * @param IConfig $config
101
-	 * @param IMailer $mailer
102
-	 * @param ILogger $logger
103
-	 * @param ITimeFactory $timeFactory
104
-	 * @param L10NFactory $l10nFactory
105
-	 * @param IUrlGenerator $urlGenerator
106
-	 * @param Defaults $defaults
107
-	 * @param ISecureRandom $random
108
-	 * @param IDBConnection $db
109
-	 * @param string $userId
110
-	 */
111
-	public function __construct(IConfig $config, IMailer $mailer, ILogger $logger,
112
-								ITimeFactory $timeFactory, L10NFactory $l10nFactory,
113
-								IURLGenerator $urlGenerator, Defaults $defaults,
114
-								ISecureRandom $random, IDBConnection $db, $userId) {
115
-		parent::__construct('');
116
-		$this->userId = $userId;
117
-		$this->config = $config;
118
-		$this->mailer = $mailer;
119
-		$this->logger = $logger;
120
-		$this->timeFactory = $timeFactory;
121
-		$this->l10nFactory = $l10nFactory;
122
-		$this->urlGenerator = $urlGenerator;
123
-		$this->random = $random;
124
-		$this->db = $db;
125
-		$this->defaults = $defaults;
126
-	}
127
-
128
-	/**
129
-	 * Event handler for the 'schedule' event.
130
-	 *
131
-	 * @param Message $iTipMessage
132
-	 * @return void
133
-	 */
134
-	public function schedule(Message $iTipMessage) {
135
-
136
-		// Not sending any emails if the system considers the update
137
-		// insignificant.
138
-		if (!$iTipMessage->significantChange) {
139
-			if (!$iTipMessage->scheduleStatus) {
140
-				$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
141
-			}
142
-			return;
143
-		}
144
-
145
-		$summary = $iTipMessage->message->VEVENT->SUMMARY;
146
-
147
-		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') {
148
-			return;
149
-		}
150
-
151
-		if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
152
-			return;
153
-		}
154
-
155
-		// don't send out mails for events that already took place
156
-		$lastOccurrence = $this->getLastOccurrence($iTipMessage->message);
157
-		$currentTime = $this->timeFactory->getTime();
158
-		if ($lastOccurrence < $currentTime) {
159
-			return;
160
-		}
161
-
162
-		// Strip off mailto:
163
-		$sender = substr($iTipMessage->sender, 7);
164
-		$recipient = substr($iTipMessage->recipient, 7);
165
-
166
-		$senderName = $iTipMessage->senderName ?: null;
167
-		$recipientName = $iTipMessage->recipientName ?: null;
168
-
169
-		/** @var VEvent $vevent */
170
-		$vevent = $iTipMessage->message->VEVENT;
171
-
172
-		$attendee = $this->getCurrentAttendee($iTipMessage);
173
-		$defaultLang = $this->config->getUserValue($this->userId, 'core', 'lang', $this->l10nFactory->findLanguage());
174
-		$lang = $this->getAttendeeLangOrDefault($defaultLang, $attendee);
175
-		$l10n = $this->l10nFactory->get('dav', $lang);
176
-
177
-		$meetingAttendeeName = $recipientName ?: $recipient;
178
-		$meetingInviteeName = $senderName ?: $sender;
179
-
180
-		$meetingTitle = $vevent->SUMMARY;
181
-		$meetingDescription = $vevent->DESCRIPTION;
182
-
183
-		$start = $vevent->DTSTART;
184
-		if (isset($vevent->DTEND)) {
185
-			$end = $vevent->DTEND;
186
-		} elseif (isset($vevent->DURATION)) {
187
-			$isFloating = $vevent->DTSTART->isFloating();
188
-			$end = clone $vevent->DTSTART;
189
-			$endDateTime = $end->getDateTime();
190
-			$endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
191
-			$end->setDateTime($endDateTime, $isFloating);
192
-		} elseif (!$vevent->DTSTART->hasTime()) {
193
-			$isFloating = $vevent->DTSTART->isFloating();
194
-			$end = clone $vevent->DTSTART;
195
-			$endDateTime = $end->getDateTime();
196
-			$endDateTime = $endDateTime->modify('+1 day');
197
-			$end->setDateTime($endDateTime, $isFloating);
198
-		} else {
199
-			$end = clone $vevent->DTSTART;
200
-		}
201
-
202
-		$meetingWhen = $this->generateWhenString($l10n, $start, $end);
203
-
204
-		$meetingUrl = $vevent->URL;
205
-		$meetingLocation = $vevent->LOCATION;
206
-
207
-		$defaultVal = '--';
208
-
209
-		$method = self::METHOD_REQUEST;
210
-		switch (strtolower($iTipMessage->method)) {
211
-			case self::METHOD_REPLY:
212
-				$method = self::METHOD_REPLY;
213
-				break;
214
-			case self::METHOD_CANCEL:
215
-				$method = self::METHOD_CANCEL;
216
-				break;
217
-		}
218
-
219
-		$data = array(
220
-			'attendee_name' => (string)$meetingAttendeeName ?: $defaultVal,
221
-			'invitee_name' => (string)$meetingInviteeName ?: $defaultVal,
222
-			'meeting_title' => (string)$meetingTitle ?: $defaultVal,
223
-			'meeting_description' => (string)$meetingDescription ?: $defaultVal,
224
-			'meeting_url' => (string)$meetingUrl ?: $defaultVal,
225
-		);
226
-
227
-		$fromEMail = \OCP\Util::getDefaultEmailAddress('invitations-noreply');
228
-		$fromName = $l10n->t('%s via %s', [$senderName, $this->defaults->getName()]);
229
-
230
-		$message = $this->mailer->createMessage()
231
-			->setFrom([$fromEMail => $fromName])
232
-			->setReplyTo([$sender => $senderName])
233
-			->setTo([$recipient => $recipientName]);
234
-
235
-		$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
236
-		$template->addHeader();
237
-
238
-		$this->addSubjectAndHeading($template, $l10n, $method, $summary,
239
-			$meetingAttendeeName, $meetingInviteeName);
240
-		$this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation,
241
-			$meetingDescription, $meetingUrl);
242
-		$this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence);
243
-
244
-		$template->addFooter();
245
-		$message->useTemplate($template);
246
-
247
-		$attachment = $this->mailer->createAttachment(
248
-			$iTipMessage->message->serialize(),
249
-			'event.ics',// TODO(leon): Make file name unique, e.g. add event id
250
-			'text/calendar; method=' . $iTipMessage->method
251
-		);
252
-		$message->attach($attachment);
253
-
254
-		try {
255
-			$failed = $this->mailer->send($message);
256
-			$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
257
-			if ($failed) {
258
-				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' =>  implode(', ', $failed)]);
259
-				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
260
-			}
261
-		} catch(\Exception $ex) {
262
-			$this->logger->logException($ex, ['app' => 'dav']);
263
-			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
264
-		}
265
-	}
266
-
267
-	/**
268
-	 * check if event took place in the past already
269
-	 * @param VCalendar $vObject
270
-	 * @return int
271
-	 */
272
-	private function getLastOccurrence(VCalendar $vObject) {
273
-		/** @var VEvent $component */
274
-		$component = $vObject->VEVENT;
275
-
276
-		$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
277
-		// Finding the last occurrence is a bit harder
278
-		if (!isset($component->RRULE)) {
279
-			if (isset($component->DTEND)) {
280
-				$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
281
-			} elseif (isset($component->DURATION)) {
282
-				/** @var \DateTime $endDate */
283
-				$endDate = clone $component->DTSTART->getDateTime();
284
-				// $component->DTEND->getDateTime() returns DateTimeImmutable
285
-				$endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
286
-				$lastOccurrence = $endDate->getTimestamp();
287
-			} elseif (!$component->DTSTART->hasTime()) {
288
-				/** @var \DateTime $endDate */
289
-				$endDate = clone $component->DTSTART->getDateTime();
290
-				// $component->DTSTART->getDateTime() returns DateTimeImmutable
291
-				$endDate = $endDate->modify('+1 day');
292
-				$lastOccurrence = $endDate->getTimestamp();
293
-			} else {
294
-				$lastOccurrence = $firstOccurrence;
295
-			}
296
-		} else {
297
-			$it = new EventIterator($vObject, (string)$component->UID);
298
-			$maxDate = new \DateTime(self::MAX_DATE);
299
-			if ($it->isInfinite()) {
300
-				$lastOccurrence = $maxDate->getTimestamp();
301
-			} else {
302
-				$end = $it->getDtEnd();
303
-				while($it->valid() && $end < $maxDate) {
304
-					$end = $it->getDtEnd();
305
-					$it->next();
306
-
307
-				}
308
-				$lastOccurrence = $end->getTimestamp();
309
-			}
310
-		}
311
-
312
-		return $lastOccurrence;
313
-	}
314
-
315
-
316
-	/**
317
-	 * @param Message $iTipMessage
318
-	 * @return null|Property
319
-	 */
320
-	private function getCurrentAttendee(Message $iTipMessage) {
321
-		/** @var VEvent $vevent */
322
-		$vevent = $iTipMessage->message->VEVENT;
323
-		$attendees = $vevent->select('ATTENDEE');
324
-		foreach ($attendees as $attendee) {
325
-			/** @var Property $attendee */
326
-			if (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
327
-				return $attendee;
328
-			}
329
-		}
330
-		return null;
331
-	}
332
-
333
-	/**
334
-	 * @param string $default
335
-	 * @param Property|null $attendee
336
-	 * @return string
337
-	 */
338
-	private function getAttendeeLangOrDefault($default, Property $attendee = null) {
339
-		if ($attendee !== null) {
340
-			$lang = $attendee->offsetGet('LANGUAGE');
341
-			if ($lang instanceof Parameter) {
342
-				return $lang->getValue();
343
-			}
344
-		}
345
-		return $default;
346
-	}
347
-
348
-	/**
349
-	 * @param IL10N $l10n
350
-	 * @param Property $dtstart
351
-	 * @param Property $dtend
352
-	 */
353
-	private function generateWhenString(IL10N $l10n, Property $dtstart, Property $dtend) {
354
-		$isAllDay = $dtstart instanceof Property\ICalendar\Date;
355
-
356
-		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
357
-		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
358
-		/** @var \DateTimeImmutable $dtstartDt */
359
-		$dtstartDt = $dtstart->getDateTime();
360
-		/** @var \DateTimeImmutable $dtendDt */
361
-		$dtendDt = $dtend->getDateTime();
362
-
363
-		$diff = $dtstartDt->diff($dtendDt);
364
-
365
-		$dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM));
366
-		$dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM));
367
-
368
-		if ($isAllDay) {
369
-			// One day event
370
-			if ($diff->days === 1) {
371
-				return $l10n->l('date', $dtstartDt, ['width' => 'medium']);
372
-			}
373
-
374
-			//event that spans over multiple days
375
-			$localeStart = $l10n->l('date', $dtstartDt, ['width' => 'medium']);
376
-			$localeEnd = $l10n->l('date', $dtendDt, ['width' => 'medium']);
377
-
378
-			return $localeStart . ' - ' . $localeEnd;
379
-		}
380
-
381
-		/** @var Property\ICalendar\DateTime $dtstart */
382
-		/** @var Property\ICalendar\DateTime $dtend */
383
-		$isFloating = $dtstart->isFloating();
384
-		$startTimezone = $endTimezone = null;
385
-		if (!$isFloating) {
386
-			$prop = $dtstart->offsetGet('TZID');
387
-			if ($prop instanceof Parameter) {
388
-				$startTimezone = $prop->getValue();
389
-			}
390
-
391
-			$prop = $dtend->offsetGet('TZID');
392
-			if ($prop instanceof Parameter) {
393
-				$endTimezone = $prop->getValue();
394
-			}
395
-		}
396
-
397
-		$localeStart = $l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
398
-			$l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
399
-
400
-		// always show full date with timezone if timezones are different
401
-		if ($startTimezone !== $endTimezone) {
402
-			$localeEnd = $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
403
-
404
-			return $localeStart . ' (' . $startTimezone . ') - ' .
405
-				$localeEnd . ' (' . $endTimezone . ')';
406
-		}
407
-
408
-		// show only end time if date is the same
409
-		if ($this->isDayEqual($dtstartDt, $dtendDt)) {
410
-			$localeEnd = $l10n->l('time', $dtendDt, ['width' => 'short']);
411
-		} else {
412
-			$localeEnd = $l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
413
-				$l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
414
-		}
415
-
416
-		return  $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')';
417
-	}
418
-
419
-	/**
420
-	 * @param \DateTime $dtStart
421
-	 * @param \DateTime $dtEnd
422
-	 * @return bool
423
-	 */
424
-	private function isDayEqual(\DateTime $dtStart, \DateTime $dtEnd) {
425
-		return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
426
-	}
427
-
428
-	/**
429
-	 * @param IEMailTemplate $template
430
-	 * @param IL10N $l10n
431
-	 * @param string $method
432
-	 * @param string $summary
433
-	 * @param string $attendeeName
434
-	 * @param string $inviteeName
435
-	 */
436
-	private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n,
437
-										  $method, $summary, $attendeeName, $inviteeName) {
438
-		if ($method === self::METHOD_CANCEL) {
439
-			$template->setSubject('Cancelled: ' . $summary);
440
-			$template->addHeading($l10n->t('Invitation canceled'), $l10n->t('Hello %s,', [$attendeeName]));
441
-			$template->addBodyText($l10n->t('The meeting »%s« with %s was canceled.', [$summary, $inviteeName]));
442
-		} else if ($method === self::METHOD_REPLY) {
443
-			$template->setSubject('Re: ' . $summary);
444
-			$template->addHeading($l10n->t('Invitation updated'), $l10n->t('Hello %s,', [$attendeeName]));
445
-			$template->addBodyText($l10n->t('The meeting »%s« with %s was updated.', [$summary, $inviteeName]));
446
-		} else {
447
-			$template->setSubject('Invitation: ' . $summary);
448
-			$template->addHeading($l10n->t('%s invited you to »%s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName]));
449
-		}
450
-
451
-	}
452
-
453
-	/**
454
-	 * @param IEMailTemplate $template
455
-	 * @param IL10N $l10n
456
-	 * @param string $time
457
-	 * @param string $location
458
-	 * @param string $description
459
-	 * @param string $url
460
-	 */
461
-	private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $location, $description, $url) {
462
-		$template->addBodyListItem($time, $l10n->t('When:'),
463
-			$this->getAbsoluteImagePath('filetypes/text-calendar.svg'));
464
-
465
-		if ($location) {
466
-			$template->addBodyListItem($location, $l10n->t('Where:'),
467
-				$this->getAbsoluteImagePath('filetypes/location.svg'));
468
-		}
469
-		if ($description) {
470
-			$template->addBodyListItem((string)$description, $l10n->t('Description:'),
471
-				$this->getAbsoluteImagePath('filetypes/text.svg'));
472
-		}
473
-		if ($url) {
474
-			$template->addBodyListItem((string)$url, $l10n->t('Link:'),
475
-				$this->getAbsoluteImagePath('filetypes/link.svg'));
476
-		}
477
-	}
478
-
479
-	/**
480
-	 * @param IEMailTemplate $template
481
-	 * @param IL10N $l10n
482
-	 * @param Message $iTipMessage
483
-	 * @param int $lastOccurrence
484
-	 */
485
-	private function addResponseButtons(IEMailTemplate $template, IL10N $l10n,
486
-										Message $iTipMessage, $lastOccurrence) {
487
-		$token = $this->createInvitationToken($iTipMessage, $lastOccurrence);
488
-
489
-		$template->addBodyButtonGroup(
490
-			$l10n->t('Accept'),
491
-			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
492
-				'token' => $token,
493
-			]),
494
-			$l10n->t('Decline'),
495
-			$this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
496
-				'token' => $token,
497
-			])
498
-		);
499
-
500
-		$moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
501
-			'token' => $token,
502
-		]);
503
-		$html = vsprintf('<small><a href="%s">%s</a></small>', [
504
-			$moreOptionsURL, $l10n->t('More options ...')
505
-		]);
506
-		$text = $l10n->t('More options at %s', [$moreOptionsURL]);
63
+    /** @var string */
64
+    private $userId;
65
+
66
+    /** @var IConfig */
67
+    private $config;
68
+
69
+    /** @var IMailer */
70
+    private $mailer;
71
+
72
+    /** @var ILogger */
73
+    private $logger;
74
+
75
+    /** @var ITimeFactory */
76
+    private $timeFactory;
77
+
78
+    /** @var L10NFactory */
79
+    private $l10nFactory;
80
+
81
+    /** @var IURLGenerator */
82
+    private $urlGenerator;
83
+
84
+    /** @var ISecureRandom */
85
+    private $random;
86
+
87
+    /** @var IDBConnection */
88
+    private $db;
89
+
90
+    /** @var Defaults */
91
+    private $defaults;
92
+
93
+    const MAX_DATE = '2038-01-01';
94
+
95
+    const METHOD_REQUEST = 'request';
96
+    const METHOD_REPLY = 'reply';
97
+    const METHOD_CANCEL = 'cancel';
98
+
99
+    /**
100
+     * @param IConfig $config
101
+     * @param IMailer $mailer
102
+     * @param ILogger $logger
103
+     * @param ITimeFactory $timeFactory
104
+     * @param L10NFactory $l10nFactory
105
+     * @param IUrlGenerator $urlGenerator
106
+     * @param Defaults $defaults
107
+     * @param ISecureRandom $random
108
+     * @param IDBConnection $db
109
+     * @param string $userId
110
+     */
111
+    public function __construct(IConfig $config, IMailer $mailer, ILogger $logger,
112
+                                ITimeFactory $timeFactory, L10NFactory $l10nFactory,
113
+                                IURLGenerator $urlGenerator, Defaults $defaults,
114
+                                ISecureRandom $random, IDBConnection $db, $userId) {
115
+        parent::__construct('');
116
+        $this->userId = $userId;
117
+        $this->config = $config;
118
+        $this->mailer = $mailer;
119
+        $this->logger = $logger;
120
+        $this->timeFactory = $timeFactory;
121
+        $this->l10nFactory = $l10nFactory;
122
+        $this->urlGenerator = $urlGenerator;
123
+        $this->random = $random;
124
+        $this->db = $db;
125
+        $this->defaults = $defaults;
126
+    }
127
+
128
+    /**
129
+     * Event handler for the 'schedule' event.
130
+     *
131
+     * @param Message $iTipMessage
132
+     * @return void
133
+     */
134
+    public function schedule(Message $iTipMessage) {
135
+
136
+        // Not sending any emails if the system considers the update
137
+        // insignificant.
138
+        if (!$iTipMessage->significantChange) {
139
+            if (!$iTipMessage->scheduleStatus) {
140
+                $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
141
+            }
142
+            return;
143
+        }
144
+
145
+        $summary = $iTipMessage->message->VEVENT->SUMMARY;
146
+
147
+        if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') {
148
+            return;
149
+        }
150
+
151
+        if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
152
+            return;
153
+        }
154
+
155
+        // don't send out mails for events that already took place
156
+        $lastOccurrence = $this->getLastOccurrence($iTipMessage->message);
157
+        $currentTime = $this->timeFactory->getTime();
158
+        if ($lastOccurrence < $currentTime) {
159
+            return;
160
+        }
161
+
162
+        // Strip off mailto:
163
+        $sender = substr($iTipMessage->sender, 7);
164
+        $recipient = substr($iTipMessage->recipient, 7);
165
+
166
+        $senderName = $iTipMessage->senderName ?: null;
167
+        $recipientName = $iTipMessage->recipientName ?: null;
168
+
169
+        /** @var VEvent $vevent */
170
+        $vevent = $iTipMessage->message->VEVENT;
171
+
172
+        $attendee = $this->getCurrentAttendee($iTipMessage);
173
+        $defaultLang = $this->config->getUserValue($this->userId, 'core', 'lang', $this->l10nFactory->findLanguage());
174
+        $lang = $this->getAttendeeLangOrDefault($defaultLang, $attendee);
175
+        $l10n = $this->l10nFactory->get('dav', $lang);
176
+
177
+        $meetingAttendeeName = $recipientName ?: $recipient;
178
+        $meetingInviteeName = $senderName ?: $sender;
179
+
180
+        $meetingTitle = $vevent->SUMMARY;
181
+        $meetingDescription = $vevent->DESCRIPTION;
182
+
183
+        $start = $vevent->DTSTART;
184
+        if (isset($vevent->DTEND)) {
185
+            $end = $vevent->DTEND;
186
+        } elseif (isset($vevent->DURATION)) {
187
+            $isFloating = $vevent->DTSTART->isFloating();
188
+            $end = clone $vevent->DTSTART;
189
+            $endDateTime = $end->getDateTime();
190
+            $endDateTime = $endDateTime->add(DateTimeParser::parse($vevent->DURATION->getValue()));
191
+            $end->setDateTime($endDateTime, $isFloating);
192
+        } elseif (!$vevent->DTSTART->hasTime()) {
193
+            $isFloating = $vevent->DTSTART->isFloating();
194
+            $end = clone $vevent->DTSTART;
195
+            $endDateTime = $end->getDateTime();
196
+            $endDateTime = $endDateTime->modify('+1 day');
197
+            $end->setDateTime($endDateTime, $isFloating);
198
+        } else {
199
+            $end = clone $vevent->DTSTART;
200
+        }
201
+
202
+        $meetingWhen = $this->generateWhenString($l10n, $start, $end);
203
+
204
+        $meetingUrl = $vevent->URL;
205
+        $meetingLocation = $vevent->LOCATION;
206
+
207
+        $defaultVal = '--';
208
+
209
+        $method = self::METHOD_REQUEST;
210
+        switch (strtolower($iTipMessage->method)) {
211
+            case self::METHOD_REPLY:
212
+                $method = self::METHOD_REPLY;
213
+                break;
214
+            case self::METHOD_CANCEL:
215
+                $method = self::METHOD_CANCEL;
216
+                break;
217
+        }
218
+
219
+        $data = array(
220
+            'attendee_name' => (string)$meetingAttendeeName ?: $defaultVal,
221
+            'invitee_name' => (string)$meetingInviteeName ?: $defaultVal,
222
+            'meeting_title' => (string)$meetingTitle ?: $defaultVal,
223
+            'meeting_description' => (string)$meetingDescription ?: $defaultVal,
224
+            'meeting_url' => (string)$meetingUrl ?: $defaultVal,
225
+        );
226
+
227
+        $fromEMail = \OCP\Util::getDefaultEmailAddress('invitations-noreply');
228
+        $fromName = $l10n->t('%s via %s', [$senderName, $this->defaults->getName()]);
229
+
230
+        $message = $this->mailer->createMessage()
231
+            ->setFrom([$fromEMail => $fromName])
232
+            ->setReplyTo([$sender => $senderName])
233
+            ->setTo([$recipient => $recipientName]);
234
+
235
+        $template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
236
+        $template->addHeader();
237
+
238
+        $this->addSubjectAndHeading($template, $l10n, $method, $summary,
239
+            $meetingAttendeeName, $meetingInviteeName);
240
+        $this->addBulletList($template, $l10n, $meetingWhen, $meetingLocation,
241
+            $meetingDescription, $meetingUrl);
242
+        $this->addResponseButtons($template, $l10n, $iTipMessage, $lastOccurrence);
243
+
244
+        $template->addFooter();
245
+        $message->useTemplate($template);
246
+
247
+        $attachment = $this->mailer->createAttachment(
248
+            $iTipMessage->message->serialize(),
249
+            'event.ics',// TODO(leon): Make file name unique, e.g. add event id
250
+            'text/calendar; method=' . $iTipMessage->method
251
+        );
252
+        $message->attach($attachment);
253
+
254
+        try {
255
+            $failed = $this->mailer->send($message);
256
+            $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
257
+            if ($failed) {
258
+                $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' =>  implode(', ', $failed)]);
259
+                $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
260
+            }
261
+        } catch(\Exception $ex) {
262
+            $this->logger->logException($ex, ['app' => 'dav']);
263
+            $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
264
+        }
265
+    }
266
+
267
+    /**
268
+     * check if event took place in the past already
269
+     * @param VCalendar $vObject
270
+     * @return int
271
+     */
272
+    private function getLastOccurrence(VCalendar $vObject) {
273
+        /** @var VEvent $component */
274
+        $component = $vObject->VEVENT;
275
+
276
+        $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
277
+        // Finding the last occurrence is a bit harder
278
+        if (!isset($component->RRULE)) {
279
+            if (isset($component->DTEND)) {
280
+                $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
281
+            } elseif (isset($component->DURATION)) {
282
+                /** @var \DateTime $endDate */
283
+                $endDate = clone $component->DTSTART->getDateTime();
284
+                // $component->DTEND->getDateTime() returns DateTimeImmutable
285
+                $endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
286
+                $lastOccurrence = $endDate->getTimestamp();
287
+            } elseif (!$component->DTSTART->hasTime()) {
288
+                /** @var \DateTime $endDate */
289
+                $endDate = clone $component->DTSTART->getDateTime();
290
+                // $component->DTSTART->getDateTime() returns DateTimeImmutable
291
+                $endDate = $endDate->modify('+1 day');
292
+                $lastOccurrence = $endDate->getTimestamp();
293
+            } else {
294
+                $lastOccurrence = $firstOccurrence;
295
+            }
296
+        } else {
297
+            $it = new EventIterator($vObject, (string)$component->UID);
298
+            $maxDate = new \DateTime(self::MAX_DATE);
299
+            if ($it->isInfinite()) {
300
+                $lastOccurrence = $maxDate->getTimestamp();
301
+            } else {
302
+                $end = $it->getDtEnd();
303
+                while($it->valid() && $end < $maxDate) {
304
+                    $end = $it->getDtEnd();
305
+                    $it->next();
306
+
307
+                }
308
+                $lastOccurrence = $end->getTimestamp();
309
+            }
310
+        }
311
+
312
+        return $lastOccurrence;
313
+    }
314
+
315
+
316
+    /**
317
+     * @param Message $iTipMessage
318
+     * @return null|Property
319
+     */
320
+    private function getCurrentAttendee(Message $iTipMessage) {
321
+        /** @var VEvent $vevent */
322
+        $vevent = $iTipMessage->message->VEVENT;
323
+        $attendees = $vevent->select('ATTENDEE');
324
+        foreach ($attendees as $attendee) {
325
+            /** @var Property $attendee */
326
+            if (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
327
+                return $attendee;
328
+            }
329
+        }
330
+        return null;
331
+    }
332
+
333
+    /**
334
+     * @param string $default
335
+     * @param Property|null $attendee
336
+     * @return string
337
+     */
338
+    private function getAttendeeLangOrDefault($default, Property $attendee = null) {
339
+        if ($attendee !== null) {
340
+            $lang = $attendee->offsetGet('LANGUAGE');
341
+            if ($lang instanceof Parameter) {
342
+                return $lang->getValue();
343
+            }
344
+        }
345
+        return $default;
346
+    }
347
+
348
+    /**
349
+     * @param IL10N $l10n
350
+     * @param Property $dtstart
351
+     * @param Property $dtend
352
+     */
353
+    private function generateWhenString(IL10N $l10n, Property $dtstart, Property $dtend) {
354
+        $isAllDay = $dtstart instanceof Property\ICalendar\Date;
355
+
356
+        /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
357
+        /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
358
+        /** @var \DateTimeImmutable $dtstartDt */
359
+        $dtstartDt = $dtstart->getDateTime();
360
+        /** @var \DateTimeImmutable $dtendDt */
361
+        $dtendDt = $dtend->getDateTime();
362
+
363
+        $diff = $dtstartDt->diff($dtendDt);
364
+
365
+        $dtstartDt = new \DateTime($dtstartDt->format(\DateTime::ATOM));
366
+        $dtendDt = new \DateTime($dtendDt->format(\DateTime::ATOM));
367
+
368
+        if ($isAllDay) {
369
+            // One day event
370
+            if ($diff->days === 1) {
371
+                return $l10n->l('date', $dtstartDt, ['width' => 'medium']);
372
+            }
373
+
374
+            //event that spans over multiple days
375
+            $localeStart = $l10n->l('date', $dtstartDt, ['width' => 'medium']);
376
+            $localeEnd = $l10n->l('date', $dtendDt, ['width' => 'medium']);
377
+
378
+            return $localeStart . ' - ' . $localeEnd;
379
+        }
380
+
381
+        /** @var Property\ICalendar\DateTime $dtstart */
382
+        /** @var Property\ICalendar\DateTime $dtend */
383
+        $isFloating = $dtstart->isFloating();
384
+        $startTimezone = $endTimezone = null;
385
+        if (!$isFloating) {
386
+            $prop = $dtstart->offsetGet('TZID');
387
+            if ($prop instanceof Parameter) {
388
+                $startTimezone = $prop->getValue();
389
+            }
390
+
391
+            $prop = $dtend->offsetGet('TZID');
392
+            if ($prop instanceof Parameter) {
393
+                $endTimezone = $prop->getValue();
394
+            }
395
+        }
396
+
397
+        $localeStart = $l10n->l('weekdayName', $dtstartDt, ['width' => 'abbreviated']) . ', ' .
398
+            $l10n->l('datetime', $dtstartDt, ['width' => 'medium|short']);
399
+
400
+        // always show full date with timezone if timezones are different
401
+        if ($startTimezone !== $endTimezone) {
402
+            $localeEnd = $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
403
+
404
+            return $localeStart . ' (' . $startTimezone . ') - ' .
405
+                $localeEnd . ' (' . $endTimezone . ')';
406
+        }
407
+
408
+        // show only end time if date is the same
409
+        if ($this->isDayEqual($dtstartDt, $dtendDt)) {
410
+            $localeEnd = $l10n->l('time', $dtendDt, ['width' => 'short']);
411
+        } else {
412
+            $localeEnd = $l10n->l('weekdayName', $dtendDt, ['width' => 'abbreviated']) . ', ' .
413
+                $l10n->l('datetime', $dtendDt, ['width' => 'medium|short']);
414
+        }
415
+
416
+        return  $localeStart . ' - ' . $localeEnd . ' (' . $startTimezone . ')';
417
+    }
418
+
419
+    /**
420
+     * @param \DateTime $dtStart
421
+     * @param \DateTime $dtEnd
422
+     * @return bool
423
+     */
424
+    private function isDayEqual(\DateTime $dtStart, \DateTime $dtEnd) {
425
+        return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
426
+    }
427
+
428
+    /**
429
+     * @param IEMailTemplate $template
430
+     * @param IL10N $l10n
431
+     * @param string $method
432
+     * @param string $summary
433
+     * @param string $attendeeName
434
+     * @param string $inviteeName
435
+     */
436
+    private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n,
437
+                                            $method, $summary, $attendeeName, $inviteeName) {
438
+        if ($method === self::METHOD_CANCEL) {
439
+            $template->setSubject('Cancelled: ' . $summary);
440
+            $template->addHeading($l10n->t('Invitation canceled'), $l10n->t('Hello %s,', [$attendeeName]));
441
+            $template->addBodyText($l10n->t('The meeting »%s« with %s was canceled.', [$summary, $inviteeName]));
442
+        } else if ($method === self::METHOD_REPLY) {
443
+            $template->setSubject('Re: ' . $summary);
444
+            $template->addHeading($l10n->t('Invitation updated'), $l10n->t('Hello %s,', [$attendeeName]));
445
+            $template->addBodyText($l10n->t('The meeting »%s« with %s was updated.', [$summary, $inviteeName]));
446
+        } else {
447
+            $template->setSubject('Invitation: ' . $summary);
448
+            $template->addHeading($l10n->t('%s invited you to »%s«', [$inviteeName, $summary]), $l10n->t('Hello %s,', [$attendeeName]));
449
+        }
450
+
451
+    }
452
+
453
+    /**
454
+     * @param IEMailTemplate $template
455
+     * @param IL10N $l10n
456
+     * @param string $time
457
+     * @param string $location
458
+     * @param string $description
459
+     * @param string $url
460
+     */
461
+    private function addBulletList(IEMailTemplate $template, IL10N $l10n, $time, $location, $description, $url) {
462
+        $template->addBodyListItem($time, $l10n->t('When:'),
463
+            $this->getAbsoluteImagePath('filetypes/text-calendar.svg'));
464
+
465
+        if ($location) {
466
+            $template->addBodyListItem($location, $l10n->t('Where:'),
467
+                $this->getAbsoluteImagePath('filetypes/location.svg'));
468
+        }
469
+        if ($description) {
470
+            $template->addBodyListItem((string)$description, $l10n->t('Description:'),
471
+                $this->getAbsoluteImagePath('filetypes/text.svg'));
472
+        }
473
+        if ($url) {
474
+            $template->addBodyListItem((string)$url, $l10n->t('Link:'),
475
+                $this->getAbsoluteImagePath('filetypes/link.svg'));
476
+        }
477
+    }
478
+
479
+    /**
480
+     * @param IEMailTemplate $template
481
+     * @param IL10N $l10n
482
+     * @param Message $iTipMessage
483
+     * @param int $lastOccurrence
484
+     */
485
+    private function addResponseButtons(IEMailTemplate $template, IL10N $l10n,
486
+                                        Message $iTipMessage, $lastOccurrence) {
487
+        $token = $this->createInvitationToken($iTipMessage, $lastOccurrence);
488
+
489
+        $template->addBodyButtonGroup(
490
+            $l10n->t('Accept'),
491
+            $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.accept', [
492
+                'token' => $token,
493
+            ]),
494
+            $l10n->t('Decline'),
495
+            $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.decline', [
496
+                'token' => $token,
497
+            ])
498
+        );
499
+
500
+        $moreOptionsURL = $this->urlGenerator->linkToRouteAbsolute('dav.invitation_response.options', [
501
+            'token' => $token,
502
+        ]);
503
+        $html = vsprintf('<small><a href="%s">%s</a></small>', [
504
+            $moreOptionsURL, $l10n->t('More options ...')
505
+        ]);
506
+        $text = $l10n->t('More options at %s', [$moreOptionsURL]);
507 507
 		
508
-		$template->addBodyText($html, $text);
509
-	}
510
-
511
-	/**
512
-	 * @param string $path
513
-	 * @return string
514
-	 */
515
-	private function getAbsoluteImagePath($path) {
516
-		return $this->urlGenerator->getAbsoluteURL(
517
-			$this->urlGenerator->imagePath('core', $path)
518
-		);
519
-	}
520
-
521
-	/**
522
-	 * @param Message $iTipMessage
523
-	 * @param int $lastOccurrence
524
-	 * @return string
525
-	 */
526
-	private function createInvitationToken(Message $iTipMessage, $lastOccurrence):string {
527
-		$token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
528
-
529
-		/** @var VEvent $vevent */
530
-		$vevent = $iTipMessage->message->VEVENT;
531
-		$attendee = $iTipMessage->recipient;
532
-		$organizer = $iTipMessage->sender;
533
-		$sequence = $iTipMessage->sequence;
534
-		$recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ?
535
-			$vevent->{'RECURRENCE-ID'}->serialize() : null;
536
-		$uid = $vevent->{'UID'};
537
-
538
-		$query = $this->db->getQueryBuilder();
539
-		$query->insert('calendar_invitations')
540
-			->values([
541
-				'token' => $query->createNamedParameter($token),
542
-				'attendee' => $query->createNamedParameter($attendee),
543
-				'organizer' => $query->createNamedParameter($organizer),
544
-				'sequence' => $query->createNamedParameter($sequence),
545
-				'recurrenceid' => $query->createNamedParameter($recurrenceId),
546
-				'expiration' => $query->createNamedParameter($lastOccurrence),
547
-				'uid' => $query->createNamedParameter($uid)
548
-			])
549
-			->execute();
550
-
551
-		return $token;
552
-	}
508
+        $template->addBodyText($html, $text);
509
+    }
510
+
511
+    /**
512
+     * @param string $path
513
+     * @return string
514
+     */
515
+    private function getAbsoluteImagePath($path) {
516
+        return $this->urlGenerator->getAbsoluteURL(
517
+            $this->urlGenerator->imagePath('core', $path)
518
+        );
519
+    }
520
+
521
+    /**
522
+     * @param Message $iTipMessage
523
+     * @param int $lastOccurrence
524
+     * @return string
525
+     */
526
+    private function createInvitationToken(Message $iTipMessage, $lastOccurrence):string {
527
+        $token = $this->random->generate(60, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
528
+
529
+        /** @var VEvent $vevent */
530
+        $vevent = $iTipMessage->message->VEVENT;
531
+        $attendee = $iTipMessage->recipient;
532
+        $organizer = $iTipMessage->sender;
533
+        $sequence = $iTipMessage->sequence;
534
+        $recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ?
535
+            $vevent->{'RECURRENCE-ID'}->serialize() : null;
536
+        $uid = $vevent->{'UID'};
537
+
538
+        $query = $this->db->getQueryBuilder();
539
+        $query->insert('calendar_invitations')
540
+            ->values([
541
+                'token' => $query->createNamedParameter($token),
542
+                'attendee' => $query->createNamedParameter($attendee),
543
+                'organizer' => $query->createNamedParameter($organizer),
544
+                'sequence' => $query->createNamedParameter($sequence),
545
+                'recurrenceid' => $query->createNamedParameter($recurrenceId),
546
+                'expiration' => $query->createNamedParameter($lastOccurrence),
547
+                'uid' => $query->createNamedParameter($uid)
548
+            ])
549
+            ->execute();
550
+
551
+        return $token;
552
+    }
553 553
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php 1 patch
Indentation   +326 added lines, -326 removed lines patch added patch discarded remove patch
@@ -32,330 +32,330 @@
 block discarded – undo
32 32
 
33 33
 abstract class AbstractPrincipalBackend implements BackendInterface {
34 34
 
35
-	/** @var IDBConnection */
36
-	private $db;
37
-
38
-	/** @var IUserSession */
39
-	private $userSession;
40
-
41
-	/** @var IGroupManager */
42
-	private $groupManager;
43
-
44
-	/** @var ILogger */
45
-	private $logger;
46
-
47
-	/** @var string */
48
-	private $principalPrefix;
49
-
50
-	/** @var string */
51
-	private $dbTableName;
52
-
53
-	/**
54
-	 * @param IDBConnection $dbConnection
55
-	 * @param IUserSession $userSession
56
-	 * @param IGroupManager $groupManager
57
-	 * @param ILogger $logger
58
-	 * @param string $principalPrefix
59
-	 * @param string $dbPrefix
60
-	 */
61
-	public function __construct(IDBConnection $dbConnection,
62
-								IUserSession $userSession,
63
-								IGroupManager $groupManager,
64
-								ILogger $logger,
65
-								$principalPrefix, $dbPrefix) {
66
-		$this->db = $dbConnection;
67
-		$this->userSession = $userSession;
68
-		$this->groupManager = $groupManager;
69
-		$this->logger = $logger;
70
-		$this->principalPrefix = $principalPrefix;
71
-		$this->dbTableName = 'calendar_' . $dbPrefix;
72
-	}
73
-
74
-	/**
75
-	 * Returns a list of principals based on a prefix.
76
-	 *
77
-	 * This prefix will often contain something like 'principals'. You are only
78
-	 * expected to return principals that are in this base path.
79
-	 *
80
-	 * You are expected to return at least a 'uri' for every user, you can
81
-	 * return any additional properties if you wish so. Common properties are:
82
-	 *   {DAV:}displayname
83
-	 *
84
-	 * @param string $prefixPath
85
-	 * @return string[]
86
-	 */
87
-	public function getPrincipalsByPrefix($prefixPath) {
88
-		$principals = [];
89
-
90
-		if ($prefixPath === $this->principalPrefix) {
91
-			$query = $this->db->getQueryBuilder();
92
-			$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname'])
93
-				->from($this->dbTableName);
94
-			$stmt = $query->execute();
95
-
96
-			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
97
-				$principals[] = $this->rowToPrincipal($row);
98
-			}
99
-
100
-			$stmt->closeCursor();
101
-		}
102
-
103
-		return $principals;
104
-	}
105
-
106
-	/**
107
-	 * Returns a specific principal, specified by it's path.
108
-	 * The returned structure should be the exact same as from
109
-	 * getPrincipalsByPrefix.
110
-	 *
111
-	 * @param string $path
112
-	 * @return array
113
-	 */
114
-	public function getPrincipalByPath($path) {
115
-		if (strpos($path, $this->principalPrefix) !== 0) {
116
-			return null;
117
-		}
118
-		list(, $name) = \Sabre\Uri\split($path);
119
-
120
-		list($backendId, $resourceId) = explode('-',  $name, 2);
121
-
122
-		$query = $this->db->getQueryBuilder();
123
-		$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname'])
124
-			->from($this->dbTableName)
125
-			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
126
-			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
127
-		$stmt = $query->execute();
128
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
129
-
130
-		if(!$row) {
131
-			return null;
132
-		}
133
-
134
-		return $this->rowToPrincipal($row);
135
-	}
136
-
137
-	/**
138
-	 * Returns the list of members for a group-principal
139
-	 *
140
-	 * @param string $principal
141
-	 * @return string[]
142
-	 */
143
-	public function getGroupMemberSet($principal) {
144
-		return [];
145
-	}
146
-
147
-	/**
148
-	 * Returns the list of groups a principal is a member of
149
-	 *
150
-	 * @param string $principal
151
-	 * @return array
152
-	 */
153
-	public function getGroupMembership($principal) {
154
-		return [];
155
-	}
156
-
157
-	/**
158
-	 * Updates the list of group members for a group principal.
159
-	 *
160
-	 * The principals should be passed as a list of uri's.
161
-	 *
162
-	 * @param string $principal
163
-	 * @param string[] $members
164
-	 * @throws Exception
165
-	 */
166
-	public function setGroupMemberSet($principal, array $members) {
167
-		throw new Exception('Setting members of the group is not supported yet');
168
-	}
169
-
170
-	/**
171
-	 * @param string $path
172
-	 * @param PropPatch $propPatch
173
-	 * @return int
174
-	 */
175
-	function updatePrincipal($path, PropPatch $propPatch) {
176
-		return 0;
177
-	}
178
-
179
-	/**
180
-	 * @param string $prefixPath
181
-	 * @param array $searchProperties
182
-	 * @param string $test
183
-	 * @return array
184
-	 */
185
-	function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
186
-		$results = [];
187
-		if (\count($searchProperties) === 0) {
188
-			return [];
189
-		}
190
-		if ($prefixPath !== $this->principalPrefix) {
191
-			return [];
192
-		}
193
-
194
-		$user = $this->userSession->getUser();
195
-		if (!$user) {
196
-			return [];
197
-		}
198
-		$usersGroups = $this->groupManager->getUserGroupIds($user);
199
-
200
-		foreach ($searchProperties as $prop => $value) {
201
-			switch ($prop) {
202
-				case '{http://sabredav.org/ns}email-address':
203
-					$query = $this->db->getQueryBuilder();
204
-					$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
205
-						->from($this->dbTableName)
206
-						->where($query->expr()->iLike('email', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%')));
207
-
208
-					$stmt = $query->execute();
209
-					$principals = [];
210
-					while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
211
-						if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
212
-							continue;
213
-						}
214
-						$principals[] = $this->rowToPrincipal($row)['uri'];
215
-					}
216
-					$results[] = $principals;
217
-
218
-					$stmt->closeCursor();
219
-					break;
220
-
221
-				case '{DAV:}displayname':
222
-					$query = $this->db->getQueryBuilder();
223
-					$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
224
-						->from($this->dbTableName)
225
-						->where($query->expr()->iLike('displayname', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%')));
226
-
227
-					$stmt = $query->execute();
228
-					$principals = [];
229
-					while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
230
-						if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
231
-							continue;
232
-						}
233
-						$principals[] = $this->rowToPrincipal($row)['uri'];
234
-					}
235
-					$results[] = $principals;
236
-
237
-					$stmt->closeCursor();
238
-					break;
239
-
240
-				default:
241
-					$results[] = [];
242
-					break;
243
-			}
244
-		}
245
-
246
-		// results is an array of arrays, so this is not the first search result
247
-		// but the results of the first searchProperty
248
-		if (count($results) === 1) {
249
-			return $results[0];
250
-		}
251
-
252
-		switch ($test) {
253
-			case 'anyof':
254
-				return array_values(array_unique(array_merge(...$results)));
255
-
256
-			case 'allof':
257
-			default:
258
-				return array_values(array_intersect(...$results));
259
-		}
260
-	}
261
-
262
-	/**
263
-	 * @param string $uri
264
-	 * @param string $principalPrefix
265
-	 * @return null|string
266
-	 */
267
-	function findByUri($uri, $principalPrefix) {
268
-		$user = $this->userSession->getUser();
269
-		if (!$user) {
270
-			return null;
271
-		}
272
-		$usersGroups = $this->groupManager->getUserGroupIds($user);
273
-
274
-		if (strpos($uri, 'mailto:') === 0) {
275
-			$email = substr($uri, 7);
276
-			$query = $this->db->getQueryBuilder();
277
-			$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
278
-				->from($this->dbTableName)
279
-				->where($query->expr()->eq('email', $query->createNamedParameter($email)));
280
-
281
-			$stmt = $query->execute();
282
-			$row = $stmt->fetch(\PDO::FETCH_ASSOC);
283
-
284
-			if(!$row) {
285
-				return null;
286
-			}
287
-			if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
288
-				return null;
289
-			}
290
-
291
-			return $this->rowToPrincipal($row)['uri'];
292
-		}
293
-
294
-		if (strpos($uri, 'principal:') === 0) {
295
-			$path = substr($uri, 10);
296
-			if (strpos($path, $this->principalPrefix) !== 0) {
297
-				return null;
298
-			}
299
-
300
-			list(, $name) = \Sabre\Uri\split($path);
301
-			list($backendId, $resourceId) = explode('-',  $name, 2);
302
-
303
-			$query = $this->db->getQueryBuilder();
304
-			$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
305
-				->from($this->dbTableName)
306
-				->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
307
-				->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
308
-			$stmt = $query->execute();
309
-			$row = $stmt->fetch(\PDO::FETCH_ASSOC);
310
-
311
-			if(!$row) {
312
-				return null;
313
-			}
314
-			if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
315
-				return null;
316
-			}
317
-
318
-			return $this->rowToPrincipal($row)['uri'];
319
-		}
320
-
321
-		return null;
322
-	}
323
-
324
-	/**
325
-	 * convert database row to principal
326
-	 */
327
-	private function rowToPrincipal($row) {
328
-		return [
329
-			'uri' => $this->principalPrefix . '/' . $row['backend_id'] . '-' . $row['resource_id'],
330
-			'{DAV:}displayname' => $row['displayname'],
331
-			'{http://sabredav.org/ns}email-address' => $row['email']
332
-		];
333
-	}
334
-
335
-	/**
336
-	 * @param $row
337
-	 * @param $userGroups
338
-	 * @return bool
339
-	 */
340
-	private function isAllowedToAccessResource($row, $userGroups) {
341
-		if (!isset($row['group_restrictions']) ||
342
-			$row['group_restrictions'] === null ||
343
-			$row['group_restrictions'] === '') {
344
-			return true;
345
-		}
346
-
347
-		// group restrictions contains something, but not parsable, deny access and log warning
348
-		$json = json_decode($row['group_restrictions']);
349
-		if (!\is_array($json)) {
350
-			$this->logger->info('group_restrictions field could not be parsed for ' . $this->dbTableName . '::' . $row['id'] . ', denying access to resource');
351
-			return false;
352
-		}
353
-
354
-		// empty array => no group restrictions
355
-		if (empty($json)) {
356
-			return true;
357
-		}
358
-
359
-		return !empty(array_intersect($json, $userGroups));
360
-	}
35
+    /** @var IDBConnection */
36
+    private $db;
37
+
38
+    /** @var IUserSession */
39
+    private $userSession;
40
+
41
+    /** @var IGroupManager */
42
+    private $groupManager;
43
+
44
+    /** @var ILogger */
45
+    private $logger;
46
+
47
+    /** @var string */
48
+    private $principalPrefix;
49
+
50
+    /** @var string */
51
+    private $dbTableName;
52
+
53
+    /**
54
+     * @param IDBConnection $dbConnection
55
+     * @param IUserSession $userSession
56
+     * @param IGroupManager $groupManager
57
+     * @param ILogger $logger
58
+     * @param string $principalPrefix
59
+     * @param string $dbPrefix
60
+     */
61
+    public function __construct(IDBConnection $dbConnection,
62
+                                IUserSession $userSession,
63
+                                IGroupManager $groupManager,
64
+                                ILogger $logger,
65
+                                $principalPrefix, $dbPrefix) {
66
+        $this->db = $dbConnection;
67
+        $this->userSession = $userSession;
68
+        $this->groupManager = $groupManager;
69
+        $this->logger = $logger;
70
+        $this->principalPrefix = $principalPrefix;
71
+        $this->dbTableName = 'calendar_' . $dbPrefix;
72
+    }
73
+
74
+    /**
75
+     * Returns a list of principals based on a prefix.
76
+     *
77
+     * This prefix will often contain something like 'principals'. You are only
78
+     * expected to return principals that are in this base path.
79
+     *
80
+     * You are expected to return at least a 'uri' for every user, you can
81
+     * return any additional properties if you wish so. Common properties are:
82
+     *   {DAV:}displayname
83
+     *
84
+     * @param string $prefixPath
85
+     * @return string[]
86
+     */
87
+    public function getPrincipalsByPrefix($prefixPath) {
88
+        $principals = [];
89
+
90
+        if ($prefixPath === $this->principalPrefix) {
91
+            $query = $this->db->getQueryBuilder();
92
+            $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname'])
93
+                ->from($this->dbTableName);
94
+            $stmt = $query->execute();
95
+
96
+            while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
97
+                $principals[] = $this->rowToPrincipal($row);
98
+            }
99
+
100
+            $stmt->closeCursor();
101
+        }
102
+
103
+        return $principals;
104
+    }
105
+
106
+    /**
107
+     * Returns a specific principal, specified by it's path.
108
+     * The returned structure should be the exact same as from
109
+     * getPrincipalsByPrefix.
110
+     *
111
+     * @param string $path
112
+     * @return array
113
+     */
114
+    public function getPrincipalByPath($path) {
115
+        if (strpos($path, $this->principalPrefix) !== 0) {
116
+            return null;
117
+        }
118
+        list(, $name) = \Sabre\Uri\split($path);
119
+
120
+        list($backendId, $resourceId) = explode('-',  $name, 2);
121
+
122
+        $query = $this->db->getQueryBuilder();
123
+        $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname'])
124
+            ->from($this->dbTableName)
125
+            ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
126
+            ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
127
+        $stmt = $query->execute();
128
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
129
+
130
+        if(!$row) {
131
+            return null;
132
+        }
133
+
134
+        return $this->rowToPrincipal($row);
135
+    }
136
+
137
+    /**
138
+     * Returns the list of members for a group-principal
139
+     *
140
+     * @param string $principal
141
+     * @return string[]
142
+     */
143
+    public function getGroupMemberSet($principal) {
144
+        return [];
145
+    }
146
+
147
+    /**
148
+     * Returns the list of groups a principal is a member of
149
+     *
150
+     * @param string $principal
151
+     * @return array
152
+     */
153
+    public function getGroupMembership($principal) {
154
+        return [];
155
+    }
156
+
157
+    /**
158
+     * Updates the list of group members for a group principal.
159
+     *
160
+     * The principals should be passed as a list of uri's.
161
+     *
162
+     * @param string $principal
163
+     * @param string[] $members
164
+     * @throws Exception
165
+     */
166
+    public function setGroupMemberSet($principal, array $members) {
167
+        throw new Exception('Setting members of the group is not supported yet');
168
+    }
169
+
170
+    /**
171
+     * @param string $path
172
+     * @param PropPatch $propPatch
173
+     * @return int
174
+     */
175
+    function updatePrincipal($path, PropPatch $propPatch) {
176
+        return 0;
177
+    }
178
+
179
+    /**
180
+     * @param string $prefixPath
181
+     * @param array $searchProperties
182
+     * @param string $test
183
+     * @return array
184
+     */
185
+    function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
186
+        $results = [];
187
+        if (\count($searchProperties) === 0) {
188
+            return [];
189
+        }
190
+        if ($prefixPath !== $this->principalPrefix) {
191
+            return [];
192
+        }
193
+
194
+        $user = $this->userSession->getUser();
195
+        if (!$user) {
196
+            return [];
197
+        }
198
+        $usersGroups = $this->groupManager->getUserGroupIds($user);
199
+
200
+        foreach ($searchProperties as $prop => $value) {
201
+            switch ($prop) {
202
+                case '{http://sabredav.org/ns}email-address':
203
+                    $query = $this->db->getQueryBuilder();
204
+                    $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
205
+                        ->from($this->dbTableName)
206
+                        ->where($query->expr()->iLike('email', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%')));
207
+
208
+                    $stmt = $query->execute();
209
+                    $principals = [];
210
+                    while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
211
+                        if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
212
+                            continue;
213
+                        }
214
+                        $principals[] = $this->rowToPrincipal($row)['uri'];
215
+                    }
216
+                    $results[] = $principals;
217
+
218
+                    $stmt->closeCursor();
219
+                    break;
220
+
221
+                case '{DAV:}displayname':
222
+                    $query = $this->db->getQueryBuilder();
223
+                    $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
224
+                        ->from($this->dbTableName)
225
+                        ->where($query->expr()->iLike('displayname', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%')));
226
+
227
+                    $stmt = $query->execute();
228
+                    $principals = [];
229
+                    while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
230
+                        if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
231
+                            continue;
232
+                        }
233
+                        $principals[] = $this->rowToPrincipal($row)['uri'];
234
+                    }
235
+                    $results[] = $principals;
236
+
237
+                    $stmt->closeCursor();
238
+                    break;
239
+
240
+                default:
241
+                    $results[] = [];
242
+                    break;
243
+            }
244
+        }
245
+
246
+        // results is an array of arrays, so this is not the first search result
247
+        // but the results of the first searchProperty
248
+        if (count($results) === 1) {
249
+            return $results[0];
250
+        }
251
+
252
+        switch ($test) {
253
+            case 'anyof':
254
+                return array_values(array_unique(array_merge(...$results)));
255
+
256
+            case 'allof':
257
+            default:
258
+                return array_values(array_intersect(...$results));
259
+        }
260
+    }
261
+
262
+    /**
263
+     * @param string $uri
264
+     * @param string $principalPrefix
265
+     * @return null|string
266
+     */
267
+    function findByUri($uri, $principalPrefix) {
268
+        $user = $this->userSession->getUser();
269
+        if (!$user) {
270
+            return null;
271
+        }
272
+        $usersGroups = $this->groupManager->getUserGroupIds($user);
273
+
274
+        if (strpos($uri, 'mailto:') === 0) {
275
+            $email = substr($uri, 7);
276
+            $query = $this->db->getQueryBuilder();
277
+            $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
278
+                ->from($this->dbTableName)
279
+                ->where($query->expr()->eq('email', $query->createNamedParameter($email)));
280
+
281
+            $stmt = $query->execute();
282
+            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
283
+
284
+            if(!$row) {
285
+                return null;
286
+            }
287
+            if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
288
+                return null;
289
+            }
290
+
291
+            return $this->rowToPrincipal($row)['uri'];
292
+        }
293
+
294
+        if (strpos($uri, 'principal:') === 0) {
295
+            $path = substr($uri, 10);
296
+            if (strpos($path, $this->principalPrefix) !== 0) {
297
+                return null;
298
+            }
299
+
300
+            list(, $name) = \Sabre\Uri\split($path);
301
+            list($backendId, $resourceId) = explode('-',  $name, 2);
302
+
303
+            $query = $this->db->getQueryBuilder();
304
+            $query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
305
+                ->from($this->dbTableName)
306
+                ->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
307
+                ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
308
+            $stmt = $query->execute();
309
+            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
310
+
311
+            if(!$row) {
312
+                return null;
313
+            }
314
+            if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
315
+                return null;
316
+            }
317
+
318
+            return $this->rowToPrincipal($row)['uri'];
319
+        }
320
+
321
+        return null;
322
+    }
323
+
324
+    /**
325
+     * convert database row to principal
326
+     */
327
+    private function rowToPrincipal($row) {
328
+        return [
329
+            'uri' => $this->principalPrefix . '/' . $row['backend_id'] . '-' . $row['resource_id'],
330
+            '{DAV:}displayname' => $row['displayname'],
331
+            '{http://sabredav.org/ns}email-address' => $row['email']
332
+        ];
333
+    }
334
+
335
+    /**
336
+     * @param $row
337
+     * @param $userGroups
338
+     * @return bool
339
+     */
340
+    private function isAllowedToAccessResource($row, $userGroups) {
341
+        if (!isset($row['group_restrictions']) ||
342
+            $row['group_restrictions'] === null ||
343
+            $row['group_restrictions'] === '') {
344
+            return true;
345
+        }
346
+
347
+        // group restrictions contains something, but not parsable, deny access and log warning
348
+        $json = json_decode($row['group_restrictions']);
349
+        if (!\is_array($json)) {
350
+            $this->logger->info('group_restrictions field could not be parsed for ' . $this->dbTableName . '::' . $row['id'] . ', denying access to resource');
351
+            return false;
352
+        }
353
+
354
+        // empty array => no group restrictions
355
+        if (empty($json)) {
356
+            return true;
357
+        }
358
+
359
+        return !empty(array_intersect($json, $userGroups));
360
+    }
361 361
 }
Please login to merge, or discard this patch.
apps/dav/lib/Controller/InvitationResponseController.php 1 patch
Indentation   +181 added lines, -181 removed lines patch added patch discarded remove patch
@@ -34,169 +34,169 @@  discard block
 block discarded – undo
34 34
 
35 35
 class InvitationResponseController extends Controller {
36 36
 
37
-	/** @var IDBConnection */
38
-	private $db;
39
-
40
-	/** @var ITimeFactory */
41
-	private $timeFactory;
42
-
43
-	/** @var InvitationResponseServer */
44
-	private $responseServer;
45
-
46
-	/**
47
-	 * InvitationResponseController constructor.
48
-	 *
49
-	 * @param string $appName
50
-	 * @param IRequest $request
51
-	 * @param IDBConnection $db
52
-	 * @param ITimeFactory $timeFactory
53
-	 * @param InvitationResponseServer $responseServer
54
-	 */
55
-	public function __construct(string $appName, IRequest $request,
56
-								IDBConnection $db, ITimeFactory $timeFactory,
57
-								InvitationResponseServer $responseServer) {
58
-		parent::__construct($appName, $request);
59
-		$this->db = $db;
60
-		$this->timeFactory = $timeFactory;
61
-		$this->responseServer = $responseServer;
62
-		// Don't run `$server->exec()`, because we just need access to the
63
-		// fully initialized schedule plugin, but we don't want Sabre/DAV
64
-		// to actually handle and reply to the request
65
-	}
66
-
67
-	/**
68
-	 * @PublicPage
69
-	 * @NoCSRFRequired
70
-	 *
71
-	 * @param string $token
72
-	 * @return TemplateResponse
73
-	 */
74
-	public function accept(string $token):TemplateResponse {
75
-		$row = $this->getTokenInformation($token);
76
-		if (!$row) {
77
-			return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest');
78
-		}
79
-
80
-		$iTipMessage = $this->buildITipResponse($row, 'ACCEPTED');
81
-		$this->responseServer->handleITipMessage($iTipMessage);
82
-		if ($iTipMessage->getScheduleStatus() === '1.2') {
83
-			return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest');
84
-		}
85
-
86
-		return new TemplateResponse($this->appName, 'schedule-response-error', [
87
-			'organizer' => $row['organizer'],
88
-		], 'guest');
89
-	}
90
-
91
-	/**
92
-	 * @PublicPage
93
-	 * @NoCSRFRequired
94
-	 *
95
-	 * @param string $token
96
-	 * @return TemplateResponse
97
-	 */
98
-	public function decline(string $token):TemplateResponse {
99
-		$row = $this->getTokenInformation($token);
100
-		if (!$row) {
101
-			return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest');
102
-		}
103
-
104
-		$iTipMessage = $this->buildITipResponse($row, 'DECLINED');
105
-		$this->responseServer->handleITipMessage($iTipMessage);
106
-
107
-		if ($iTipMessage->getScheduleStatus() === '1.2') {
108
-			return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest');
109
-		}
110
-
111
-		return new TemplateResponse($this->appName, 'schedule-response-error', [
112
-			'organizer' => $row['organizer'],
113
-		], 'guest');
114
-	}
115
-
116
-	/**
117
-	 * @PublicPage
118
-	 * @NoCSRFRequired
119
-	 *
120
-	 * @param string $token
121
-	 * @return TemplateResponse
122
-	 */
123
-	public function options(string $token):TemplateResponse {
124
-		return new TemplateResponse($this->appName, 'schedule-response-options', [
125
-			'token' => $token
126
-		], 'guest');
127
-	}
128
-
129
-	/**
130
-	 * @PublicPage
131
-	 * @NoCSRFRequired
132
-	 *
133
-	 * @param string $token
134
-	 *
135
-	 * @return TemplateResponse
136
-	 */
137
-	public function processMoreOptionsResult(string $token):TemplateResponse {
138
-		$partstat = $this->request->getParam('partStat');
139
-		$guests = (int) $this->request->getParam('guests');
140
-		$comment = $this->request->getParam('comment');
141
-
142
-		$row = $this->getTokenInformation($token);
143
-		if (!$row || !\in_array($partstat, ['ACCEPTED', 'DECLINED', 'TENTATIVE'])) {
144
-			return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest');
145
-		}
146
-
147
-		$iTipMessage = $this->buildITipResponse($row, $partstat, $guests, $comment);
148
-		$this->responseServer->handleITipMessage($iTipMessage);
149
-		if ($iTipMessage->getScheduleStatus() === '1.2') {
150
-			return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest');
151
-		}
152
-
153
-		return new TemplateResponse($this->appName, 'schedule-response-error', [
154
-			'organizer' => $row['organizer'],
155
-		], 'guest');
156
-	}
157
-
158
-	/**
159
-	 * @param string $token
160
-	 * @return array|null
161
-	 */
162
-	private function getTokenInformation(string $token) {
163
-		$query = $this->db->getQueryBuilder();
164
-		$query->select('*')
165
-			->from('calendar_invitations')
166
-			->where($query->expr()->eq('token', $query->createNamedParameter($token)));
167
-		$stmt = $query->execute();
168
-		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
169
-
170
-		if(!$row) {
171
-			return null;
172
-		}
173
-
174
-		$currentTime = $this->timeFactory->getTime();
175
-		if (((int) $row['expiration']) < $currentTime) {
176
-			return null;
177
-		}
178
-
179
-		return $row;
180
-	}
181
-
182
-	/**
183
-	 * @param array $row
184
-	 * @param string $partStat participation status of attendee - SEE RFC 5545
185
-	 * @param int|null $guests
186
-	 * @param string|null $comment
187
-	 * @return Message
188
-	 */
189
-	private function buildITipResponse(array $row, string $partStat, int $guests=null,
190
-									   string $comment=null):Message {
191
-		$iTipMessage = new Message();
192
-		$iTipMessage->uid = $row['uid'];
193
-		$iTipMessage->component = 'VEVENT';
194
-		$iTipMessage->method = 'REPLY';
195
-		$iTipMessage->sequence = $row['sequence'];
196
-		$iTipMessage->sender = $row['attendee'];
197
-		$iTipMessage->recipient = $row['organizer'];
198
-
199
-		$message = <<<EOF
37
+    /** @var IDBConnection */
38
+    private $db;
39
+
40
+    /** @var ITimeFactory */
41
+    private $timeFactory;
42
+
43
+    /** @var InvitationResponseServer */
44
+    private $responseServer;
45
+
46
+    /**
47
+     * InvitationResponseController constructor.
48
+     *
49
+     * @param string $appName
50
+     * @param IRequest $request
51
+     * @param IDBConnection $db
52
+     * @param ITimeFactory $timeFactory
53
+     * @param InvitationResponseServer $responseServer
54
+     */
55
+    public function __construct(string $appName, IRequest $request,
56
+                                IDBConnection $db, ITimeFactory $timeFactory,
57
+                                InvitationResponseServer $responseServer) {
58
+        parent::__construct($appName, $request);
59
+        $this->db = $db;
60
+        $this->timeFactory = $timeFactory;
61
+        $this->responseServer = $responseServer;
62
+        // Don't run `$server->exec()`, because we just need access to the
63
+        // fully initialized schedule plugin, but we don't want Sabre/DAV
64
+        // to actually handle and reply to the request
65
+    }
66
+
67
+    /**
68
+     * @PublicPage
69
+     * @NoCSRFRequired
70
+     *
71
+     * @param string $token
72
+     * @return TemplateResponse
73
+     */
74
+    public function accept(string $token):TemplateResponse {
75
+        $row = $this->getTokenInformation($token);
76
+        if (!$row) {
77
+            return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest');
78
+        }
79
+
80
+        $iTipMessage = $this->buildITipResponse($row, 'ACCEPTED');
81
+        $this->responseServer->handleITipMessage($iTipMessage);
82
+        if ($iTipMessage->getScheduleStatus() === '1.2') {
83
+            return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest');
84
+        }
85
+
86
+        return new TemplateResponse($this->appName, 'schedule-response-error', [
87
+            'organizer' => $row['organizer'],
88
+        ], 'guest');
89
+    }
90
+
91
+    /**
92
+     * @PublicPage
93
+     * @NoCSRFRequired
94
+     *
95
+     * @param string $token
96
+     * @return TemplateResponse
97
+     */
98
+    public function decline(string $token):TemplateResponse {
99
+        $row = $this->getTokenInformation($token);
100
+        if (!$row) {
101
+            return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest');
102
+        }
103
+
104
+        $iTipMessage = $this->buildITipResponse($row, 'DECLINED');
105
+        $this->responseServer->handleITipMessage($iTipMessage);
106
+
107
+        if ($iTipMessage->getScheduleStatus() === '1.2') {
108
+            return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest');
109
+        }
110
+
111
+        return new TemplateResponse($this->appName, 'schedule-response-error', [
112
+            'organizer' => $row['organizer'],
113
+        ], 'guest');
114
+    }
115
+
116
+    /**
117
+     * @PublicPage
118
+     * @NoCSRFRequired
119
+     *
120
+     * @param string $token
121
+     * @return TemplateResponse
122
+     */
123
+    public function options(string $token):TemplateResponse {
124
+        return new TemplateResponse($this->appName, 'schedule-response-options', [
125
+            'token' => $token
126
+        ], 'guest');
127
+    }
128
+
129
+    /**
130
+     * @PublicPage
131
+     * @NoCSRFRequired
132
+     *
133
+     * @param string $token
134
+     *
135
+     * @return TemplateResponse
136
+     */
137
+    public function processMoreOptionsResult(string $token):TemplateResponse {
138
+        $partstat = $this->request->getParam('partStat');
139
+        $guests = (int) $this->request->getParam('guests');
140
+        $comment = $this->request->getParam('comment');
141
+
142
+        $row = $this->getTokenInformation($token);
143
+        if (!$row || !\in_array($partstat, ['ACCEPTED', 'DECLINED', 'TENTATIVE'])) {
144
+            return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest');
145
+        }
146
+
147
+        $iTipMessage = $this->buildITipResponse($row, $partstat, $guests, $comment);
148
+        $this->responseServer->handleITipMessage($iTipMessage);
149
+        if ($iTipMessage->getScheduleStatus() === '1.2') {
150
+            return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest');
151
+        }
152
+
153
+        return new TemplateResponse($this->appName, 'schedule-response-error', [
154
+            'organizer' => $row['organizer'],
155
+        ], 'guest');
156
+    }
157
+
158
+    /**
159
+     * @param string $token
160
+     * @return array|null
161
+     */
162
+    private function getTokenInformation(string $token) {
163
+        $query = $this->db->getQueryBuilder();
164
+        $query->select('*')
165
+            ->from('calendar_invitations')
166
+            ->where($query->expr()->eq('token', $query->createNamedParameter($token)));
167
+        $stmt = $query->execute();
168
+        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
169
+
170
+        if(!$row) {
171
+            return null;
172
+        }
173
+
174
+        $currentTime = $this->timeFactory->getTime();
175
+        if (((int) $row['expiration']) < $currentTime) {
176
+            return null;
177
+        }
178
+
179
+        return $row;
180
+    }
181
+
182
+    /**
183
+     * @param array $row
184
+     * @param string $partStat participation status of attendee - SEE RFC 5545
185
+     * @param int|null $guests
186
+     * @param string|null $comment
187
+     * @return Message
188
+     */
189
+    private function buildITipResponse(array $row, string $partStat, int $guests=null,
190
+                                        string $comment=null):Message {
191
+        $iTipMessage = new Message();
192
+        $iTipMessage->uid = $row['uid'];
193
+        $iTipMessage->component = 'VEVENT';
194
+        $iTipMessage->method = 'REPLY';
195
+        $iTipMessage->sequence = $row['sequence'];
196
+        $iTipMessage->sender = $row['attendee'];
197
+        $iTipMessage->recipient = $row['organizer'];
198
+
199
+        $message = <<<EOF
200 200
 BEGIN:VCALENDAR
201 201
 PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
202 202
 METHOD:REPLY
@@ -211,26 +211,26 @@  discard block
 block discarded – undo
211 211
 END:VCALENDAR
212 212
 EOF;
213 213
 
214
-		$vObject = Reader::read(vsprintf($message, [
215
-			$partStat, $row['attendee'], $row['organizer'],
216
-			$row['uid'], $row['sequence'] ?? 0, $row['recurrenceid'] ?? ''
217
-		]));
218
-		$vEvent = $vObject->{'VEVENT'};
219
-		/** @var \Sabre\VObject\Property\ICalendar\CalAddress $attendee */
220
-		$attendee = $vEvent->{'ATTENDEE'};
214
+        $vObject = Reader::read(vsprintf($message, [
215
+            $partStat, $row['attendee'], $row['organizer'],
216
+            $row['uid'], $row['sequence'] ?? 0, $row['recurrenceid'] ?? ''
217
+        ]));
218
+        $vEvent = $vObject->{'VEVENT'};
219
+        /** @var \Sabre\VObject\Property\ICalendar\CalAddress $attendee */
220
+        $attendee = $vEvent->{'ATTENDEE'};
221 221
 
222
-		$vEvent->DTSTAMP = date('Ymd\\THis\\Z', $this->timeFactory->getTime());
222
+        $vEvent->DTSTAMP = date('Ymd\\THis\\Z', $this->timeFactory->getTime());
223 223
 
224
-		if ($comment) {
225
-			$attendee->add('X-RESPONSE-COMMENT', $comment);
226
-			$vEvent->add('COMMENT', $comment);
227
-		}
228
-		if ($guests) {
229
-			$attendee->add('X-NUM-GUESTS', $guests);
230
-		}
224
+        if ($comment) {
225
+            $attendee->add('X-RESPONSE-COMMENT', $comment);
226
+            $vEvent->add('COMMENT', $comment);
227
+        }
228
+        if ($guests) {
229
+            $attendee->add('X-NUM-GUESTS', $guests);
230
+        }
231 231
 
232
-		$iTipMessage->message = $vObject;
232
+        $iTipMessage->message = $vObject;
233 233
 
234
-		return $iTipMessage;
235
-	}
234
+        return $iTipMessage;
235
+    }
236 236
 }
Please login to merge, or discard this patch.