Completed
Push — master ( 40b79f...8d55b1 )
by Joas
30:25
created
lib/public/Comments/ICommentsManager.php 1 patch
Indentation   +471 added lines, -471 removed lines patch added patch discarded remove patch
@@ -18,475 +18,475 @@
 block discarded – undo
18 18
  * @since 9.0.0
19 19
  */
20 20
 interface ICommentsManager {
21
-	/**
22
-	 * @const DELETED_USER type and id for a user that has been deleted
23
-	 * @see deleteReferencesOfActor
24
-	 * @since 9.0.0
25
-	 *
26
-	 * To be used as replacement for user type actors in deleteReferencesOfActor().
27
-	 *
28
-	 * User interfaces shall show "Deleted user" as display name, if needed.
29
-	 */
30
-	public const DELETED_USER = 'deleted_users';
31
-
32
-	/**
33
-	 * returns a comment instance
34
-	 *
35
-	 * @param string $id the ID of the comment
36
-	 * @return IComment
37
-	 * @throws NotFoundException
38
-	 * @since 9.0.0
39
-	 */
40
-	public function get($id);
41
-
42
-	/**
43
-	 * Returns the comment specified by the id and all it's child comments
44
-	 *
45
-	 * @param string $id
46
-	 * @param int $limit max number of entries to return, 0 returns all
47
-	 * @param int $offset the start entry
48
-	 * @return array{comment: IComment, replies: list<array{comment: IComment, replies: array<empty, empty>}>}
49
-	 * @since 9.0.0
50
-	 *
51
-	 * The return array looks like this
52
-	 * [
53
-	 * 	 'comment' => IComment, // root comment
54
-	 *   'replies' =>
55
-	 *   [
56
-	 *     0 =>
57
-	 *     [
58
-	 *       'comment' => IComment,
59
-	 *       'replies' =>
60
-	 *       [
61
-	 *         0 =>
62
-	 *         [
63
-	 *           'comment' => IComment,
64
-	 *           'replies' => [ … ]
65
-	 *         ],
66
-	 *         …
67
-	 *       ]
68
-	 *     ]
69
-	 *     1 =>
70
-	 *     [
71
-	 *       'comment' => IComment,
72
-	 *       'replies'=> [ … ]
73
-	 *     ],
74
-	 *     …
75
-	 *   ]
76
-	 * ]
77
-	 */
78
-	public function getTree($id, $limit = 0, $offset = 0);
79
-
80
-	/**
81
-	 * returns comments for a specific object (e.g. a file).
82
-	 *
83
-	 * The sort order is always newest to oldest.
84
-	 *
85
-	 * @param string $objectType the object type, e.g. 'files'
86
-	 * @param string $objectId the id of the object
87
-	 * @param int $limit optional, number of maximum comments to be returned. if
88
-	 *                   not specified, all comments are returned.
89
-	 * @param int $offset optional, starting point
90
-	 * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
91
-	 *                                     that may be returned
92
-	 * @return list<IComment>
93
-	 * @since 9.0.0
94
-	 */
95
-	public function getForObject(
96
-		$objectType,
97
-		$objectId,
98
-		$limit = 0,
99
-		$offset = 0,
100
-		?\DateTime $notOlderThan = null,
101
-	);
102
-
103
-	/**
104
-	 * @param string $objectType the object type, e.g. 'files'
105
-	 * @param string $objectId the id of the object
106
-	 * @param int $lastKnownCommentId the last known comment (will be used as offset)
107
-	 * @param string $sortDirection direction of the comments (`asc` or `desc`)
108
-	 * @param int $limit optional, number of maximum comments to be returned. if
109
-	 *                   set to 0, all comments are returned.
110
-	 * @param bool $includeLastKnown
111
-	 * @param string $topmostParentId Limit the comments to a list of replies and its original root comment
112
-	 * @return list<IComment>
113
-	 * @since 14.0.0
114
-	 * @deprecated 24.0.0 - Use getCommentsWithVerbForObjectSinceComment instead
115
-	 */
116
-	public function getForObjectSince(
117
-		string $objectType,
118
-		string $objectId,
119
-		int $lastKnownCommentId,
120
-		string $sortDirection = 'asc',
121
-		int $limit = 30,
122
-		bool $includeLastKnown = false,
123
-		string $topmostParentId = '',
124
-	): array;
125
-
126
-	/**
127
-	 * @param string $objectType the object type, e.g. 'files'
128
-	 * @param string $objectId the id of the object
129
-	 * @param string[] $verbs List of verbs to filter by
130
-	 * @param int $lastKnownCommentId the last known comment (will be used as offset)
131
-	 * @param string $sortDirection direction of the comments (`asc` or `desc`)
132
-	 * @param int $limit optional, number of maximum comments to be returned. if
133
-	 *                   set to 0, all comments are returned.
134
-	 * @param bool $includeLastKnown
135
-	 * @param string $topmostParentId Limit the comments to a list of replies and its original root comment
136
-	 * @return list<IComment>
137
-	 * @since 24.0.0
138
-	 */
139
-	public function getCommentsWithVerbForObjectSinceComment(
140
-		string $objectType,
141
-		string $objectId,
142
-		array $verbs,
143
-		int $lastKnownCommentId,
144
-		string $sortDirection = 'asc',
145
-		int $limit = 30,
146
-		bool $includeLastKnown = false,
147
-		string $topmostParentId = '',
148
-	): array;
149
-
150
-	/**
151
-	 * Search for comments with a given content
152
-	 *
153
-	 * @param string $search content to search for
154
-	 * @param string $objectType Limit the search by object type
155
-	 * @param string $objectId Limit the search by object id
156
-	 * @param string $verb Limit the verb of the comment
157
-	 * @param int $offset
158
-	 * @param int $limit
159
-	 * @return list<IComment>
160
-	 * @since 14.0.0
161
-	 */
162
-	public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array;
163
-
164
-	/**
165
-	 * Search for comments on one or more objects with a given content
166
-	 *
167
-	 * @param string $search content to search for
168
-	 * @param string $objectType Limit the search by object type
169
-	 * @param array $objectIds Limit the search by object ids
170
-	 * @param string $verb Limit the verb of the comment
171
-	 * @param int $offset
172
-	 * @param int $limit
173
-	 * @return IComment[]
174
-	 * @since 21.0.0
175
-	 */
176
-	public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array;
177
-
178
-	/**
179
-	 * @param $objectType string the object type, e.g. 'files'
180
-	 * @param $objectId string the id of the object
181
-	 * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
182
-	 *                                     that may be returned
183
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
184
-	 * @return int
185
-	 * @since 9.0.0
186
-	 */
187
-	public function getNumberOfCommentsForObject($objectType, $objectId, ?\DateTime $notOlderThan = null, $verb = '');
188
-
189
-	/**
190
-	 * @param $objectType string the object type, e.g. 'files'
191
-	 * @param $objectIds string[] the ids of the object
192
-	 * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
193
-	 *                                     that may be returned
194
-	 * @param string $verb Limit the verb of the comment
195
-	 * @return array<string, int>
196
-	 * @since 32.0.0
197
-	 */
198
-	public function getNumberOfCommentsForObjects(string $objectType, array $objectIds, ?\DateTime $notOlderThan = null, string $verb = ''): array;
199
-
200
-	/**
201
-	 * @param string $objectType the object type, e.g. 'files'
202
-	 * @param string[] $objectIds the id of the object
203
-	 * @param IUser $user
204
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
205
-	 * @return array Map with object id => # of unread comments
206
-	 * @psalm-return array<string, int>
207
-	 * @since 21.0.0
208
-	 */
209
-	public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array;
210
-
211
-	/**
212
-	 * @param string $objectType
213
-	 * @param string $objectId
214
-	 * @param int $lastRead
215
-	 * @param string $verb
216
-	 * @return int
217
-	 * @since 21.0.0
218
-	 * @deprecated 24.0.0 - Use getNumberOfCommentsWithVerbsForObjectSinceComment instead
219
-	 */
220
-	public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int;
221
-
222
-
223
-	/**
224
-	 * @param string $objectType
225
-	 * @param string $objectId
226
-	 * @param int $lastRead
227
-	 * @param string[] $verbs
228
-	 * @return int
229
-	 * @since 24.0.0
230
-	 */
231
-	public function getNumberOfCommentsWithVerbsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, array $verbs): int;
232
-
233
-	/**
234
-	 * @param string $objectType
235
-	 * @param string $objectId
236
-	 * @param \DateTime $beforeDate
237
-	 * @param string $verb
238
-	 * @return int
239
-	 * @since 21.0.0
240
-	 */
241
-	public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int;
242
-
243
-	/**
244
-	 * @param string $objectType
245
-	 * @param string $objectId
246
-	 * @param string $verb
247
-	 * @param string $actorType
248
-	 * @param string[] $actors
249
-	 * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
250
-	 * @psalm-return array<string, \DateTime>
251
-	 * @since 21.0.0
252
-	 */
253
-	public function getLastCommentDateByActor(
254
-		string $objectType,
255
-		string $objectId,
256
-		string $verb,
257
-		string $actorType,
258
-		array $actors,
259
-	): array;
260
-
261
-	/**
262
-	 * Get the number of unread comments for all files in a folder
263
-	 *
264
-	 * @param int $folderId
265
-	 * @param IUser $user
266
-	 * @return array [$fileId => $unreadCount]
267
-	 * @since 12.0.0
268
-	 * @deprecated 29.0.0 use getNumberOfUnreadCommentsForObjects instead
269
-	 */
270
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
271
-
272
-	/**
273
-	 * creates a new comment and returns it. At this point of time, it is not
274
-	 * saved in the used data storage. Use save() after setting other fields
275
-	 * of the comment (e.g. message or verb).
276
-	 *
277
-	 * @param string $actorType the actor type (e.g. 'users')
278
-	 * @param string $actorId a user id
279
-	 * @param string $objectType the object type the comment is attached to
280
-	 * @param string $objectId the object id the comment is attached to
281
-	 * @return IComment
282
-	 * @since 9.0.0
283
-	 */
284
-	public function create($actorType, $actorId, $objectType, $objectId);
285
-
286
-	/**
287
-	 * permanently deletes the comment specified by the ID
288
-	 *
289
-	 * When the comment has child comments, their parent ID will be changed to
290
-	 * the parent ID of the item that is to be deleted.
291
-	 *
292
-	 * @param string $id
293
-	 * @return bool
294
-	 * @since 9.0.0
295
-	 */
296
-	public function delete($id);
297
-
298
-	/**
299
-	 * Get comment related with user reaction
300
-	 *
301
-	 * Throws PreConditionNotMetException when the system haven't the minimum requirements to
302
-	 * use reactions
303
-	 *
304
-	 * @param int $parentId
305
-	 * @param string $actorType
306
-	 * @param string $actorId
307
-	 * @param string $reaction
308
-	 * @return IComment
309
-	 * @throws NotFoundException
310
-	 * @throws PreConditionNotMetException
311
-	 * @since 24.0.0
312
-	 */
313
-	public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment;
314
-
315
-	/**
316
-	 * Retrieve all reactions of a message
317
-	 *
318
-	 * Throws PreConditionNotMetException when the system haven't the minimum requirements to
319
-	 * use reactions
320
-	 *
321
-	 * @param int $parentId
322
-	 * @return IComment[]
323
-	 * @throws PreConditionNotMetException
324
-	 * @since 24.0.0
325
-	 */
326
-	public function retrieveAllReactions(int $parentId): array;
327
-
328
-	/**
329
-	 * Retrieve all reactions with specific reaction of a message
330
-	 *
331
-	 * Throws PreConditionNotMetException when the system haven't the minimum requirements to
332
-	 * use reactions
333
-	 *
334
-	 * @param int $parentId
335
-	 * @param string $reaction
336
-	 * @return IComment[]
337
-	 * @throws PreConditionNotMetException
338
-	 * @since 24.0.0
339
-	 */
340
-	public function retrieveAllReactionsWithSpecificReaction(int $parentId, string $reaction): array;
341
-
342
-	/**
343
-	 * Support reactions
344
-	 *
345
-	 * @return bool
346
-	 * @since 24.0.0
347
-	 */
348
-	public function supportReactions(): bool;
349
-
350
-	/**
351
-	 * saves the comment permanently
352
-	 *
353
-	 * if the supplied comment has an empty ID, a new entry comment will be
354
-	 * saved and the instance updated with the new ID.
355
-	 *
356
-	 * Otherwise, an existing comment will be updated.
357
-	 *
358
-	 * Throws NotFoundException when a comment that is to be updated does not
359
-	 * exist anymore at this point of time.
360
-	 *
361
-	 * @param IComment $comment
362
-	 * @return bool
363
-	 * @throws NotFoundException
364
-	 * @since 9.0.0
365
-	 */
366
-	public function save(IComment $comment);
367
-
368
-	/**
369
-	 * Deletes all references to specific actor (e.g. on user delete) of a comment.
370
-	 * The comment itself must not get lost/deleted.
371
-	 *
372
-	 * A 'users' type actor (type and id) should get replaced by the
373
-	 * value of the DELETED_USER constant of this interface.
374
-	 *
375
-	 * @param string $actorType the actor type (e.g. 'users')
376
-	 * @param string $actorId a user id
377
-	 * @return boolean whether the deletion was successful
378
-	 * @since 9.0.0
379
-	 */
380
-	public function deleteReferencesOfActor($actorType, $actorId);
381
-
382
-	/**
383
-	 * Deletes all comments made of a specific object (e.g. on file delete).
384
-	 *
385
-	 * @param string $objectType the object type (e.g. 'files')
386
-	 * @param string $objectId e.g. the file id
387
-	 * @return boolean whether the deletion was successful
388
-	 * @since 9.0.0
389
-	 */
390
-	public function deleteCommentsAtObject($objectType, $objectId);
391
-
392
-	/**
393
-	 * sets the read marker for a given file to the specified date for the
394
-	 * provided user
395
-	 *
396
-	 * @param string $objectType
397
-	 * @param string $objectId
398
-	 * @param \DateTime $dateTime
399
-	 * @param \OCP\IUser $user
400
-	 * @since 9.0.0
401
-	 */
402
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
403
-
404
-	/**
405
-	 * returns the read marker for a given file to the specified date for the
406
-	 * provided user. It returns null, when the marker is not present, i.e.
407
-	 * no comments were marked as read.
408
-	 *
409
-	 * @param string $objectType
410
-	 * @param string $objectId
411
-	 * @param \OCP\IUser $user
412
-	 * @return \DateTime|null
413
-	 * @since 9.0.0
414
-	 */
415
-	public function getReadMark($objectType, $objectId, \OCP\IUser $user);
416
-
417
-	/**
418
-	 * deletes the read markers for the specified user
419
-	 *
420
-	 * @param \OCP\IUser $user
421
-	 * @return bool
422
-	 * @since 9.0.0
423
-	 */
424
-	public function deleteReadMarksFromUser(\OCP\IUser $user);
425
-
426
-	/**
427
-	 * deletes the read markers on the specified object
428
-	 *
429
-	 * @param string $objectType
430
-	 * @param string $objectId
431
-	 * @return bool
432
-	 * @since 9.0.0
433
-	 */
434
-	public function deleteReadMarksOnObject($objectType, $objectId);
435
-
436
-	/**
437
-	 * registers an Entity to the manager, so event notifications can be send
438
-	 * to consumers of the comments infrastructure
439
-	 *
440
-	 * @param \Closure $closure
441
-	 * @return void
442
-	 * @since 11.0.0
443
-	 */
444
-	public function registerEventHandler(\Closure $closure);
445
-
446
-	/**
447
-	 * registers a method that resolves an ID to a display name for a given type
448
-	 *
449
-	 * @param string $type
450
-	 * @param \Closure $closure
451
-	 * @return void
452
-	 * @throws \OutOfBoundsException
453
-	 * @since 11.0.0
454
-	 *
455
-	 * Only one resolver shall be registered per type. Otherwise a
456
-	 * \OutOfBoundsException has to thrown.
457
-	 */
458
-	public function registerDisplayNameResolver($type, \Closure $closure);
459
-
460
-	/**
461
-	 * resolves a given ID of a given Type to a display name.
462
-	 *
463
-	 * @param string $type
464
-	 * @param string $id
465
-	 * @return string
466
-	 * @throws \OutOfBoundsException
467
-	 * @since 11.0.0
468
-	 *
469
-	 * If a provided type was not registered, an \OutOfBoundsException shall
470
-	 * be thrown. It is upon the resolver discretion what to return of the
471
-	 * provided ID is unknown. It must be ensured that a string is returned.
472
-	 */
473
-	public function resolveDisplayName($type, $id);
474
-
475
-	/**
476
-	 * Load the Comments app into the page
477
-	 *
478
-	 * @since 21.0.0
479
-	 */
480
-	public function load(): void;
481
-
482
-	/**
483
-	 * Delete comments with field expire_date less than current date
484
-	 * Only will delete the message related with the object.
485
-	 *
486
-	 * @param string $objectType the object type (e.g. 'files')
487
-	 * @param string $objectId e.g. the file id, leave empty to expire on all objects of this type
488
-	 * @return boolean true if at least one row was deleted
489
-	 * @since 25.0.0
490
-	 */
491
-	public function deleteCommentsExpiredAtObject(string $objectType, string $objectId = ''): bool;
21
+    /**
22
+     * @const DELETED_USER type and id for a user that has been deleted
23
+     * @see deleteReferencesOfActor
24
+     * @since 9.0.0
25
+     *
26
+     * To be used as replacement for user type actors in deleteReferencesOfActor().
27
+     *
28
+     * User interfaces shall show "Deleted user" as display name, if needed.
29
+     */
30
+    public const DELETED_USER = 'deleted_users';
31
+
32
+    /**
33
+     * returns a comment instance
34
+     *
35
+     * @param string $id the ID of the comment
36
+     * @return IComment
37
+     * @throws NotFoundException
38
+     * @since 9.0.0
39
+     */
40
+    public function get($id);
41
+
42
+    /**
43
+     * Returns the comment specified by the id and all it's child comments
44
+     *
45
+     * @param string $id
46
+     * @param int $limit max number of entries to return, 0 returns all
47
+     * @param int $offset the start entry
48
+     * @return array{comment: IComment, replies: list<array{comment: IComment, replies: array<empty, empty>}>}
49
+     * @since 9.0.0
50
+     *
51
+     * The return array looks like this
52
+     * [
53
+     * 	 'comment' => IComment, // root comment
54
+     *   'replies' =>
55
+     *   [
56
+     *     0 =>
57
+     *     [
58
+     *       'comment' => IComment,
59
+     *       'replies' =>
60
+     *       [
61
+     *         0 =>
62
+     *         [
63
+     *           'comment' => IComment,
64
+     *           'replies' => [ … ]
65
+     *         ],
66
+     *         …
67
+     *       ]
68
+     *     ]
69
+     *     1 =>
70
+     *     [
71
+     *       'comment' => IComment,
72
+     *       'replies'=> [ … ]
73
+     *     ],
74
+     *     …
75
+     *   ]
76
+     * ]
77
+     */
78
+    public function getTree($id, $limit = 0, $offset = 0);
79
+
80
+    /**
81
+     * returns comments for a specific object (e.g. a file).
82
+     *
83
+     * The sort order is always newest to oldest.
84
+     *
85
+     * @param string $objectType the object type, e.g. 'files'
86
+     * @param string $objectId the id of the object
87
+     * @param int $limit optional, number of maximum comments to be returned. if
88
+     *                   not specified, all comments are returned.
89
+     * @param int $offset optional, starting point
90
+     * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
91
+     *                                     that may be returned
92
+     * @return list<IComment>
93
+     * @since 9.0.0
94
+     */
95
+    public function getForObject(
96
+        $objectType,
97
+        $objectId,
98
+        $limit = 0,
99
+        $offset = 0,
100
+        ?\DateTime $notOlderThan = null,
101
+    );
102
+
103
+    /**
104
+     * @param string $objectType the object type, e.g. 'files'
105
+     * @param string $objectId the id of the object
106
+     * @param int $lastKnownCommentId the last known comment (will be used as offset)
107
+     * @param string $sortDirection direction of the comments (`asc` or `desc`)
108
+     * @param int $limit optional, number of maximum comments to be returned. if
109
+     *                   set to 0, all comments are returned.
110
+     * @param bool $includeLastKnown
111
+     * @param string $topmostParentId Limit the comments to a list of replies and its original root comment
112
+     * @return list<IComment>
113
+     * @since 14.0.0
114
+     * @deprecated 24.0.0 - Use getCommentsWithVerbForObjectSinceComment instead
115
+     */
116
+    public function getForObjectSince(
117
+        string $objectType,
118
+        string $objectId,
119
+        int $lastKnownCommentId,
120
+        string $sortDirection = 'asc',
121
+        int $limit = 30,
122
+        bool $includeLastKnown = false,
123
+        string $topmostParentId = '',
124
+    ): array;
125
+
126
+    /**
127
+     * @param string $objectType the object type, e.g. 'files'
128
+     * @param string $objectId the id of the object
129
+     * @param string[] $verbs List of verbs to filter by
130
+     * @param int $lastKnownCommentId the last known comment (will be used as offset)
131
+     * @param string $sortDirection direction of the comments (`asc` or `desc`)
132
+     * @param int $limit optional, number of maximum comments to be returned. if
133
+     *                   set to 0, all comments are returned.
134
+     * @param bool $includeLastKnown
135
+     * @param string $topmostParentId Limit the comments to a list of replies and its original root comment
136
+     * @return list<IComment>
137
+     * @since 24.0.0
138
+     */
139
+    public function getCommentsWithVerbForObjectSinceComment(
140
+        string $objectType,
141
+        string $objectId,
142
+        array $verbs,
143
+        int $lastKnownCommentId,
144
+        string $sortDirection = 'asc',
145
+        int $limit = 30,
146
+        bool $includeLastKnown = false,
147
+        string $topmostParentId = '',
148
+    ): array;
149
+
150
+    /**
151
+     * Search for comments with a given content
152
+     *
153
+     * @param string $search content to search for
154
+     * @param string $objectType Limit the search by object type
155
+     * @param string $objectId Limit the search by object id
156
+     * @param string $verb Limit the verb of the comment
157
+     * @param int $offset
158
+     * @param int $limit
159
+     * @return list<IComment>
160
+     * @since 14.0.0
161
+     */
162
+    public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array;
163
+
164
+    /**
165
+     * Search for comments on one or more objects with a given content
166
+     *
167
+     * @param string $search content to search for
168
+     * @param string $objectType Limit the search by object type
169
+     * @param array $objectIds Limit the search by object ids
170
+     * @param string $verb Limit the verb of the comment
171
+     * @param int $offset
172
+     * @param int $limit
173
+     * @return IComment[]
174
+     * @since 21.0.0
175
+     */
176
+    public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array;
177
+
178
+    /**
179
+     * @param $objectType string the object type, e.g. 'files'
180
+     * @param $objectId string the id of the object
181
+     * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
182
+     *                                     that may be returned
183
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
184
+     * @return int
185
+     * @since 9.0.0
186
+     */
187
+    public function getNumberOfCommentsForObject($objectType, $objectId, ?\DateTime $notOlderThan = null, $verb = '');
188
+
189
+    /**
190
+     * @param $objectType string the object type, e.g. 'files'
191
+     * @param $objectIds string[] the ids of the object
192
+     * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
193
+     *                                     that may be returned
194
+     * @param string $verb Limit the verb of the comment
195
+     * @return array<string, int>
196
+     * @since 32.0.0
197
+     */
198
+    public function getNumberOfCommentsForObjects(string $objectType, array $objectIds, ?\DateTime $notOlderThan = null, string $verb = ''): array;
199
+
200
+    /**
201
+     * @param string $objectType the object type, e.g. 'files'
202
+     * @param string[] $objectIds the id of the object
203
+     * @param IUser $user
204
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
205
+     * @return array Map with object id => # of unread comments
206
+     * @psalm-return array<string, int>
207
+     * @since 21.0.0
208
+     */
209
+    public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array;
210
+
211
+    /**
212
+     * @param string $objectType
213
+     * @param string $objectId
214
+     * @param int $lastRead
215
+     * @param string $verb
216
+     * @return int
217
+     * @since 21.0.0
218
+     * @deprecated 24.0.0 - Use getNumberOfCommentsWithVerbsForObjectSinceComment instead
219
+     */
220
+    public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int;
221
+
222
+
223
+    /**
224
+     * @param string $objectType
225
+     * @param string $objectId
226
+     * @param int $lastRead
227
+     * @param string[] $verbs
228
+     * @return int
229
+     * @since 24.0.0
230
+     */
231
+    public function getNumberOfCommentsWithVerbsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, array $verbs): int;
232
+
233
+    /**
234
+     * @param string $objectType
235
+     * @param string $objectId
236
+     * @param \DateTime $beforeDate
237
+     * @param string $verb
238
+     * @return int
239
+     * @since 21.0.0
240
+     */
241
+    public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int;
242
+
243
+    /**
244
+     * @param string $objectType
245
+     * @param string $objectId
246
+     * @param string $verb
247
+     * @param string $actorType
248
+     * @param string[] $actors
249
+     * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
250
+     * @psalm-return array<string, \DateTime>
251
+     * @since 21.0.0
252
+     */
253
+    public function getLastCommentDateByActor(
254
+        string $objectType,
255
+        string $objectId,
256
+        string $verb,
257
+        string $actorType,
258
+        array $actors,
259
+    ): array;
260
+
261
+    /**
262
+     * Get the number of unread comments for all files in a folder
263
+     *
264
+     * @param int $folderId
265
+     * @param IUser $user
266
+     * @return array [$fileId => $unreadCount]
267
+     * @since 12.0.0
268
+     * @deprecated 29.0.0 use getNumberOfUnreadCommentsForObjects instead
269
+     */
270
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
271
+
272
+    /**
273
+     * creates a new comment and returns it. At this point of time, it is not
274
+     * saved in the used data storage. Use save() after setting other fields
275
+     * of the comment (e.g. message or verb).
276
+     *
277
+     * @param string $actorType the actor type (e.g. 'users')
278
+     * @param string $actorId a user id
279
+     * @param string $objectType the object type the comment is attached to
280
+     * @param string $objectId the object id the comment is attached to
281
+     * @return IComment
282
+     * @since 9.0.0
283
+     */
284
+    public function create($actorType, $actorId, $objectType, $objectId);
285
+
286
+    /**
287
+     * permanently deletes the comment specified by the ID
288
+     *
289
+     * When the comment has child comments, their parent ID will be changed to
290
+     * the parent ID of the item that is to be deleted.
291
+     *
292
+     * @param string $id
293
+     * @return bool
294
+     * @since 9.0.0
295
+     */
296
+    public function delete($id);
297
+
298
+    /**
299
+     * Get comment related with user reaction
300
+     *
301
+     * Throws PreConditionNotMetException when the system haven't the minimum requirements to
302
+     * use reactions
303
+     *
304
+     * @param int $parentId
305
+     * @param string $actorType
306
+     * @param string $actorId
307
+     * @param string $reaction
308
+     * @return IComment
309
+     * @throws NotFoundException
310
+     * @throws PreConditionNotMetException
311
+     * @since 24.0.0
312
+     */
313
+    public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment;
314
+
315
+    /**
316
+     * Retrieve all reactions of a message
317
+     *
318
+     * Throws PreConditionNotMetException when the system haven't the minimum requirements to
319
+     * use reactions
320
+     *
321
+     * @param int $parentId
322
+     * @return IComment[]
323
+     * @throws PreConditionNotMetException
324
+     * @since 24.0.0
325
+     */
326
+    public function retrieveAllReactions(int $parentId): array;
327
+
328
+    /**
329
+     * Retrieve all reactions with specific reaction of a message
330
+     *
331
+     * Throws PreConditionNotMetException when the system haven't the minimum requirements to
332
+     * use reactions
333
+     *
334
+     * @param int $parentId
335
+     * @param string $reaction
336
+     * @return IComment[]
337
+     * @throws PreConditionNotMetException
338
+     * @since 24.0.0
339
+     */
340
+    public function retrieveAllReactionsWithSpecificReaction(int $parentId, string $reaction): array;
341
+
342
+    /**
343
+     * Support reactions
344
+     *
345
+     * @return bool
346
+     * @since 24.0.0
347
+     */
348
+    public function supportReactions(): bool;
349
+
350
+    /**
351
+     * saves the comment permanently
352
+     *
353
+     * if the supplied comment has an empty ID, a new entry comment will be
354
+     * saved and the instance updated with the new ID.
355
+     *
356
+     * Otherwise, an existing comment will be updated.
357
+     *
358
+     * Throws NotFoundException when a comment that is to be updated does not
359
+     * exist anymore at this point of time.
360
+     *
361
+     * @param IComment $comment
362
+     * @return bool
363
+     * @throws NotFoundException
364
+     * @since 9.0.0
365
+     */
366
+    public function save(IComment $comment);
367
+
368
+    /**
369
+     * Deletes all references to specific actor (e.g. on user delete) of a comment.
370
+     * The comment itself must not get lost/deleted.
371
+     *
372
+     * A 'users' type actor (type and id) should get replaced by the
373
+     * value of the DELETED_USER constant of this interface.
374
+     *
375
+     * @param string $actorType the actor type (e.g. 'users')
376
+     * @param string $actorId a user id
377
+     * @return boolean whether the deletion was successful
378
+     * @since 9.0.0
379
+     */
380
+    public function deleteReferencesOfActor($actorType, $actorId);
381
+
382
+    /**
383
+     * Deletes all comments made of a specific object (e.g. on file delete).
384
+     *
385
+     * @param string $objectType the object type (e.g. 'files')
386
+     * @param string $objectId e.g. the file id
387
+     * @return boolean whether the deletion was successful
388
+     * @since 9.0.0
389
+     */
390
+    public function deleteCommentsAtObject($objectType, $objectId);
391
+
392
+    /**
393
+     * sets the read marker for a given file to the specified date for the
394
+     * provided user
395
+     *
396
+     * @param string $objectType
397
+     * @param string $objectId
398
+     * @param \DateTime $dateTime
399
+     * @param \OCP\IUser $user
400
+     * @since 9.0.0
401
+     */
402
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
403
+
404
+    /**
405
+     * returns the read marker for a given file to the specified date for the
406
+     * provided user. It returns null, when the marker is not present, i.e.
407
+     * no comments were marked as read.
408
+     *
409
+     * @param string $objectType
410
+     * @param string $objectId
411
+     * @param \OCP\IUser $user
412
+     * @return \DateTime|null
413
+     * @since 9.0.0
414
+     */
415
+    public function getReadMark($objectType, $objectId, \OCP\IUser $user);
416
+
417
+    /**
418
+     * deletes the read markers for the specified user
419
+     *
420
+     * @param \OCP\IUser $user
421
+     * @return bool
422
+     * @since 9.0.0
423
+     */
424
+    public function deleteReadMarksFromUser(\OCP\IUser $user);
425
+
426
+    /**
427
+     * deletes the read markers on the specified object
428
+     *
429
+     * @param string $objectType
430
+     * @param string $objectId
431
+     * @return bool
432
+     * @since 9.0.0
433
+     */
434
+    public function deleteReadMarksOnObject($objectType, $objectId);
435
+
436
+    /**
437
+     * registers an Entity to the manager, so event notifications can be send
438
+     * to consumers of the comments infrastructure
439
+     *
440
+     * @param \Closure $closure
441
+     * @return void
442
+     * @since 11.0.0
443
+     */
444
+    public function registerEventHandler(\Closure $closure);
445
+
446
+    /**
447
+     * registers a method that resolves an ID to a display name for a given type
448
+     *
449
+     * @param string $type
450
+     * @param \Closure $closure
451
+     * @return void
452
+     * @throws \OutOfBoundsException
453
+     * @since 11.0.0
454
+     *
455
+     * Only one resolver shall be registered per type. Otherwise a
456
+     * \OutOfBoundsException has to thrown.
457
+     */
458
+    public function registerDisplayNameResolver($type, \Closure $closure);
459
+
460
+    /**
461
+     * resolves a given ID of a given Type to a display name.
462
+     *
463
+     * @param string $type
464
+     * @param string $id
465
+     * @return string
466
+     * @throws \OutOfBoundsException
467
+     * @since 11.0.0
468
+     *
469
+     * If a provided type was not registered, an \OutOfBoundsException shall
470
+     * be thrown. It is upon the resolver discretion what to return of the
471
+     * provided ID is unknown. It must be ensured that a string is returned.
472
+     */
473
+    public function resolveDisplayName($type, $id);
474
+
475
+    /**
476
+     * Load the Comments app into the page
477
+     *
478
+     * @since 21.0.0
479
+     */
480
+    public function load(): void;
481
+
482
+    /**
483
+     * Delete comments with field expire_date less than current date
484
+     * Only will delete the message related with the object.
485
+     *
486
+     * @param string $objectType the object type (e.g. 'files')
487
+     * @param string $objectId e.g. the file id, leave empty to expire on all objects of this type
488
+     * @return boolean true if at least one row was deleted
489
+     * @since 25.0.0
490
+     */
491
+    public function deleteCommentsExpiredAtObject(string $objectType, string $objectId = ''): bool;
492 492
 }
Please login to merge, or discard this patch.
lib/private/OpenMetrics/Exporters/FilesByType.php 1 patch
Indentation   +47 added lines, -47 removed lines patch added patch discarded remove patch
@@ -23,58 +23,58 @@
 block discarded – undo
23 23
  * Cached exporter, refreshed every 30 minutes
24 24
  */
25 25
 class FilesByType extends Cached {
26
-	public function __construct(
27
-		ICacheFactory $cacheFactory,
28
-		private IDBConnection $connection,
29
-		private IMimeTypeLoader $mimetypeLoader,
30
-	) {
31
-		parent::__construct($cacheFactory);
32
-	}
26
+    public function __construct(
27
+        ICacheFactory $cacheFactory,
28
+        private IDBConnection $connection,
29
+        private IMimeTypeLoader $mimetypeLoader,
30
+    ) {
31
+        parent::__construct($cacheFactory);
32
+    }
33 33
 
34
-	#[Override]
35
-	public function name(): string {
36
-		return 'files';
37
-	}
34
+    #[Override]
35
+    public function name(): string {
36
+        return 'files';
37
+    }
38 38
 
39
-	#[Override]
40
-	public function type(): MetricType {
41
-		return MetricType::gauge;
42
-	}
39
+    #[Override]
40
+    public function type(): MetricType {
41
+        return MetricType::gauge;
42
+    }
43 43
 
44
-	#[Override]
45
-	public function unit(): string {
46
-		return 'files';
47
-	}
44
+    #[Override]
45
+    public function unit(): string {
46
+        return 'files';
47
+    }
48 48
 
49
-	#[Override]
50
-	public function help(): string {
51
-		return 'Number of files by type';
52
-	}
49
+    #[Override]
50
+    public function help(): string {
51
+        return 'Number of files by type';
52
+    }
53 53
 
54
-	#[Override]
55
-	public function getTTL(): int {
56
-		return 30 * 60;
57
-	}
54
+    #[Override]
55
+    public function getTTL(): int {
56
+        return 30 * 60;
57
+    }
58 58
 
59
-	#[Override]
60
-	public function gatherMetrics(): Generator {
61
-		$qb = $this->connection->getQueryBuilder()->runAcrossAllShards();
62
-		$metrics = $qb->select('mimetype', $qb->func()->count('*', 'count'))
63
-			->from('filecache')
64
-			->groupBy('mimetype')
65
-			->executeQuery();
59
+    #[Override]
60
+    public function gatherMetrics(): Generator {
61
+        $qb = $this->connection->getQueryBuilder()->runAcrossAllShards();
62
+        $metrics = $qb->select('mimetype', $qb->func()->count('*', 'count'))
63
+            ->from('filecache')
64
+            ->groupBy('mimetype')
65
+            ->executeQuery();
66 66
 
67
-		if ($metrics->rowCount() === 0) {
68
-			yield new Metric(0);
69
-			return;
70
-		}
71
-		$now = time();
72
-		foreach ($metrics->iterateAssociative() as $count) {
73
-			yield new Metric(
74
-				$count['count'],
75
-				['mimetype' => $this->mimetypeLoader->getMimetypeById($count['mimetype']) ?? ''],
76
-				$now,
77
-			);
78
-		}
79
-	}
67
+        if ($metrics->rowCount() === 0) {
68
+            yield new Metric(0);
69
+            return;
70
+        }
71
+        $now = time();
72
+        foreach ($metrics->iterateAssociative() as $count) {
73
+            yield new Metric(
74
+                $count['count'],
75
+                ['mimetype' => $this->mimetypeLoader->getMimetypeById($count['mimetype']) ?? ''],
76
+                $now,
77
+            );
78
+        }
79
+    }
80 80
 }
Please login to merge, or discard this patch.
apps/webhook_listeners/lib/Service/TokenService.php 1 patch
Indentation   +129 added lines, -129 removed lines patch added patch discarded remove patch
@@ -21,140 +21,140 @@
 block discarded – undo
21 21
 use Psr\Log\LoggerInterface;
22 22
 
23 23
 class TokenService {
24
-	public function __construct(
25
-		private IProvider $tokenProvider,
26
-		private IURLGenerator $urlGenerator,
27
-		private ISecureRandom $random,
28
-		private EphemeralTokenMapper $tokenMapper,
29
-		private LoggerInterface $logger,
30
-		private ITimeFactory $time,
31
-		private IFactory $l10nFactory,
32
-		private IUserManager $userManager,
33
-	) {
34
-	}
24
+    public function __construct(
25
+        private IProvider $tokenProvider,
26
+        private IURLGenerator $urlGenerator,
27
+        private ISecureRandom $random,
28
+        private EphemeralTokenMapper $tokenMapper,
29
+        private LoggerInterface $logger,
30
+        private ITimeFactory $time,
31
+        private IFactory $l10nFactory,
32
+        private IUserManager $userManager,
33
+    ) {
34
+    }
35 35
 
36
-	/**
37
-	 * creates an array which includes two arrays of tokens: 'user_ids' and 'user_roles'
38
-	 * The array ['user_ids' => ['jane', 'bob'], 'user_roles' => ['owner', 'trigger']]
39
-	 * as requested tokens in the registered webhook produces a result like
40
-	 * [
41
-	 * 		['user_ids' => [
42
-	 * 			['jane' => [
43
-	 * 				'userId' => 'jane',
44
-	 * 				'token' => 'abcdtokenabcd1'
45
-	 * 				'baseUrl' => 'https://nextcloud.example'
46
-	 * 			],
47
-	 * 			['bob'=> [
48
-	 * 				'userId' => 'bob',
49
-	 * 				'token' => 'abcdtokenabcd2'
50
-	 * 				'baseUrl' => 'https://nextcloud.example'
51
-	 * 			],
52
-	 * 		],
53
-	 * 		'owner' => [
54
-	 * 			'userId' => 'admin',
55
-	 * 			'token' => 'abcdtokenabcd3'
56
-	 * 			'baseUrl' => 'https://nextcloud.example'
57
-	 * 		],
58
-	 * 		'trigger' => [
59
-	 * 			'userId' => 'user1',
60
-	 * 			'token' => 'abcdtokenabcd4'
61
-	 * 			'baseUrl' => 'https://nextcloud.example'
62
-	 * 		],
63
-	 * ]
64
-	 * Created auth tokens are valid for 1 hour.
65
-	 *
66
-	 * @param WebhookListener $webhookListener
67
-	 * @param ?string $triggerUserId the user that triggered the webhook call
68
-	 * @return array{
69
-	 *     user_ids?: array<string, array{baseUrl: string, token: string, userId: mixed}>,
70
-	 *     trigger?: array{baseUrl: string, token: string, userId: string},
71
-	 *     owner?: array{baseUrl: string, token: string, userId: string},
72
-	 * }
73
-	 */
74
-	public function getTokens(WebhookListener $webhookListener, ?string $triggerUserId): array {
75
-		$tokens = [];
36
+    /**
37
+     * creates an array which includes two arrays of tokens: 'user_ids' and 'user_roles'
38
+     * The array ['user_ids' => ['jane', 'bob'], 'user_roles' => ['owner', 'trigger']]
39
+     * as requested tokens in the registered webhook produces a result like
40
+     * [
41
+     * 		['user_ids' => [
42
+     * 			['jane' => [
43
+     * 				'userId' => 'jane',
44
+     * 				'token' => 'abcdtokenabcd1'
45
+     * 				'baseUrl' => 'https://nextcloud.example'
46
+     * 			],
47
+     * 			['bob'=> [
48
+     * 				'userId' => 'bob',
49
+     * 				'token' => 'abcdtokenabcd2'
50
+     * 				'baseUrl' => 'https://nextcloud.example'
51
+     * 			],
52
+     * 		],
53
+     * 		'owner' => [
54
+     * 			'userId' => 'admin',
55
+     * 			'token' => 'abcdtokenabcd3'
56
+     * 			'baseUrl' => 'https://nextcloud.example'
57
+     * 		],
58
+     * 		'trigger' => [
59
+     * 			'userId' => 'user1',
60
+     * 			'token' => 'abcdtokenabcd4'
61
+     * 			'baseUrl' => 'https://nextcloud.example'
62
+     * 		],
63
+     * ]
64
+     * Created auth tokens are valid for 1 hour.
65
+     *
66
+     * @param WebhookListener $webhookListener
67
+     * @param ?string $triggerUserId the user that triggered the webhook call
68
+     * @return array{
69
+     *     user_ids?: array<string, array{baseUrl: string, token: string, userId: mixed}>,
70
+     *     trigger?: array{baseUrl: string, token: string, userId: string},
71
+     *     owner?: array{baseUrl: string, token: string, userId: string},
72
+     * }
73
+     */
74
+    public function getTokens(WebhookListener $webhookListener, ?string $triggerUserId): array {
75
+        $tokens = [];
76 76
 
77
-		$tokenNeeded = $webhookListener->getTokenNeeded();
78
-		if (isset($tokenNeeded['user_ids'])) {
79
-			$tokens = [
80
-				'user_ids' => [],
81
-			];
82
-			foreach ($tokenNeeded['user_ids'] as $userId) {
83
-				try {
84
-					$tokens['user_ids'][$userId] = [
85
-						'userId' => $userId,
86
-						'token' => $this->createEphemeralToken($userId),
87
-						'baseUrl' => $this->urlGenerator->getBaseUrl()
88
-					];
89
-				} catch (\Exception $e) {
90
-					$this->logger->error('Webhook token creation for user ' . $userId . ' failed: ' . $e->getMessage(), ['exception' => $e]);
91
-				}
77
+        $tokenNeeded = $webhookListener->getTokenNeeded();
78
+        if (isset($tokenNeeded['user_ids'])) {
79
+            $tokens = [
80
+                'user_ids' => [],
81
+            ];
82
+            foreach ($tokenNeeded['user_ids'] as $userId) {
83
+                try {
84
+                    $tokens['user_ids'][$userId] = [
85
+                        'userId' => $userId,
86
+                        'token' => $this->createEphemeralToken($userId),
87
+                        'baseUrl' => $this->urlGenerator->getBaseUrl()
88
+                    ];
89
+                } catch (\Exception $e) {
90
+                    $this->logger->error('Webhook token creation for user ' . $userId . ' failed: ' . $e->getMessage(), ['exception' => $e]);
91
+                }
92 92
 
93
-			}
94
-		}
95
-		if (isset($tokenNeeded['user_roles'])) {
96
-			foreach ($tokenNeeded['user_roles'] as $user_role) {
97
-				switch ($user_role) {
98
-					case 'owner':
99
-						// token for the person who created the flow
100
-						$ownerId = $webhookListener->getUserId();
101
-						if (is_null($ownerId)) { // no owner uid available
102
-							break;
103
-						}
104
-						$tokens['owner'] = [
105
-							'userId' => $ownerId,
106
-							'token' => $this->createEphemeralToken($ownerId),
107
-							'baseUrl' => $this->urlGenerator->getBaseUrl()
108
-						];
109
-						break;
110
-					case 'trigger':
111
-						// token for the person who triggered the webhook
112
-						if (is_null($triggerUserId)) { // no trigger uid available
113
-							break;
114
-						}
115
-						$tokens['trigger'] = [
116
-							'userId' => $triggerUserId,
117
-							'token' => $this->createEphemeralToken($triggerUserId),
118
-							'baseUrl' => $this->urlGenerator->getBaseUrl()
119
-						];
120
-						break;
121
-					default:
122
-						$this->logger->error('Webhook token creation for user role ' . $user_role . ' not defined. ', ['Not defined' => $user_role]);
93
+            }
94
+        }
95
+        if (isset($tokenNeeded['user_roles'])) {
96
+            foreach ($tokenNeeded['user_roles'] as $user_role) {
97
+                switch ($user_role) {
98
+                    case 'owner':
99
+                        // token for the person who created the flow
100
+                        $ownerId = $webhookListener->getUserId();
101
+                        if (is_null($ownerId)) { // no owner uid available
102
+                            break;
103
+                        }
104
+                        $tokens['owner'] = [
105
+                            'userId' => $ownerId,
106
+                            'token' => $this->createEphemeralToken($ownerId),
107
+                            'baseUrl' => $this->urlGenerator->getBaseUrl()
108
+                        ];
109
+                        break;
110
+                    case 'trigger':
111
+                        // token for the person who triggered the webhook
112
+                        if (is_null($triggerUserId)) { // no trigger uid available
113
+                            break;
114
+                        }
115
+                        $tokens['trigger'] = [
116
+                            'userId' => $triggerUserId,
117
+                            'token' => $this->createEphemeralToken($triggerUserId),
118
+                            'baseUrl' => $this->urlGenerator->getBaseUrl()
119
+                        ];
120
+                        break;
121
+                    default:
122
+                        $this->logger->error('Webhook token creation for user role ' . $user_role . ' not defined. ', ['Not defined' => $user_role]);
123 123
 
124
-				}
125
-			}
126
-		}
127
-		return $tokens;
128
-	}
129
-	private function createEphemeralToken(string $userId): string {
130
-		$token = $this->generateRandomDeviceToken();
124
+                }
125
+            }
126
+        }
127
+        return $tokens;
128
+    }
129
+    private function createEphemeralToken(string $userId): string {
130
+        $token = $this->generateRandomDeviceToken();
131 131
 
132
-		// we need the user`s language to have the token name showing up in the session list in the correct language
133
-		$user = $this->userManager->get($userId);
134
-		$lang = $this->l10nFactory->getUserLanguage($user);
135
-		$l = $this->l10nFactory->get('webhook_listeners', $lang);
136
-		$name = $l->t('Ephemeral webhook authentication');
137
-		$password = null;
138
-		$deviceToken = $this->tokenProvider->generateToken(
139
-			$token,
140
-			$userId,
141
-			$userId,
142
-			$password,
143
-			$name,
144
-			IToken::PERMANENT_TOKEN);
132
+        // we need the user`s language to have the token name showing up in the session list in the correct language
133
+        $user = $this->userManager->get($userId);
134
+        $lang = $this->l10nFactory->getUserLanguage($user);
135
+        $l = $this->l10nFactory->get('webhook_listeners', $lang);
136
+        $name = $l->t('Ephemeral webhook authentication');
137
+        $password = null;
138
+        $deviceToken = $this->tokenProvider->generateToken(
139
+            $token,
140
+            $userId,
141
+            $userId,
142
+            $password,
143
+            $name,
144
+            IToken::PERMANENT_TOKEN);
145 145
 
146
-		$this->tokenMapper->addEphemeralToken(
147
-			$deviceToken->getId(),
148
-			$userId,
149
-			$this->time->getTime());
150
-		return $token;
151
-	}
146
+        $this->tokenMapper->addEphemeralToken(
147
+            $deviceToken->getId(),
148
+            $userId,
149
+            $this->time->getTime());
150
+        return $token;
151
+    }
152 152
 
153
-	private function generateRandomDeviceToken(): string {
154
-		$groups = [];
155
-		for ($i = 0; $i < 5; $i++) {
156
-			$groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
157
-		}
158
-		return implode('-', $groups);
159
-	}
153
+    private function generateRandomDeviceToken(): string {
154
+        $groups = [];
155
+        for ($i = 0; $i < 5; $i++) {
156
+            $groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
157
+        }
158
+        return implode('-', $groups);
159
+    }
160 160
 }
Please login to merge, or discard this patch.
tests/lib/Comments/CommentTest.php 1 patch
Indentation   +200 added lines, -200 removed lines patch added patch discarded remove patch
@@ -15,204 +15,204 @@
 block discarded – undo
15 15
 use Test\TestCase;
16 16
 
17 17
 class CommentTest extends TestCase {
18
-	/**
19
-	 * @throws IllegalIDChangeException
20
-	 */
21
-	public function testSettersValidInput(): void {
22
-		$comment = new Comment();
23
-
24
-		$id = 'comment23';
25
-		$parentId = 'comment11.5';
26
-		$topMostParentId = 'comment11.0';
27
-		$childrenCount = 6;
28
-		$message = 'I like to comment comment';
29
-		$verb = 'comment';
30
-		$actor = ['type' => 'users', 'id' => 'alice'];
31
-		$creationDT = new \DateTime();
32
-		$latestChildDT = new \DateTime('yesterday');
33
-		$object = ['type' => 'files', 'id' => 'file64'];
34
-		$referenceId = sha1('referenceId');
35
-		$metaData = ['last_edit_actor_id' => 'admin'];
36
-
37
-		$comment
38
-			->setId($id)
39
-			->setParentId($parentId)
40
-			->setTopmostParentId($topMostParentId)
41
-			->setChildrenCount($childrenCount)
42
-			->setMessage($message)
43
-			->setVerb($verb)
44
-			->setActor($actor['type'], $actor['id'])
45
-			->setCreationDateTime($creationDT)
46
-			->setLatestChildDateTime($latestChildDT)
47
-			->setObject($object['type'], $object['id'])
48
-			->setReferenceId($referenceId)
49
-			->setMetaData($metaData);
50
-
51
-		$this->assertSame($id, $comment->getId());
52
-		$this->assertSame($parentId, $comment->getParentId());
53
-		$this->assertSame($topMostParentId, $comment->getTopmostParentId());
54
-		$this->assertSame($childrenCount, $comment->getChildrenCount());
55
-		$this->assertSame($message, $comment->getMessage());
56
-		$this->assertSame($verb, $comment->getVerb());
57
-		$this->assertSame($actor['type'], $comment->getActorType());
58
-		$this->assertSame($actor['id'], $comment->getActorId());
59
-		$this->assertSame($creationDT, $comment->getCreationDateTime());
60
-		$this->assertSame($latestChildDT, $comment->getLatestChildDateTime());
61
-		$this->assertSame($object['type'], $comment->getObjectType());
62
-		$this->assertSame($object['id'], $comment->getObjectId());
63
-		$this->assertSame($referenceId, $comment->getReferenceId());
64
-		$this->assertSame($metaData, $comment->getMetaData());
65
-	}
66
-
67
-
68
-	public function testSetIdIllegalInput(): void {
69
-		$this->expectException(IllegalIDChangeException::class);
70
-
71
-		$comment = new Comment();
72
-
73
-		$comment->setId('c23');
74
-		$comment->setId('c17');
75
-	}
76
-
77
-	/**
78
-	 * @throws IllegalIDChangeException
79
-	 */
80
-	public function testResetId(): void {
81
-		$comment = new Comment();
82
-		$comment->setId('c23');
83
-		$comment->setId('');
84
-
85
-		$this->assertSame('', $comment->getId());
86
-	}
87
-
88
-	public static function simpleSetterProvider(): array {
89
-		return [
90
-			['Id', true],
91
-			['TopmostParentId', true],
92
-			['ParentId', true],
93
-			['Message', true],
94
-			['Verb', true],
95
-			['Verb', ''],
96
-			['ChildrenCount', true],
97
-		];
98
-	}
99
-
100
-	#[DataProvider(methodName: 'simpleSetterProvider')]
101
-	public function testSimpleSetterInvalidInput($field, $input): void {
102
-		$this->expectException(\InvalidArgumentException::class);
103
-
104
-		$comment = new Comment();
105
-		$setter = 'set' . $field;
106
-
107
-		$comment->$setter($input);
108
-	}
109
-
110
-	public static function roleSetterProvider(): array {
111
-		return [
112
-			['Actor', true, true],
113
-			['Actor', 'users', true],
114
-			['Actor', true, 'alice'],
115
-			['Actor', ' ', ' '],
116
-			['Object', true, true],
117
-			['Object', 'files', true],
118
-			['Object', true, 'file64'],
119
-			['Object', ' ', ' '],
120
-		];
121
-	}
122
-
123
-	#[DataProvider(methodName: 'roleSetterProvider')]
124
-	public function testSetRoleInvalidInput($role, $type, $id): void {
125
-		$this->expectException(\InvalidArgumentException::class);
126
-
127
-		$comment = new Comment();
128
-		$setter = 'set' . $role;
129
-		$comment->$setter($type, $id);
130
-	}
131
-
132
-
133
-	public function testSetUberlongMessage(): void {
134
-		$this->expectException(MessageTooLongException::class);
135
-
136
-		$comment = new Comment();
137
-		$msg = str_pad('', IComment::MAX_MESSAGE_LENGTH + 1, 'x');
138
-		$comment->setMessage($msg);
139
-	}
140
-
141
-	public static function mentionsProvider(): array {
142
-		return [
143
-			[
144
-				'@alice @bob look look, a cook!',
145
-				[['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']],
146
-			],
147
-			[
148
-				'no mentions in this message',
149
-				[]
150
-			],
151
-			[
152
-				'@alice @bob look look, a duplication @alice test @bob!',
153
-				[['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']],
154
-			],
155
-			[
156
-				'@alice is the author, notify @bob, nevertheless mention her!',
157
-				[['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']],
158
-				/* author: */ 'alice'
159
-			],
160
-			[
161
-				'@foobar and @barfoo you should know, @[email protected] is valid'
162
-					. ' and so is @[email protected]@foobar.io I hope that clarifies everything.'
163
-					. ' cc @23452-4333-54353-2342 @yolo!'
164
-					. ' however the most important thing to know is that www.croissant.com/@oil is not valid'
165
-					. ' and won\'t match anything at all',
166
-				[
167
-					['type' => 'user', 'id' => '[email protected]@foobar.io'],
168
-					['type' => 'user', 'id' => '23452-4333-54353-2342'],
169
-					['type' => 'user', 'id' => '[email protected]'],
170
-					['type' => 'user', 'id' => 'foobar'],
171
-					['type' => 'user', 'id' => 'barfoo'],
172
-					['type' => 'user', 'id' => 'yolo'],
173
-				],
174
-			],
175
-			[
176
-				'@@chef is also a valid mention, no matter how strange it looks',
177
-				[['type' => 'user', 'id' => '@chef']],
178
-			],
179
-			[
180
-				'Also @"user with spaces" are now supported',
181
-				[['type' => 'user', 'id' => 'user with spaces']],
182
-			],
183
-			[
184
-				'Also @"guest/0123456789abcdef" are now supported',
185
-				[['type' => 'guest', 'id' => 'guest/0123456789abcdef']],
186
-			],
187
-			[
188
-				'Also @"group/My Group ID 321" are now supported',
189
-				[['type' => 'group', 'id' => 'My Group ID 321']],
190
-			],
191
-			[
192
-				'Welcome federation @"federated_group/My Group ID 321" @"federated_team/Former Cirle" @"federated_user/cloudId@http://example.tld:8080/nextcloud"! Now freshly supported',
193
-				[
194
-					['type' => 'federated_user', 'id' => 'cloudId@http://example.tld:8080/nextcloud'],
195
-					['type' => 'federated_group', 'id' => 'My Group ID 321'],
196
-					['type' => 'federated_team', 'id' => 'Former Cirle'],
197
-				],
198
-			],
199
-			[
200
-				'Emails are supported since 30.0.2 right? @"email/aa23d315de327cfc330f0401ea061005b2b0cdd45ec8346f12664dd1f34cb886"',
201
-				[
202
-					['type' => 'email', 'id' => 'aa23d315de327cfc330f0401ea061005b2b0cdd45ec8346f12664dd1f34cb886'],
203
-				],
204
-			],
205
-		];
206
-	}
207
-
208
-	#[DataProvider(methodName: 'mentionsProvider')]
209
-	public function testMentions(string $message, array $expectedMentions, ?string $author = null): void {
210
-		$comment = new Comment();
211
-		$comment->setMessage($message);
212
-		if (!is_null($author)) {
213
-			$comment->setActor('user', $author);
214
-		}
215
-		$mentions = $comment->getMentions();
216
-		$this->assertSame($expectedMentions, $mentions);
217
-	}
18
+    /**
19
+     * @throws IllegalIDChangeException
20
+     */
21
+    public function testSettersValidInput(): void {
22
+        $comment = new Comment();
23
+
24
+        $id = 'comment23';
25
+        $parentId = 'comment11.5';
26
+        $topMostParentId = 'comment11.0';
27
+        $childrenCount = 6;
28
+        $message = 'I like to comment comment';
29
+        $verb = 'comment';
30
+        $actor = ['type' => 'users', 'id' => 'alice'];
31
+        $creationDT = new \DateTime();
32
+        $latestChildDT = new \DateTime('yesterday');
33
+        $object = ['type' => 'files', 'id' => 'file64'];
34
+        $referenceId = sha1('referenceId');
35
+        $metaData = ['last_edit_actor_id' => 'admin'];
36
+
37
+        $comment
38
+            ->setId($id)
39
+            ->setParentId($parentId)
40
+            ->setTopmostParentId($topMostParentId)
41
+            ->setChildrenCount($childrenCount)
42
+            ->setMessage($message)
43
+            ->setVerb($verb)
44
+            ->setActor($actor['type'], $actor['id'])
45
+            ->setCreationDateTime($creationDT)
46
+            ->setLatestChildDateTime($latestChildDT)
47
+            ->setObject($object['type'], $object['id'])
48
+            ->setReferenceId($referenceId)
49
+            ->setMetaData($metaData);
50
+
51
+        $this->assertSame($id, $comment->getId());
52
+        $this->assertSame($parentId, $comment->getParentId());
53
+        $this->assertSame($topMostParentId, $comment->getTopmostParentId());
54
+        $this->assertSame($childrenCount, $comment->getChildrenCount());
55
+        $this->assertSame($message, $comment->getMessage());
56
+        $this->assertSame($verb, $comment->getVerb());
57
+        $this->assertSame($actor['type'], $comment->getActorType());
58
+        $this->assertSame($actor['id'], $comment->getActorId());
59
+        $this->assertSame($creationDT, $comment->getCreationDateTime());
60
+        $this->assertSame($latestChildDT, $comment->getLatestChildDateTime());
61
+        $this->assertSame($object['type'], $comment->getObjectType());
62
+        $this->assertSame($object['id'], $comment->getObjectId());
63
+        $this->assertSame($referenceId, $comment->getReferenceId());
64
+        $this->assertSame($metaData, $comment->getMetaData());
65
+    }
66
+
67
+
68
+    public function testSetIdIllegalInput(): void {
69
+        $this->expectException(IllegalIDChangeException::class);
70
+
71
+        $comment = new Comment();
72
+
73
+        $comment->setId('c23');
74
+        $comment->setId('c17');
75
+    }
76
+
77
+    /**
78
+     * @throws IllegalIDChangeException
79
+     */
80
+    public function testResetId(): void {
81
+        $comment = new Comment();
82
+        $comment->setId('c23');
83
+        $comment->setId('');
84
+
85
+        $this->assertSame('', $comment->getId());
86
+    }
87
+
88
+    public static function simpleSetterProvider(): array {
89
+        return [
90
+            ['Id', true],
91
+            ['TopmostParentId', true],
92
+            ['ParentId', true],
93
+            ['Message', true],
94
+            ['Verb', true],
95
+            ['Verb', ''],
96
+            ['ChildrenCount', true],
97
+        ];
98
+    }
99
+
100
+    #[DataProvider(methodName: 'simpleSetterProvider')]
101
+    public function testSimpleSetterInvalidInput($field, $input): void {
102
+        $this->expectException(\InvalidArgumentException::class);
103
+
104
+        $comment = new Comment();
105
+        $setter = 'set' . $field;
106
+
107
+        $comment->$setter($input);
108
+    }
109
+
110
+    public static function roleSetterProvider(): array {
111
+        return [
112
+            ['Actor', true, true],
113
+            ['Actor', 'users', true],
114
+            ['Actor', true, 'alice'],
115
+            ['Actor', ' ', ' '],
116
+            ['Object', true, true],
117
+            ['Object', 'files', true],
118
+            ['Object', true, 'file64'],
119
+            ['Object', ' ', ' '],
120
+        ];
121
+    }
122
+
123
+    #[DataProvider(methodName: 'roleSetterProvider')]
124
+    public function testSetRoleInvalidInput($role, $type, $id): void {
125
+        $this->expectException(\InvalidArgumentException::class);
126
+
127
+        $comment = new Comment();
128
+        $setter = 'set' . $role;
129
+        $comment->$setter($type, $id);
130
+    }
131
+
132
+
133
+    public function testSetUberlongMessage(): void {
134
+        $this->expectException(MessageTooLongException::class);
135
+
136
+        $comment = new Comment();
137
+        $msg = str_pad('', IComment::MAX_MESSAGE_LENGTH + 1, 'x');
138
+        $comment->setMessage($msg);
139
+    }
140
+
141
+    public static function mentionsProvider(): array {
142
+        return [
143
+            [
144
+                '@alice @bob look look, a cook!',
145
+                [['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']],
146
+            ],
147
+            [
148
+                'no mentions in this message',
149
+                []
150
+            ],
151
+            [
152
+                '@alice @bob look look, a duplication @alice test @bob!',
153
+                [['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']],
154
+            ],
155
+            [
156
+                '@alice is the author, notify @bob, nevertheless mention her!',
157
+                [['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']],
158
+                /* author: */ 'alice'
159
+            ],
160
+            [
161
+                '@foobar and @barfoo you should know, @[email protected] is valid'
162
+                    . ' and so is @[email protected]@foobar.io I hope that clarifies everything.'
163
+                    . ' cc @23452-4333-54353-2342 @yolo!'
164
+                    . ' however the most important thing to know is that www.croissant.com/@oil is not valid'
165
+                    . ' and won\'t match anything at all',
166
+                [
167
+                    ['type' => 'user', 'id' => '[email protected]@foobar.io'],
168
+                    ['type' => 'user', 'id' => '23452-4333-54353-2342'],
169
+                    ['type' => 'user', 'id' => '[email protected]'],
170
+                    ['type' => 'user', 'id' => 'foobar'],
171
+                    ['type' => 'user', 'id' => 'barfoo'],
172
+                    ['type' => 'user', 'id' => 'yolo'],
173
+                ],
174
+            ],
175
+            [
176
+                '@@chef is also a valid mention, no matter how strange it looks',
177
+                [['type' => 'user', 'id' => '@chef']],
178
+            ],
179
+            [
180
+                'Also @"user with spaces" are now supported',
181
+                [['type' => 'user', 'id' => 'user with spaces']],
182
+            ],
183
+            [
184
+                'Also @"guest/0123456789abcdef" are now supported',
185
+                [['type' => 'guest', 'id' => 'guest/0123456789abcdef']],
186
+            ],
187
+            [
188
+                'Also @"group/My Group ID 321" are now supported',
189
+                [['type' => 'group', 'id' => 'My Group ID 321']],
190
+            ],
191
+            [
192
+                'Welcome federation @"federated_group/My Group ID 321" @"federated_team/Former Cirle" @"federated_user/cloudId@http://example.tld:8080/nextcloud"! Now freshly supported',
193
+                [
194
+                    ['type' => 'federated_user', 'id' => 'cloudId@http://example.tld:8080/nextcloud'],
195
+                    ['type' => 'federated_group', 'id' => 'My Group ID 321'],
196
+                    ['type' => 'federated_team', 'id' => 'Former Cirle'],
197
+                ],
198
+            ],
199
+            [
200
+                'Emails are supported since 30.0.2 right? @"email/aa23d315de327cfc330f0401ea061005b2b0cdd45ec8346f12664dd1f34cb886"',
201
+                [
202
+                    ['type' => 'email', 'id' => 'aa23d315de327cfc330f0401ea061005b2b0cdd45ec8346f12664dd1f34cb886'],
203
+                ],
204
+            ],
205
+        ];
206
+    }
207
+
208
+    #[DataProvider(methodName: 'mentionsProvider')]
209
+    public function testMentions(string $message, array $expectedMentions, ?string $author = null): void {
210
+        $comment = new Comment();
211
+        $comment->setMessage($message);
212
+        if (!is_null($author)) {
213
+            $comment->setActor('user', $author);
214
+        }
215
+        $mentions = $comment->getMentions();
216
+        $this->assertSame($expectedMentions, $mentions);
217
+    }
218 218
 }
Please login to merge, or discard this patch.
tests/lib/Comments/ManagerTest.php 2 patches
Indentation   +2489 added lines, -2489 removed lines patch added patch discarded remove patch
@@ -37,2494 +37,2494 @@
 block discarded – undo
37 37
  */
38 38
 #[Group(name: 'DB')]
39 39
 class ManagerTest extends TestCase {
40
-	private IDBConnection $connection;
41
-	private IRootFolder&MockObject $rootFolder;
42
-
43
-	protected function setUp(): void {
44
-		parent::setUp();
45
-
46
-		$this->connection = Server::get(IDBConnection::class);
47
-		$this->rootFolder = $this->createMock(IRootFolder::class);
48
-
49
-		/** @psalm-suppress DeprecatedMethod */
50
-		$sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*comments`');
51
-		$this->connection->prepare($sql)->execute();
52
-		/** @psalm-suppress DeprecatedMethod */
53
-		$sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*reactions`');
54
-		$this->connection->prepare($sql)->execute();
55
-	}
56
-
57
-	protected function addDatabaseEntry(?string $parentId, ?string $topmostParentId, ?\DateTimeInterface $creationDT = null, ?\DateTimeInterface $latestChildDT = null, $objectId = null, ?\DateTimeInterface $expireDate = null): string {
58
-		$creationDT ??= new \DateTime();
59
-		$latestChildDT ??= new \DateTime('yesterday');
60
-		$objectId ??= 'file64';
61
-
62
-		$qb = $this->connection->getQueryBuilder();
63
-		$qb
64
-			->insert('comments')
65
-			->values([
66
-				'parent_id' => $qb->createNamedParameter($parentId),
67
-				'topmost_parent_id' => $qb->createNamedParameter($topmostParentId),
68
-				'children_count' => $qb->createNamedParameter(2),
69
-				'actor_type' => $qb->createNamedParameter('users'),
70
-				'actor_id' => $qb->createNamedParameter('alice'),
71
-				'message' => $qb->createNamedParameter('nice one'),
72
-				'verb' => $qb->createNamedParameter('comment'),
73
-				'creation_timestamp' => $qb->createNamedParameter($creationDT, IQueryBuilder::PARAM_DATETIME_MUTABLE),
74
-				'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, IQueryBuilder::PARAM_DATETIME_MUTABLE),
75
-				'object_type' => $qb->createNamedParameter('files'),
76
-				'object_id' => $qb->createNamedParameter($objectId),
77
-				'expire_date' => $qb->createNamedParameter($expireDate, IQueryBuilder::PARAM_DATETIME_MUTABLE),
78
-				'reference_id' => $qb->createNamedParameter('referenceId'),
79
-				'meta_data' => $qb->createNamedParameter(json_encode(['last_edit_actor_id' => 'admin'])),
80
-			])
81
-			->executeStatement();
82
-
83
-		return (string)$qb->getLastInsertId();
84
-	}
85
-
86
-	protected function getManager(): Manager {
87
-		/** @psalm-suppress DeprecatedInterface No way around at the moment */
88
-		return new Manager(
89
-			$this->connection,
90
-			$this->createMock(LoggerInterface::class),
91
-			$this->createMock(IConfig::class),
92
-			$this->createMock(ITimeFactory::class),
93
-			new EmojiHelper($this->connection),
94
-			$this->createMock(IInitialStateService::class),
95
-			$this->rootFolder,
96
-			$this->createMock(IEventDispatcher::class),
97
-		);
98
-	}
99
-
100
-
101
-	public function testGetCommentNotFound(): void {
102
-		$this->expectException(NotFoundException::class);
103
-
104
-		$manager = $this->getManager();
105
-		$manager->get('22');
106
-	}
107
-
108
-
109
-	public function testGetCommentNotFoundInvalidInput(): void {
110
-		$this->expectException(\InvalidArgumentException::class);
111
-
112
-		$manager = $this->getManager();
113
-		$manager->get('unexisting22');
114
-	}
115
-
116
-	public function testGetComment(): void {
117
-		$manager = $this->getManager();
118
-
119
-		$creationDT = new \DateTime('yesterday');
120
-		$latestChildDT = new \DateTime();
121
-
122
-		$qb = $this->connection->getQueryBuilder();
123
-		$qb
124
-			->insert('comments')
125
-			->values([
126
-				'parent_id' => $qb->createNamedParameter('2'),
127
-				'topmost_parent_id' => $qb->createNamedParameter('1'),
128
-				'children_count' => $qb->createNamedParameter(2),
129
-				'actor_type' => $qb->createNamedParameter('users'),
130
-				'actor_id' => $qb->createNamedParameter('alice'),
131
-				'message' => $qb->createNamedParameter('nice one'),
132
-				'verb' => $qb->createNamedParameter('comment'),
133
-				'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
134
-				'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
135
-				'object_type' => $qb->createNamedParameter('files'),
136
-				'object_id' => $qb->createNamedParameter('file64'),
137
-				'reference_id' => $qb->createNamedParameter('referenceId'),
138
-				'meta_data' => $qb->createNamedParameter(json_encode(['last_edit_actor_id' => 'admin'])),
139
-			])
140
-			->executeStatement();
141
-
142
-		$id = (string)$qb->getLastInsertId();
143
-
144
-		$comment = $manager->get($id);
145
-		$this->assertInstanceOf(IComment::class, $comment);
146
-		$this->assertSame($id, $comment->getId());
147
-		$this->assertSame('2', $comment->getParentId());
148
-		$this->assertSame('1', $comment->getTopmostParentId());
149
-		$this->assertSame(2, $comment->getChildrenCount());
150
-		$this->assertSame('users', $comment->getActorType());
151
-		$this->assertSame('alice', $comment->getActorId());
152
-		$this->assertSame('nice one', $comment->getMessage());
153
-		$this->assertSame('comment', $comment->getVerb());
154
-		$this->assertSame('files', $comment->getObjectType());
155
-		$this->assertSame('file64', $comment->getObjectId());
156
-		$this->assertEquals($creationDT->getTimestamp(), $comment->getCreationDateTime()->getTimestamp());
157
-		$this->assertEquals($latestChildDT->getTimestamp(), $comment->getLatestChildDateTime()->getTimestamp());
158
-		$this->assertEquals('referenceId', $comment->getReferenceId());
159
-		$this->assertEquals(['last_edit_actor_id' => 'admin'], $comment->getMetaData());
160
-	}
161
-
162
-	public function testGetTreeNotFound(): void {
163
-		$this->expectException(NotFoundException::class);
164
-
165
-		$manager = $this->getManager();
166
-		$manager->getTree('22');
167
-	}
168
-
169
-	public function testGetTreeNotFoundInvalidIpnut(): void {
170
-		$this->expectException(\InvalidArgumentException::class);
171
-
172
-		$manager = $this->getManager();
173
-		$manager->getTree('unexisting22');
174
-	}
175
-
176
-	public function testGetTree(): void {
177
-		$headId = $this->addDatabaseEntry('0', '0');
178
-
179
-		$this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
180
-		$this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
181
-		$id = $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
182
-
183
-		$manager = $this->getManager();
184
-		$tree = $manager->getTree($headId);
185
-
186
-		// Verifying the root comment
187
-		$this->assertArrayHasKey('comment', $tree);
188
-		$this->assertInstanceOf(IComment::class, $tree['comment']);
189
-		$this->assertSame($headId, $tree['comment']->getId());
190
-		$this->assertArrayHasKey('replies', $tree);
191
-		$this->assertCount(3, $tree['replies']);
192
-
193
-		// one level deep
194
-		foreach ($tree['replies'] as $reply) {
195
-			$this->assertInstanceOf(IComment::class, $reply['comment']);
196
-			$this->assertSame((string)$id, $reply['comment']->getId());
197
-			$this->assertCount(0, $reply['replies']);
198
-			$id--;
199
-		}
200
-	}
201
-
202
-	public function testGetTreeNoReplies(): void {
203
-		$id = $this->addDatabaseEntry('0', '0');
204
-
205
-		$manager = $this->getManager();
206
-		$tree = $manager->getTree($id);
207
-
208
-		// Verifying the root comment
209
-		$this->assertArrayHasKey('comment', $tree);
210
-		$this->assertInstanceOf(IComment::class, $tree['comment']);
211
-		$this->assertSame($id, $tree['comment']->getId());
212
-		$this->assertArrayHasKey('replies', $tree);
213
-		$this->assertCount(0, $tree['replies']);
214
-	}
215
-
216
-	public function testGetTreeWithLimitAndOffset(): void {
217
-		$headId = $this->addDatabaseEntry('0', '0');
218
-
219
-		$this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
220
-		$this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
221
-		$this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
222
-		$idToVerify = $this->addDatabaseEntry($headId, $headId, new \DateTime());
223
-
224
-		$manager = $this->getManager();
225
-
226
-		for ($offset = 0; $offset < 3; $offset += 2) {
227
-			$tree = $manager->getTree($headId, 2, $offset);
228
-
229
-			// Verifying the root comment
230
-			$this->assertArrayHasKey('comment', $tree);
231
-			$this->assertInstanceOf(IComment::class, $tree['comment']);
232
-			$this->assertSame($headId, $tree['comment']->getId());
233
-			$this->assertArrayHasKey('replies', $tree);
234
-			$this->assertCount(2, $tree['replies']);
235
-
236
-			// one level deep
237
-			foreach ($tree['replies'] as $reply) {
238
-				$this->assertInstanceOf(IComment::class, $reply['comment']);
239
-				$this->assertSame((string)$idToVerify, (string)$reply['comment']->getId());
240
-				$this->assertCount(0, $reply['replies']);
241
-				$idToVerify--;
242
-			}
243
-		}
244
-	}
245
-
246
-	public function testGetForObject(): void {
247
-		$this->addDatabaseEntry('0', '0');
248
-
249
-		$manager = $this->getManager();
250
-		$comments = $manager->getForObject('files', 'file64');
251
-
252
-		$this->assertIsArray($comments);
253
-		$this->assertCount(1, $comments);
254
-		$this->assertInstanceOf(IComment::class, $comments[0]);
255
-		$this->assertSame('nice one', $comments[0]->getMessage());
256
-	}
257
-
258
-	public function testGetForObjectWithLimitAndOffset(): void {
259
-		$this->addDatabaseEntry('0', '0', new \DateTime('-6 hours'));
260
-		$this->addDatabaseEntry('0', '0', new \DateTime('-5 hours'));
261
-		$this->addDatabaseEntry('1', '1', new \DateTime('-4 hours'));
262
-		$this->addDatabaseEntry('0', '0', new \DateTime('-3 hours'));
263
-		$this->addDatabaseEntry('2', '2', new \DateTime('-2 hours'));
264
-		$this->addDatabaseEntry('2', '2', new \DateTime('-1 hours'));
265
-		$idToVerify = $this->addDatabaseEntry('3', '1', new \DateTime());
266
-
267
-		$manager = $this->getManager();
268
-		$offset = 0;
269
-		do {
270
-			$comments = $manager->getForObject('files', 'file64', 3, $offset);
271
-
272
-			$this->assertIsArray($comments);
273
-			foreach ($comments as $key => $comment) {
274
-				$this->assertInstanceOf(IComment::class, $comment);
275
-				$this->assertSame('nice one', $comment->getMessage());
276
-				$this->assertSame((string)$idToVerify, $comment->getId(), 'ID wrong for comment ' . $key . ' on offset: ' . $offset);
277
-				$idToVerify--;
278
-			}
279
-			$offset += 3;
280
-		} while (count($comments) > 0);
281
-	}
282
-
283
-	public function testGetForObjectWithDateTimeConstraint(): void {
284
-		$this->addDatabaseEntry('0', '0', new \DateTime('-6 hours'));
285
-		$this->addDatabaseEntry('0', '0', new \DateTime('-5 hours'));
286
-		$id1 = $this->addDatabaseEntry('0', '0', new \DateTime('-3 hours'));
287
-		$id2 = $this->addDatabaseEntry('2', '2', new \DateTime('-2 hours'));
288
-
289
-		$manager = $this->getManager();
290
-		$comments = $manager->getForObject('files', 'file64', 0, 0, new \DateTime('-4 hours'));
291
-
292
-		$this->assertCount(2, $comments);
293
-		$this->assertSame($id2, $comments[0]->getId());
294
-		$this->assertSame($id1, $comments[1]->getId());
295
-	}
296
-
297
-	public function testGetForObjectWithLimitAndOffsetAndDateTimeConstraint(): void {
298
-		$this->addDatabaseEntry('0', '0', new \DateTime('-7 hours'));
299
-		$this->addDatabaseEntry('0', '0', new \DateTime('-6 hours'));
300
-		$this->addDatabaseEntry('1', '1', new \DateTime('-5 hours'));
301
-		$this->addDatabaseEntry('0', '0', new \DateTime('-3 hours'));
302
-		$this->addDatabaseEntry('2', '2', new \DateTime('-2 hours'));
303
-		$this->addDatabaseEntry('2', '2', new \DateTime('-1 hours'));
304
-		$idToVerify = $this->addDatabaseEntry('3', '1', new \DateTime());
305
-
306
-		$manager = $this->getManager();
307
-		$offset = 0;
308
-		do {
309
-			$comments = $manager->getForObject('files', 'file64', 3, $offset, new \DateTime('-4 hours'));
310
-
311
-			$this->assertIsArray($comments);
312
-			foreach ($comments as $comment) {
313
-				$this->assertInstanceOf(IComment::class, $comment);
314
-				$this->assertSame('nice one', $comment->getMessage());
315
-				$this->assertSame((string)$idToVerify, $comment->getId());
316
-				$this->assertGreaterThanOrEqual(4, $comment->getId());
317
-				$idToVerify--;
318
-			}
319
-			$offset += 3;
320
-		} while (count($comments) > 0);
321
-	}
322
-
323
-	public function testGetNumberOfCommentsForObject(): void {
324
-		for ($i = 1; $i < 5; $i++) {
325
-			$this->addDatabaseEntry('0', '0');
326
-		}
327
-
328
-		$manager = $this->getManager();
329
-
330
-		$amount = $manager->getNumberOfCommentsForObject('untype', '00');
331
-		$this->assertSame(0, $amount);
332
-
333
-		$amount = $manager->getNumberOfCommentsForObject('files', 'file64');
334
-		$this->assertSame(4, $amount);
335
-	}
336
-
337
-	public function testGetNumberOfUnreadCommentsForFolder(): void {
338
-		$folder = $this->createMock(Folder::class);
339
-		$fileIds = range(1111, 1114);
340
-		$children = array_map(function (int $id) {
341
-			$file = $this->createMock(Folder::class);
342
-			$file->method('getId')
343
-				->willReturn($id);
344
-			return $file;
345
-		}, $fileIds);
346
-		$folder->method('getId')->willReturn(1000);
347
-		$folder->method('getDirectoryListing')->willReturn($children);
348
-		$this->rootFolder->method('getFirstNodeById')
349
-			->with($folder->getId())
350
-			->willReturn($folder);
351
-
352
-		// 2 comment for 1111 with 1 before read marker
353
-		// 2 comments for 1112 with no read marker
354
-		// 1 comment for 1113 before read marker
355
-		// 1 comment for 1114 with no read marker
356
-		$this->addDatabaseEntry('0', '0', null, null, $fileIds[1]);
357
-		for ($i = 0; $i < 4; $i++) {
358
-			$this->addDatabaseEntry('0', '0', null, null, $fileIds[$i]);
359
-		}
360
-		$this->addDatabaseEntry('0', '0', (new \DateTime())->modify('-2 days'), null, $fileIds[0]);
361
-		/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
362
-		$user = $this->createMock(IUser::class);
363
-		$user->expects($this->any())
364
-			->method('getUID')
365
-			->willReturn('comment_test');
366
-
367
-		$manager = $this->getManager();
368
-
369
-		$manager->setReadMark('files', (string)$fileIds[0], (new \DateTime())->modify('-1 days'), $user);
370
-		$manager->setReadMark('files', (string)$fileIds[2], (new \DateTime()), $user);
371
-
372
-		$amount = $manager->getNumberOfUnreadCommentsForFolder($folder->getId(), $user);
373
-		$this->assertEquals([
374
-			$fileIds[0] => 1,
375
-			$fileIds[1] => 2,
376
-			$fileIds[3] => 1,
377
-		], $amount);
378
-	}
379
-
380
-	#[DataProvider(methodName: 'dataGetForObjectSince')]
381
-	public function testGetForObjectSince(?int $lastKnown, string $order, int $limit, int $resultFrom, int $resultTo): void {
382
-		$ids = [];
383
-		$ids[] = $this->addDatabaseEntry('0', '0');
384
-		$ids[] = $this->addDatabaseEntry('0', '0');
385
-		$ids[] = $this->addDatabaseEntry('0', '0');
386
-		$ids[] = $this->addDatabaseEntry('0', '0');
387
-		$ids[] = $this->addDatabaseEntry('0', '0');
388
-
389
-		$manager = $this->getManager();
390
-		$comments = $manager->getForObjectSince('files', 'file64', ($lastKnown === null ? 0 : (int)$ids[$lastKnown]), $order, $limit);
391
-
392
-		$expected = array_slice($ids, $resultFrom, $resultTo - $resultFrom + 1);
393
-		if ($order === 'desc') {
394
-			$expected = array_reverse($expected);
395
-		}
396
-
397
-		$this->assertSame($expected, array_map(static fn (IComment $c): string => $c->getId(), $comments));
398
-	}
399
-
400
-	public static function dataGetForObjectSince(): array {
401
-		return [
402
-			[null, 'asc', 20, 0, 4],
403
-			[null, 'asc', 2, 0, 1],
404
-			[null, 'desc', 20, 0, 4],
405
-			[null, 'desc', 2, 3, 4],
406
-			[1, 'asc', 20, 2, 4],
407
-			[1, 'asc', 2, 2, 3],
408
-			[3, 'desc', 20, 0, 2],
409
-			[3, 'desc', 2, 1, 2],
410
-		];
411
-	}
412
-
413
-	public static function invalidCreateArgsProvider(): array {
414
-		return [
415
-			['', 'aId-1', 'oType-1', 'oId-1'],
416
-			['aType-1', '', 'oType-1', 'oId-1'],
417
-			['aType-1', 'aId-1', '', 'oId-1'],
418
-			['aType-1', 'aId-1', 'oType-1', ''],
419
-			[1, 'aId-1', 'oType-1', 'oId-1'],
420
-			['aType-1', 1, 'oType-1', 'oId-1'],
421
-			['aType-1', 'aId-1', 1, 'oId-1'],
422
-			['aType-1', 'aId-1', 'oType-1', 1],
423
-		];
424
-	}
425
-
426
-	#[DataProvider(methodName: 'invalidCreateArgsProvider')]
427
-	public function testCreateCommentInvalidArguments(string|int $aType, string|int $aId, string|int $oType, string|int $oId): void {
428
-		$this->expectException(\InvalidArgumentException::class);
429
-
430
-		$manager = $this->getManager();
431
-		$manager->create($aType, $aId, $oType, $oId);
432
-	}
433
-
434
-	public function testCreateComment(): void {
435
-		$actorType = 'bot';
436
-		$actorId = 'bob';
437
-		$objectType = 'weather';
438
-		$objectId = 'bielefeld';
439
-
440
-		$comment = $this->getManager()->create($actorType, $actorId, $objectType, $objectId);
441
-		$this->assertInstanceOf(IComment::class, $comment);
442
-		$this->assertSame($actorType, $comment->getActorType());
443
-		$this->assertSame($actorId, $comment->getActorId());
444
-		$this->assertSame($objectType, $comment->getObjectType());
445
-		$this->assertSame($objectId, $comment->getObjectId());
446
-	}
447
-
448
-
449
-	public function testDelete(): void {
450
-		$this->expectException(NotFoundException::class);
451
-
452
-		$manager = $this->getManager();
453
-
454
-		$done = $manager->delete('404');
455
-		$this->assertFalse($done);
456
-
457
-		$done = $manager->delete('%');
458
-		$this->assertFalse($done);
459
-
460
-		$done = $manager->delete('');
461
-		$this->assertFalse($done);
462
-
463
-		$id = $this->addDatabaseEntry('0', '0');
464
-		$comment = $manager->get($id);
465
-		$this->assertInstanceOf(IComment::class, $comment);
466
-		$done = $manager->delete($id);
467
-		$this->assertTrue($done);
468
-		$manager->get($id);
469
-	}
470
-
471
-	#[DataProvider(methodName: 'providerTestSave')]
472
-	public function testSave(string $message, string $actorId, string $verb, ?string $parentId, ?string $id = ''): IComment {
473
-		$manager = $this->getManager();
474
-		$comment = new Comment();
475
-		$comment
476
-			->setId($id)
477
-			->setActor('users', $actorId)
478
-			->setObject('files', 'file64')
479
-			->setMessage($message)
480
-			->setVerb($verb);
481
-		if ($parentId) {
482
-			$comment->setParentId($parentId);
483
-		}
484
-
485
-		$saveSuccessful = $manager->save($comment);
486
-		$this->assertTrue($saveSuccessful, 'Comment saving was not successful');
487
-		$this->assertNotEquals('', $comment->getId(), 'Comment ID should not be empty');
488
-		$this->assertNotEquals('0', $comment->getId(), 'Comment ID should not be string \'0\'');
489
-		$this->assertNotNull($comment->getCreationDateTime(), 'Comment creation date should not be null');
490
-
491
-		$loadedComment = $manager->get($comment->getId());
492
-		$this->assertSame($comment->getMessage(), $loadedComment->getMessage(), 'Comment message should match');
493
-		$this->assertEquals($comment->getCreationDateTime()->getTimestamp(), $loadedComment->getCreationDateTime()->getTimestamp(), 'Comment creation date should match');
494
-		return $comment;
495
-	}
496
-
497
-	public static function providerTestSave(): array {
498
-		return [
499
-			['very beautiful, I am impressed!', 'alice', 'comment', null],
500
-		];
501
-	}
502
-
503
-	public function testSaveUpdate(): void {
504
-		$manager = $this->getManager();
505
-		$comment = new Comment();
506
-		$comment
507
-			->setActor('users', 'alice')
508
-			->setObject('files', 'file64')
509
-			->setMessage('very beautiful, I am impressed!')
510
-			->setVerb('comment')
511
-			->setExpireDate(new \DateTime('+2 hours'));
512
-
513
-		$manager->save($comment);
514
-
515
-		$loadedComment = $manager->get($comment->getId());
516
-		// Compare current object with database values
517
-		$this->assertSame($comment->getMessage(), $loadedComment->getMessage());
518
-		$this->assertSame(
519
-			$comment->getExpireDate()->format('Y-m-d H:i:s'),
520
-			$loadedComment->getExpireDate()->format('Y-m-d H:i:s')
521
-		);
522
-
523
-		// Preserve the original comment to compare after update
524
-		$original = clone $comment;
525
-
526
-		// Update values
527
-		$comment->setMessage('very beautiful, I am really so much impressed!')
528
-			->setExpireDate(new \DateTime('+1 hours'));
529
-		$manager->save($comment);
530
-
531
-		$loadedComment = $manager->get($comment->getId());
532
-		// Compare current object with database values
533
-		$this->assertSame($comment->getMessage(), $loadedComment->getMessage());
534
-		$this->assertSame(
535
-			$comment->getExpireDate()->format('Y-m-d H:i:s'),
536
-			$loadedComment->getExpireDate()->format('Y-m-d H:i:s')
537
-		);
538
-
539
-		// Compare original object with database values
540
-		$this->assertNotSame($original->getMessage(), $loadedComment->getMessage());
541
-		$this->assertNotSame(
542
-			$original->getExpireDate()->format('Y-m-d H:i:s'),
543
-			$loadedComment->getExpireDate()->format('Y-m-d H:i:s')
544
-		);
545
-	}
546
-
547
-
548
-	public function testSaveUpdateException(): void {
549
-		$manager = $this->getManager();
550
-		$comment = new Comment();
551
-		$comment
552
-			->setActor('users', 'alice')
553
-			->setObject('files', 'file64')
554
-			->setMessage('very beautiful, I am impressed!')
555
-			->setVerb('comment');
556
-
557
-		$manager->save($comment);
558
-
559
-		$manager->delete($comment->getId());
560
-
561
-		$comment->setMessage('very beautiful, I am really so much impressed!');
562
-		$this->expectException(NotFoundException::class);
563
-		$manager->save($comment);
564
-	}
565
-
566
-
567
-	public function testSaveIncomplete(): void {
568
-
569
-		$manager = $this->getManager();
570
-		$comment = new Comment();
571
-		$comment->setMessage('from no one to nothing');
572
-
573
-		$this->expectException(\UnexpectedValueException::class);
574
-		$manager->save($comment);
575
-	}
576
-
577
-	public function testSaveAsChild(): void {
578
-		$id = $this->addDatabaseEntry('0', '0');
579
-
580
-		$manager = $this->getManager();
581
-
582
-		for ($i = 0; $i < 3; $i++) {
583
-			$comment = new Comment();
584
-			$comment
585
-				->setActor('users', 'alice')
586
-				->setObject('files', 'file64')
587
-				->setParentId($id)
588
-				->setMessage('full ack')
589
-				->setVerb('comment')
590
-				// setting the creation time avoids using sleep() while making sure to test with different timestamps
591
-				->setCreationDateTime(new \DateTime('+' . $i . ' minutes'));
592
-
593
-			$manager->save($comment);
594
-
595
-			$this->assertSame($id, $comment->getTopmostParentId());
596
-			$parentComment = $manager->get($id);
597
-			$this->assertSame($i + 1, $parentComment->getChildrenCount());
598
-			$this->assertEquals($comment->getCreationDateTime()->getTimestamp(), $parentComment->getLatestChildDateTime()->getTimestamp());
599
-		}
600
-	}
601
-
602
-	public static function invalidActorArgsProvider(): array {
603
-		return
604
-			[
605
-				['', ''],
606
-				[1, 'alice'],
607
-				['users', 1],
608
-			];
609
-	}
610
-
611
-	#[DataProvider(methodName: 'invalidActorArgsProvider')]
612
-	public function testDeleteReferencesOfActorInvalidInput(string|int $type, string|int $id): void {
613
-		$this->expectException(\InvalidArgumentException::class);
614
-
615
-		$manager = $this->getManager();
616
-		$manager->deleteReferencesOfActor($type, $id);
617
-	}
618
-
619
-	public function testDeleteReferencesOfActor(): void {
620
-		$ids = [];
621
-		$ids[] = $this->addDatabaseEntry('0', '0');
622
-		$ids[] = $this->addDatabaseEntry('0', '0');
623
-		$ids[] = $this->addDatabaseEntry('0', '0');
624
-
625
-		$manager = $this->getManager();
626
-
627
-		// just to make sure they are really set, with correct actor data
628
-		$comment = $manager->get($ids[1]);
629
-		$this->assertSame('users', $comment->getActorType());
630
-		$this->assertSame('alice', $comment->getActorId());
631
-
632
-		$wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice');
633
-		$this->assertTrue($wasSuccessful);
634
-
635
-		foreach ($ids as $id) {
636
-			$comment = $manager->get($id);
637
-			$this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorType());
638
-			$this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorId());
639
-		}
640
-
641
-		// actor info is gone from DB, but when database interaction is alright,
642
-		// we still expect to get true back
643
-		$wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice');
644
-		$this->assertTrue($wasSuccessful);
645
-	}
646
-
647
-	public function testDeleteReferencesOfActorWithUserManagement(): void {
648
-		$user = Server::get(IUserManager::class)->createUser('xenia', 'NotAnEasyPassword123456+');
649
-		$this->assertInstanceOf(IUser::class, $user);
650
-
651
-		$manager = Server::get(ICommentsManager::class);
652
-		$comment = $manager->create('users', $user->getUID(), 'files', 'file64');
653
-		$comment
654
-			->setMessage('Most important comment I ever left on the Internet.')
655
-			->setVerb('comment');
656
-		$status = $manager->save($comment);
657
-		$this->assertTrue($status);
658
-
659
-		$commentID = $comment->getId();
660
-		$user->delete();
661
-
662
-		$comment = $manager->get($commentID);
663
-		$this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorType());
664
-		$this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorId());
665
-	}
666
-
667
-	public static function invalidObjectArgsProvider(): array {
668
-		return
669
-			[
670
-				['', ''],
671
-				[1, 'file64'],
672
-				['files', 1],
673
-			];
674
-	}
675
-
676
-	#[DataProvider(methodName: 'invalidObjectArgsProvider')]
677
-	public function testDeleteCommentsAtObjectInvalidInput(string|int $type, string|int $id): void {
678
-		$this->expectException(\InvalidArgumentException::class);
679
-
680
-		$manager = $this->getManager();
681
-		$manager->deleteCommentsAtObject($type, $id);
682
-	}
683
-
684
-	public function testDeleteCommentsAtObject(): void {
685
-		$ids = [];
686
-		$ids[] = $this->addDatabaseEntry('0', '0');
687
-		$ids[] = $this->addDatabaseEntry('0', '0');
688
-		$ids[] = $this->addDatabaseEntry('0', '0');
689
-
690
-		$manager = $this->getManager();
691
-
692
-		// just to make sure they are really set, with correct actor data
693
-		$comment = $manager->get($ids[1]);
694
-		$this->assertSame('files', $comment->getObjectType());
695
-		$this->assertSame('file64', $comment->getObjectId());
696
-
697
-		$wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64');
698
-		$this->assertTrue($wasSuccessful);
699
-
700
-		$verified = 0;
701
-		foreach ($ids as $id) {
702
-			try {
703
-				$manager->get($id);
704
-			} catch (NotFoundException) {
705
-				$verified++;
706
-			}
707
-		}
708
-		$this->assertSame(3, $verified);
709
-
710
-		// actor info is gone from DB, but when database interaction is alright,
711
-		// we still expect to get true back
712
-		$wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64');
713
-		$this->assertTrue($wasSuccessful);
714
-	}
715
-
716
-	public function testDeleteCommentsExpiredAtObjectTypeAndId(): void {
717
-		$ids = [];
718
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('+2 hours'));
719
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('+2 hours'));
720
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('+2 hours'));
721
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('-2 hours'));
722
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('-2 hours'));
723
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('-2 hours'));
724
-
725
-		/** @psalm-suppress DeprecatedInterface No way around at the moment */
726
-		$manager = new Manager(
727
-			$this->connection,
728
-			$this->createMock(LoggerInterface::class),
729
-			$this->createMock(IConfig::class),
730
-			Server::get(ITimeFactory::class),
731
-			new EmojiHelper($this->connection),
732
-			$this->createMock(IInitialStateService::class),
733
-			$this->rootFolder,
734
-			$this->createMock(IEventDispatcher::class)
735
-		);
736
-
737
-		// just to make sure they are really set, with correct actor data
738
-		$comment = $manager->get($ids[1]);
739
-		$this->assertSame('files', $comment->getObjectType());
740
-		$this->assertSame('file64', $comment->getObjectId());
741
-
742
-		$deleted = $manager->deleteCommentsExpiredAtObject('files', 'file64');
743
-		$this->assertTrue($deleted);
744
-
745
-		$deleted = 0;
746
-		$exists = 0;
747
-		foreach ($ids as $id) {
748
-			try {
749
-				$manager->get($id);
750
-				$exists++;
751
-			} catch (NotFoundException) {
752
-				$deleted++;
753
-			}
754
-		}
755
-		$this->assertSame(3, $exists);
756
-		$this->assertSame(3, $deleted);
757
-
758
-		// actor info is gone from DB, but when database interaction is alright,
759
-		// we still expect to get true back
760
-		$deleted = $manager->deleteCommentsExpiredAtObject('files', 'file64');
761
-		$this->assertFalse($deleted);
762
-	}
763
-
764
-	public function testDeleteCommentsExpiredAtObjectType(): void {
765
-		$ids = [];
766
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file1', new \DateTime('-2 hours'));
767
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file2', new \DateTime('-2 hours'));
768
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime('-2 hours'));
769
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime());
770
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime());
771
-		$ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime());
772
-
773
-		/** @psalm-suppress DeprecatedInterface No way around at the moment */
774
-		$manager = new Manager(
775
-			$this->connection,
776
-			$this->createMock(LoggerInterface::class),
777
-			$this->createMock(IConfig::class),
778
-			Server::get(ITimeFactory::class),
779
-			new EmojiHelper($this->connection),
780
-			$this->createMock(IInitialStateService::class),
781
-			$this->rootFolder,
782
-			$this->createMock(IEventDispatcher::class)
783
-		);
784
-
785
-		$deleted = $manager->deleteCommentsExpiredAtObject('files');
786
-		$this->assertTrue($deleted);
787
-
788
-		$deleted = 0;
789
-		$exists = 0;
790
-		foreach ($ids as $id) {
791
-			try {
792
-				$manager->get($id);
793
-				$exists++;
794
-			} catch (NotFoundException) {
795
-				$deleted++;
796
-			}
797
-		}
798
-		$this->assertSame(0, $exists);
799
-		$this->assertSame(6, $deleted);
800
-
801
-		// actor info is gone from DB, but when database interaction is alright,
802
-		// we still expect to get true back
803
-		$deleted = $manager->deleteCommentsExpiredAtObject('files');
804
-		$this->assertFalse($deleted);
805
-	}
806
-
807
-	public function testSetMarkRead(): void {
808
-		/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
809
-		$user = $this->createMock(IUser::class);
810
-		$user->expects($this->any())
811
-			->method('getUID')
812
-			->willReturn('alice');
813
-
814
-		$dateTimeSet = new \DateTime();
815
-
816
-		$manager = $this->getManager();
817
-		$manager->setReadMark('robot', '36', $dateTimeSet, $user);
818
-
819
-		$dateTimeGet = $manager->getReadMark('robot', '36', $user);
820
-
821
-		$this->assertEquals($dateTimeSet->getTimestamp(), $dateTimeGet->getTimestamp());
822
-	}
823
-
824
-	public function testSetMarkReadUpdate(): void {
825
-		/** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
826
-		$user = $this->createMock(IUser::class);
827
-		$user->expects($this->any())
828
-			->method('getUID')
829
-			->willReturn('alice');
830
-
831
-		$dateTimeSet = new \DateTime('yesterday');
832
-
833
-		$manager = $this->getManager();
834
-		$manager->setReadMark('robot', '36', $dateTimeSet, $user);
835
-
836
-		$dateTimeSet = new \DateTime('today');
837
-		$manager->setReadMark('robot', '36', $dateTimeSet, $user);
838
-
839
-		$dateTimeGet = $manager->getReadMark('robot', '36', $user);
840
-
841
-		$this->assertEquals($dateTimeSet, $dateTimeGet);
842
-	}
843
-
844
-	public function testReadMarkDeleteUser(): void {
845
-		$user = $this->createMock(IUser::class);
846
-		$user->expects($this->any())
847
-			->method('getUID')
848
-			->willReturn('alice');
849
-
850
-		$dateTimeSet = new \DateTime();
851
-
852
-		$manager = $this->getManager();
853
-		$manager->setReadMark('robot', '36', $dateTimeSet, $user);
854
-
855
-		$manager->deleteReadMarksFromUser($user);
856
-		$dateTimeGet = $manager->getReadMark('robot', '36', $user);
857
-
858
-		$this->assertNull($dateTimeGet);
859
-	}
860
-
861
-	public function testReadMarkDeleteObject(): void {
862
-		$user = $this->createMock(IUser::class);
863
-		$user->expects($this->any())
864
-			->method('getUID')
865
-			->willReturn('alice');
866
-
867
-		$dateTimeSet = new \DateTime();
868
-
869
-		$manager = $this->getManager();
870
-		$manager->setReadMark('robot', '36', $dateTimeSet, $user);
871
-
872
-		$manager->deleteReadMarksOnObject('robot', '36');
873
-		$dateTimeGet = $manager->getReadMark('robot', '36', $user);
874
-
875
-		$this->assertNull($dateTimeGet);
876
-	}
877
-
878
-	public function testSendEvent(): void {
879
-		/** @psalm-suppress DeprecatedInterface Test for deprecated interface */
880
-		$handler1 = $this->createMock(ICommentsEventHandler::class);
881
-		$handler1->expects($this->exactly(4))
882
-			->method('handle');
40
+    private IDBConnection $connection;
41
+    private IRootFolder&MockObject $rootFolder;
42
+
43
+    protected function setUp(): void {
44
+        parent::setUp();
45
+
46
+        $this->connection = Server::get(IDBConnection::class);
47
+        $this->rootFolder = $this->createMock(IRootFolder::class);
48
+
49
+        /** @psalm-suppress DeprecatedMethod */
50
+        $sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*comments`');
51
+        $this->connection->prepare($sql)->execute();
52
+        /** @psalm-suppress DeprecatedMethod */
53
+        $sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*reactions`');
54
+        $this->connection->prepare($sql)->execute();
55
+    }
56
+
57
+    protected function addDatabaseEntry(?string $parentId, ?string $topmostParentId, ?\DateTimeInterface $creationDT = null, ?\DateTimeInterface $latestChildDT = null, $objectId = null, ?\DateTimeInterface $expireDate = null): string {
58
+        $creationDT ??= new \DateTime();
59
+        $latestChildDT ??= new \DateTime('yesterday');
60
+        $objectId ??= 'file64';
61
+
62
+        $qb = $this->connection->getQueryBuilder();
63
+        $qb
64
+            ->insert('comments')
65
+            ->values([
66
+                'parent_id' => $qb->createNamedParameter($parentId),
67
+                'topmost_parent_id' => $qb->createNamedParameter($topmostParentId),
68
+                'children_count' => $qb->createNamedParameter(2),
69
+                'actor_type' => $qb->createNamedParameter('users'),
70
+                'actor_id' => $qb->createNamedParameter('alice'),
71
+                'message' => $qb->createNamedParameter('nice one'),
72
+                'verb' => $qb->createNamedParameter('comment'),
73
+                'creation_timestamp' => $qb->createNamedParameter($creationDT, IQueryBuilder::PARAM_DATETIME_MUTABLE),
74
+                'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, IQueryBuilder::PARAM_DATETIME_MUTABLE),
75
+                'object_type' => $qb->createNamedParameter('files'),
76
+                'object_id' => $qb->createNamedParameter($objectId),
77
+                'expire_date' => $qb->createNamedParameter($expireDate, IQueryBuilder::PARAM_DATETIME_MUTABLE),
78
+                'reference_id' => $qb->createNamedParameter('referenceId'),
79
+                'meta_data' => $qb->createNamedParameter(json_encode(['last_edit_actor_id' => 'admin'])),
80
+            ])
81
+            ->executeStatement();
82
+
83
+        return (string)$qb->getLastInsertId();
84
+    }
85
+
86
+    protected function getManager(): Manager {
87
+        /** @psalm-suppress DeprecatedInterface No way around at the moment */
88
+        return new Manager(
89
+            $this->connection,
90
+            $this->createMock(LoggerInterface::class),
91
+            $this->createMock(IConfig::class),
92
+            $this->createMock(ITimeFactory::class),
93
+            new EmojiHelper($this->connection),
94
+            $this->createMock(IInitialStateService::class),
95
+            $this->rootFolder,
96
+            $this->createMock(IEventDispatcher::class),
97
+        );
98
+    }
99
+
100
+
101
+    public function testGetCommentNotFound(): void {
102
+        $this->expectException(NotFoundException::class);
103
+
104
+        $manager = $this->getManager();
105
+        $manager->get('22');
106
+    }
107
+
108
+
109
+    public function testGetCommentNotFoundInvalidInput(): void {
110
+        $this->expectException(\InvalidArgumentException::class);
111
+
112
+        $manager = $this->getManager();
113
+        $manager->get('unexisting22');
114
+    }
115
+
116
+    public function testGetComment(): void {
117
+        $manager = $this->getManager();
118
+
119
+        $creationDT = new \DateTime('yesterday');
120
+        $latestChildDT = new \DateTime();
121
+
122
+        $qb = $this->connection->getQueryBuilder();
123
+        $qb
124
+            ->insert('comments')
125
+            ->values([
126
+                'parent_id' => $qb->createNamedParameter('2'),
127
+                'topmost_parent_id' => $qb->createNamedParameter('1'),
128
+                'children_count' => $qb->createNamedParameter(2),
129
+                'actor_type' => $qb->createNamedParameter('users'),
130
+                'actor_id' => $qb->createNamedParameter('alice'),
131
+                'message' => $qb->createNamedParameter('nice one'),
132
+                'verb' => $qb->createNamedParameter('comment'),
133
+                'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
134
+                'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
135
+                'object_type' => $qb->createNamedParameter('files'),
136
+                'object_id' => $qb->createNamedParameter('file64'),
137
+                'reference_id' => $qb->createNamedParameter('referenceId'),
138
+                'meta_data' => $qb->createNamedParameter(json_encode(['last_edit_actor_id' => 'admin'])),
139
+            ])
140
+            ->executeStatement();
141
+
142
+        $id = (string)$qb->getLastInsertId();
143
+
144
+        $comment = $manager->get($id);
145
+        $this->assertInstanceOf(IComment::class, $comment);
146
+        $this->assertSame($id, $comment->getId());
147
+        $this->assertSame('2', $comment->getParentId());
148
+        $this->assertSame('1', $comment->getTopmostParentId());
149
+        $this->assertSame(2, $comment->getChildrenCount());
150
+        $this->assertSame('users', $comment->getActorType());
151
+        $this->assertSame('alice', $comment->getActorId());
152
+        $this->assertSame('nice one', $comment->getMessage());
153
+        $this->assertSame('comment', $comment->getVerb());
154
+        $this->assertSame('files', $comment->getObjectType());
155
+        $this->assertSame('file64', $comment->getObjectId());
156
+        $this->assertEquals($creationDT->getTimestamp(), $comment->getCreationDateTime()->getTimestamp());
157
+        $this->assertEquals($latestChildDT->getTimestamp(), $comment->getLatestChildDateTime()->getTimestamp());
158
+        $this->assertEquals('referenceId', $comment->getReferenceId());
159
+        $this->assertEquals(['last_edit_actor_id' => 'admin'], $comment->getMetaData());
160
+    }
161
+
162
+    public function testGetTreeNotFound(): void {
163
+        $this->expectException(NotFoundException::class);
164
+
165
+        $manager = $this->getManager();
166
+        $manager->getTree('22');
167
+    }
168
+
169
+    public function testGetTreeNotFoundInvalidIpnut(): void {
170
+        $this->expectException(\InvalidArgumentException::class);
171
+
172
+        $manager = $this->getManager();
173
+        $manager->getTree('unexisting22');
174
+    }
175
+
176
+    public function testGetTree(): void {
177
+        $headId = $this->addDatabaseEntry('0', '0');
178
+
179
+        $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
180
+        $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
181
+        $id = $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
182
+
183
+        $manager = $this->getManager();
184
+        $tree = $manager->getTree($headId);
185
+
186
+        // Verifying the root comment
187
+        $this->assertArrayHasKey('comment', $tree);
188
+        $this->assertInstanceOf(IComment::class, $tree['comment']);
189
+        $this->assertSame($headId, $tree['comment']->getId());
190
+        $this->assertArrayHasKey('replies', $tree);
191
+        $this->assertCount(3, $tree['replies']);
192
+
193
+        // one level deep
194
+        foreach ($tree['replies'] as $reply) {
195
+            $this->assertInstanceOf(IComment::class, $reply['comment']);
196
+            $this->assertSame((string)$id, $reply['comment']->getId());
197
+            $this->assertCount(0, $reply['replies']);
198
+            $id--;
199
+        }
200
+    }
201
+
202
+    public function testGetTreeNoReplies(): void {
203
+        $id = $this->addDatabaseEntry('0', '0');
204
+
205
+        $manager = $this->getManager();
206
+        $tree = $manager->getTree($id);
207
+
208
+        // Verifying the root comment
209
+        $this->assertArrayHasKey('comment', $tree);
210
+        $this->assertInstanceOf(IComment::class, $tree['comment']);
211
+        $this->assertSame($id, $tree['comment']->getId());
212
+        $this->assertArrayHasKey('replies', $tree);
213
+        $this->assertCount(0, $tree['replies']);
214
+    }
215
+
216
+    public function testGetTreeWithLimitAndOffset(): void {
217
+        $headId = $this->addDatabaseEntry('0', '0');
218
+
219
+        $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
220
+        $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
221
+        $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
222
+        $idToVerify = $this->addDatabaseEntry($headId, $headId, new \DateTime());
223
+
224
+        $manager = $this->getManager();
225
+
226
+        for ($offset = 0; $offset < 3; $offset += 2) {
227
+            $tree = $manager->getTree($headId, 2, $offset);
228
+
229
+            // Verifying the root comment
230
+            $this->assertArrayHasKey('comment', $tree);
231
+            $this->assertInstanceOf(IComment::class, $tree['comment']);
232
+            $this->assertSame($headId, $tree['comment']->getId());
233
+            $this->assertArrayHasKey('replies', $tree);
234
+            $this->assertCount(2, $tree['replies']);
235
+
236
+            // one level deep
237
+            foreach ($tree['replies'] as $reply) {
238
+                $this->assertInstanceOf(IComment::class, $reply['comment']);
239
+                $this->assertSame((string)$idToVerify, (string)$reply['comment']->getId());
240
+                $this->assertCount(0, $reply['replies']);
241
+                $idToVerify--;
242
+            }
243
+        }
244
+    }
245
+
246
+    public function testGetForObject(): void {
247
+        $this->addDatabaseEntry('0', '0');
248
+
249
+        $manager = $this->getManager();
250
+        $comments = $manager->getForObject('files', 'file64');
251
+
252
+        $this->assertIsArray($comments);
253
+        $this->assertCount(1, $comments);
254
+        $this->assertInstanceOf(IComment::class, $comments[0]);
255
+        $this->assertSame('nice one', $comments[0]->getMessage());
256
+    }
257
+
258
+    public function testGetForObjectWithLimitAndOffset(): void {
259
+        $this->addDatabaseEntry('0', '0', new \DateTime('-6 hours'));
260
+        $this->addDatabaseEntry('0', '0', new \DateTime('-5 hours'));
261
+        $this->addDatabaseEntry('1', '1', new \DateTime('-4 hours'));
262
+        $this->addDatabaseEntry('0', '0', new \DateTime('-3 hours'));
263
+        $this->addDatabaseEntry('2', '2', new \DateTime('-2 hours'));
264
+        $this->addDatabaseEntry('2', '2', new \DateTime('-1 hours'));
265
+        $idToVerify = $this->addDatabaseEntry('3', '1', new \DateTime());
266
+
267
+        $manager = $this->getManager();
268
+        $offset = 0;
269
+        do {
270
+            $comments = $manager->getForObject('files', 'file64', 3, $offset);
271
+
272
+            $this->assertIsArray($comments);
273
+            foreach ($comments as $key => $comment) {
274
+                $this->assertInstanceOf(IComment::class, $comment);
275
+                $this->assertSame('nice one', $comment->getMessage());
276
+                $this->assertSame((string)$idToVerify, $comment->getId(), 'ID wrong for comment ' . $key . ' on offset: ' . $offset);
277
+                $idToVerify--;
278
+            }
279
+            $offset += 3;
280
+        } while (count($comments) > 0);
281
+    }
282
+
283
+    public function testGetForObjectWithDateTimeConstraint(): void {
284
+        $this->addDatabaseEntry('0', '0', new \DateTime('-6 hours'));
285
+        $this->addDatabaseEntry('0', '0', new \DateTime('-5 hours'));
286
+        $id1 = $this->addDatabaseEntry('0', '0', new \DateTime('-3 hours'));
287
+        $id2 = $this->addDatabaseEntry('2', '2', new \DateTime('-2 hours'));
288
+
289
+        $manager = $this->getManager();
290
+        $comments = $manager->getForObject('files', 'file64', 0, 0, new \DateTime('-4 hours'));
291
+
292
+        $this->assertCount(2, $comments);
293
+        $this->assertSame($id2, $comments[0]->getId());
294
+        $this->assertSame($id1, $comments[1]->getId());
295
+    }
296
+
297
+    public function testGetForObjectWithLimitAndOffsetAndDateTimeConstraint(): void {
298
+        $this->addDatabaseEntry('0', '0', new \DateTime('-7 hours'));
299
+        $this->addDatabaseEntry('0', '0', new \DateTime('-6 hours'));
300
+        $this->addDatabaseEntry('1', '1', new \DateTime('-5 hours'));
301
+        $this->addDatabaseEntry('0', '0', new \DateTime('-3 hours'));
302
+        $this->addDatabaseEntry('2', '2', new \DateTime('-2 hours'));
303
+        $this->addDatabaseEntry('2', '2', new \DateTime('-1 hours'));
304
+        $idToVerify = $this->addDatabaseEntry('3', '1', new \DateTime());
305
+
306
+        $manager = $this->getManager();
307
+        $offset = 0;
308
+        do {
309
+            $comments = $manager->getForObject('files', 'file64', 3, $offset, new \DateTime('-4 hours'));
310
+
311
+            $this->assertIsArray($comments);
312
+            foreach ($comments as $comment) {
313
+                $this->assertInstanceOf(IComment::class, $comment);
314
+                $this->assertSame('nice one', $comment->getMessage());
315
+                $this->assertSame((string)$idToVerify, $comment->getId());
316
+                $this->assertGreaterThanOrEqual(4, $comment->getId());
317
+                $idToVerify--;
318
+            }
319
+            $offset += 3;
320
+        } while (count($comments) > 0);
321
+    }
322
+
323
+    public function testGetNumberOfCommentsForObject(): void {
324
+        for ($i = 1; $i < 5; $i++) {
325
+            $this->addDatabaseEntry('0', '0');
326
+        }
327
+
328
+        $manager = $this->getManager();
329
+
330
+        $amount = $manager->getNumberOfCommentsForObject('untype', '00');
331
+        $this->assertSame(0, $amount);
332
+
333
+        $amount = $manager->getNumberOfCommentsForObject('files', 'file64');
334
+        $this->assertSame(4, $amount);
335
+    }
336
+
337
+    public function testGetNumberOfUnreadCommentsForFolder(): void {
338
+        $folder = $this->createMock(Folder::class);
339
+        $fileIds = range(1111, 1114);
340
+        $children = array_map(function (int $id) {
341
+            $file = $this->createMock(Folder::class);
342
+            $file->method('getId')
343
+                ->willReturn($id);
344
+            return $file;
345
+        }, $fileIds);
346
+        $folder->method('getId')->willReturn(1000);
347
+        $folder->method('getDirectoryListing')->willReturn($children);
348
+        $this->rootFolder->method('getFirstNodeById')
349
+            ->with($folder->getId())
350
+            ->willReturn($folder);
351
+
352
+        // 2 comment for 1111 with 1 before read marker
353
+        // 2 comments for 1112 with no read marker
354
+        // 1 comment for 1113 before read marker
355
+        // 1 comment for 1114 with no read marker
356
+        $this->addDatabaseEntry('0', '0', null, null, $fileIds[1]);
357
+        for ($i = 0; $i < 4; $i++) {
358
+            $this->addDatabaseEntry('0', '0', null, null, $fileIds[$i]);
359
+        }
360
+        $this->addDatabaseEntry('0', '0', (new \DateTime())->modify('-2 days'), null, $fileIds[0]);
361
+        /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
362
+        $user = $this->createMock(IUser::class);
363
+        $user->expects($this->any())
364
+            ->method('getUID')
365
+            ->willReturn('comment_test');
366
+
367
+        $manager = $this->getManager();
368
+
369
+        $manager->setReadMark('files', (string)$fileIds[0], (new \DateTime())->modify('-1 days'), $user);
370
+        $manager->setReadMark('files', (string)$fileIds[2], (new \DateTime()), $user);
371
+
372
+        $amount = $manager->getNumberOfUnreadCommentsForFolder($folder->getId(), $user);
373
+        $this->assertEquals([
374
+            $fileIds[0] => 1,
375
+            $fileIds[1] => 2,
376
+            $fileIds[3] => 1,
377
+        ], $amount);
378
+    }
379
+
380
+    #[DataProvider(methodName: 'dataGetForObjectSince')]
381
+    public function testGetForObjectSince(?int $lastKnown, string $order, int $limit, int $resultFrom, int $resultTo): void {
382
+        $ids = [];
383
+        $ids[] = $this->addDatabaseEntry('0', '0');
384
+        $ids[] = $this->addDatabaseEntry('0', '0');
385
+        $ids[] = $this->addDatabaseEntry('0', '0');
386
+        $ids[] = $this->addDatabaseEntry('0', '0');
387
+        $ids[] = $this->addDatabaseEntry('0', '0');
388
+
389
+        $manager = $this->getManager();
390
+        $comments = $manager->getForObjectSince('files', 'file64', ($lastKnown === null ? 0 : (int)$ids[$lastKnown]), $order, $limit);
391
+
392
+        $expected = array_slice($ids, $resultFrom, $resultTo - $resultFrom + 1);
393
+        if ($order === 'desc') {
394
+            $expected = array_reverse($expected);
395
+        }
396
+
397
+        $this->assertSame($expected, array_map(static fn (IComment $c): string => $c->getId(), $comments));
398
+    }
399
+
400
+    public static function dataGetForObjectSince(): array {
401
+        return [
402
+            [null, 'asc', 20, 0, 4],
403
+            [null, 'asc', 2, 0, 1],
404
+            [null, 'desc', 20, 0, 4],
405
+            [null, 'desc', 2, 3, 4],
406
+            [1, 'asc', 20, 2, 4],
407
+            [1, 'asc', 2, 2, 3],
408
+            [3, 'desc', 20, 0, 2],
409
+            [3, 'desc', 2, 1, 2],
410
+        ];
411
+    }
412
+
413
+    public static function invalidCreateArgsProvider(): array {
414
+        return [
415
+            ['', 'aId-1', 'oType-1', 'oId-1'],
416
+            ['aType-1', '', 'oType-1', 'oId-1'],
417
+            ['aType-1', 'aId-1', '', 'oId-1'],
418
+            ['aType-1', 'aId-1', 'oType-1', ''],
419
+            [1, 'aId-1', 'oType-1', 'oId-1'],
420
+            ['aType-1', 1, 'oType-1', 'oId-1'],
421
+            ['aType-1', 'aId-1', 1, 'oId-1'],
422
+            ['aType-1', 'aId-1', 'oType-1', 1],
423
+        ];
424
+    }
425
+
426
+    #[DataProvider(methodName: 'invalidCreateArgsProvider')]
427
+    public function testCreateCommentInvalidArguments(string|int $aType, string|int $aId, string|int $oType, string|int $oId): void {
428
+        $this->expectException(\InvalidArgumentException::class);
429
+
430
+        $manager = $this->getManager();
431
+        $manager->create($aType, $aId, $oType, $oId);
432
+    }
433
+
434
+    public function testCreateComment(): void {
435
+        $actorType = 'bot';
436
+        $actorId = 'bob';
437
+        $objectType = 'weather';
438
+        $objectId = 'bielefeld';
439
+
440
+        $comment = $this->getManager()->create($actorType, $actorId, $objectType, $objectId);
441
+        $this->assertInstanceOf(IComment::class, $comment);
442
+        $this->assertSame($actorType, $comment->getActorType());
443
+        $this->assertSame($actorId, $comment->getActorId());
444
+        $this->assertSame($objectType, $comment->getObjectType());
445
+        $this->assertSame($objectId, $comment->getObjectId());
446
+    }
447
+
448
+
449
+    public function testDelete(): void {
450
+        $this->expectException(NotFoundException::class);
451
+
452
+        $manager = $this->getManager();
453
+
454
+        $done = $manager->delete('404');
455
+        $this->assertFalse($done);
456
+
457
+        $done = $manager->delete('%');
458
+        $this->assertFalse($done);
459
+
460
+        $done = $manager->delete('');
461
+        $this->assertFalse($done);
462
+
463
+        $id = $this->addDatabaseEntry('0', '0');
464
+        $comment = $manager->get($id);
465
+        $this->assertInstanceOf(IComment::class, $comment);
466
+        $done = $manager->delete($id);
467
+        $this->assertTrue($done);
468
+        $manager->get($id);
469
+    }
470
+
471
+    #[DataProvider(methodName: 'providerTestSave')]
472
+    public function testSave(string $message, string $actorId, string $verb, ?string $parentId, ?string $id = ''): IComment {
473
+        $manager = $this->getManager();
474
+        $comment = new Comment();
475
+        $comment
476
+            ->setId($id)
477
+            ->setActor('users', $actorId)
478
+            ->setObject('files', 'file64')
479
+            ->setMessage($message)
480
+            ->setVerb($verb);
481
+        if ($parentId) {
482
+            $comment->setParentId($parentId);
483
+        }
484
+
485
+        $saveSuccessful = $manager->save($comment);
486
+        $this->assertTrue($saveSuccessful, 'Comment saving was not successful');
487
+        $this->assertNotEquals('', $comment->getId(), 'Comment ID should not be empty');
488
+        $this->assertNotEquals('0', $comment->getId(), 'Comment ID should not be string \'0\'');
489
+        $this->assertNotNull($comment->getCreationDateTime(), 'Comment creation date should not be null');
490
+
491
+        $loadedComment = $manager->get($comment->getId());
492
+        $this->assertSame($comment->getMessage(), $loadedComment->getMessage(), 'Comment message should match');
493
+        $this->assertEquals($comment->getCreationDateTime()->getTimestamp(), $loadedComment->getCreationDateTime()->getTimestamp(), 'Comment creation date should match');
494
+        return $comment;
495
+    }
496
+
497
+    public static function providerTestSave(): array {
498
+        return [
499
+            ['very beautiful, I am impressed!', 'alice', 'comment', null],
500
+        ];
501
+    }
502
+
503
+    public function testSaveUpdate(): void {
504
+        $manager = $this->getManager();
505
+        $comment = new Comment();
506
+        $comment
507
+            ->setActor('users', 'alice')
508
+            ->setObject('files', 'file64')
509
+            ->setMessage('very beautiful, I am impressed!')
510
+            ->setVerb('comment')
511
+            ->setExpireDate(new \DateTime('+2 hours'));
512
+
513
+        $manager->save($comment);
514
+
515
+        $loadedComment = $manager->get($comment->getId());
516
+        // Compare current object with database values
517
+        $this->assertSame($comment->getMessage(), $loadedComment->getMessage());
518
+        $this->assertSame(
519
+            $comment->getExpireDate()->format('Y-m-d H:i:s'),
520
+            $loadedComment->getExpireDate()->format('Y-m-d H:i:s')
521
+        );
522
+
523
+        // Preserve the original comment to compare after update
524
+        $original = clone $comment;
525
+
526
+        // Update values
527
+        $comment->setMessage('very beautiful, I am really so much impressed!')
528
+            ->setExpireDate(new \DateTime('+1 hours'));
529
+        $manager->save($comment);
530
+
531
+        $loadedComment = $manager->get($comment->getId());
532
+        // Compare current object with database values
533
+        $this->assertSame($comment->getMessage(), $loadedComment->getMessage());
534
+        $this->assertSame(
535
+            $comment->getExpireDate()->format('Y-m-d H:i:s'),
536
+            $loadedComment->getExpireDate()->format('Y-m-d H:i:s')
537
+        );
538
+
539
+        // Compare original object with database values
540
+        $this->assertNotSame($original->getMessage(), $loadedComment->getMessage());
541
+        $this->assertNotSame(
542
+            $original->getExpireDate()->format('Y-m-d H:i:s'),
543
+            $loadedComment->getExpireDate()->format('Y-m-d H:i:s')
544
+        );
545
+    }
546
+
547
+
548
+    public function testSaveUpdateException(): void {
549
+        $manager = $this->getManager();
550
+        $comment = new Comment();
551
+        $comment
552
+            ->setActor('users', 'alice')
553
+            ->setObject('files', 'file64')
554
+            ->setMessage('very beautiful, I am impressed!')
555
+            ->setVerb('comment');
556
+
557
+        $manager->save($comment);
558
+
559
+        $manager->delete($comment->getId());
560
+
561
+        $comment->setMessage('very beautiful, I am really so much impressed!');
562
+        $this->expectException(NotFoundException::class);
563
+        $manager->save($comment);
564
+    }
565
+
566
+
567
+    public function testSaveIncomplete(): void {
568
+
569
+        $manager = $this->getManager();
570
+        $comment = new Comment();
571
+        $comment->setMessage('from no one to nothing');
572
+
573
+        $this->expectException(\UnexpectedValueException::class);
574
+        $manager->save($comment);
575
+    }
576
+
577
+    public function testSaveAsChild(): void {
578
+        $id = $this->addDatabaseEntry('0', '0');
579
+
580
+        $manager = $this->getManager();
581
+
582
+        for ($i = 0; $i < 3; $i++) {
583
+            $comment = new Comment();
584
+            $comment
585
+                ->setActor('users', 'alice')
586
+                ->setObject('files', 'file64')
587
+                ->setParentId($id)
588
+                ->setMessage('full ack')
589
+                ->setVerb('comment')
590
+                // setting the creation time avoids using sleep() while making sure to test with different timestamps
591
+                ->setCreationDateTime(new \DateTime('+' . $i . ' minutes'));
592
+
593
+            $manager->save($comment);
594
+
595
+            $this->assertSame($id, $comment->getTopmostParentId());
596
+            $parentComment = $manager->get($id);
597
+            $this->assertSame($i + 1, $parentComment->getChildrenCount());
598
+            $this->assertEquals($comment->getCreationDateTime()->getTimestamp(), $parentComment->getLatestChildDateTime()->getTimestamp());
599
+        }
600
+    }
601
+
602
+    public static function invalidActorArgsProvider(): array {
603
+        return
604
+            [
605
+                ['', ''],
606
+                [1, 'alice'],
607
+                ['users', 1],
608
+            ];
609
+    }
610
+
611
+    #[DataProvider(methodName: 'invalidActorArgsProvider')]
612
+    public function testDeleteReferencesOfActorInvalidInput(string|int $type, string|int $id): void {
613
+        $this->expectException(\InvalidArgumentException::class);
614
+
615
+        $manager = $this->getManager();
616
+        $manager->deleteReferencesOfActor($type, $id);
617
+    }
618
+
619
+    public function testDeleteReferencesOfActor(): void {
620
+        $ids = [];
621
+        $ids[] = $this->addDatabaseEntry('0', '0');
622
+        $ids[] = $this->addDatabaseEntry('0', '0');
623
+        $ids[] = $this->addDatabaseEntry('0', '0');
624
+
625
+        $manager = $this->getManager();
626
+
627
+        // just to make sure they are really set, with correct actor data
628
+        $comment = $manager->get($ids[1]);
629
+        $this->assertSame('users', $comment->getActorType());
630
+        $this->assertSame('alice', $comment->getActorId());
631
+
632
+        $wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice');
633
+        $this->assertTrue($wasSuccessful);
634
+
635
+        foreach ($ids as $id) {
636
+            $comment = $manager->get($id);
637
+            $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorType());
638
+            $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorId());
639
+        }
640
+
641
+        // actor info is gone from DB, but when database interaction is alright,
642
+        // we still expect to get true back
643
+        $wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice');
644
+        $this->assertTrue($wasSuccessful);
645
+    }
646
+
647
+    public function testDeleteReferencesOfActorWithUserManagement(): void {
648
+        $user = Server::get(IUserManager::class)->createUser('xenia', 'NotAnEasyPassword123456+');
649
+        $this->assertInstanceOf(IUser::class, $user);
650
+
651
+        $manager = Server::get(ICommentsManager::class);
652
+        $comment = $manager->create('users', $user->getUID(), 'files', 'file64');
653
+        $comment
654
+            ->setMessage('Most important comment I ever left on the Internet.')
655
+            ->setVerb('comment');
656
+        $status = $manager->save($comment);
657
+        $this->assertTrue($status);
658
+
659
+        $commentID = $comment->getId();
660
+        $user->delete();
661
+
662
+        $comment = $manager->get($commentID);
663
+        $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorType());
664
+        $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorId());
665
+    }
666
+
667
+    public static function invalidObjectArgsProvider(): array {
668
+        return
669
+            [
670
+                ['', ''],
671
+                [1, 'file64'],
672
+                ['files', 1],
673
+            ];
674
+    }
675
+
676
+    #[DataProvider(methodName: 'invalidObjectArgsProvider')]
677
+    public function testDeleteCommentsAtObjectInvalidInput(string|int $type, string|int $id): void {
678
+        $this->expectException(\InvalidArgumentException::class);
679
+
680
+        $manager = $this->getManager();
681
+        $manager->deleteCommentsAtObject($type, $id);
682
+    }
683
+
684
+    public function testDeleteCommentsAtObject(): void {
685
+        $ids = [];
686
+        $ids[] = $this->addDatabaseEntry('0', '0');
687
+        $ids[] = $this->addDatabaseEntry('0', '0');
688
+        $ids[] = $this->addDatabaseEntry('0', '0');
689
+
690
+        $manager = $this->getManager();
691
+
692
+        // just to make sure they are really set, with correct actor data
693
+        $comment = $manager->get($ids[1]);
694
+        $this->assertSame('files', $comment->getObjectType());
695
+        $this->assertSame('file64', $comment->getObjectId());
696
+
697
+        $wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64');
698
+        $this->assertTrue($wasSuccessful);
699
+
700
+        $verified = 0;
701
+        foreach ($ids as $id) {
702
+            try {
703
+                $manager->get($id);
704
+            } catch (NotFoundException) {
705
+                $verified++;
706
+            }
707
+        }
708
+        $this->assertSame(3, $verified);
709
+
710
+        // actor info is gone from DB, but when database interaction is alright,
711
+        // we still expect to get true back
712
+        $wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64');
713
+        $this->assertTrue($wasSuccessful);
714
+    }
715
+
716
+    public function testDeleteCommentsExpiredAtObjectTypeAndId(): void {
717
+        $ids = [];
718
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('+2 hours'));
719
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('+2 hours'));
720
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('+2 hours'));
721
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('-2 hours'));
722
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('-2 hours'));
723
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, null, new \DateTime('-2 hours'));
724
+
725
+        /** @psalm-suppress DeprecatedInterface No way around at the moment */
726
+        $manager = new Manager(
727
+            $this->connection,
728
+            $this->createMock(LoggerInterface::class),
729
+            $this->createMock(IConfig::class),
730
+            Server::get(ITimeFactory::class),
731
+            new EmojiHelper($this->connection),
732
+            $this->createMock(IInitialStateService::class),
733
+            $this->rootFolder,
734
+            $this->createMock(IEventDispatcher::class)
735
+        );
736
+
737
+        // just to make sure they are really set, with correct actor data
738
+        $comment = $manager->get($ids[1]);
739
+        $this->assertSame('files', $comment->getObjectType());
740
+        $this->assertSame('file64', $comment->getObjectId());
741
+
742
+        $deleted = $manager->deleteCommentsExpiredAtObject('files', 'file64');
743
+        $this->assertTrue($deleted);
744
+
745
+        $deleted = 0;
746
+        $exists = 0;
747
+        foreach ($ids as $id) {
748
+            try {
749
+                $manager->get($id);
750
+                $exists++;
751
+            } catch (NotFoundException) {
752
+                $deleted++;
753
+            }
754
+        }
755
+        $this->assertSame(3, $exists);
756
+        $this->assertSame(3, $deleted);
757
+
758
+        // actor info is gone from DB, but when database interaction is alright,
759
+        // we still expect to get true back
760
+        $deleted = $manager->deleteCommentsExpiredAtObject('files', 'file64');
761
+        $this->assertFalse($deleted);
762
+    }
763
+
764
+    public function testDeleteCommentsExpiredAtObjectType(): void {
765
+        $ids = [];
766
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file1', new \DateTime('-2 hours'));
767
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file2', new \DateTime('-2 hours'));
768
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime('-2 hours'));
769
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime());
770
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime());
771
+        $ids[] = $this->addDatabaseEntry('0', '0', null, null, 'file3', new \DateTime());
772
+
773
+        /** @psalm-suppress DeprecatedInterface No way around at the moment */
774
+        $manager = new Manager(
775
+            $this->connection,
776
+            $this->createMock(LoggerInterface::class),
777
+            $this->createMock(IConfig::class),
778
+            Server::get(ITimeFactory::class),
779
+            new EmojiHelper($this->connection),
780
+            $this->createMock(IInitialStateService::class),
781
+            $this->rootFolder,
782
+            $this->createMock(IEventDispatcher::class)
783
+        );
784
+
785
+        $deleted = $manager->deleteCommentsExpiredAtObject('files');
786
+        $this->assertTrue($deleted);
787
+
788
+        $deleted = 0;
789
+        $exists = 0;
790
+        foreach ($ids as $id) {
791
+            try {
792
+                $manager->get($id);
793
+                $exists++;
794
+            } catch (NotFoundException) {
795
+                $deleted++;
796
+            }
797
+        }
798
+        $this->assertSame(0, $exists);
799
+        $this->assertSame(6, $deleted);
800
+
801
+        // actor info is gone from DB, but when database interaction is alright,
802
+        // we still expect to get true back
803
+        $deleted = $manager->deleteCommentsExpiredAtObject('files');
804
+        $this->assertFalse($deleted);
805
+    }
806
+
807
+    public function testSetMarkRead(): void {
808
+        /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
809
+        $user = $this->createMock(IUser::class);
810
+        $user->expects($this->any())
811
+            ->method('getUID')
812
+            ->willReturn('alice');
813
+
814
+        $dateTimeSet = new \DateTime();
815
+
816
+        $manager = $this->getManager();
817
+        $manager->setReadMark('robot', '36', $dateTimeSet, $user);
818
+
819
+        $dateTimeGet = $manager->getReadMark('robot', '36', $user);
820
+
821
+        $this->assertEquals($dateTimeSet->getTimestamp(), $dateTimeGet->getTimestamp());
822
+    }
823
+
824
+    public function testSetMarkReadUpdate(): void {
825
+        /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
826
+        $user = $this->createMock(IUser::class);
827
+        $user->expects($this->any())
828
+            ->method('getUID')
829
+            ->willReturn('alice');
830
+
831
+        $dateTimeSet = new \DateTime('yesterday');
832
+
833
+        $manager = $this->getManager();
834
+        $manager->setReadMark('robot', '36', $dateTimeSet, $user);
835
+
836
+        $dateTimeSet = new \DateTime('today');
837
+        $manager->setReadMark('robot', '36', $dateTimeSet, $user);
838
+
839
+        $dateTimeGet = $manager->getReadMark('robot', '36', $user);
840
+
841
+        $this->assertEquals($dateTimeSet, $dateTimeGet);
842
+    }
843
+
844
+    public function testReadMarkDeleteUser(): void {
845
+        $user = $this->createMock(IUser::class);
846
+        $user->expects($this->any())
847
+            ->method('getUID')
848
+            ->willReturn('alice');
849
+
850
+        $dateTimeSet = new \DateTime();
851
+
852
+        $manager = $this->getManager();
853
+        $manager->setReadMark('robot', '36', $dateTimeSet, $user);
854
+
855
+        $manager->deleteReadMarksFromUser($user);
856
+        $dateTimeGet = $manager->getReadMark('robot', '36', $user);
857
+
858
+        $this->assertNull($dateTimeGet);
859
+    }
860
+
861
+    public function testReadMarkDeleteObject(): void {
862
+        $user = $this->createMock(IUser::class);
863
+        $user->expects($this->any())
864
+            ->method('getUID')
865
+            ->willReturn('alice');
866
+
867
+        $dateTimeSet = new \DateTime();
868
+
869
+        $manager = $this->getManager();
870
+        $manager->setReadMark('robot', '36', $dateTimeSet, $user);
871
+
872
+        $manager->deleteReadMarksOnObject('robot', '36');
873
+        $dateTimeGet = $manager->getReadMark('robot', '36', $user);
874
+
875
+        $this->assertNull($dateTimeGet);
876
+    }
877
+
878
+    public function testSendEvent(): void {
879
+        /** @psalm-suppress DeprecatedInterface Test for deprecated interface */
880
+        $handler1 = $this->createMock(ICommentsEventHandler::class);
881
+        $handler1->expects($this->exactly(4))
882
+            ->method('handle');
883 883
 
884
-		/** @psalm-suppress DeprecatedInterface Test for deprecated interface */
885
-		$handler2 = $this->createMock(ICommentsEventHandler::class);
886
-		$handler1->expects($this->exactly(4))
887
-			->method('handle');
888
-
889
-		$manager = $this->getManager();
890
-		$manager->registerEventHandler(function () use ($handler1) {
891
-			return $handler1;
892
-		});
893
-		$manager->registerEventHandler(function () use ($handler2) {
894
-			return $handler2;
895
-		});
896
-
897
-		$comment = new Comment();
898
-		$comment
899
-			->setActor('users', 'alice')
900
-			->setObject('files', 'file64')
901
-			->setMessage('very beautiful, I am impressed!')
902
-			->setVerb('comment');
903
-
904
-		// Add event
905
-		$manager->save($comment);
906
-
907
-		// Update event
908
-		$comment->setMessage('Different topic');
909
-		$manager->save($comment);
910
-
911
-		// Delete event
912
-		$manager->delete($comment->getId());
913
-	}
914
-
915
-	public function testResolveDisplayName(): void {
916
-		$manager = $this->getManager();
917
-
918
-		$planetClosure = function ($name) {
919
-			return ucfirst($name);
920
-		};
921
-
922
-		$galaxyClosure = function ($name) {
923
-			return strtoupper($name);
924
-		};
925
-
926
-		$manager->registerDisplayNameResolver('planet', $planetClosure);
927
-		$manager->registerDisplayNameResolver('galaxy', $galaxyClosure);
928
-
929
-		$this->assertSame('Neptune', $manager->resolveDisplayName('planet', 'neptune'));
930
-		$this->assertSame('SOMBRERO', $manager->resolveDisplayName('galaxy', 'sombrero'));
931
-	}
932
-
933
-
934
-	public function testRegisterResolverDuplicate(): void {
935
-		$this->expectException(\OutOfBoundsException::class);
936
-
937
-		$manager = $this->getManager();
938
-
939
-		$planetClosure = static fn (string $name): string => ucfirst($name);
940
-		$manager->registerDisplayNameResolver('planet', $planetClosure);
941
-		$manager->registerDisplayNameResolver('planet', $planetClosure);
942
-	}
943
-
944
-
945
-	public function testResolveDisplayNameUnregisteredType(): void {
946
-		$this->expectException(\OutOfBoundsException::class);
947
-
948
-		$manager = $this->getManager();
949
-
950
-		$planetClosure = static fn (string $name): string => ucfirst($name);
951
-		$manager->registerDisplayNameResolver('planet', $planetClosure);
952
-		$manager->resolveDisplayName('galaxy', 'sombrero');
953
-	}
954
-
955
-	public function testResolveDisplayNameDirtyResolver(): void {
956
-		$manager = $this->getManager();
957
-
958
-		$planetClosure = static fn (): null => null;
959
-		$manager->registerDisplayNameResolver('planet', $planetClosure);
960
-		$this->assertIsString($manager->resolveDisplayName('planet', 'neptune'));
961
-	}
962
-
963
-	private function skipIfNotSupport4ByteUTF(): void {
964
-		if (!$this->getManager()->supportReactions()) {
965
-			$this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8');
966
-		}
967
-	}
968
-
969
-	#[DataProvider(methodName: 'providerTestReactionAddAndDelete')]
970
-	public function testReactionAddAndDelete(array $comments, array $reactionsExpected): void {
971
-		$this->skipIfNotSupport4ByteUTF();
972
-		$manager = $this->getManager();
973
-
974
-		$processedComments = $this->proccessComments($comments);
975
-		$comment = end($processedComments);
976
-		if ($comment->getParentId()) {
977
-			$parent = $manager->get($comment->getParentId());
978
-			$this->assertEqualsCanonicalizing($reactionsExpected, $parent->getReactions());
979
-		}
980
-	}
981
-
982
-	public static function providerTestReactionAddAndDelete(): array {
983
-		return[
984
-			[
985
-				[
986
-					['message', 'alice', 'comment', null],
987
-				], [],
988
-			],
989
-			[
990
-				[
991
-					['message', 'alice', 'comment', null],
992
-					['
Please login to merge, or discard this patch.
Spacing   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 			])
81 81
 			->executeStatement();
82 82
 
83
-		return (string)$qb->getLastInsertId();
83
+		return (string) $qb->getLastInsertId();
84 84
 	}
85 85
 
86 86
 	protected function getManager(): Manager {
@@ -139,7 +139,7 @@  discard block
 block discarded – undo
139 139
 			])
140 140
 			->executeStatement();
141 141
 
142
-		$id = (string)$qb->getLastInsertId();
142
+		$id = (string) $qb->getLastInsertId();
143 143
 
144 144
 		$comment = $manager->get($id);
145 145
 		$this->assertInstanceOf(IComment::class, $comment);
@@ -193,7 +193,7 @@  discard block
 block discarded – undo
193 193
 		// one level deep
194 194
 		foreach ($tree['replies'] as $reply) {
195 195
 			$this->assertInstanceOf(IComment::class, $reply['comment']);
196
-			$this->assertSame((string)$id, $reply['comment']->getId());
196
+			$this->assertSame((string) $id, $reply['comment']->getId());
197 197
 			$this->assertCount(0, $reply['replies']);
198 198
 			$id--;
199 199
 		}
@@ -236,7 +236,7 @@  discard block
 block discarded – undo
236 236
 			// one level deep
237 237
 			foreach ($tree['replies'] as $reply) {
238 238
 				$this->assertInstanceOf(IComment::class, $reply['comment']);
239
-				$this->assertSame((string)$idToVerify, (string)$reply['comment']->getId());
239
+				$this->assertSame((string) $idToVerify, (string) $reply['comment']->getId());
240 240
 				$this->assertCount(0, $reply['replies']);
241 241
 				$idToVerify--;
242 242
 			}
@@ -273,7 +273,7 @@  discard block
 block discarded – undo
273 273
 			foreach ($comments as $key => $comment) {
274 274
 				$this->assertInstanceOf(IComment::class, $comment);
275 275
 				$this->assertSame('nice one', $comment->getMessage());
276
-				$this->assertSame((string)$idToVerify, $comment->getId(), 'ID wrong for comment ' . $key . ' on offset: ' . $offset);
276
+				$this->assertSame((string) $idToVerify, $comment->getId(), 'ID wrong for comment '.$key.' on offset: '.$offset);
277 277
 				$idToVerify--;
278 278
 			}
279 279
 			$offset += 3;
@@ -312,7 +312,7 @@  discard block
 block discarded – undo
312 312
 			foreach ($comments as $comment) {
313 313
 				$this->assertInstanceOf(IComment::class, $comment);
314 314
 				$this->assertSame('nice one', $comment->getMessage());
315
-				$this->assertSame((string)$idToVerify, $comment->getId());
315
+				$this->assertSame((string) $idToVerify, $comment->getId());
316 316
 				$this->assertGreaterThanOrEqual(4, $comment->getId());
317 317
 				$idToVerify--;
318 318
 			}
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
 	public function testGetNumberOfUnreadCommentsForFolder(): void {
338 338
 		$folder = $this->createMock(Folder::class);
339 339
 		$fileIds = range(1111, 1114);
340
-		$children = array_map(function (int $id) {
340
+		$children = array_map(function(int $id) {
341 341
 			$file = $this->createMock(Folder::class);
342 342
 			$file->method('getId')
343 343
 				->willReturn($id);
@@ -366,8 +366,8 @@  discard block
 block discarded – undo
366 366
 
367 367
 		$manager = $this->getManager();
368 368
 
369
-		$manager->setReadMark('files', (string)$fileIds[0], (new \DateTime())->modify('-1 days'), $user);
370
-		$manager->setReadMark('files', (string)$fileIds[2], (new \DateTime()), $user);
369
+		$manager->setReadMark('files', (string) $fileIds[0], (new \DateTime())->modify('-1 days'), $user);
370
+		$manager->setReadMark('files', (string) $fileIds[2], (new \DateTime()), $user);
371 371
 
372 372
 		$amount = $manager->getNumberOfUnreadCommentsForFolder($folder->getId(), $user);
373 373
 		$this->assertEquals([
@@ -387,7 +387,7 @@  discard block
 block discarded – undo
387 387
 		$ids[] = $this->addDatabaseEntry('0', '0');
388 388
 
389 389
 		$manager = $this->getManager();
390
-		$comments = $manager->getForObjectSince('files', 'file64', ($lastKnown === null ? 0 : (int)$ids[$lastKnown]), $order, $limit);
390
+		$comments = $manager->getForObjectSince('files', 'file64', ($lastKnown === null ? 0 : (int) $ids[$lastKnown]), $order, $limit);
391 391
 
392 392
 		$expected = array_slice($ids, $resultFrom, $resultTo - $resultFrom + 1);
393 393
 		if ($order === 'desc') {
@@ -424,7 +424,7 @@  discard block
 block discarded – undo
424 424
 	}
425 425
 
426 426
 	#[DataProvider(methodName: 'invalidCreateArgsProvider')]
427
-	public function testCreateCommentInvalidArguments(string|int $aType, string|int $aId, string|int $oType, string|int $oId): void {
427
+	public function testCreateCommentInvalidArguments(string | int $aType, string | int $aId, string | int $oType, string | int $oId): void {
428 428
 		$this->expectException(\InvalidArgumentException::class);
429 429
 
430 430
 		$manager = $this->getManager();
@@ -588,7 +588,7 @@  discard block
 block discarded – undo
588 588
 				->setMessage('full ack')
589 589
 				->setVerb('comment')
590 590
 				// setting the creation time avoids using sleep() while making sure to test with different timestamps
591
-				->setCreationDateTime(new \DateTime('+' . $i . ' minutes'));
591
+				->setCreationDateTime(new \DateTime('+'.$i.' minutes'));
592 592
 
593 593
 			$manager->save($comment);
594 594
 
@@ -609,7 +609,7 @@  discard block
 block discarded – undo
609 609
 	}
610 610
 
611 611
 	#[DataProvider(methodName: 'invalidActorArgsProvider')]
612
-	public function testDeleteReferencesOfActorInvalidInput(string|int $type, string|int $id): void {
612
+	public function testDeleteReferencesOfActorInvalidInput(string | int $type, string | int $id): void {
613 613
 		$this->expectException(\InvalidArgumentException::class);
614 614
 
615 615
 		$manager = $this->getManager();
@@ -674,7 +674,7 @@  discard block
 block discarded – undo
674 674
 	}
675 675
 
676 676
 	#[DataProvider(methodName: 'invalidObjectArgsProvider')]
677
-	public function testDeleteCommentsAtObjectInvalidInput(string|int $type, string|int $id): void {
677
+	public function testDeleteCommentsAtObjectInvalidInput(string | int $type, string | int $id): void {
678 678
 		$this->expectException(\InvalidArgumentException::class);
679 679
 
680 680
 		$manager = $this->getManager();
@@ -887,10 +887,10 @@  discard block
 block discarded – undo
887 887
 			->method('handle');
888 888
 
889 889
 		$manager = $this->getManager();
890
-		$manager->registerEventHandler(function () use ($handler1) {
890
+		$manager->registerEventHandler(function() use ($handler1) {
891 891
 			return $handler1;
892 892
 		});
893
-		$manager->registerEventHandler(function () use ($handler2) {
893
+		$manager->registerEventHandler(function() use ($handler2) {
894 894
 			return $handler2;
895 895
 		});
896 896
 
@@ -915,11 +915,11 @@  discard block
 block discarded – undo
915 915
 	public function testResolveDisplayName(): void {
916 916
 		$manager = $this->getManager();
917 917
 
918
-		$planetClosure = function ($name) {
918
+		$planetClosure = function($name) {
919 919
 			return ucfirst($name);
920 920
 		};
921 921
 
922
-		$galaxyClosure = function ($name) {
922
+		$galaxyClosure = function($name) {
923 923
 			return strtoupper($name);
924 924
 		};
925 925
 
@@ -1042,10 +1042,10 @@  discard block
 block discarded – undo
1042 1042
 			}
1043 1043
 			$id = '';
1044 1044
 			if ($verb === 'reaction_deleted') {
1045
-				$id = $comments[$message . '#' . $actorId]->getId();
1045
+				$id = $comments[$message.'#'.$actorId]->getId();
1046 1046
 			}
1047 1047
 			$comment = $this->testSave($message, $actorId, $verb, $parentId, $id);
1048
-			$comments[$comment->getMessage() . '#' . $comment->getActorId()] = $comment;
1048
+			$comments[$comment->getMessage().'#'.$comment->getActorId()] = $comment;
1049 1049
 		}
1050 1050
 		$this->connection->commit();
1051 1051
 		return $comments;
@@ -1058,8 +1058,8 @@  discard block
 block discarded – undo
1058 1058
 
1059 1059
 		$processedComments = $this->proccessComments($comments);
1060 1060
 		$comment = reset($processedComments);
1061
-		$all = $manager->retrieveAllReactions((int)$comment->getId());
1062
-		$actual = array_map(static function (IComment $row): array {
1061
+		$all = $manager->retrieveAllReactions((int) $comment->getId());
1062
+		$actual = array_map(static function(IComment $row): array {
1063 1063
 			return [
1064 1064
 				$row->getActorId(),
1065 1065
 				$row->getMessage(),
@@ -2320,8 +2320,8 @@  discard block
 block discarded – undo
2320 2320
 
2321 2321
 		$processedComments = $this->proccessComments($comments);
2322 2322
 		$comment = reset($processedComments);
2323
-		$all = $manager->retrieveAllReactionsWithSpecificReaction((int)$comment->getId(), $reaction);
2324
-		$actual = array_map(static function (IComment $row): array {
2323
+		$all = $manager->retrieveAllReactionsWithSpecificReaction((int) $comment->getId(), $reaction);
2324
+		$actual = array_map(static function(IComment $row): array {
2325 2325
 			return [
2326 2326
 				$row->getActorId(),
2327 2327
 				$row->getMessage(),
@@ -2379,8 +2379,8 @@  discard block
 block discarded – undo
2379 2379
 		if ($notFound) {
2380 2380
 			$this->expectException(NotFoundException::class);
2381 2381
 		}
2382
-		$comment = $processedComments[$expected['message'] . '#' . $expected['actorId']];
2383
-		$actual = $manager->getReactionComment((int)$comment->getParentId(), $comment->getActorType(), $comment->getActorId(), $comment->getMessage());
2382
+		$comment = $processedComments[$expected['message'].'#'.$expected['actorId']];
2383
+		$actual = $manager->getReactionComment((int) $comment->getParentId(), $comment->getActorType(), $comment->getActorId(), $comment->getMessage());
2384 2384
 		if (!$notFound) {
2385 2385
 			$this->assertEquals($expected['message'], $actual->getMessage());
2386 2386
 			$this->assertEquals($expected['actorId'], $actual->getActorId());
Please login to merge, or discard this patch.
tests/lib/Comments/FakeManager.php 1 patch
Indentation   +148 added lines, -148 removed lines patch added patch discarded remove patch
@@ -16,155 +16,155 @@
 block discarded – undo
16 16
  * Class FakeManager
17 17
  */
18 18
 class FakeManager implements ICommentsManager {
19
-	public function get($id): IComment {
20
-		throw new \Exception('Not implemented');
21
-	}
22
-
23
-	public function getTree($id, $limit = 0, $offset = 0): array {
24
-		return ['comment' => new Comment(), 'replies' => []];
25
-	}
26
-
27
-	public function getForObject(
28
-		$objectType,
29
-		$objectId,
30
-		$limit = 0,
31
-		$offset = 0,
32
-		?\DateTime $notOlderThan = null,
33
-	): array {
34
-		return [];
35
-	}
36
-
37
-	public function getForObjectSince(
38
-		string $objectType,
39
-		string $objectId,
40
-		int $lastKnownCommentId,
41
-		string $sortDirection = 'asc',
42
-		int $limit = 30,
43
-		bool $includeLastKnown = false,
44
-		string $topmostParentId = '',
45
-	): array {
46
-		return [];
47
-	}
48
-
49
-	public function getCommentsWithVerbForObjectSinceComment(
50
-		string $objectType,
51
-		string $objectId,
52
-		array $verbs,
53
-		int $lastKnownCommentId,
54
-		string $sortDirection = 'asc',
55
-		int $limit = 30,
56
-		bool $includeLastKnown = false,
57
-		string $topmostParentId = '',
58
-	): array {
59
-		return [];
60
-	}
61
-
62
-	public function getNumberOfCommentsForObject($objectType, $objectId, ?\DateTime $notOlderThan = null, $verb = ''): int {
63
-		return 0;
64
-	}
65
-
66
-	public function getNumberOfCommentsForObjects(string $objectType, array $objectIds, ?\DateTime $notOlderThan = null, string $verb = ''): array {
67
-		return array_fill_keys($objectIds, 0);
68
-	}
69
-
70
-	public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array {
71
-		return [];
72
-	}
73
-
74
-	public function create($actorType, $actorId, $objectType, $objectId): IComment {
75
-		return new Comment();
76
-	}
77
-
78
-	public function delete($id): bool {
79
-		return false;
80
-	}
81
-
82
-	public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment {
83
-		return new Comment();
84
-	}
85
-
86
-	public function retrieveAllReactions(int $parentId): array {
87
-		return [];
88
-	}
89
-
90
-	public function retrieveAllReactionsWithSpecificReaction(int $parentId, string $reaction): array {
91
-		return [];
92
-	}
93
-
94
-	public function supportReactions(): bool {
95
-		return false;
96
-	}
97
-
98
-	public function save(IComment $comment): bool {
99
-		return false;
100
-	}
101
-
102
-	public function deleteReferencesOfActor($actorType, $actorId): bool {
103
-		return false;
104
-	}
105
-
106
-	public function deleteCommentsAtObject($objectType, $objectId): bool {
107
-		return false;
108
-	}
109
-
110
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user): bool {
111
-		return false;
112
-	}
113
-
114
-	public function getReadMark($objectType, $objectId, IUser $user): bool {
115
-		return false;
116
-	}
117
-
118
-	public function deleteReadMarksFromUser(IUser $user): bool {
119
-		return false;
120
-	}
121
-
122
-	public function deleteReadMarksOnObject($objectType, $objectId): bool {
123
-		return false;
124
-	}
125
-
126
-	public function registerEventHandler(\Closure $closure): void {
127
-	}
128
-
129
-	public function registerDisplayNameResolver($type, \Closure $closure): void {
130
-	}
131
-
132
-	public function resolveDisplayName($type, $id): string {
133
-		return '';
134
-	}
135
-
136
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user): array {
137
-		return [];
138
-	}
139
-
140
-	public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array {
141
-		return [];
142
-	}
143
-
144
-	public function load(): void {
145
-	}
146
-
147
-	public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array {
148
-		return [];
149
-	}
150
-
151
-	public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int {
152
-		return 0;
153
-	}
154
-
155
-	public function getNumberOfCommentsWithVerbsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, array $verbs): int {
156
-		return 0;
157
-	}
19
+    public function get($id): IComment {
20
+        throw new \Exception('Not implemented');
21
+    }
22
+
23
+    public function getTree($id, $limit = 0, $offset = 0): array {
24
+        return ['comment' => new Comment(), 'replies' => []];
25
+    }
26
+
27
+    public function getForObject(
28
+        $objectType,
29
+        $objectId,
30
+        $limit = 0,
31
+        $offset = 0,
32
+        ?\DateTime $notOlderThan = null,
33
+    ): array {
34
+        return [];
35
+    }
36
+
37
+    public function getForObjectSince(
38
+        string $objectType,
39
+        string $objectId,
40
+        int $lastKnownCommentId,
41
+        string $sortDirection = 'asc',
42
+        int $limit = 30,
43
+        bool $includeLastKnown = false,
44
+        string $topmostParentId = '',
45
+    ): array {
46
+        return [];
47
+    }
48
+
49
+    public function getCommentsWithVerbForObjectSinceComment(
50
+        string $objectType,
51
+        string $objectId,
52
+        array $verbs,
53
+        int $lastKnownCommentId,
54
+        string $sortDirection = 'asc',
55
+        int $limit = 30,
56
+        bool $includeLastKnown = false,
57
+        string $topmostParentId = '',
58
+    ): array {
59
+        return [];
60
+    }
61
+
62
+    public function getNumberOfCommentsForObject($objectType, $objectId, ?\DateTime $notOlderThan = null, $verb = ''): int {
63
+        return 0;
64
+    }
65
+
66
+    public function getNumberOfCommentsForObjects(string $objectType, array $objectIds, ?\DateTime $notOlderThan = null, string $verb = ''): array {
67
+        return array_fill_keys($objectIds, 0);
68
+    }
69
+
70
+    public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array {
71
+        return [];
72
+    }
73
+
74
+    public function create($actorType, $actorId, $objectType, $objectId): IComment {
75
+        return new Comment();
76
+    }
77
+
78
+    public function delete($id): bool {
79
+        return false;
80
+    }
81
+
82
+    public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment {
83
+        return new Comment();
84
+    }
85
+
86
+    public function retrieveAllReactions(int $parentId): array {
87
+        return [];
88
+    }
89
+
90
+    public function retrieveAllReactionsWithSpecificReaction(int $parentId, string $reaction): array {
91
+        return [];
92
+    }
93
+
94
+    public function supportReactions(): bool {
95
+        return false;
96
+    }
97
+
98
+    public function save(IComment $comment): bool {
99
+        return false;
100
+    }
101
+
102
+    public function deleteReferencesOfActor($actorType, $actorId): bool {
103
+        return false;
104
+    }
105
+
106
+    public function deleteCommentsAtObject($objectType, $objectId): bool {
107
+        return false;
108
+    }
109
+
110
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user): bool {
111
+        return false;
112
+    }
113
+
114
+    public function getReadMark($objectType, $objectId, IUser $user): bool {
115
+        return false;
116
+    }
117
+
118
+    public function deleteReadMarksFromUser(IUser $user): bool {
119
+        return false;
120
+    }
121
+
122
+    public function deleteReadMarksOnObject($objectType, $objectId): bool {
123
+        return false;
124
+    }
125
+
126
+    public function registerEventHandler(\Closure $closure): void {
127
+    }
128
+
129
+    public function registerDisplayNameResolver($type, \Closure $closure): void {
130
+    }
131
+
132
+    public function resolveDisplayName($type, $id): string {
133
+        return '';
134
+    }
135
+
136
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user): array {
137
+        return [];
138
+    }
139
+
140
+    public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array {
141
+        return [];
142
+    }
143
+
144
+    public function load(): void {
145
+    }
146
+
147
+    public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array {
148
+        return [];
149
+    }
150
+
151
+    public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int {
152
+        return 0;
153
+    }
154
+
155
+    public function getNumberOfCommentsWithVerbsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, array $verbs): int {
156
+        return 0;
157
+    }
158 158
 
159
-	public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int {
160
-		return 0;
161
-	}
159
+    public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int {
160
+        return 0;
161
+    }
162 162
 
163
-	public function getLastCommentDateByActor(string $objectType, string $objectId, string $verb, string $actorType, array $actors): array {
164
-		return [];
165
-	}
163
+    public function getLastCommentDateByActor(string $objectType, string $objectId, string $verb, string $actorType, array $actors): array {
164
+        return [];
165
+    }
166 166
 
167
-	public function deleteCommentsExpiredAtObject(string $objectType, string $objectId = ''): bool {
168
-		return true;
169
-	}
167
+    public function deleteCommentsExpiredAtObject(string $objectType, string $objectId = ''): bool {
168
+        return true;
169
+    }
170 170
 }
Please login to merge, or discard this patch.
tests/lib/TestCase.php 2 patches
Indentation   +492 added lines, -492 removed lines patch added patch discarded remove patch
@@ -37,496 +37,496 @@
 block discarded – undo
37 37
 use Psr\Container\ContainerExceptionInterface;
38 38
 
39 39
 abstract class TestCase extends \PHPUnit\Framework\TestCase {
40
-	private QueueBus $commandBus;
41
-
42
-	protected static ?IDBConnection $realDatabase = null;
43
-	private static bool $wasDatabaseAllowed = false;
44
-	protected array $services = [];
45
-
46
-	protected function onNotSuccessfulTest(\Throwable $t): never {
47
-		$this->restoreAllServices();
48
-
49
-		// restore database connection
50
-		if (!$this->IsDatabaseAccessAllowed()) {
51
-			\OC::$server->registerService(IDBConnection::class, function () {
52
-				return self::$realDatabase;
53
-			});
54
-		}
55
-
56
-		parent::onNotSuccessfulTest($t);
57
-	}
58
-
59
-	public function overwriteService(string $name, mixed $newService): bool {
60
-		if (isset($this->services[$name])) {
61
-			return false;
62
-		}
63
-
64
-		try {
65
-			$this->services[$name] = Server::get($name);
66
-		} catch (ContainerExceptionInterface $e) {
67
-			$this->services[$name] = false;
68
-		}
69
-		/** @psalm-suppress InternalMethod */
70
-		$container = \OC::$server->getAppContainerForService($name);
71
-		$container = $container ?? \OC::$server;
72
-
73
-		$container->registerService($name, function () use ($newService) {
74
-			return $newService;
75
-		});
76
-
77
-		return true;
78
-	}
79
-
80
-	public function restoreService(string $name): bool {
81
-		if (isset($this->services[$name])) {
82
-			$oldService = $this->services[$name];
83
-
84
-			/** @psalm-suppress InternalMethod */
85
-			$container = \OC::$server->getAppContainerForService($name);
86
-			$container = $container ?? \OC::$server;
87
-
88
-			if ($oldService !== false) {
89
-				$container->registerService($name, function () use ($oldService) {
90
-					return $oldService;
91
-				});
92
-			} else {
93
-				unset($container[$oldService]);
94
-			}
95
-
96
-
97
-			unset($this->services[$name]);
98
-			return true;
99
-		}
100
-
101
-		return false;
102
-	}
103
-
104
-	public function restoreAllServices(): void {
105
-		foreach ($this->services as $name => $service) {
106
-			$this->restoreService($name);
107
-		}
108
-	}
109
-
110
-	protected function getTestTraits(): array {
111
-		$traits = [];
112
-		$class = $this;
113
-		do {
114
-			$traits = array_merge(class_uses($class), $traits);
115
-		} while ($class = get_parent_class($class));
116
-		foreach ($traits as $trait => $same) {
117
-			$traits = array_merge(class_uses($trait), $traits);
118
-		}
119
-		$traits = array_unique($traits);
120
-		return array_filter($traits, function ($trait) {
121
-			return substr($trait, 0, 5) === 'Test\\';
122
-		});
123
-	}
124
-
125
-	protected function setUp(): void {
126
-		// overwrite the command bus with one we can run ourselves
127
-		$this->commandBus = new QueueBus();
128
-		$this->overwriteService('AsyncCommandBus', $this->commandBus);
129
-		$this->overwriteService(IBus::class, $this->commandBus);
130
-
131
-		// detect database access
132
-		self::$wasDatabaseAllowed = true;
133
-		if (!$this->IsDatabaseAccessAllowed()) {
134
-			self::$wasDatabaseAllowed = false;
135
-			if (is_null(self::$realDatabase)) {
136
-				self::$realDatabase = Server::get(IDBConnection::class);
137
-			}
138
-			/** @psalm-suppress InternalMethod */
139
-			\OC::$server->registerService(IDBConnection::class, function (): void {
140
-				$this->fail('Your test case is not allowed to access the database.');
141
-			});
142
-		}
143
-
144
-		$traits = $this->getTestTraits();
145
-		foreach ($traits as $trait) {
146
-			$methodName = 'setUp' . basename(str_replace('\\', '/', $trait));
147
-			if (method_exists($this, $methodName)) {
148
-				call_user_func([$this, $methodName]);
149
-			}
150
-		}
151
-	}
152
-
153
-	protected function tearDown(): void {
154
-		$this->restoreAllServices();
155
-
156
-		// restore database connection
157
-		if (!$this->IsDatabaseAccessAllowed()) {
158
-			/** @psalm-suppress InternalMethod */
159
-			\OC::$server->registerService(IDBConnection::class, function () {
160
-				return self::$realDatabase;
161
-			});
162
-		}
163
-
164
-		// further cleanup
165
-		$hookExceptions = \OC_Hook::$thrownExceptions;
166
-		\OC_Hook::$thrownExceptions = [];
167
-		Server::get(ILockingProvider::class)->releaseAll();
168
-		if (!empty($hookExceptions)) {
169
-			throw $hookExceptions[0];
170
-		}
171
-
172
-		// fail hard if xml errors have not been cleaned up
173
-		$errors = libxml_get_errors();
174
-		libxml_clear_errors();
175
-		if (!empty($errors)) {
176
-			self::assertEquals([], $errors, 'There have been xml parsing errors');
177
-		}
178
-
179
-		if ($this->IsDatabaseAccessAllowed()) {
180
-			Storage::getGlobalCache()->clearCache();
181
-		}
182
-
183
-		// tearDown the traits
184
-		$traits = $this->getTestTraits();
185
-		foreach ($traits as $trait) {
186
-			$methodName = 'tearDown' . basename(str_replace('\\', '/', $trait));
187
-			if (method_exists($this, $methodName)) {
188
-				call_user_func([$this, $methodName]);
189
-			}
190
-		}
191
-	}
192
-
193
-	/**
194
-	 * Allows us to test private methods/properties
195
-	 *
196
-	 * @param $object
197
-	 * @param $methodName
198
-	 * @param array $parameters
199
-	 * @return mixed
200
-	 */
201
-	protected static function invokePrivate($object, $methodName, array $parameters = []) {
202
-		if (is_string($object)) {
203
-			$className = $object;
204
-		} else {
205
-			$className = get_class($object);
206
-		}
207
-		$reflection = new \ReflectionClass($className);
208
-
209
-		if ($reflection->hasMethod($methodName)) {
210
-			$method = $reflection->getMethod($methodName);
211
-			return $method->invokeArgs($object, $parameters);
212
-		} elseif ($reflection->hasProperty($methodName)) {
213
-			$property = $reflection->getProperty($methodName);
214
-
215
-			if (!empty($parameters)) {
216
-				if ($property->isStatic()) {
217
-					$property->setValue(null, array_pop($parameters));
218
-				} else {
219
-					$property->setValue($object, array_pop($parameters));
220
-				}
221
-			}
222
-
223
-			if (is_object($object)) {
224
-				return $property->getValue($object);
225
-			}
226
-
227
-			return $property->getValue();
228
-		} elseif ($reflection->hasConstant($methodName)) {
229
-			return $reflection->getConstant($methodName);
230
-		}
231
-
232
-		return false;
233
-	}
234
-
235
-	/**
236
-	 * Returns a unique identifier as uniqid() is not reliable sometimes
237
-	 *
238
-	 * @param string $prefix
239
-	 * @param int $length
240
-	 * @return string
241
-	 */
242
-	protected static function getUniqueID($prefix = '', $length = 13) {
243
-		return $prefix . Server::get(ISecureRandom::class)->generate(
244
-			$length,
245
-			// Do not use dots and slashes as we use the value for file names
246
-			ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER
247
-		);
248
-	}
249
-
250
-	/**
251
-	 * Filter methods
252
-	 *
253
-	 * Returns all methods of the given class,
254
-	 * that are public or abstract and not in the ignoreMethods list,
255
-	 * to be able to fill onlyMethods() with an inverted list.
256
-	 *
257
-	 * @param string $className
258
-	 * @param string[] $filterMethods
259
-	 * @return string[]
260
-	 */
261
-	public function filterClassMethods(string $className, array $filterMethods): array {
262
-		$class = new \ReflectionClass($className);
263
-
264
-		$methods = [];
265
-		foreach ($class->getMethods() as $method) {
266
-			if (($method->isPublic() || $method->isAbstract()) && !in_array($method->getName(), $filterMethods, true)) {
267
-				$methods[] = $method->getName();
268
-			}
269
-		}
270
-
271
-		return $methods;
272
-	}
273
-
274
-	public static function tearDownAfterClass(): void {
275
-		if (!self::$wasDatabaseAllowed && self::$realDatabase !== null) {
276
-			// in case an error is thrown in a test, PHPUnit jumps straight to tearDownAfterClass,
277
-			// so we need the database again
278
-			\OC::$server->registerService(IDBConnection::class, function () {
279
-				return self::$realDatabase;
280
-			});
281
-		}
282
-		$dataDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data-autotest');
283
-		if (self::$wasDatabaseAllowed && Server::get(IDBConnection::class)) {
284
-			$db = Server::get(IDBConnection::class);
285
-			if ($db->inTransaction()) {
286
-				$db->rollBack();
287
-				throw new \Exception('There was a transaction still in progress and needed to be rolled back. Please fix this in your test.');
288
-			}
289
-			$queryBuilder = $db->getQueryBuilder();
290
-
291
-			self::tearDownAfterClassCleanShares($queryBuilder);
292
-			self::tearDownAfterClassCleanStorages($queryBuilder);
293
-			self::tearDownAfterClassCleanFileCache($queryBuilder);
294
-		}
295
-		self::tearDownAfterClassCleanStrayDataFiles($dataDir);
296
-		self::tearDownAfterClassCleanStrayHooks();
297
-		self::tearDownAfterClassCleanStrayLocks();
298
-
299
-		// Ensure we start with fresh instances of some classes to reduce side-effects between tests
300
-		/** @psalm-suppress DeprecatedMethod */
301
-		unset(\OC::$server[Factory::class]);
302
-		/** @psalm-suppress DeprecatedMethod */
303
-		unset(\OC::$server[AppFetcher::class]);
304
-		/** @psalm-suppress DeprecatedMethod */
305
-		unset(\OC::$server[Installer::class]);
306
-		/** @psalm-suppress DeprecatedMethod */
307
-		unset(\OC::$server[Updater::class]);
308
-
309
-		/** @var SetupManager $setupManager */
310
-		$setupManager = Server::get(SetupManager::class);
311
-		$setupManager->tearDown();
312
-
313
-		/** @var MountProviderCollection $mountProviderCollection */
314
-		$mountProviderCollection = Server::get(MountProviderCollection::class);
315
-		$mountProviderCollection->clearProviders();
316
-
317
-		/** @var IConfig $config */
318
-		$config = Server::get(IConfig::class);
319
-		$mountProviderCollection->registerProvider(new CacheMountProvider($config));
320
-		$mountProviderCollection->registerHomeProvider(new LocalHomeMountProvider());
321
-		$objectStoreConfig = Server::get(PrimaryObjectStoreConfig::class);
322
-		$mountProviderCollection->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
323
-
324
-		$setupManager->setupRoot();
325
-
326
-		parent::tearDownAfterClass();
327
-	}
328
-
329
-	/**
330
-	 * Remove all entries from the share table
331
-	 */
332
-	protected static function tearDownAfterClassCleanShares(IQueryBuilder $queryBuilder): void {
333
-		$queryBuilder->delete('share')
334
-			->executeStatement();
335
-	}
336
-
337
-	/**
338
-	 * Remove all entries from the storages table
339
-	 */
340
-	protected static function tearDownAfterClassCleanStorages(IQueryBuilder $queryBuilder): void {
341
-		$queryBuilder->delete('storages')
342
-			->executeStatement();
343
-	}
344
-
345
-	/**
346
-	 * Remove all entries from the filecache table
347
-	 */
348
-	protected static function tearDownAfterClassCleanFileCache(IQueryBuilder $queryBuilder): void {
349
-		$queryBuilder->delete('filecache')
350
-			->runAcrossAllShards()
351
-			->executeStatement();
352
-	}
353
-
354
-	/**
355
-	 * Remove all unused files from the data dir
356
-	 *
357
-	 * @param string $dataDir
358
-	 */
359
-	protected static function tearDownAfterClassCleanStrayDataFiles(string $dataDir): void {
360
-		$knownEntries = [
361
-			'nextcloud.log' => true,
362
-			'audit.log' => true,
363
-			'owncloud.db' => true,
364
-			'.ocdata' => true,
365
-			'..' => true,
366
-			'.' => true,
367
-		];
368
-
369
-		if ($dh = opendir($dataDir)) {
370
-			while (($file = readdir($dh)) !== false) {
371
-				if (!isset($knownEntries[$file])) {
372
-					self::tearDownAfterClassCleanStrayDataUnlinkDir($dataDir . '/' . $file);
373
-				}
374
-			}
375
-			closedir($dh);
376
-		}
377
-	}
378
-
379
-	/**
380
-	 * Recursive delete files and folders from a given directory
381
-	 *
382
-	 * @param string $dir
383
-	 */
384
-	protected static function tearDownAfterClassCleanStrayDataUnlinkDir(string $dir): void {
385
-		if ($dh = @opendir($dir)) {
386
-			while (($file = readdir($dh)) !== false) {
387
-				if (Filesystem::isIgnoredDir($file)) {
388
-					continue;
389
-				}
390
-				$path = $dir . '/' . $file;
391
-				if (is_dir($path)) {
392
-					self::tearDownAfterClassCleanStrayDataUnlinkDir($path);
393
-				} else {
394
-					@unlink($path);
395
-				}
396
-			}
397
-			closedir($dh);
398
-		}
399
-		@rmdir($dir);
400
-	}
401
-
402
-	/**
403
-	 * Clean up the list of hooks
404
-	 */
405
-	protected static function tearDownAfterClassCleanStrayHooks(): void {
406
-		\OC_Hook::clear();
407
-	}
408
-
409
-	/**
410
-	 * Clean up the list of locks
411
-	 */
412
-	protected static function tearDownAfterClassCleanStrayLocks(): void {
413
-		Server::get(ILockingProvider::class)->releaseAll();
414
-	}
415
-
416
-	/**
417
-	 * Login and setup FS as a given user,
418
-	 * sets the given user as the current user.
419
-	 *
420
-	 * @param string $user user id or empty for a generic FS
421
-	 */
422
-	protected static function loginAsUser(string $user = ''): void {
423
-		self::logout();
424
-		Filesystem::tearDown();
425
-		\OC_User::setUserId($user);
426
-		$userManager = Server::get(IUserManager::class);
427
-		$setupManager = Server::get(SetupManager::class);
428
-		$userObject = $userManager->get($user);
429
-		if (!is_null($userObject)) {
430
-			$userObject->updateLastLoginTimestamp();
431
-			$setupManager->setupForUser($userObject);
432
-			$rootFolder = Server::get(IRootFolder::class);
433
-			$rootFolder->getUserFolder($user);
434
-		}
435
-	}
436
-
437
-	/**
438
-	 * Logout the current user and tear down the filesystem.
439
-	 */
440
-	protected static function logout(): void {
441
-		Server::get(SetupManager::class)->tearDown();
442
-		$userSession = Server::get(\OC\User\Session::class);
443
-		$userSession->getSession()->set('user_id', '');
444
-		// needed for fully logout
445
-		$userSession->setUser(null);
446
-	}
447
-
448
-	/**
449
-	 * Run all commands pushed to the bus
450
-	 */
451
-	protected function runCommands(): void {
452
-		$setupManager = Server::get(SetupManager::class);
453
-		$session = Server::get(IUserSession::class);
454
-		$user = $session->getUser();
455
-
456
-		$setupManager->tearDown(); // commands can't reply on the fs being setup
457
-		$this->commandBus->run();
458
-		$setupManager->tearDown();
459
-
460
-		if ($user) {
461
-			$setupManager->setupForUser($user);
462
-		}
463
-	}
464
-
465
-	/**
466
-	 * Check if the given path is locked with a given type
467
-	 *
468
-	 * @param View $view view
469
-	 * @param string $path path to check
470
-	 * @param int $type lock type
471
-	 * @param bool $onMountPoint true to check the mount point instead of the
472
-	 *                           mounted storage
473
-	 *
474
-	 * @return boolean true if the file is locked with the
475
-	 *                 given type, false otherwise
476
-	 */
477
-	protected function isFileLocked(View $view, string $path, int $type, bool $onMountPoint = false) {
478
-		// Note: this seems convoluted but is necessary because
479
-		// the format of the lock key depends on the storage implementation
480
-		// (in our case mostly md5)
481
-
482
-		if ($type === ILockingProvider::LOCK_SHARED) {
483
-			// to check if the file has a shared lock, try acquiring an exclusive lock
484
-			$checkType = ILockingProvider::LOCK_EXCLUSIVE;
485
-		} else {
486
-			// a shared lock cannot be set if exclusive lock is in place
487
-			$checkType = ILockingProvider::LOCK_SHARED;
488
-		}
489
-		try {
490
-			$view->lockFile($path, $checkType, $onMountPoint);
491
-			// no exception, which means the lock of $type is not set
492
-			// clean up
493
-			$view->unlockFile($path, $checkType, $onMountPoint);
494
-			return false;
495
-		} catch (LockedException $e) {
496
-			// we could not acquire the counter-lock, which means
497
-			// the lock of $type was in place
498
-			return true;
499
-		}
500
-	}
501
-
502
-	/**
503
-	 * @return list<string>
504
-	 */
505
-	protected function getGroupAnnotations(): array {
506
-		if (method_exists($this, 'getAnnotations')) {
507
-			$annotations = $this->getAnnotations();
508
-			return $annotations['class']['group'] ?? [];
509
-		}
510
-
511
-		$r = new \ReflectionClass($this);
512
-		$doc = $r->getDocComment();
513
-
514
-		if (class_exists(Group::class)) {
515
-			$attributes = array_map(function (\ReflectionAttribute $attribute): string {
516
-				/** @var Group $group */
517
-				$group = $attribute->newInstance();
518
-				return $group->name();
519
-			}, $r->getAttributes(Group::class));
520
-			if (count($attributes) > 0) {
521
-				return $attributes;
522
-			}
523
-		}
524
-		preg_match_all('#@group\s+(.*?)\n#s', $doc, $annotations);
525
-		return $annotations[1] ?? [];
526
-	}
527
-
528
-	protected function IsDatabaseAccessAllowed(): bool {
529
-		$annotations = $this->getGroupAnnotations();
530
-		return in_array('DB', $annotations) || in_array('SLOWDB', $annotations);
531
-	}
40
+    private QueueBus $commandBus;
41
+
42
+    protected static ?IDBConnection $realDatabase = null;
43
+    private static bool $wasDatabaseAllowed = false;
44
+    protected array $services = [];
45
+
46
+    protected function onNotSuccessfulTest(\Throwable $t): never {
47
+        $this->restoreAllServices();
48
+
49
+        // restore database connection
50
+        if (!$this->IsDatabaseAccessAllowed()) {
51
+            \OC::$server->registerService(IDBConnection::class, function () {
52
+                return self::$realDatabase;
53
+            });
54
+        }
55
+
56
+        parent::onNotSuccessfulTest($t);
57
+    }
58
+
59
+    public function overwriteService(string $name, mixed $newService): bool {
60
+        if (isset($this->services[$name])) {
61
+            return false;
62
+        }
63
+
64
+        try {
65
+            $this->services[$name] = Server::get($name);
66
+        } catch (ContainerExceptionInterface $e) {
67
+            $this->services[$name] = false;
68
+        }
69
+        /** @psalm-suppress InternalMethod */
70
+        $container = \OC::$server->getAppContainerForService($name);
71
+        $container = $container ?? \OC::$server;
72
+
73
+        $container->registerService($name, function () use ($newService) {
74
+            return $newService;
75
+        });
76
+
77
+        return true;
78
+    }
79
+
80
+    public function restoreService(string $name): bool {
81
+        if (isset($this->services[$name])) {
82
+            $oldService = $this->services[$name];
83
+
84
+            /** @psalm-suppress InternalMethod */
85
+            $container = \OC::$server->getAppContainerForService($name);
86
+            $container = $container ?? \OC::$server;
87
+
88
+            if ($oldService !== false) {
89
+                $container->registerService($name, function () use ($oldService) {
90
+                    return $oldService;
91
+                });
92
+            } else {
93
+                unset($container[$oldService]);
94
+            }
95
+
96
+
97
+            unset($this->services[$name]);
98
+            return true;
99
+        }
100
+
101
+        return false;
102
+    }
103
+
104
+    public function restoreAllServices(): void {
105
+        foreach ($this->services as $name => $service) {
106
+            $this->restoreService($name);
107
+        }
108
+    }
109
+
110
+    protected function getTestTraits(): array {
111
+        $traits = [];
112
+        $class = $this;
113
+        do {
114
+            $traits = array_merge(class_uses($class), $traits);
115
+        } while ($class = get_parent_class($class));
116
+        foreach ($traits as $trait => $same) {
117
+            $traits = array_merge(class_uses($trait), $traits);
118
+        }
119
+        $traits = array_unique($traits);
120
+        return array_filter($traits, function ($trait) {
121
+            return substr($trait, 0, 5) === 'Test\\';
122
+        });
123
+    }
124
+
125
+    protected function setUp(): void {
126
+        // overwrite the command bus with one we can run ourselves
127
+        $this->commandBus = new QueueBus();
128
+        $this->overwriteService('AsyncCommandBus', $this->commandBus);
129
+        $this->overwriteService(IBus::class, $this->commandBus);
130
+
131
+        // detect database access
132
+        self::$wasDatabaseAllowed = true;
133
+        if (!$this->IsDatabaseAccessAllowed()) {
134
+            self::$wasDatabaseAllowed = false;
135
+            if (is_null(self::$realDatabase)) {
136
+                self::$realDatabase = Server::get(IDBConnection::class);
137
+            }
138
+            /** @psalm-suppress InternalMethod */
139
+            \OC::$server->registerService(IDBConnection::class, function (): void {
140
+                $this->fail('Your test case is not allowed to access the database.');
141
+            });
142
+        }
143
+
144
+        $traits = $this->getTestTraits();
145
+        foreach ($traits as $trait) {
146
+            $methodName = 'setUp' . basename(str_replace('\\', '/', $trait));
147
+            if (method_exists($this, $methodName)) {
148
+                call_user_func([$this, $methodName]);
149
+            }
150
+        }
151
+    }
152
+
153
+    protected function tearDown(): void {
154
+        $this->restoreAllServices();
155
+
156
+        // restore database connection
157
+        if (!$this->IsDatabaseAccessAllowed()) {
158
+            /** @psalm-suppress InternalMethod */
159
+            \OC::$server->registerService(IDBConnection::class, function () {
160
+                return self::$realDatabase;
161
+            });
162
+        }
163
+
164
+        // further cleanup
165
+        $hookExceptions = \OC_Hook::$thrownExceptions;
166
+        \OC_Hook::$thrownExceptions = [];
167
+        Server::get(ILockingProvider::class)->releaseAll();
168
+        if (!empty($hookExceptions)) {
169
+            throw $hookExceptions[0];
170
+        }
171
+
172
+        // fail hard if xml errors have not been cleaned up
173
+        $errors = libxml_get_errors();
174
+        libxml_clear_errors();
175
+        if (!empty($errors)) {
176
+            self::assertEquals([], $errors, 'There have been xml parsing errors');
177
+        }
178
+
179
+        if ($this->IsDatabaseAccessAllowed()) {
180
+            Storage::getGlobalCache()->clearCache();
181
+        }
182
+
183
+        // tearDown the traits
184
+        $traits = $this->getTestTraits();
185
+        foreach ($traits as $trait) {
186
+            $methodName = 'tearDown' . basename(str_replace('\\', '/', $trait));
187
+            if (method_exists($this, $methodName)) {
188
+                call_user_func([$this, $methodName]);
189
+            }
190
+        }
191
+    }
192
+
193
+    /**
194
+     * Allows us to test private methods/properties
195
+     *
196
+     * @param $object
197
+     * @param $methodName
198
+     * @param array $parameters
199
+     * @return mixed
200
+     */
201
+    protected static function invokePrivate($object, $methodName, array $parameters = []) {
202
+        if (is_string($object)) {
203
+            $className = $object;
204
+        } else {
205
+            $className = get_class($object);
206
+        }
207
+        $reflection = new \ReflectionClass($className);
208
+
209
+        if ($reflection->hasMethod($methodName)) {
210
+            $method = $reflection->getMethod($methodName);
211
+            return $method->invokeArgs($object, $parameters);
212
+        } elseif ($reflection->hasProperty($methodName)) {
213
+            $property = $reflection->getProperty($methodName);
214
+
215
+            if (!empty($parameters)) {
216
+                if ($property->isStatic()) {
217
+                    $property->setValue(null, array_pop($parameters));
218
+                } else {
219
+                    $property->setValue($object, array_pop($parameters));
220
+                }
221
+            }
222
+
223
+            if (is_object($object)) {
224
+                return $property->getValue($object);
225
+            }
226
+
227
+            return $property->getValue();
228
+        } elseif ($reflection->hasConstant($methodName)) {
229
+            return $reflection->getConstant($methodName);
230
+        }
231
+
232
+        return false;
233
+    }
234
+
235
+    /**
236
+     * Returns a unique identifier as uniqid() is not reliable sometimes
237
+     *
238
+     * @param string $prefix
239
+     * @param int $length
240
+     * @return string
241
+     */
242
+    protected static function getUniqueID($prefix = '', $length = 13) {
243
+        return $prefix . Server::get(ISecureRandom::class)->generate(
244
+            $length,
245
+            // Do not use dots and slashes as we use the value for file names
246
+            ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER
247
+        );
248
+    }
249
+
250
+    /**
251
+     * Filter methods
252
+     *
253
+     * Returns all methods of the given class,
254
+     * that are public or abstract and not in the ignoreMethods list,
255
+     * to be able to fill onlyMethods() with an inverted list.
256
+     *
257
+     * @param string $className
258
+     * @param string[] $filterMethods
259
+     * @return string[]
260
+     */
261
+    public function filterClassMethods(string $className, array $filterMethods): array {
262
+        $class = new \ReflectionClass($className);
263
+
264
+        $methods = [];
265
+        foreach ($class->getMethods() as $method) {
266
+            if (($method->isPublic() || $method->isAbstract()) && !in_array($method->getName(), $filterMethods, true)) {
267
+                $methods[] = $method->getName();
268
+            }
269
+        }
270
+
271
+        return $methods;
272
+    }
273
+
274
+    public static function tearDownAfterClass(): void {
275
+        if (!self::$wasDatabaseAllowed && self::$realDatabase !== null) {
276
+            // in case an error is thrown in a test, PHPUnit jumps straight to tearDownAfterClass,
277
+            // so we need the database again
278
+            \OC::$server->registerService(IDBConnection::class, function () {
279
+                return self::$realDatabase;
280
+            });
281
+        }
282
+        $dataDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data-autotest');
283
+        if (self::$wasDatabaseAllowed && Server::get(IDBConnection::class)) {
284
+            $db = Server::get(IDBConnection::class);
285
+            if ($db->inTransaction()) {
286
+                $db->rollBack();
287
+                throw new \Exception('There was a transaction still in progress and needed to be rolled back. Please fix this in your test.');
288
+            }
289
+            $queryBuilder = $db->getQueryBuilder();
290
+
291
+            self::tearDownAfterClassCleanShares($queryBuilder);
292
+            self::tearDownAfterClassCleanStorages($queryBuilder);
293
+            self::tearDownAfterClassCleanFileCache($queryBuilder);
294
+        }
295
+        self::tearDownAfterClassCleanStrayDataFiles($dataDir);
296
+        self::tearDownAfterClassCleanStrayHooks();
297
+        self::tearDownAfterClassCleanStrayLocks();
298
+
299
+        // Ensure we start with fresh instances of some classes to reduce side-effects between tests
300
+        /** @psalm-suppress DeprecatedMethod */
301
+        unset(\OC::$server[Factory::class]);
302
+        /** @psalm-suppress DeprecatedMethod */
303
+        unset(\OC::$server[AppFetcher::class]);
304
+        /** @psalm-suppress DeprecatedMethod */
305
+        unset(\OC::$server[Installer::class]);
306
+        /** @psalm-suppress DeprecatedMethod */
307
+        unset(\OC::$server[Updater::class]);
308
+
309
+        /** @var SetupManager $setupManager */
310
+        $setupManager = Server::get(SetupManager::class);
311
+        $setupManager->tearDown();
312
+
313
+        /** @var MountProviderCollection $mountProviderCollection */
314
+        $mountProviderCollection = Server::get(MountProviderCollection::class);
315
+        $mountProviderCollection->clearProviders();
316
+
317
+        /** @var IConfig $config */
318
+        $config = Server::get(IConfig::class);
319
+        $mountProviderCollection->registerProvider(new CacheMountProvider($config));
320
+        $mountProviderCollection->registerHomeProvider(new LocalHomeMountProvider());
321
+        $objectStoreConfig = Server::get(PrimaryObjectStoreConfig::class);
322
+        $mountProviderCollection->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
323
+
324
+        $setupManager->setupRoot();
325
+
326
+        parent::tearDownAfterClass();
327
+    }
328
+
329
+    /**
330
+     * Remove all entries from the share table
331
+     */
332
+    protected static function tearDownAfterClassCleanShares(IQueryBuilder $queryBuilder): void {
333
+        $queryBuilder->delete('share')
334
+            ->executeStatement();
335
+    }
336
+
337
+    /**
338
+     * Remove all entries from the storages table
339
+     */
340
+    protected static function tearDownAfterClassCleanStorages(IQueryBuilder $queryBuilder): void {
341
+        $queryBuilder->delete('storages')
342
+            ->executeStatement();
343
+    }
344
+
345
+    /**
346
+     * Remove all entries from the filecache table
347
+     */
348
+    protected static function tearDownAfterClassCleanFileCache(IQueryBuilder $queryBuilder): void {
349
+        $queryBuilder->delete('filecache')
350
+            ->runAcrossAllShards()
351
+            ->executeStatement();
352
+    }
353
+
354
+    /**
355
+     * Remove all unused files from the data dir
356
+     *
357
+     * @param string $dataDir
358
+     */
359
+    protected static function tearDownAfterClassCleanStrayDataFiles(string $dataDir): void {
360
+        $knownEntries = [
361
+            'nextcloud.log' => true,
362
+            'audit.log' => true,
363
+            'owncloud.db' => true,
364
+            '.ocdata' => true,
365
+            '..' => true,
366
+            '.' => true,
367
+        ];
368
+
369
+        if ($dh = opendir($dataDir)) {
370
+            while (($file = readdir($dh)) !== false) {
371
+                if (!isset($knownEntries[$file])) {
372
+                    self::tearDownAfterClassCleanStrayDataUnlinkDir($dataDir . '/' . $file);
373
+                }
374
+            }
375
+            closedir($dh);
376
+        }
377
+    }
378
+
379
+    /**
380
+     * Recursive delete files and folders from a given directory
381
+     *
382
+     * @param string $dir
383
+     */
384
+    protected static function tearDownAfterClassCleanStrayDataUnlinkDir(string $dir): void {
385
+        if ($dh = @opendir($dir)) {
386
+            while (($file = readdir($dh)) !== false) {
387
+                if (Filesystem::isIgnoredDir($file)) {
388
+                    continue;
389
+                }
390
+                $path = $dir . '/' . $file;
391
+                if (is_dir($path)) {
392
+                    self::tearDownAfterClassCleanStrayDataUnlinkDir($path);
393
+                } else {
394
+                    @unlink($path);
395
+                }
396
+            }
397
+            closedir($dh);
398
+        }
399
+        @rmdir($dir);
400
+    }
401
+
402
+    /**
403
+     * Clean up the list of hooks
404
+     */
405
+    protected static function tearDownAfterClassCleanStrayHooks(): void {
406
+        \OC_Hook::clear();
407
+    }
408
+
409
+    /**
410
+     * Clean up the list of locks
411
+     */
412
+    protected static function tearDownAfterClassCleanStrayLocks(): void {
413
+        Server::get(ILockingProvider::class)->releaseAll();
414
+    }
415
+
416
+    /**
417
+     * Login and setup FS as a given user,
418
+     * sets the given user as the current user.
419
+     *
420
+     * @param string $user user id or empty for a generic FS
421
+     */
422
+    protected static function loginAsUser(string $user = ''): void {
423
+        self::logout();
424
+        Filesystem::tearDown();
425
+        \OC_User::setUserId($user);
426
+        $userManager = Server::get(IUserManager::class);
427
+        $setupManager = Server::get(SetupManager::class);
428
+        $userObject = $userManager->get($user);
429
+        if (!is_null($userObject)) {
430
+            $userObject->updateLastLoginTimestamp();
431
+            $setupManager->setupForUser($userObject);
432
+            $rootFolder = Server::get(IRootFolder::class);
433
+            $rootFolder->getUserFolder($user);
434
+        }
435
+    }
436
+
437
+    /**
438
+     * Logout the current user and tear down the filesystem.
439
+     */
440
+    protected static function logout(): void {
441
+        Server::get(SetupManager::class)->tearDown();
442
+        $userSession = Server::get(\OC\User\Session::class);
443
+        $userSession->getSession()->set('user_id', '');
444
+        // needed for fully logout
445
+        $userSession->setUser(null);
446
+    }
447
+
448
+    /**
449
+     * Run all commands pushed to the bus
450
+     */
451
+    protected function runCommands(): void {
452
+        $setupManager = Server::get(SetupManager::class);
453
+        $session = Server::get(IUserSession::class);
454
+        $user = $session->getUser();
455
+
456
+        $setupManager->tearDown(); // commands can't reply on the fs being setup
457
+        $this->commandBus->run();
458
+        $setupManager->tearDown();
459
+
460
+        if ($user) {
461
+            $setupManager->setupForUser($user);
462
+        }
463
+    }
464
+
465
+    /**
466
+     * Check if the given path is locked with a given type
467
+     *
468
+     * @param View $view view
469
+     * @param string $path path to check
470
+     * @param int $type lock type
471
+     * @param bool $onMountPoint true to check the mount point instead of the
472
+     *                           mounted storage
473
+     *
474
+     * @return boolean true if the file is locked with the
475
+     *                 given type, false otherwise
476
+     */
477
+    protected function isFileLocked(View $view, string $path, int $type, bool $onMountPoint = false) {
478
+        // Note: this seems convoluted but is necessary because
479
+        // the format of the lock key depends on the storage implementation
480
+        // (in our case mostly md5)
481
+
482
+        if ($type === ILockingProvider::LOCK_SHARED) {
483
+            // to check if the file has a shared lock, try acquiring an exclusive lock
484
+            $checkType = ILockingProvider::LOCK_EXCLUSIVE;
485
+        } else {
486
+            // a shared lock cannot be set if exclusive lock is in place
487
+            $checkType = ILockingProvider::LOCK_SHARED;
488
+        }
489
+        try {
490
+            $view->lockFile($path, $checkType, $onMountPoint);
491
+            // no exception, which means the lock of $type is not set
492
+            // clean up
493
+            $view->unlockFile($path, $checkType, $onMountPoint);
494
+            return false;
495
+        } catch (LockedException $e) {
496
+            // we could not acquire the counter-lock, which means
497
+            // the lock of $type was in place
498
+            return true;
499
+        }
500
+    }
501
+
502
+    /**
503
+     * @return list<string>
504
+     */
505
+    protected function getGroupAnnotations(): array {
506
+        if (method_exists($this, 'getAnnotations')) {
507
+            $annotations = $this->getAnnotations();
508
+            return $annotations['class']['group'] ?? [];
509
+        }
510
+
511
+        $r = new \ReflectionClass($this);
512
+        $doc = $r->getDocComment();
513
+
514
+        if (class_exists(Group::class)) {
515
+            $attributes = array_map(function (\ReflectionAttribute $attribute): string {
516
+                /** @var Group $group */
517
+                $group = $attribute->newInstance();
518
+                return $group->name();
519
+            }, $r->getAttributes(Group::class));
520
+            if (count($attributes) > 0) {
521
+                return $attributes;
522
+            }
523
+        }
524
+        preg_match_all('#@group\s+(.*?)\n#s', $doc, $annotations);
525
+        return $annotations[1] ?? [];
526
+    }
527
+
528
+    protected function IsDatabaseAccessAllowed(): bool {
529
+        $annotations = $this->getGroupAnnotations();
530
+        return in_array('DB', $annotations) || in_array('SLOWDB', $annotations);
531
+    }
532 532
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -48,7 +48,7 @@  discard block
 block discarded – undo
48 48
 
49 49
 		// restore database connection
50 50
 		if (!$this->IsDatabaseAccessAllowed()) {
51
-			\OC::$server->registerService(IDBConnection::class, function () {
51
+			\OC::$server->registerService(IDBConnection::class, function() {
52 52
 				return self::$realDatabase;
53 53
 			});
54 54
 		}
@@ -70,7 +70,7 @@  discard block
 block discarded – undo
70 70
 		$container = \OC::$server->getAppContainerForService($name);
71 71
 		$container = $container ?? \OC::$server;
72 72
 
73
-		$container->registerService($name, function () use ($newService) {
73
+		$container->registerService($name, function() use ($newService) {
74 74
 			return $newService;
75 75
 		});
76 76
 
@@ -86,7 +86,7 @@  discard block
 block discarded – undo
86 86
 			$container = $container ?? \OC::$server;
87 87
 
88 88
 			if ($oldService !== false) {
89
-				$container->registerService($name, function () use ($oldService) {
89
+				$container->registerService($name, function() use ($oldService) {
90 90
 					return $oldService;
91 91
 				});
92 92
 			} else {
@@ -117,7 +117,7 @@  discard block
 block discarded – undo
117 117
 			$traits = array_merge(class_uses($trait), $traits);
118 118
 		}
119 119
 		$traits = array_unique($traits);
120
-		return array_filter($traits, function ($trait) {
120
+		return array_filter($traits, function($trait) {
121 121
 			return substr($trait, 0, 5) === 'Test\\';
122 122
 		});
123 123
 	}
@@ -136,14 +136,14 @@  discard block
 block discarded – undo
136 136
 				self::$realDatabase = Server::get(IDBConnection::class);
137 137
 			}
138 138
 			/** @psalm-suppress InternalMethod */
139
-			\OC::$server->registerService(IDBConnection::class, function (): void {
139
+			\OC::$server->registerService(IDBConnection::class, function(): void {
140 140
 				$this->fail('Your test case is not allowed to access the database.');
141 141
 			});
142 142
 		}
143 143
 
144 144
 		$traits = $this->getTestTraits();
145 145
 		foreach ($traits as $trait) {
146
-			$methodName = 'setUp' . basename(str_replace('\\', '/', $trait));
146
+			$methodName = 'setUp'.basename(str_replace('\\', '/', $trait));
147 147
 			if (method_exists($this, $methodName)) {
148 148
 				call_user_func([$this, $methodName]);
149 149
 			}
@@ -156,7 +156,7 @@  discard block
 block discarded – undo
156 156
 		// restore database connection
157 157
 		if (!$this->IsDatabaseAccessAllowed()) {
158 158
 			/** @psalm-suppress InternalMethod */
159
-			\OC::$server->registerService(IDBConnection::class, function () {
159
+			\OC::$server->registerService(IDBConnection::class, function() {
160 160
 				return self::$realDatabase;
161 161
 			});
162 162
 		}
@@ -183,7 +183,7 @@  discard block
 block discarded – undo
183 183
 		// tearDown the traits
184 184
 		$traits = $this->getTestTraits();
185 185
 		foreach ($traits as $trait) {
186
-			$methodName = 'tearDown' . basename(str_replace('\\', '/', $trait));
186
+			$methodName = 'tearDown'.basename(str_replace('\\', '/', $trait));
187 187
 			if (method_exists($this, $methodName)) {
188 188
 				call_user_func([$this, $methodName]);
189 189
 			}
@@ -240,10 +240,10 @@  discard block
 block discarded – undo
240 240
 	 * @return string
241 241
 	 */
242 242
 	protected static function getUniqueID($prefix = '', $length = 13) {
243
-		return $prefix . Server::get(ISecureRandom::class)->generate(
243
+		return $prefix.Server::get(ISecureRandom::class)->generate(
244 244
 			$length,
245 245
 			// Do not use dots and slashes as we use the value for file names
246
-			ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER
246
+			ISecureRandom::CHAR_DIGITS.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER
247 247
 		);
248 248
 	}
249 249
 
@@ -275,11 +275,11 @@  discard block
 block discarded – undo
275 275
 		if (!self::$wasDatabaseAllowed && self::$realDatabase !== null) {
276 276
 			// in case an error is thrown in a test, PHPUnit jumps straight to tearDownAfterClass,
277 277
 			// so we need the database again
278
-			\OC::$server->registerService(IDBConnection::class, function () {
278
+			\OC::$server->registerService(IDBConnection::class, function() {
279 279
 				return self::$realDatabase;
280 280
 			});
281 281
 		}
282
-		$dataDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data-autotest');
282
+		$dataDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT.'/data-autotest');
283 283
 		if (self::$wasDatabaseAllowed && Server::get(IDBConnection::class)) {
284 284
 			$db = Server::get(IDBConnection::class);
285 285
 			if ($db->inTransaction()) {
@@ -369,7 +369,7 @@  discard block
 block discarded – undo
369 369
 		if ($dh = opendir($dataDir)) {
370 370
 			while (($file = readdir($dh)) !== false) {
371 371
 				if (!isset($knownEntries[$file])) {
372
-					self::tearDownAfterClassCleanStrayDataUnlinkDir($dataDir . '/' . $file);
372
+					self::tearDownAfterClassCleanStrayDataUnlinkDir($dataDir.'/'.$file);
373 373
 				}
374 374
 			}
375 375
 			closedir($dh);
@@ -387,7 +387,7 @@  discard block
 block discarded – undo
387 387
 				if (Filesystem::isIgnoredDir($file)) {
388 388
 					continue;
389 389
 				}
390
-				$path = $dir . '/' . $file;
390
+				$path = $dir.'/'.$file;
391 391
 				if (is_dir($path)) {
392 392
 					self::tearDownAfterClassCleanStrayDataUnlinkDir($path);
393 393
 				} else {
@@ -512,7 +512,7 @@  discard block
 block discarded – undo
512 512
 		$doc = $r->getDocComment();
513 513
 
514 514
 		if (class_exists(Group::class)) {
515
-			$attributes = array_map(function (\ReflectionAttribute $attribute): string {
515
+			$attributes = array_map(function(\ReflectionAttribute $attribute): string {
516 516
 				/** @var Group $group */
517 517
 				$group = $attribute->newInstance();
518 518
 				return $group->name();
Please login to merge, or discard this patch.