Passed
Push — master ( eb9faa...c3969d )
by Morris
11:38 queued 11s
created
lib/public/Comments/ICommentsManager.php 1 patch
Indentation   +330 added lines, -330 removed lines patch added patch discarded remove patch
@@ -39,359 +39,359 @@
 block discarded – undo
39 39
  */
40 40
 interface ICommentsManager {
41 41
 
42
-	/**
43
-	 * @const DELETED_USER type and id for a user that has been deleted
44
-	 * @see deleteReferencesOfActor
45
-	 * @since 9.0.0
46
-	 *
47
-	 * To be used as replacement for user type actors in deleteReferencesOfActor().
48
-	 *
49
-	 * User interfaces shall show "Deleted user" as display name, if needed.
50
-	 */
51
-	public const DELETED_USER = 'deleted_users';
42
+    /**
43
+     * @const DELETED_USER type and id for a user that has been deleted
44
+     * @see deleteReferencesOfActor
45
+     * @since 9.0.0
46
+     *
47
+     * To be used as replacement for user type actors in deleteReferencesOfActor().
48
+     *
49
+     * User interfaces shall show "Deleted user" as display name, if needed.
50
+     */
51
+    public const DELETED_USER = 'deleted_users';
52 52
 
53
-	/**
54
-	 * returns a comment instance
55
-	 *
56
-	 * @param string $id the ID of the comment
57
-	 * @return IComment
58
-	 * @throws NotFoundException
59
-	 * @since 9.0.0
60
-	 */
61
-	public function get($id);
53
+    /**
54
+     * returns a comment instance
55
+     *
56
+     * @param string $id the ID of the comment
57
+     * @return IComment
58
+     * @throws NotFoundException
59
+     * @since 9.0.0
60
+     */
61
+    public function get($id);
62 62
 
63
-	/**
64
-	 * returns the comment specified by the id and all it's child comments
65
-	 *
66
-	 * @param string $id
67
-	 * @param int $limit max number of entries to return, 0 returns all
68
-	 * @param int $offset the start entry
69
-	 * @return array
70
-	 * @since 9.0.0
71
-	 *
72
-	 * The return array looks like this
73
-	 * [
74
-	 * 	 'comment' => IComment, // root comment
75
-	 *   'replies' =>
76
-	 *   [
77
-	 *     0 =>
78
-	 *     [
79
-	 *       'comment' => IComment,
80
-	 *       'replies' =>
81
-	 *       [
82
-	 *         0 =>
83
-	 *         [
84
-	 *           'comment' => IComment,
85
-	 *           'replies' => [ … ]
86
-	 *         ],
87
-	 *         …
88
-	 *       ]
89
-	 *     ]
90
-	 *     1 =>
91
-	 *     [
92
-	 *       'comment' => IComment,
93
-	 *       'replies'=> [ … ]
94
-	 *     ],
95
-	 *     …
96
-	 *   ]
97
-	 * ]
98
-	 */
99
-	public function getTree($id, $limit = 0, $offset = 0);
63
+    /**
64
+     * returns the comment specified by the id and all it's child comments
65
+     *
66
+     * @param string $id
67
+     * @param int $limit max number of entries to return, 0 returns all
68
+     * @param int $offset the start entry
69
+     * @return array
70
+     * @since 9.0.0
71
+     *
72
+     * The return array looks like this
73
+     * [
74
+     * 	 'comment' => IComment, // root comment
75
+     *   'replies' =>
76
+     *   [
77
+     *     0 =>
78
+     *     [
79
+     *       'comment' => IComment,
80
+     *       'replies' =>
81
+     *       [
82
+     *         0 =>
83
+     *         [
84
+     *           'comment' => IComment,
85
+     *           'replies' => [ … ]
86
+     *         ],
87
+     *         …
88
+     *       ]
89
+     *     ]
90
+     *     1 =>
91
+     *     [
92
+     *       'comment' => IComment,
93
+     *       'replies'=> [ … ]
94
+     *     ],
95
+     *     …
96
+     *   ]
97
+     * ]
98
+     */
99
+    public function getTree($id, $limit = 0, $offset = 0);
100 100
 
101
-	/**
102
-	 * returns comments for a specific object (e.g. a file).
103
-	 *
104
-	 * The sort order is always newest to oldest.
105
-	 *
106
-	 * @param string $objectType the object type, e.g. 'files'
107
-	 * @param string $objectId the id of the object
108
-	 * @param int $limit optional, number of maximum comments to be returned. if
109
-	 * not specified, all comments are returned.
110
-	 * @param int $offset optional, starting point
111
-	 * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
112
-	 * that may be returned
113
-	 * @return IComment[]
114
-	 * @since 9.0.0
115
-	 */
116
-	public function getForObject(
117
-			$objectType,
118
-			$objectId,
119
-			$limit = 0,
120
-			$offset = 0,
121
-			\DateTime $notOlderThan = null
122
-	);
101
+    /**
102
+     * returns comments for a specific object (e.g. a file).
103
+     *
104
+     * The sort order is always newest to oldest.
105
+     *
106
+     * @param string $objectType the object type, e.g. 'files'
107
+     * @param string $objectId the id of the object
108
+     * @param int $limit optional, number of maximum comments to be returned. if
109
+     * not specified, all comments are returned.
110
+     * @param int $offset optional, starting point
111
+     * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
112
+     * that may be returned
113
+     * @return IComment[]
114
+     * @since 9.0.0
115
+     */
116
+    public function getForObject(
117
+            $objectType,
118
+            $objectId,
119
+            $limit = 0,
120
+            $offset = 0,
121
+            \DateTime $notOlderThan = null
122
+    );
123 123
 
124
-	/**
125
-	 * @param string $objectType the object type, e.g. 'files'
126
-	 * @param string $objectId the id of the object
127
-	 * @param int $lastKnownCommentId the last known comment (will be used as offset)
128
-	 * @param string $sortDirection direction of the comments (`asc` or `desc`)
129
-	 * @param int $limit optional, number of maximum comments to be returned. if
130
-	 * set to 0, all comments are returned.
131
-	 * @param bool $includeLastKnown
132
-	 * @return IComment[]
133
-	 * @since 14.0.0
134
-	 */
135
-	public function getForObjectSince(
136
-		string $objectType,
137
-		string $objectId,
138
-		int $lastKnownCommentId,
139
-		string $sortDirection = 'asc',
140
-		int $limit = 30,
141
-		bool $includeLastKnown = false
142
-	): array;
124
+    /**
125
+     * @param string $objectType the object type, e.g. 'files'
126
+     * @param string $objectId the id of the object
127
+     * @param int $lastKnownCommentId the last known comment (will be used as offset)
128
+     * @param string $sortDirection direction of the comments (`asc` or `desc`)
129
+     * @param int $limit optional, number of maximum comments to be returned. if
130
+     * set to 0, all comments are returned.
131
+     * @param bool $includeLastKnown
132
+     * @return IComment[]
133
+     * @since 14.0.0
134
+     */
135
+    public function getForObjectSince(
136
+        string $objectType,
137
+        string $objectId,
138
+        int $lastKnownCommentId,
139
+        string $sortDirection = 'asc',
140
+        int $limit = 30,
141
+        bool $includeLastKnown = false
142
+    ): array;
143 143
 
144
-	/**
145
-	 * Search for comments with a given content
146
-	 *
147
-	 * @param string $search content to search for
148
-	 * @param string $objectType Limit the search by object type
149
-	 * @param string $objectId Limit the search by object id
150
-	 * @param string $verb Limit the verb of the comment
151
-	 * @param int $offset
152
-	 * @param int $limit
153
-	 * @return IComment[]
154
-	 * @since 14.0.0
155
-	 */
156
-	public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array;
144
+    /**
145
+     * Search for comments with a given content
146
+     *
147
+     * @param string $search content to search for
148
+     * @param string $objectType Limit the search by object type
149
+     * @param string $objectId Limit the search by object id
150
+     * @param string $verb Limit the verb of the comment
151
+     * @param int $offset
152
+     * @param int $limit
153
+     * @return IComment[]
154
+     * @since 14.0.0
155
+     */
156
+    public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array;
157 157
 
158
-	/**
159
-	 * Search for comments on one or more objects with a given content
160
-	 *
161
-	 * @param string $search content to search for
162
-	 * @param string $objectType Limit the search by object type
163
-	 * @param array $objectIds Limit the search by object ids
164
-	 * @param string $verb Limit the verb of the comment
165
-	 * @param int $offset
166
-	 * @param int $limit
167
-	 * @return IComment[]
168
-	 * @since 21.0.0
169
-	 */
170
-	public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array;
158
+    /**
159
+     * Search for comments on one or more objects with a given content
160
+     *
161
+     * @param string $search content to search for
162
+     * @param string $objectType Limit the search by object type
163
+     * @param array $objectIds Limit the search by object ids
164
+     * @param string $verb Limit the verb of the comment
165
+     * @param int $offset
166
+     * @param int $limit
167
+     * @return IComment[]
168
+     * @since 21.0.0
169
+     */
170
+    public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array;
171 171
 
172
-	/**
173
-	 * @param $objectType string the object type, e.g. 'files'
174
-	 * @param $objectId string the id of the object
175
-	 * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
176
-	 * that may be returned
177
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
178
-	 * @return Int
179
-	 * @since 9.0.0
180
-	 */
181
-	public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '');
172
+    /**
173
+     * @param $objectType string the object type, e.g. 'files'
174
+     * @param $objectId string the id of the object
175
+     * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
176
+     * that may be returned
177
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
178
+     * @return Int
179
+     * @since 9.0.0
180
+     */
181
+    public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '');
182 182
 
183
-	/**
184
-	 * @param string $objectType the object type, e.g. 'files'
185
-	 * @param string[] $objectIds the id of the object
186
-	 * @param IUser $user
187
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
188
-	 * @return array Map with object id => # of unread comments
189
-	 * @psalm-return array<string, int>
190
-	 * @since 21.0.0
191
-	 */
192
-	public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array;
183
+    /**
184
+     * @param string $objectType the object type, e.g. 'files'
185
+     * @param string[] $objectIds the id of the object
186
+     * @param IUser $user
187
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
188
+     * @return array Map with object id => # of unread comments
189
+     * @psalm-return array<string, int>
190
+     * @since 21.0.0
191
+     */
192
+    public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array;
193 193
 
194
-	/**
195
-	 * @param string $objectType
196
-	 * @param string $objectId
197
-	 * @param int $lastRead
198
-	 * @param string $verb
199
-	 * @return int
200
-	 * @since 21.0.0
201
-	 */
202
-	public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int;
194
+    /**
195
+     * @param string $objectType
196
+     * @param string $objectId
197
+     * @param int $lastRead
198
+     * @param string $verb
199
+     * @return int
200
+     * @since 21.0.0
201
+     */
202
+    public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int;
203 203
 
204
-	/**
205
-	 * @param string $objectType
206
-	 * @param string $objectId
207
-	 * @param \DateTime $beforeDate
208
-	 * @param string $verb
209
-	 * @return int
210
-	 * @since 21.0.0
211
-	 */
212
-	public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int;
204
+    /**
205
+     * @param string $objectType
206
+     * @param string $objectId
207
+     * @param \DateTime $beforeDate
208
+     * @param string $verb
209
+     * @return int
210
+     * @since 21.0.0
211
+     */
212
+    public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int;
213 213
 
214
-	/**
215
-	 * @param string $objectType
216
-	 * @param string $objectId
217
-	 * @param string $verb
218
-	 * @param string $actorType
219
-	 * @param string[] $actors
220
-	 * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
221
-	 * @psalm-return array<string, \DateTime>
222
-	 * @since 21.0.0
223
-	 */
224
-	public function getLastCommentDateByActor(
225
-		string $objectType,
226
-		string $objectId,
227
-		string $verb,
228
-		string $actorType,
229
-		array $actors
230
-	): array;
214
+    /**
215
+     * @param string $objectType
216
+     * @param string $objectId
217
+     * @param string $verb
218
+     * @param string $actorType
219
+     * @param string[] $actors
220
+     * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
221
+     * @psalm-return array<string, \DateTime>
222
+     * @since 21.0.0
223
+     */
224
+    public function getLastCommentDateByActor(
225
+        string $objectType,
226
+        string $objectId,
227
+        string $verb,
228
+        string $actorType,
229
+        array $actors
230
+    ): array;
231 231
 
232
-	/**
233
-	 * Get the number of unread comments for all files in a folder
234
-	 *
235
-	 * @param int $folderId
236
-	 * @param IUser $user
237
-	 * @return array [$fileId => $unreadCount]
238
-	 * @since 12.0.0
239
-	 */
240
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
232
+    /**
233
+     * Get the number of unread comments for all files in a folder
234
+     *
235
+     * @param int $folderId
236
+     * @param IUser $user
237
+     * @return array [$fileId => $unreadCount]
238
+     * @since 12.0.0
239
+     */
240
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
241 241
 
242
-	/**
243
-	 * creates a new comment and returns it. At this point of time, it is not
244
-	 * saved in the used data storage. Use save() after setting other fields
245
-	 * of the comment (e.g. message or verb).
246
-	 *
247
-	 * @param string $actorType the actor type (e.g. 'users')
248
-	 * @param string $actorId a user id
249
-	 * @param string $objectType the object type the comment is attached to
250
-	 * @param string $objectId the object id the comment is attached to
251
-	 * @return IComment
252
-	 * @since 9.0.0
253
-	 */
254
-	public function create($actorType, $actorId, $objectType, $objectId);
242
+    /**
243
+     * creates a new comment and returns it. At this point of time, it is not
244
+     * saved in the used data storage. Use save() after setting other fields
245
+     * of the comment (e.g. message or verb).
246
+     *
247
+     * @param string $actorType the actor type (e.g. 'users')
248
+     * @param string $actorId a user id
249
+     * @param string $objectType the object type the comment is attached to
250
+     * @param string $objectId the object id the comment is attached to
251
+     * @return IComment
252
+     * @since 9.0.0
253
+     */
254
+    public function create($actorType, $actorId, $objectType, $objectId);
255 255
 
256
-	/**
257
-	 * permanently deletes the comment specified by the ID
258
-	 *
259
-	 * When the comment has child comments, their parent ID will be changed to
260
-	 * the parent ID of the item that is to be deleted.
261
-	 *
262
-	 * @param string $id
263
-	 * @return bool
264
-	 * @since 9.0.0
265
-	 */
266
-	public function delete($id);
256
+    /**
257
+     * permanently deletes the comment specified by the ID
258
+     *
259
+     * When the comment has child comments, their parent ID will be changed to
260
+     * the parent ID of the item that is to be deleted.
261
+     *
262
+     * @param string $id
263
+     * @return bool
264
+     * @since 9.0.0
265
+     */
266
+    public function delete($id);
267 267
 
268
-	/**
269
-	 * saves the comment permanently
270
-	 *
271
-	 * if the supplied comment has an empty ID, a new entry comment will be
272
-	 * saved and the instance updated with the new ID.
273
-	 *
274
-	 * Otherwise, an existing comment will be updated.
275
-	 *
276
-	 * Throws NotFoundException when a comment that is to be updated does not
277
-	 * exist anymore at this point of time.
278
-	 *
279
-	 * @param IComment $comment
280
-	 * @return bool
281
-	 * @throws NotFoundException
282
-	 * @since 9.0.0
283
-	 */
284
-	public function save(IComment $comment);
268
+    /**
269
+     * saves the comment permanently
270
+     *
271
+     * if the supplied comment has an empty ID, a new entry comment will be
272
+     * saved and the instance updated with the new ID.
273
+     *
274
+     * Otherwise, an existing comment will be updated.
275
+     *
276
+     * Throws NotFoundException when a comment that is to be updated does not
277
+     * exist anymore at this point of time.
278
+     *
279
+     * @param IComment $comment
280
+     * @return bool
281
+     * @throws NotFoundException
282
+     * @since 9.0.0
283
+     */
284
+    public function save(IComment $comment);
285 285
 
286
-	/**
287
-	 * removes references to specific actor (e.g. on user delete) of a comment.
288
-	 * The comment itself must not get lost/deleted.
289
-	 *
290
-	 * A 'users' type actor (type and id) should get replaced by the
291
-	 * value of the DELETED_USER constant of this interface.
292
-	 *
293
-	 * @param string $actorType the actor type (e.g. 'users')
294
-	 * @param string $actorId a user id
295
-	 * @return boolean
296
-	 * @since 9.0.0
297
-	 */
298
-	public function deleteReferencesOfActor($actorType, $actorId);
286
+    /**
287
+     * removes references to specific actor (e.g. on user delete) of a comment.
288
+     * The comment itself must not get lost/deleted.
289
+     *
290
+     * A 'users' type actor (type and id) should get replaced by the
291
+     * value of the DELETED_USER constant of this interface.
292
+     *
293
+     * @param string $actorType the actor type (e.g. 'users')
294
+     * @param string $actorId a user id
295
+     * @return boolean
296
+     * @since 9.0.0
297
+     */
298
+    public function deleteReferencesOfActor($actorType, $actorId);
299 299
 
300
-	/**
301
-	 * deletes all comments made of a specific object (e.g. on file delete)
302
-	 *
303
-	 * @param string $objectType the object type (e.g. 'files')
304
-	 * @param string $objectId e.g. the file id
305
-	 * @return boolean
306
-	 * @since 9.0.0
307
-	 */
308
-	public function deleteCommentsAtObject($objectType, $objectId);
300
+    /**
301
+     * deletes all comments made of a specific object (e.g. on file delete)
302
+     *
303
+     * @param string $objectType the object type (e.g. 'files')
304
+     * @param string $objectId e.g. the file id
305
+     * @return boolean
306
+     * @since 9.0.0
307
+     */
308
+    public function deleteCommentsAtObject($objectType, $objectId);
309 309
 
310
-	/**
311
-	 * sets the read marker for a given file to the specified date for the
312
-	 * provided user
313
-	 *
314
-	 * @param string $objectType
315
-	 * @param string $objectId
316
-	 * @param \DateTime $dateTime
317
-	 * @param \OCP\IUser $user
318
-	 * @since 9.0.0
319
-	 */
320
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
310
+    /**
311
+     * sets the read marker for a given file to the specified date for the
312
+     * provided user
313
+     *
314
+     * @param string $objectType
315
+     * @param string $objectId
316
+     * @param \DateTime $dateTime
317
+     * @param \OCP\IUser $user
318
+     * @since 9.0.0
319
+     */
320
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
321 321
 
322
-	/**
323
-	 * returns the read marker for a given file to the specified date for the
324
-	 * provided user. It returns null, when the marker is not present, i.e.
325
-	 * no comments were marked as read.
326
-	 *
327
-	 * @param string $objectType
328
-	 * @param string $objectId
329
-	 * @param \OCP\IUser $user
330
-	 * @return \DateTime|null
331
-	 * @since 9.0.0
332
-	 */
333
-	public function getReadMark($objectType, $objectId, \OCP\IUser $user);
322
+    /**
323
+     * returns the read marker for a given file to the specified date for the
324
+     * provided user. It returns null, when the marker is not present, i.e.
325
+     * no comments were marked as read.
326
+     *
327
+     * @param string $objectType
328
+     * @param string $objectId
329
+     * @param \OCP\IUser $user
330
+     * @return \DateTime|null
331
+     * @since 9.0.0
332
+     */
333
+    public function getReadMark($objectType, $objectId, \OCP\IUser $user);
334 334
 
335
-	/**
336
-	 * deletes the read markers for the specified user
337
-	 *
338
-	 * @param \OCP\IUser $user
339
-	 * @return bool
340
-	 * @since 9.0.0
341
-	 */
342
-	public function deleteReadMarksFromUser(\OCP\IUser $user);
335
+    /**
336
+     * deletes the read markers for the specified user
337
+     *
338
+     * @param \OCP\IUser $user
339
+     * @return bool
340
+     * @since 9.0.0
341
+     */
342
+    public function deleteReadMarksFromUser(\OCP\IUser $user);
343 343
 
344
-	/**
345
-	 * deletes the read markers on the specified object
346
-	 *
347
-	 * @param string $objectType
348
-	 * @param string $objectId
349
-	 * @return bool
350
-	 * @since 9.0.0
351
-	 */
352
-	public function deleteReadMarksOnObject($objectType, $objectId);
344
+    /**
345
+     * deletes the read markers on the specified object
346
+     *
347
+     * @param string $objectType
348
+     * @param string $objectId
349
+     * @return bool
350
+     * @since 9.0.0
351
+     */
352
+    public function deleteReadMarksOnObject($objectType, $objectId);
353 353
 
354
-	/**
355
-	 * registers an Entity to the manager, so event notifications can be send
356
-	 * to consumers of the comments infrastructure
357
-	 *
358
-	 * @param \Closure $closure
359
-	 * @since 11.0.0
360
-	 */
361
-	public function registerEventHandler(\Closure $closure);
354
+    /**
355
+     * registers an Entity to the manager, so event notifications can be send
356
+     * to consumers of the comments infrastructure
357
+     *
358
+     * @param \Closure $closure
359
+     * @since 11.0.0
360
+     */
361
+    public function registerEventHandler(\Closure $closure);
362 362
 
363
-	/**
364
-	 * registers a method that resolves an ID to a display name for a given type
365
-	 *
366
-	 * @param string $type
367
-	 * @param \Closure $closure
368
-	 * @throws \OutOfBoundsException
369
-	 * @since 11.0.0
370
-	 *
371
-	 * Only one resolver shall be registered per type. Otherwise a
372
-	 * \OutOfBoundsException has to thrown.
373
-	 */
374
-	public function registerDisplayNameResolver($type, \Closure $closure);
363
+    /**
364
+     * registers a method that resolves an ID to a display name for a given type
365
+     *
366
+     * @param string $type
367
+     * @param \Closure $closure
368
+     * @throws \OutOfBoundsException
369
+     * @since 11.0.0
370
+     *
371
+     * Only one resolver shall be registered per type. Otherwise a
372
+     * \OutOfBoundsException has to thrown.
373
+     */
374
+    public function registerDisplayNameResolver($type, \Closure $closure);
375 375
 
376
-	/**
377
-	 * resolves a given ID of a given Type to a display name.
378
-	 *
379
-	 * @param string $type
380
-	 * @param string $id
381
-	 * @return string
382
-	 * @throws \OutOfBoundsException
383
-	 * @since 11.0.0
384
-	 *
385
-	 * If a provided type was not registered, an \OutOfBoundsException shall
386
-	 * be thrown. It is upon the resolver discretion what to return of the
387
-	 * provided ID is unknown. It must be ensured that a string is returned.
388
-	 */
389
-	public function resolveDisplayName($type, $id);
376
+    /**
377
+     * resolves a given ID of a given Type to a display name.
378
+     *
379
+     * @param string $type
380
+     * @param string $id
381
+     * @return string
382
+     * @throws \OutOfBoundsException
383
+     * @since 11.0.0
384
+     *
385
+     * If a provided type was not registered, an \OutOfBoundsException shall
386
+     * be thrown. It is upon the resolver discretion what to return of the
387
+     * provided ID is unknown. It must be ensured that a string is returned.
388
+     */
389
+    public function resolveDisplayName($type, $id);
390 390
 
391
-	/**
392
-	 * Load the Comments app into the page
393
-	 *
394
-	 * @since 21.0.0
395
-	 */
396
-	public function load(): void;
391
+    /**
392
+     * Load the Comments app into the page
393
+     *
394
+     * @since 21.0.0
395
+     */
396
+    public function load(): void;
397 397
 }
Please login to merge, or discard this patch.
lib/private/Comments/Manager.php 1 patch
Indentation   +1260 added lines, -1260 removed lines patch added patch discarded remove patch
@@ -47,1264 +47,1264 @@
 block discarded – undo
47 47
 
48 48
 class Manager implements ICommentsManager {
49 49
 
50
-	/** @var  IDBConnection */
51
-	protected $dbConn;
52
-
53
-	/** @var  LoggerInterface */
54
-	protected $logger;
55
-
56
-	/** @var IConfig */
57
-	protected $config;
58
-
59
-	/** @var ITimeFactory */
60
-	protected $timeFactory;
61
-
62
-	/** @var IInitialStateService */
63
-	protected $initialStateService;
64
-
65
-	/** @var IComment[] */
66
-	protected $commentsCache = [];
67
-
68
-	/** @var  \Closure[] */
69
-	protected $eventHandlerClosures = [];
70
-
71
-	/** @var  ICommentsEventHandler[] */
72
-	protected $eventHandlers = [];
73
-
74
-	/** @var \Closure[] */
75
-	protected $displayNameResolvers = [];
76
-
77
-	public function __construct(IDBConnection $dbConn,
78
-								LoggerInterface $logger,
79
-								IConfig $config,
80
-								ITimeFactory $timeFactory,
81
-								IInitialStateService $initialStateService) {
82
-		$this->dbConn = $dbConn;
83
-		$this->logger = $logger;
84
-		$this->config = $config;
85
-		$this->timeFactory = $timeFactory;
86
-		$this->initialStateService = $initialStateService;
87
-	}
88
-
89
-	/**
90
-	 * converts data base data into PHP native, proper types as defined by
91
-	 * IComment interface.
92
-	 *
93
-	 * @param array $data
94
-	 * @return array
95
-	 */
96
-	protected function normalizeDatabaseData(array $data) {
97
-		$data['id'] = (string)$data['id'];
98
-		$data['parent_id'] = (string)$data['parent_id'];
99
-		$data['topmost_parent_id'] = (string)$data['topmost_parent_id'];
100
-		$data['creation_timestamp'] = new \DateTime($data['creation_timestamp']);
101
-		if (!is_null($data['latest_child_timestamp'])) {
102
-			$data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']);
103
-		}
104
-		$data['children_count'] = (int)$data['children_count'];
105
-		$data['reference_id'] = $data['reference_id'] ?? null;
106
-		return $data;
107
-	}
108
-
109
-
110
-	/**
111
-	 * @param array $data
112
-	 * @return IComment
113
-	 */
114
-	public function getCommentFromData(array $data): IComment {
115
-		return new Comment($this->normalizeDatabaseData($data));
116
-	}
117
-
118
-	/**
119
-	 * prepares a comment for an insert or update operation after making sure
120
-	 * all necessary fields have a value assigned.
121
-	 *
122
-	 * @param IComment $comment
123
-	 * @return IComment returns the same updated IComment instance as provided
124
-	 *                  by parameter for convenience
125
-	 * @throws \UnexpectedValueException
126
-	 */
127
-	protected function prepareCommentForDatabaseWrite(IComment $comment) {
128
-		if (!$comment->getActorType()
129
-			|| $comment->getActorId() === ''
130
-			|| !$comment->getObjectType()
131
-			|| $comment->getObjectId() === ''
132
-			|| !$comment->getVerb()
133
-		) {
134
-			throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving');
135
-		}
136
-
137
-		if ($comment->getId() === '') {
138
-			$comment->setChildrenCount(0);
139
-			$comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC')));
140
-			$comment->setLatestChildDateTime(null);
141
-		}
142
-
143
-		if (is_null($comment->getCreationDateTime())) {
144
-			$comment->setCreationDateTime(new \DateTime());
145
-		}
146
-
147
-		if ($comment->getParentId() !== '0') {
148
-			$comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId()));
149
-		} else {
150
-			$comment->setTopmostParentId('0');
151
-		}
152
-
153
-		$this->cache($comment);
154
-
155
-		return $comment;
156
-	}
157
-
158
-	/**
159
-	 * returns the topmost parent id of a given comment identified by ID
160
-	 *
161
-	 * @param string $id
162
-	 * @return string
163
-	 * @throws NotFoundException
164
-	 */
165
-	protected function determineTopmostParentId($id) {
166
-		$comment = $this->get($id);
167
-		if ($comment->getParentId() === '0') {
168
-			return $comment->getId();
169
-		}
170
-
171
-		return $this->determineTopmostParentId($comment->getParentId());
172
-	}
173
-
174
-	/**
175
-	 * updates child information of a comment
176
-	 *
177
-	 * @param string $id
178
-	 * @param \DateTime $cDateTime the date time of the most recent child
179
-	 * @throws NotFoundException
180
-	 */
181
-	protected function updateChildrenInformation($id, \DateTime $cDateTime) {
182
-		$qb = $this->dbConn->getQueryBuilder();
183
-		$query = $qb->select($qb->func()->count('id'))
184
-			->from('comments')
185
-			->where($qb->expr()->eq('parent_id', $qb->createParameter('id')))
186
-			->setParameter('id', $id);
187
-
188
-		$resultStatement = $query->execute();
189
-		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
190
-		$resultStatement->closeCursor();
191
-		$children = (int)$data[0];
192
-
193
-		$comment = $this->get($id);
194
-		$comment->setChildrenCount($children);
195
-		$comment->setLatestChildDateTime($cDateTime);
196
-		$this->save($comment);
197
-	}
198
-
199
-	/**
200
-	 * Tests whether actor or object type and id parameters are acceptable.
201
-	 * Throws exception if not.
202
-	 *
203
-	 * @param string $role
204
-	 * @param string $type
205
-	 * @param string $id
206
-	 * @throws \InvalidArgumentException
207
-	 */
208
-	protected function checkRoleParameters($role, $type, $id) {
209
-		if (
210
-			!is_string($type) || empty($type)
211
-			|| !is_string($id) || empty($id)
212
-		) {
213
-			throw new \InvalidArgumentException($role . ' parameters must be string and not empty');
214
-		}
215
-	}
216
-
217
-	/**
218
-	 * run-time caches a comment
219
-	 *
220
-	 * @param IComment $comment
221
-	 */
222
-	protected function cache(IComment $comment) {
223
-		$id = $comment->getId();
224
-		if (empty($id)) {
225
-			return;
226
-		}
227
-		$this->commentsCache[(string)$id] = $comment;
228
-	}
229
-
230
-	/**
231
-	 * removes an entry from the comments run time cache
232
-	 *
233
-	 * @param mixed $id the comment's id
234
-	 */
235
-	protected function uncache($id) {
236
-		$id = (string)$id;
237
-		if (isset($this->commentsCache[$id])) {
238
-			unset($this->commentsCache[$id]);
239
-		}
240
-	}
241
-
242
-	/**
243
-	 * returns a comment instance
244
-	 *
245
-	 * @param string $id the ID of the comment
246
-	 * @return IComment
247
-	 * @throws NotFoundException
248
-	 * @throws \InvalidArgumentException
249
-	 * @since 9.0.0
250
-	 */
251
-	public function get($id) {
252
-		if ((int)$id === 0) {
253
-			throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.');
254
-		}
255
-
256
-		if (isset($this->commentsCache[$id])) {
257
-			return $this->commentsCache[$id];
258
-		}
259
-
260
-		$qb = $this->dbConn->getQueryBuilder();
261
-		$resultStatement = $qb->select('*')
262
-			->from('comments')
263
-			->where($qb->expr()->eq('id', $qb->createParameter('id')))
264
-			->setParameter('id', $id, IQueryBuilder::PARAM_INT)
265
-			->execute();
266
-
267
-		$data = $resultStatement->fetch();
268
-		$resultStatement->closeCursor();
269
-		if (!$data) {
270
-			throw new NotFoundException();
271
-		}
272
-
273
-
274
-		$comment = $this->getCommentFromData($data);
275
-		$this->cache($comment);
276
-		return $comment;
277
-	}
278
-
279
-	/**
280
-	 * returns the comment specified by the id and all it's child comments.
281
-	 * At this point of time, we do only support one level depth.
282
-	 *
283
-	 * @param string $id
284
-	 * @param int $limit max number of entries to return, 0 returns all
285
-	 * @param int $offset the start entry
286
-	 * @return array
287
-	 * @since 9.0.0
288
-	 *
289
-	 * The return array looks like this
290
-	 * [
291
-	 *   'comment' => IComment, // root comment
292
-	 *   'replies' =>
293
-	 *   [
294
-	 *     0 =>
295
-	 *     [
296
-	 *       'comment' => IComment,
297
-	 *       'replies' => []
298
-	 *     ]
299
-	 *     1 =>
300
-	 *     [
301
-	 *       'comment' => IComment,
302
-	 *       'replies'=> []
303
-	 *     ],
304
-	 *     …
305
-	 *   ]
306
-	 * ]
307
-	 */
308
-	public function getTree($id, $limit = 0, $offset = 0) {
309
-		$tree = [];
310
-		$tree['comment'] = $this->get($id);
311
-		$tree['replies'] = [];
312
-
313
-		$qb = $this->dbConn->getQueryBuilder();
314
-		$query = $qb->select('*')
315
-			->from('comments')
316
-			->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id')))
317
-			->orderBy('creation_timestamp', 'DESC')
318
-			->setParameter('id', $id);
319
-
320
-		if ($limit > 0) {
321
-			$query->setMaxResults($limit);
322
-		}
323
-		if ($offset > 0) {
324
-			$query->setFirstResult($offset);
325
-		}
326
-
327
-		$resultStatement = $query->execute();
328
-		while ($data = $resultStatement->fetch()) {
329
-			$comment = $this->getCommentFromData($data);
330
-			$this->cache($comment);
331
-			$tree['replies'][] = [
332
-				'comment' => $comment,
333
-				'replies' => []
334
-			];
335
-		}
336
-		$resultStatement->closeCursor();
337
-
338
-		return $tree;
339
-	}
340
-
341
-	/**
342
-	 * returns comments for a specific object (e.g. a file).
343
-	 *
344
-	 * The sort order is always newest to oldest.
345
-	 *
346
-	 * @param string $objectType the object type, e.g. 'files'
347
-	 * @param string $objectId the id of the object
348
-	 * @param int $limit optional, number of maximum comments to be returned. if
349
-	 * not specified, all comments are returned.
350
-	 * @param int $offset optional, starting point
351
-	 * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
352
-	 * that may be returned
353
-	 * @return IComment[]
354
-	 * @since 9.0.0
355
-	 */
356
-	public function getForObject(
357
-		$objectType,
358
-		$objectId,
359
-		$limit = 0,
360
-		$offset = 0,
361
-		\DateTime $notOlderThan = null
362
-	) {
363
-		$comments = [];
364
-
365
-		$qb = $this->dbConn->getQueryBuilder();
366
-		$query = $qb->select('*')
367
-			->from('comments')
368
-			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
369
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
370
-			->orderBy('creation_timestamp', 'DESC')
371
-			->setParameter('type', $objectType)
372
-			->setParameter('id', $objectId);
373
-
374
-		if ($limit > 0) {
375
-			$query->setMaxResults($limit);
376
-		}
377
-		if ($offset > 0) {
378
-			$query->setFirstResult($offset);
379
-		}
380
-		if (!is_null($notOlderThan)) {
381
-			$query
382
-				->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
383
-				->setParameter('notOlderThan', $notOlderThan, 'datetime');
384
-		}
385
-
386
-		$resultStatement = $query->execute();
387
-		while ($data = $resultStatement->fetch()) {
388
-			$comment = $this->getCommentFromData($data);
389
-			$this->cache($comment);
390
-			$comments[] = $comment;
391
-		}
392
-		$resultStatement->closeCursor();
393
-
394
-		return $comments;
395
-	}
396
-
397
-	/**
398
-	 * @param string $objectType the object type, e.g. 'files'
399
-	 * @param string $objectId the id of the object
400
-	 * @param int $lastKnownCommentId the last known comment (will be used as offset)
401
-	 * @param string $sortDirection direction of the comments (`asc` or `desc`)
402
-	 * @param int $limit optional, number of maximum comments to be returned. if
403
-	 * set to 0, all comments are returned.
404
-	 * @param bool $includeLastKnown
405
-	 * @return IComment[]
406
-	 * @return array
407
-	 */
408
-	public function getForObjectSince(
409
-		string $objectType,
410
-		string $objectId,
411
-		int $lastKnownCommentId,
412
-		string $sortDirection = 'asc',
413
-		int $limit = 30,
414
-		bool $includeLastKnown = false
415
-	): array {
416
-		$comments = [];
417
-
418
-		$query = $this->dbConn->getQueryBuilder();
419
-		$query->select('*')
420
-			->from('comments')
421
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
422
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
423
-			->orderBy('creation_timestamp', $sortDirection === 'desc' ? 'DESC' : 'ASC')
424
-			->addOrderBy('id', $sortDirection === 'desc' ? 'DESC' : 'ASC');
425
-
426
-		if ($limit > 0) {
427
-			$query->setMaxResults($limit);
428
-		}
429
-
430
-		$lastKnownComment = $lastKnownCommentId > 0 ? $this->getLastKnownComment(
431
-			$objectType,
432
-			$objectId,
433
-			$lastKnownCommentId
434
-		) : null;
435
-		if ($lastKnownComment instanceof IComment) {
436
-			$lastKnownCommentDateTime = $lastKnownComment->getCreationDateTime();
437
-			if ($sortDirection === 'desc') {
438
-				if ($includeLastKnown) {
439
-					$idComparison = $query->expr()->lte('id', $query->createNamedParameter($lastKnownCommentId));
440
-				} else {
441
-					$idComparison = $query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId));
442
-				}
443
-				$query->andWhere(
444
-					$query->expr()->orX(
445
-						$query->expr()->lt(
446
-							'creation_timestamp',
447
-							$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
448
-							IQueryBuilder::PARAM_DATE
449
-						),
450
-						$query->expr()->andX(
451
-							$query->expr()->eq(
452
-								'creation_timestamp',
453
-								$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
454
-								IQueryBuilder::PARAM_DATE
455
-							),
456
-							$idComparison
457
-						)
458
-					)
459
-				);
460
-			} else {
461
-				if ($includeLastKnown) {
462
-					$idComparison = $query->expr()->gte('id', $query->createNamedParameter($lastKnownCommentId));
463
-				} else {
464
-					$idComparison = $query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId));
465
-				}
466
-				$query->andWhere(
467
-					$query->expr()->orX(
468
-						$query->expr()->gt(
469
-							'creation_timestamp',
470
-							$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
471
-							IQueryBuilder::PARAM_DATE
472
-						),
473
-						$query->expr()->andX(
474
-							$query->expr()->eq(
475
-								'creation_timestamp',
476
-								$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
477
-								IQueryBuilder::PARAM_DATE
478
-							),
479
-							$idComparison
480
-						)
481
-					)
482
-				);
483
-			}
484
-		}
485
-
486
-		$resultStatement = $query->execute();
487
-		while ($data = $resultStatement->fetch()) {
488
-			$comment = $this->getCommentFromData($data);
489
-			$this->cache($comment);
490
-			$comments[] = $comment;
491
-		}
492
-		$resultStatement->closeCursor();
493
-
494
-		return $comments;
495
-	}
496
-
497
-	/**
498
-	 * @param string $objectType the object type, e.g. 'files'
499
-	 * @param string $objectId the id of the object
500
-	 * @param int $id the comment to look for
501
-	 * @return Comment|null
502
-	 */
503
-	protected function getLastKnownComment(string $objectType,
504
-										   string $objectId,
505
-										   int $id) {
506
-		$query = $this->dbConn->getQueryBuilder();
507
-		$query->select('*')
508
-			->from('comments')
509
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
510
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
511
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
512
-
513
-		$result = $query->execute();
514
-		$row = $result->fetch();
515
-		$result->closeCursor();
516
-
517
-		if ($row) {
518
-			$comment = $this->getCommentFromData($row);
519
-			$this->cache($comment);
520
-			return $comment;
521
-		}
522
-
523
-		return null;
524
-	}
525
-
526
-	/**
527
-	 * Search for comments with a given content
528
-	 *
529
-	 * @param string $search content to search for
530
-	 * @param string $objectType Limit the search by object type
531
-	 * @param string $objectId Limit the search by object id
532
-	 * @param string $verb Limit the verb of the comment
533
-	 * @param int $offset
534
-	 * @param int $limit
535
-	 * @return IComment[]
536
-	 */
537
-	public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array {
538
-		$objectIds = [];
539
-		if ($objectId) {
540
-			$objectIds[] = $objectIds;
541
-		}
542
-		return $this->searchForObjects($search, $objectType, $objectIds, $verb, $offset, $limit);
543
-	}
544
-
545
-	/**
546
-	 * Search for comments on one or more objects with a given content
547
-	 *
548
-	 * @param string $search content to search for
549
-	 * @param string $objectType Limit the search by object type
550
-	 * @param array $objectIds Limit the search by object ids
551
-	 * @param string $verb Limit the verb of the comment
552
-	 * @param int $offset
553
-	 * @param int $limit
554
-	 * @return IComment[]
555
-	 */
556
-	public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array {
557
-		$query = $this->dbConn->getQueryBuilder();
558
-
559
-		$query->select('*')
560
-			->from('comments')
561
-			->where($query->expr()->iLike('message', $query->createNamedParameter(
562
-				'%' . $this->dbConn->escapeLikeParameter($search). '%'
563
-			)))
564
-			->orderBy('creation_timestamp', 'DESC')
565
-			->addOrderBy('id', 'DESC')
566
-			->setMaxResults($limit);
567
-
568
-		if ($objectType !== '') {
569
-			$query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType)));
570
-		}
571
-		if (!empty($objectIds)) {
572
-			$query->andWhere($query->expr()->in('object_id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY)));
573
-		}
574
-		if ($verb !== '') {
575
-			$query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
576
-		}
577
-		if ($offset !== 0) {
578
-			$query->setFirstResult($offset);
579
-		}
580
-
581
-		$comments = [];
582
-		$result = $query->execute();
583
-		while ($data = $result->fetch()) {
584
-			$comment = $this->getCommentFromData($data);
585
-			$this->cache($comment);
586
-			$comments[] = $comment;
587
-		}
588
-		$result->closeCursor();
589
-
590
-		return $comments;
591
-	}
592
-
593
-	/**
594
-	 * @param $objectType string the object type, e.g. 'files'
595
-	 * @param $objectId string the id of the object
596
-	 * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
597
-	 * that may be returned
598
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
599
-	 * @return Int
600
-	 * @since 9.0.0
601
-	 */
602
-	public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '') {
603
-		$qb = $this->dbConn->getQueryBuilder();
604
-		$query = $qb->select($qb->func()->count('id'))
605
-			->from('comments')
606
-			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
607
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
608
-			->setParameter('type', $objectType)
609
-			->setParameter('id', $objectId);
610
-
611
-		if (!is_null($notOlderThan)) {
612
-			$query
613
-				->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
614
-				->setParameter('notOlderThan', $notOlderThan, 'datetime');
615
-		}
616
-
617
-		if ($verb !== '') {
618
-			$query->andWhere($qb->expr()->eq('verb', $qb->createNamedParameter($verb)));
619
-		}
620
-
621
-		$resultStatement = $query->execute();
622
-		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
623
-		$resultStatement->closeCursor();
624
-		return (int)$data[0];
625
-	}
626
-
627
-	/**
628
-	 * @param string $objectType the object type, e.g. 'files'
629
-	 * @param string[] $objectIds the id of the object
630
-	 * @param IUser $user
631
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
632
-	 * @return array Map with object id => # of unread comments
633
-	 * @psalm-return array<string, int>
634
-	 * @since 21.0.0
635
-	 */
636
-	public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array {
637
-		$query = $this->dbConn->getQueryBuilder();
638
-		$query->select('c.object_id', $query->func()->count('c.id', 'num_comments'))
639
-			->from('comments', 'c')
640
-			->leftJoin('c', 'comments_read_markers', 'm', $query->expr()->andX(
641
-				$query->expr()->eq('m.user_id', $query->createNamedParameter($user->getUID())),
642
-				$query->expr()->eq('c.object_type', 'm.object_type'),
643
-				$query->expr()->eq('c.object_id', 'm.object_id')
644
-			))
645
-			->where($query->expr()->eq('c.object_type', $query->createNamedParameter($objectType)))
646
-			->andWhere($query->expr()->in('c.object_id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY)))
647
-			->andWhere($query->expr()->orX(
648
-				$query->expr()->gt('c.creation_timestamp', 'm.marker_datetime'),
649
-				$query->expr()->isNull('m.marker_datetime')
650
-			))
651
-			->groupBy('c.object_id');
652
-
653
-		if ($verb !== '') {
654
-			$query->andWhere($query->expr()->eq('c.verb', $query->createNamedParameter($verb)));
655
-		}
656
-
657
-		$result = $query->execute();
658
-		$unreadComments = array_fill_keys($objectIds, 0);
659
-		while ($row = $result->fetch()) {
660
-			$unreadComments[$row['object_id']] = (int) $row['num_comments'];
661
-		}
662
-		$result->closeCursor();
663
-
664
-		return $unreadComments;
665
-	}
666
-
667
-	/**
668
-	 * @param string $objectType
669
-	 * @param string $objectId
670
-	 * @param int $lastRead
671
-	 * @param string $verb
672
-	 * @return int
673
-	 * @since 21.0.0
674
-	 */
675
-	public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int {
676
-		$query = $this->dbConn->getQueryBuilder();
677
-		$query->select($query->func()->count('id', 'num_messages'))
678
-			->from('comments')
679
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
680
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
681
-			->andWhere($query->expr()->gt('id', $query->createNamedParameter($lastRead)));
682
-
683
-		if ($verb !== '') {
684
-			$query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
685
-		}
686
-
687
-		$result = $query->execute();
688
-		$data = $result->fetch();
689
-		$result->closeCursor();
690
-
691
-		return (int) ($data['num_messages'] ?? 0);
692
-	}
693
-
694
-	/**
695
-	 * @param string $objectType
696
-	 * @param string $objectId
697
-	 * @param \DateTime $beforeDate
698
-	 * @param string $verb
699
-	 * @return int
700
-	 * @since 21.0.0
701
-	 */
702
-	public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int {
703
-		$query = $this->dbConn->getQueryBuilder();
704
-		$query->select('id')
705
-			->from('comments')
706
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
707
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
708
-			->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATE)))
709
-			->orderBy('creation_timestamp', 'desc');
710
-
711
-		if ($verb !== '') {
712
-			$query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
713
-		}
714
-
715
-		$result = $query->execute();
716
-		$data = $result->fetch();
717
-		$result->closeCursor();
718
-
719
-		return (int) ($data['id'] ?? 0);
720
-	}
721
-
722
-	/**
723
-	 * @param string $objectType
724
-	 * @param string $objectId
725
-	 * @param string $verb
726
-	 * @param string $actorType
727
-	 * @param string[] $actors
728
-	 * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
729
-	 * @psalm-return array<string, \DateTime>
730
-	 * @since 21.0.0
731
-	 */
732
-	public function getLastCommentDateByActor(
733
-		string $objectType,
734
-		string $objectId,
735
-		string $verb,
736
-		string $actorType,
737
-		array $actors
738
-	): array {
739
-		$lastComments = [];
740
-
741
-		$query = $this->dbConn->getQueryBuilder();
742
-		$query->select('actor_id')
743
-			->selectAlias($query->createFunction('MAX(' . $query->getColumnName('creation_timestamp') . ')'), 'last_comment')
744
-			->from('comments')
745
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
746
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
747
-			->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)))
748
-			->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
749
-			->andWhere($query->expr()->in('actor_id', $query->createNamedParameter($actors, IQueryBuilder::PARAM_STR_ARRAY)))
750
-			->groupBy('actor_id');
751
-
752
-		$result = $query->execute();
753
-		while ($row = $result->fetch()) {
754
-			$lastComments[$row['actor_id']] = $this->timeFactory->getDateTime($row['last_comment']);
755
-		}
756
-		$result->closeCursor();
757
-
758
-		return $lastComments;
759
-	}
760
-
761
-	/**
762
-	 * Get the number of unread comments for all files in a folder
763
-	 *
764
-	 * @param int $folderId
765
-	 * @param IUser $user
766
-	 * @return array [$fileId => $unreadCount]
767
-	 */
768
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) {
769
-		$qb = $this->dbConn->getQueryBuilder();
770
-
771
-		$query = $qb->select('f.fileid')
772
-			->addSelect($qb->func()->count('c.id', 'num_ids'))
773
-			->from('filecache', 'f')
774
-			->leftJoin('f', 'comments', 'c', $qb->expr()->andX(
775
-				$qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT)),
776
-				$qb->expr()->eq('c.object_type', $qb->createNamedParameter('files'))
777
-			))
778
-			->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX(
779
-				$qb->expr()->eq('c.object_id', 'm.object_id'),
780
-				$qb->expr()->eq('m.object_type', $qb->createNamedParameter('files'))
781
-			))
782
-			->where(
783
-				$qb->expr()->andX(
784
-					$qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)),
785
-					$qb->expr()->orX(
786
-						$qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')),
787
-						$qb->expr()->isNull('c.object_type')
788
-					),
789
-					$qb->expr()->orX(
790
-						$qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')),
791
-						$qb->expr()->isNull('m.object_type')
792
-					),
793
-					$qb->expr()->orX(
794
-						$qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID())),
795
-						$qb->expr()->isNull('m.user_id')
796
-					),
797
-					$qb->expr()->orX(
798
-						$qb->expr()->gt('c.creation_timestamp', 'm.marker_datetime'),
799
-						$qb->expr()->isNull('m.marker_datetime')
800
-					)
801
-				)
802
-			)->groupBy('f.fileid');
803
-
804
-		$resultStatement = $query->execute();
805
-
806
-		$results = [];
807
-		while ($row = $resultStatement->fetch()) {
808
-			$results[$row['fileid']] = (int) $row['num_ids'];
809
-		}
810
-		$resultStatement->closeCursor();
811
-		return $results;
812
-	}
813
-
814
-	/**
815
-	 * creates a new comment and returns it. At this point of time, it is not
816
-	 * saved in the used data storage. Use save() after setting other fields
817
-	 * of the comment (e.g. message or verb).
818
-	 *
819
-	 * @param string $actorType the actor type (e.g. 'users')
820
-	 * @param string $actorId a user id
821
-	 * @param string $objectType the object type the comment is attached to
822
-	 * @param string $objectId the object id the comment is attached to
823
-	 * @return IComment
824
-	 * @since 9.0.0
825
-	 */
826
-	public function create($actorType, $actorId, $objectType, $objectId) {
827
-		$comment = new Comment();
828
-		$comment
829
-			->setActor($actorType, $actorId)
830
-			->setObject($objectType, $objectId);
831
-		return $comment;
832
-	}
833
-
834
-	/**
835
-	 * permanently deletes the comment specified by the ID
836
-	 *
837
-	 * When the comment has child comments, their parent ID will be changed to
838
-	 * the parent ID of the item that is to be deleted.
839
-	 *
840
-	 * @param string $id
841
-	 * @return bool
842
-	 * @throws \InvalidArgumentException
843
-	 * @since 9.0.0
844
-	 */
845
-	public function delete($id) {
846
-		if (!is_string($id)) {
847
-			throw new \InvalidArgumentException('Parameter must be string');
848
-		}
849
-
850
-		try {
851
-			$comment = $this->get($id);
852
-		} catch (\Exception $e) {
853
-			// Ignore exceptions, we just don't fire a hook then
854
-			$comment = null;
855
-		}
856
-
857
-		$qb = $this->dbConn->getQueryBuilder();
858
-		$query = $qb->delete('comments')
859
-			->where($qb->expr()->eq('id', $qb->createParameter('id')))
860
-			->setParameter('id', $id);
861
-
862
-		try {
863
-			$affectedRows = $query->execute();
864
-			$this->uncache($id);
865
-		} catch (DriverException $e) {
866
-			$this->logger->error($e->getMessage(), [
867
-				'exception' => $e,
868
-				'app' => 'core_comments',
869
-			]);
870
-			return false;
871
-		}
872
-
873
-		if ($affectedRows > 0 && $comment instanceof IComment) {
874
-			$this->sendEvent(CommentsEvent::EVENT_DELETE, $comment);
875
-		}
876
-
877
-		return ($affectedRows > 0);
878
-	}
879
-
880
-	/**
881
-	 * saves the comment permanently
882
-	 *
883
-	 * if the supplied comment has an empty ID, a new entry comment will be
884
-	 * saved and the instance updated with the new ID.
885
-	 *
886
-	 * Otherwise, an existing comment will be updated.
887
-	 *
888
-	 * Throws NotFoundException when a comment that is to be updated does not
889
-	 * exist anymore at this point of time.
890
-	 *
891
-	 * @param IComment $comment
892
-	 * @return bool
893
-	 * @throws NotFoundException
894
-	 * @since 9.0.0
895
-	 */
896
-	public function save(IComment $comment) {
897
-		if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') {
898
-			$result = $this->insert($comment);
899
-		} else {
900
-			$result = $this->update($comment);
901
-		}
902
-
903
-		if ($result && !!$comment->getParentId()) {
904
-			$this->updateChildrenInformation(
905
-				$comment->getParentId(),
906
-				$comment->getCreationDateTime()
907
-			);
908
-			$this->cache($comment);
909
-		}
910
-
911
-		return $result;
912
-	}
913
-
914
-	/**
915
-	 * inserts the provided comment in the database
916
-	 *
917
-	 * @param IComment $comment
918
-	 * @return bool
919
-	 */
920
-	protected function insert(IComment $comment): bool {
921
-		try {
922
-			$result = $this->insertQuery($comment, true);
923
-		} catch (InvalidFieldNameException $e) {
924
-			// The reference id field was only added in Nextcloud 19.
925
-			// In order to not cause too long waiting times on the update,
926
-			// it was decided to only add it lazy, as it is also not a critical
927
-			// feature, but only helps to have a better experience while commenting.
928
-			// So in case the reference_id field is missing,
929
-			// we simply save the comment without that field.
930
-			$result = $this->insertQuery($comment, false);
931
-		}
932
-
933
-		return $result;
934
-	}
935
-
936
-	protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool {
937
-		$qb = $this->dbConn->getQueryBuilder();
938
-
939
-		$values = [
940
-			'parent_id' => $qb->createNamedParameter($comment->getParentId()),
941
-			'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
942
-			'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
943
-			'actor_type' => $qb->createNamedParameter($comment->getActorType()),
944
-			'actor_id' => $qb->createNamedParameter($comment->getActorId()),
945
-			'message' => $qb->createNamedParameter($comment->getMessage()),
946
-			'verb' => $qb->createNamedParameter($comment->getVerb()),
947
-			'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
948
-			'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
949
-			'object_type' => $qb->createNamedParameter($comment->getObjectType()),
950
-			'object_id' => $qb->createNamedParameter($comment->getObjectId()),
951
-		];
952
-
953
-		if ($tryWritingReferenceId) {
954
-			$values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId());
955
-		}
956
-
957
-		$affectedRows = $qb->insert('comments')
958
-			->values($values)
959
-			->execute();
960
-
961
-		if ($affectedRows > 0) {
962
-			$comment->setId((string)$qb->getLastInsertId());
963
-			$this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
964
-		}
965
-
966
-		return $affectedRows > 0;
967
-	}
968
-
969
-	/**
970
-	 * updates a Comment data row
971
-	 *
972
-	 * @param IComment $comment
973
-	 * @return bool
974
-	 * @throws NotFoundException
975
-	 */
976
-	protected function update(IComment $comment) {
977
-		// for properly working preUpdate Events we need the old comments as is
978
-		// in the DB and overcome caching. Also avoid that outdated information stays.
979
-		$this->uncache($comment->getId());
980
-		$this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
981
-		$this->uncache($comment->getId());
982
-
983
-		try {
984
-			$result = $this->updateQuery($comment, true);
985
-		} catch (InvalidFieldNameException $e) {
986
-			// See function insert() for explanation
987
-			$result = $this->updateQuery($comment, false);
988
-		}
989
-
990
-		$this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment);
991
-
992
-		return $result;
993
-	}
994
-
995
-	protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool {
996
-		$qb = $this->dbConn->getQueryBuilder();
997
-		$qb
998
-			->update('comments')
999
-			->set('parent_id', $qb->createNamedParameter($comment->getParentId()))
1000
-			->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId()))
1001
-			->set('children_count', $qb->createNamedParameter($comment->getChildrenCount()))
1002
-			->set('actor_type', $qb->createNamedParameter($comment->getActorType()))
1003
-			->set('actor_id', $qb->createNamedParameter($comment->getActorId()))
1004
-			->set('message', $qb->createNamedParameter($comment->getMessage()))
1005
-			->set('verb', $qb->createNamedParameter($comment->getVerb()))
1006
-			->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'))
1007
-			->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
1008
-			->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
1009
-			->set('object_id', $qb->createNamedParameter($comment->getObjectId()));
1010
-
1011
-		if ($tryWritingReferenceId) {
1012
-			$qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()));
1013
-		}
1014
-
1015
-		$affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())))
1016
-			->execute();
1017
-
1018
-		if ($affectedRows === 0) {
1019
-			throw new NotFoundException('Comment to update does ceased to exist');
1020
-		}
1021
-
1022
-		return $affectedRows > 0;
1023
-	}
1024
-
1025
-	/**
1026
-	 * removes references to specific actor (e.g. on user delete) of a comment.
1027
-	 * The comment itself must not get lost/deleted.
1028
-	 *
1029
-	 * @param string $actorType the actor type (e.g. 'users')
1030
-	 * @param string $actorId a user id
1031
-	 * @return boolean
1032
-	 * @since 9.0.0
1033
-	 */
1034
-	public function deleteReferencesOfActor($actorType, $actorId) {
1035
-		$this->checkRoleParameters('Actor', $actorType, $actorId);
1036
-
1037
-		$qb = $this->dbConn->getQueryBuilder();
1038
-		$affectedRows = $qb
1039
-			->update('comments')
1040
-			->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1041
-			->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1042
-			->where($qb->expr()->eq('actor_type', $qb->createParameter('type')))
1043
-			->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id')))
1044
-			->setParameter('type', $actorType)
1045
-			->setParameter('id', $actorId)
1046
-			->execute();
1047
-
1048
-		$this->commentsCache = [];
1049
-
1050
-		return is_int($affectedRows);
1051
-	}
1052
-
1053
-	/**
1054
-	 * deletes all comments made of a specific object (e.g. on file delete)
1055
-	 *
1056
-	 * @param string $objectType the object type (e.g. 'files')
1057
-	 * @param string $objectId e.g. the file id
1058
-	 * @return boolean
1059
-	 * @since 9.0.0
1060
-	 */
1061
-	public function deleteCommentsAtObject($objectType, $objectId) {
1062
-		$this->checkRoleParameters('Object', $objectType, $objectId);
1063
-
1064
-		$qb = $this->dbConn->getQueryBuilder();
1065
-		$affectedRows = $qb
1066
-			->delete('comments')
1067
-			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
1068
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
1069
-			->setParameter('type', $objectType)
1070
-			->setParameter('id', $objectId)
1071
-			->execute();
1072
-
1073
-		$this->commentsCache = [];
1074
-
1075
-		return is_int($affectedRows);
1076
-	}
1077
-
1078
-	/**
1079
-	 * deletes the read markers for the specified user
1080
-	 *
1081
-	 * @param \OCP\IUser $user
1082
-	 * @return bool
1083
-	 * @since 9.0.0
1084
-	 */
1085
-	public function deleteReadMarksFromUser(IUser $user) {
1086
-		$qb = $this->dbConn->getQueryBuilder();
1087
-		$query = $qb->delete('comments_read_markers')
1088
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1089
-			->setParameter('user_id', $user->getUID());
1090
-
1091
-		try {
1092
-			$affectedRows = $query->execute();
1093
-		} catch (DriverException $e) {
1094
-			$this->logger->error($e->getMessage(), [
1095
-				'exception' => $e,
1096
-				'app' => 'core_comments',
1097
-			]);
1098
-			return false;
1099
-		}
1100
-		return ($affectedRows > 0);
1101
-	}
1102
-
1103
-	/**
1104
-	 * sets the read marker for a given file to the specified date for the
1105
-	 * provided user
1106
-	 *
1107
-	 * @param string $objectType
1108
-	 * @param string $objectId
1109
-	 * @param \DateTime $dateTime
1110
-	 * @param IUser $user
1111
-	 * @since 9.0.0
1112
-	 */
1113
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) {
1114
-		$this->checkRoleParameters('Object', $objectType, $objectId);
1115
-
1116
-		$qb = $this->dbConn->getQueryBuilder();
1117
-		$values = [
1118
-			'user_id' => $qb->createNamedParameter($user->getUID()),
1119
-			'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'),
1120
-			'object_type' => $qb->createNamedParameter($objectType),
1121
-			'object_id' => $qb->createNamedParameter($objectId),
1122
-		];
1123
-
1124
-		// Strategy: try to update, if this does not return affected rows, do an insert.
1125
-		$affectedRows = $qb
1126
-			->update('comments_read_markers')
1127
-			->set('user_id', $values['user_id'])
1128
-			->set('marker_datetime', $values['marker_datetime'])
1129
-			->set('object_type', $values['object_type'])
1130
-			->set('object_id', $values['object_id'])
1131
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1132
-			->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1133
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1134
-			->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1135
-			->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1136
-			->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1137
-			->execute();
1138
-
1139
-		if ($affectedRows > 0) {
1140
-			return;
1141
-		}
1142
-
1143
-		$qb->insert('comments_read_markers')
1144
-			->values($values)
1145
-			->execute();
1146
-	}
1147
-
1148
-	/**
1149
-	 * returns the read marker for a given file to the specified date for the
1150
-	 * provided user. It returns null, when the marker is not present, i.e.
1151
-	 * no comments were marked as read.
1152
-	 *
1153
-	 * @param string $objectType
1154
-	 * @param string $objectId
1155
-	 * @param IUser $user
1156
-	 * @return \DateTime|null
1157
-	 * @since 9.0.0
1158
-	 */
1159
-	public function getReadMark($objectType, $objectId, IUser $user) {
1160
-		$qb = $this->dbConn->getQueryBuilder();
1161
-		$resultStatement = $qb->select('marker_datetime')
1162
-			->from('comments_read_markers')
1163
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1164
-			->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1165
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1166
-			->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1167
-			->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1168
-			->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1169
-			->execute();
1170
-
1171
-		$data = $resultStatement->fetch();
1172
-		$resultStatement->closeCursor();
1173
-		if (!$data || is_null($data['marker_datetime'])) {
1174
-			return null;
1175
-		}
1176
-
1177
-		return new \DateTime($data['marker_datetime']);
1178
-	}
1179
-
1180
-	/**
1181
-	 * deletes the read markers on the specified object
1182
-	 *
1183
-	 * @param string $objectType
1184
-	 * @param string $objectId
1185
-	 * @return bool
1186
-	 * @since 9.0.0
1187
-	 */
1188
-	public function deleteReadMarksOnObject($objectType, $objectId) {
1189
-		$this->checkRoleParameters('Object', $objectType, $objectId);
1190
-
1191
-		$qb = $this->dbConn->getQueryBuilder();
1192
-		$query = $qb->delete('comments_read_markers')
1193
-			->where($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1194
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1195
-			->setParameter('object_type', $objectType)
1196
-			->setParameter('object_id', $objectId);
1197
-
1198
-		try {
1199
-			$affectedRows = $query->execute();
1200
-		} catch (DriverException $e) {
1201
-			$this->logger->error($e->getMessage(), [
1202
-				'exception' => $e,
1203
-				'app' => 'core_comments',
1204
-			]);
1205
-			return false;
1206
-		}
1207
-		return ($affectedRows > 0);
1208
-	}
1209
-
1210
-	/**
1211
-	 * registers an Entity to the manager, so event notifications can be send
1212
-	 * to consumers of the comments infrastructure
1213
-	 *
1214
-	 * @param \Closure $closure
1215
-	 */
1216
-	public function registerEventHandler(\Closure $closure) {
1217
-		$this->eventHandlerClosures[] = $closure;
1218
-		$this->eventHandlers = [];
1219
-	}
1220
-
1221
-	/**
1222
-	 * registers a method that resolves an ID to a display name for a given type
1223
-	 *
1224
-	 * @param string $type
1225
-	 * @param \Closure $closure
1226
-	 * @throws \OutOfBoundsException
1227
-	 * @since 11.0.0
1228
-	 *
1229
-	 * Only one resolver shall be registered per type. Otherwise a
1230
-	 * \OutOfBoundsException has to thrown.
1231
-	 */
1232
-	public function registerDisplayNameResolver($type, \Closure $closure) {
1233
-		if (!is_string($type)) {
1234
-			throw new \InvalidArgumentException('String expected.');
1235
-		}
1236
-		if (isset($this->displayNameResolvers[$type])) {
1237
-			throw new \OutOfBoundsException('Displayname resolver for this type already registered');
1238
-		}
1239
-		$this->displayNameResolvers[$type] = $closure;
1240
-	}
1241
-
1242
-	/**
1243
-	 * resolves a given ID of a given Type to a display name.
1244
-	 *
1245
-	 * @param string $type
1246
-	 * @param string $id
1247
-	 * @return string
1248
-	 * @throws \OutOfBoundsException
1249
-	 * @since 11.0.0
1250
-	 *
1251
-	 * If a provided type was not registered, an \OutOfBoundsException shall
1252
-	 * be thrown. It is upon the resolver discretion what to return of the
1253
-	 * provided ID is unknown. It must be ensured that a string is returned.
1254
-	 */
1255
-	public function resolveDisplayName($type, $id) {
1256
-		if (!is_string($type)) {
1257
-			throw new \InvalidArgumentException('String expected.');
1258
-		}
1259
-		if (!isset($this->displayNameResolvers[$type])) {
1260
-			throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1261
-		}
1262
-		return (string)$this->displayNameResolvers[$type]($id);
1263
-	}
1264
-
1265
-	/**
1266
-	 * returns valid, registered entities
1267
-	 *
1268
-	 * @return \OCP\Comments\ICommentsEventHandler[]
1269
-	 */
1270
-	private function getEventHandlers() {
1271
-		if (!empty($this->eventHandlers)) {
1272
-			return $this->eventHandlers;
1273
-		}
1274
-
1275
-		$this->eventHandlers = [];
1276
-		foreach ($this->eventHandlerClosures as $name => $closure) {
1277
-			$entity = $closure();
1278
-			if (!($entity instanceof ICommentsEventHandler)) {
1279
-				throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface');
1280
-			}
1281
-			$this->eventHandlers[$name] = $entity;
1282
-		}
1283
-
1284
-		return $this->eventHandlers;
1285
-	}
1286
-
1287
-	/**
1288
-	 * sends notifications to the registered entities
1289
-	 *
1290
-	 * @param $eventType
1291
-	 * @param IComment $comment
1292
-	 */
1293
-	private function sendEvent($eventType, IComment $comment) {
1294
-		$entities = $this->getEventHandlers();
1295
-		$event = new CommentsEvent($eventType, $comment);
1296
-		foreach ($entities as $entity) {
1297
-			$entity->handle($event);
1298
-		}
1299
-	}
1300
-
1301
-	/**
1302
-	 * Load the Comments app into the page
1303
-	 *
1304
-	 * @since 21.0.0
1305
-	 */
1306
-	public function load(): void {
1307
-		$this->initialStateService->provideInitialState(Application::APP_ID, 'max-message-length', IComment::MAX_MESSAGE_LENGTH);
1308
-		Util::addScript(Application::APP_ID, 'comments-app');
1309
-	}
50
+    /** @var  IDBConnection */
51
+    protected $dbConn;
52
+
53
+    /** @var  LoggerInterface */
54
+    protected $logger;
55
+
56
+    /** @var IConfig */
57
+    protected $config;
58
+
59
+    /** @var ITimeFactory */
60
+    protected $timeFactory;
61
+
62
+    /** @var IInitialStateService */
63
+    protected $initialStateService;
64
+
65
+    /** @var IComment[] */
66
+    protected $commentsCache = [];
67
+
68
+    /** @var  \Closure[] */
69
+    protected $eventHandlerClosures = [];
70
+
71
+    /** @var  ICommentsEventHandler[] */
72
+    protected $eventHandlers = [];
73
+
74
+    /** @var \Closure[] */
75
+    protected $displayNameResolvers = [];
76
+
77
+    public function __construct(IDBConnection $dbConn,
78
+                                LoggerInterface $logger,
79
+                                IConfig $config,
80
+                                ITimeFactory $timeFactory,
81
+                                IInitialStateService $initialStateService) {
82
+        $this->dbConn = $dbConn;
83
+        $this->logger = $logger;
84
+        $this->config = $config;
85
+        $this->timeFactory = $timeFactory;
86
+        $this->initialStateService = $initialStateService;
87
+    }
88
+
89
+    /**
90
+     * converts data base data into PHP native, proper types as defined by
91
+     * IComment interface.
92
+     *
93
+     * @param array $data
94
+     * @return array
95
+     */
96
+    protected function normalizeDatabaseData(array $data) {
97
+        $data['id'] = (string)$data['id'];
98
+        $data['parent_id'] = (string)$data['parent_id'];
99
+        $data['topmost_parent_id'] = (string)$data['topmost_parent_id'];
100
+        $data['creation_timestamp'] = new \DateTime($data['creation_timestamp']);
101
+        if (!is_null($data['latest_child_timestamp'])) {
102
+            $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']);
103
+        }
104
+        $data['children_count'] = (int)$data['children_count'];
105
+        $data['reference_id'] = $data['reference_id'] ?? null;
106
+        return $data;
107
+    }
108
+
109
+
110
+    /**
111
+     * @param array $data
112
+     * @return IComment
113
+     */
114
+    public function getCommentFromData(array $data): IComment {
115
+        return new Comment($this->normalizeDatabaseData($data));
116
+    }
117
+
118
+    /**
119
+     * prepares a comment for an insert or update operation after making sure
120
+     * all necessary fields have a value assigned.
121
+     *
122
+     * @param IComment $comment
123
+     * @return IComment returns the same updated IComment instance as provided
124
+     *                  by parameter for convenience
125
+     * @throws \UnexpectedValueException
126
+     */
127
+    protected function prepareCommentForDatabaseWrite(IComment $comment) {
128
+        if (!$comment->getActorType()
129
+            || $comment->getActorId() === ''
130
+            || !$comment->getObjectType()
131
+            || $comment->getObjectId() === ''
132
+            || !$comment->getVerb()
133
+        ) {
134
+            throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving');
135
+        }
136
+
137
+        if ($comment->getId() === '') {
138
+            $comment->setChildrenCount(0);
139
+            $comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC')));
140
+            $comment->setLatestChildDateTime(null);
141
+        }
142
+
143
+        if (is_null($comment->getCreationDateTime())) {
144
+            $comment->setCreationDateTime(new \DateTime());
145
+        }
146
+
147
+        if ($comment->getParentId() !== '0') {
148
+            $comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId()));
149
+        } else {
150
+            $comment->setTopmostParentId('0');
151
+        }
152
+
153
+        $this->cache($comment);
154
+
155
+        return $comment;
156
+    }
157
+
158
+    /**
159
+     * returns the topmost parent id of a given comment identified by ID
160
+     *
161
+     * @param string $id
162
+     * @return string
163
+     * @throws NotFoundException
164
+     */
165
+    protected function determineTopmostParentId($id) {
166
+        $comment = $this->get($id);
167
+        if ($comment->getParentId() === '0') {
168
+            return $comment->getId();
169
+        }
170
+
171
+        return $this->determineTopmostParentId($comment->getParentId());
172
+    }
173
+
174
+    /**
175
+     * updates child information of a comment
176
+     *
177
+     * @param string $id
178
+     * @param \DateTime $cDateTime the date time of the most recent child
179
+     * @throws NotFoundException
180
+     */
181
+    protected function updateChildrenInformation($id, \DateTime $cDateTime) {
182
+        $qb = $this->dbConn->getQueryBuilder();
183
+        $query = $qb->select($qb->func()->count('id'))
184
+            ->from('comments')
185
+            ->where($qb->expr()->eq('parent_id', $qb->createParameter('id')))
186
+            ->setParameter('id', $id);
187
+
188
+        $resultStatement = $query->execute();
189
+        $data = $resultStatement->fetch(\PDO::FETCH_NUM);
190
+        $resultStatement->closeCursor();
191
+        $children = (int)$data[0];
192
+
193
+        $comment = $this->get($id);
194
+        $comment->setChildrenCount($children);
195
+        $comment->setLatestChildDateTime($cDateTime);
196
+        $this->save($comment);
197
+    }
198
+
199
+    /**
200
+     * Tests whether actor or object type and id parameters are acceptable.
201
+     * Throws exception if not.
202
+     *
203
+     * @param string $role
204
+     * @param string $type
205
+     * @param string $id
206
+     * @throws \InvalidArgumentException
207
+     */
208
+    protected function checkRoleParameters($role, $type, $id) {
209
+        if (
210
+            !is_string($type) || empty($type)
211
+            || !is_string($id) || empty($id)
212
+        ) {
213
+            throw new \InvalidArgumentException($role . ' parameters must be string and not empty');
214
+        }
215
+    }
216
+
217
+    /**
218
+     * run-time caches a comment
219
+     *
220
+     * @param IComment $comment
221
+     */
222
+    protected function cache(IComment $comment) {
223
+        $id = $comment->getId();
224
+        if (empty($id)) {
225
+            return;
226
+        }
227
+        $this->commentsCache[(string)$id] = $comment;
228
+    }
229
+
230
+    /**
231
+     * removes an entry from the comments run time cache
232
+     *
233
+     * @param mixed $id the comment's id
234
+     */
235
+    protected function uncache($id) {
236
+        $id = (string)$id;
237
+        if (isset($this->commentsCache[$id])) {
238
+            unset($this->commentsCache[$id]);
239
+        }
240
+    }
241
+
242
+    /**
243
+     * returns a comment instance
244
+     *
245
+     * @param string $id the ID of the comment
246
+     * @return IComment
247
+     * @throws NotFoundException
248
+     * @throws \InvalidArgumentException
249
+     * @since 9.0.0
250
+     */
251
+    public function get($id) {
252
+        if ((int)$id === 0) {
253
+            throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.');
254
+        }
255
+
256
+        if (isset($this->commentsCache[$id])) {
257
+            return $this->commentsCache[$id];
258
+        }
259
+
260
+        $qb = $this->dbConn->getQueryBuilder();
261
+        $resultStatement = $qb->select('*')
262
+            ->from('comments')
263
+            ->where($qb->expr()->eq('id', $qb->createParameter('id')))
264
+            ->setParameter('id', $id, IQueryBuilder::PARAM_INT)
265
+            ->execute();
266
+
267
+        $data = $resultStatement->fetch();
268
+        $resultStatement->closeCursor();
269
+        if (!$data) {
270
+            throw new NotFoundException();
271
+        }
272
+
273
+
274
+        $comment = $this->getCommentFromData($data);
275
+        $this->cache($comment);
276
+        return $comment;
277
+    }
278
+
279
+    /**
280
+     * returns the comment specified by the id and all it's child comments.
281
+     * At this point of time, we do only support one level depth.
282
+     *
283
+     * @param string $id
284
+     * @param int $limit max number of entries to return, 0 returns all
285
+     * @param int $offset the start entry
286
+     * @return array
287
+     * @since 9.0.0
288
+     *
289
+     * The return array looks like this
290
+     * [
291
+     *   'comment' => IComment, // root comment
292
+     *   'replies' =>
293
+     *   [
294
+     *     0 =>
295
+     *     [
296
+     *       'comment' => IComment,
297
+     *       'replies' => []
298
+     *     ]
299
+     *     1 =>
300
+     *     [
301
+     *       'comment' => IComment,
302
+     *       'replies'=> []
303
+     *     ],
304
+     *     …
305
+     *   ]
306
+     * ]
307
+     */
308
+    public function getTree($id, $limit = 0, $offset = 0) {
309
+        $tree = [];
310
+        $tree['comment'] = $this->get($id);
311
+        $tree['replies'] = [];
312
+
313
+        $qb = $this->dbConn->getQueryBuilder();
314
+        $query = $qb->select('*')
315
+            ->from('comments')
316
+            ->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id')))
317
+            ->orderBy('creation_timestamp', 'DESC')
318
+            ->setParameter('id', $id);
319
+
320
+        if ($limit > 0) {
321
+            $query->setMaxResults($limit);
322
+        }
323
+        if ($offset > 0) {
324
+            $query->setFirstResult($offset);
325
+        }
326
+
327
+        $resultStatement = $query->execute();
328
+        while ($data = $resultStatement->fetch()) {
329
+            $comment = $this->getCommentFromData($data);
330
+            $this->cache($comment);
331
+            $tree['replies'][] = [
332
+                'comment' => $comment,
333
+                'replies' => []
334
+            ];
335
+        }
336
+        $resultStatement->closeCursor();
337
+
338
+        return $tree;
339
+    }
340
+
341
+    /**
342
+     * returns comments for a specific object (e.g. a file).
343
+     *
344
+     * The sort order is always newest to oldest.
345
+     *
346
+     * @param string $objectType the object type, e.g. 'files'
347
+     * @param string $objectId the id of the object
348
+     * @param int $limit optional, number of maximum comments to be returned. if
349
+     * not specified, all comments are returned.
350
+     * @param int $offset optional, starting point
351
+     * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
352
+     * that may be returned
353
+     * @return IComment[]
354
+     * @since 9.0.0
355
+     */
356
+    public function getForObject(
357
+        $objectType,
358
+        $objectId,
359
+        $limit = 0,
360
+        $offset = 0,
361
+        \DateTime $notOlderThan = null
362
+    ) {
363
+        $comments = [];
364
+
365
+        $qb = $this->dbConn->getQueryBuilder();
366
+        $query = $qb->select('*')
367
+            ->from('comments')
368
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
369
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
370
+            ->orderBy('creation_timestamp', 'DESC')
371
+            ->setParameter('type', $objectType)
372
+            ->setParameter('id', $objectId);
373
+
374
+        if ($limit > 0) {
375
+            $query->setMaxResults($limit);
376
+        }
377
+        if ($offset > 0) {
378
+            $query->setFirstResult($offset);
379
+        }
380
+        if (!is_null($notOlderThan)) {
381
+            $query
382
+                ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
383
+                ->setParameter('notOlderThan', $notOlderThan, 'datetime');
384
+        }
385
+
386
+        $resultStatement = $query->execute();
387
+        while ($data = $resultStatement->fetch()) {
388
+            $comment = $this->getCommentFromData($data);
389
+            $this->cache($comment);
390
+            $comments[] = $comment;
391
+        }
392
+        $resultStatement->closeCursor();
393
+
394
+        return $comments;
395
+    }
396
+
397
+    /**
398
+     * @param string $objectType the object type, e.g. 'files'
399
+     * @param string $objectId the id of the object
400
+     * @param int $lastKnownCommentId the last known comment (will be used as offset)
401
+     * @param string $sortDirection direction of the comments (`asc` or `desc`)
402
+     * @param int $limit optional, number of maximum comments to be returned. if
403
+     * set to 0, all comments are returned.
404
+     * @param bool $includeLastKnown
405
+     * @return IComment[]
406
+     * @return array
407
+     */
408
+    public function getForObjectSince(
409
+        string $objectType,
410
+        string $objectId,
411
+        int $lastKnownCommentId,
412
+        string $sortDirection = 'asc',
413
+        int $limit = 30,
414
+        bool $includeLastKnown = false
415
+    ): array {
416
+        $comments = [];
417
+
418
+        $query = $this->dbConn->getQueryBuilder();
419
+        $query->select('*')
420
+            ->from('comments')
421
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
422
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
423
+            ->orderBy('creation_timestamp', $sortDirection === 'desc' ? 'DESC' : 'ASC')
424
+            ->addOrderBy('id', $sortDirection === 'desc' ? 'DESC' : 'ASC');
425
+
426
+        if ($limit > 0) {
427
+            $query->setMaxResults($limit);
428
+        }
429
+
430
+        $lastKnownComment = $lastKnownCommentId > 0 ? $this->getLastKnownComment(
431
+            $objectType,
432
+            $objectId,
433
+            $lastKnownCommentId
434
+        ) : null;
435
+        if ($lastKnownComment instanceof IComment) {
436
+            $lastKnownCommentDateTime = $lastKnownComment->getCreationDateTime();
437
+            if ($sortDirection === 'desc') {
438
+                if ($includeLastKnown) {
439
+                    $idComparison = $query->expr()->lte('id', $query->createNamedParameter($lastKnownCommentId));
440
+                } else {
441
+                    $idComparison = $query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId));
442
+                }
443
+                $query->andWhere(
444
+                    $query->expr()->orX(
445
+                        $query->expr()->lt(
446
+                            'creation_timestamp',
447
+                            $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
448
+                            IQueryBuilder::PARAM_DATE
449
+                        ),
450
+                        $query->expr()->andX(
451
+                            $query->expr()->eq(
452
+                                'creation_timestamp',
453
+                                $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
454
+                                IQueryBuilder::PARAM_DATE
455
+                            ),
456
+                            $idComparison
457
+                        )
458
+                    )
459
+                );
460
+            } else {
461
+                if ($includeLastKnown) {
462
+                    $idComparison = $query->expr()->gte('id', $query->createNamedParameter($lastKnownCommentId));
463
+                } else {
464
+                    $idComparison = $query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId));
465
+                }
466
+                $query->andWhere(
467
+                    $query->expr()->orX(
468
+                        $query->expr()->gt(
469
+                            'creation_timestamp',
470
+                            $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
471
+                            IQueryBuilder::PARAM_DATE
472
+                        ),
473
+                        $query->expr()->andX(
474
+                            $query->expr()->eq(
475
+                                'creation_timestamp',
476
+                                $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
477
+                                IQueryBuilder::PARAM_DATE
478
+                            ),
479
+                            $idComparison
480
+                        )
481
+                    )
482
+                );
483
+            }
484
+        }
485
+
486
+        $resultStatement = $query->execute();
487
+        while ($data = $resultStatement->fetch()) {
488
+            $comment = $this->getCommentFromData($data);
489
+            $this->cache($comment);
490
+            $comments[] = $comment;
491
+        }
492
+        $resultStatement->closeCursor();
493
+
494
+        return $comments;
495
+    }
496
+
497
+    /**
498
+     * @param string $objectType the object type, e.g. 'files'
499
+     * @param string $objectId the id of the object
500
+     * @param int $id the comment to look for
501
+     * @return Comment|null
502
+     */
503
+    protected function getLastKnownComment(string $objectType,
504
+                                            string $objectId,
505
+                                            int $id) {
506
+        $query = $this->dbConn->getQueryBuilder();
507
+        $query->select('*')
508
+            ->from('comments')
509
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
510
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
511
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
512
+
513
+        $result = $query->execute();
514
+        $row = $result->fetch();
515
+        $result->closeCursor();
516
+
517
+        if ($row) {
518
+            $comment = $this->getCommentFromData($row);
519
+            $this->cache($comment);
520
+            return $comment;
521
+        }
522
+
523
+        return null;
524
+    }
525
+
526
+    /**
527
+     * Search for comments with a given content
528
+     *
529
+     * @param string $search content to search for
530
+     * @param string $objectType Limit the search by object type
531
+     * @param string $objectId Limit the search by object id
532
+     * @param string $verb Limit the verb of the comment
533
+     * @param int $offset
534
+     * @param int $limit
535
+     * @return IComment[]
536
+     */
537
+    public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array {
538
+        $objectIds = [];
539
+        if ($objectId) {
540
+            $objectIds[] = $objectIds;
541
+        }
542
+        return $this->searchForObjects($search, $objectType, $objectIds, $verb, $offset, $limit);
543
+    }
544
+
545
+    /**
546
+     * Search for comments on one or more objects with a given content
547
+     *
548
+     * @param string $search content to search for
549
+     * @param string $objectType Limit the search by object type
550
+     * @param array $objectIds Limit the search by object ids
551
+     * @param string $verb Limit the verb of the comment
552
+     * @param int $offset
553
+     * @param int $limit
554
+     * @return IComment[]
555
+     */
556
+    public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array {
557
+        $query = $this->dbConn->getQueryBuilder();
558
+
559
+        $query->select('*')
560
+            ->from('comments')
561
+            ->where($query->expr()->iLike('message', $query->createNamedParameter(
562
+                '%' . $this->dbConn->escapeLikeParameter($search). '%'
563
+            )))
564
+            ->orderBy('creation_timestamp', 'DESC')
565
+            ->addOrderBy('id', 'DESC')
566
+            ->setMaxResults($limit);
567
+
568
+        if ($objectType !== '') {
569
+            $query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType)));
570
+        }
571
+        if (!empty($objectIds)) {
572
+            $query->andWhere($query->expr()->in('object_id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY)));
573
+        }
574
+        if ($verb !== '') {
575
+            $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
576
+        }
577
+        if ($offset !== 0) {
578
+            $query->setFirstResult($offset);
579
+        }
580
+
581
+        $comments = [];
582
+        $result = $query->execute();
583
+        while ($data = $result->fetch()) {
584
+            $comment = $this->getCommentFromData($data);
585
+            $this->cache($comment);
586
+            $comments[] = $comment;
587
+        }
588
+        $result->closeCursor();
589
+
590
+        return $comments;
591
+    }
592
+
593
+    /**
594
+     * @param $objectType string the object type, e.g. 'files'
595
+     * @param $objectId string the id of the object
596
+     * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
597
+     * that may be returned
598
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
599
+     * @return Int
600
+     * @since 9.0.0
601
+     */
602
+    public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '') {
603
+        $qb = $this->dbConn->getQueryBuilder();
604
+        $query = $qb->select($qb->func()->count('id'))
605
+            ->from('comments')
606
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
607
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
608
+            ->setParameter('type', $objectType)
609
+            ->setParameter('id', $objectId);
610
+
611
+        if (!is_null($notOlderThan)) {
612
+            $query
613
+                ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
614
+                ->setParameter('notOlderThan', $notOlderThan, 'datetime');
615
+        }
616
+
617
+        if ($verb !== '') {
618
+            $query->andWhere($qb->expr()->eq('verb', $qb->createNamedParameter($verb)));
619
+        }
620
+
621
+        $resultStatement = $query->execute();
622
+        $data = $resultStatement->fetch(\PDO::FETCH_NUM);
623
+        $resultStatement->closeCursor();
624
+        return (int)$data[0];
625
+    }
626
+
627
+    /**
628
+     * @param string $objectType the object type, e.g. 'files'
629
+     * @param string[] $objectIds the id of the object
630
+     * @param IUser $user
631
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
632
+     * @return array Map with object id => # of unread comments
633
+     * @psalm-return array<string, int>
634
+     * @since 21.0.0
635
+     */
636
+    public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array {
637
+        $query = $this->dbConn->getQueryBuilder();
638
+        $query->select('c.object_id', $query->func()->count('c.id', 'num_comments'))
639
+            ->from('comments', 'c')
640
+            ->leftJoin('c', 'comments_read_markers', 'm', $query->expr()->andX(
641
+                $query->expr()->eq('m.user_id', $query->createNamedParameter($user->getUID())),
642
+                $query->expr()->eq('c.object_type', 'm.object_type'),
643
+                $query->expr()->eq('c.object_id', 'm.object_id')
644
+            ))
645
+            ->where($query->expr()->eq('c.object_type', $query->createNamedParameter($objectType)))
646
+            ->andWhere($query->expr()->in('c.object_id', $query->createNamedParameter($objectIds, IQueryBuilder::PARAM_STR_ARRAY)))
647
+            ->andWhere($query->expr()->orX(
648
+                $query->expr()->gt('c.creation_timestamp', 'm.marker_datetime'),
649
+                $query->expr()->isNull('m.marker_datetime')
650
+            ))
651
+            ->groupBy('c.object_id');
652
+
653
+        if ($verb !== '') {
654
+            $query->andWhere($query->expr()->eq('c.verb', $query->createNamedParameter($verb)));
655
+        }
656
+
657
+        $result = $query->execute();
658
+        $unreadComments = array_fill_keys($objectIds, 0);
659
+        while ($row = $result->fetch()) {
660
+            $unreadComments[$row['object_id']] = (int) $row['num_comments'];
661
+        }
662
+        $result->closeCursor();
663
+
664
+        return $unreadComments;
665
+    }
666
+
667
+    /**
668
+     * @param string $objectType
669
+     * @param string $objectId
670
+     * @param int $lastRead
671
+     * @param string $verb
672
+     * @return int
673
+     * @since 21.0.0
674
+     */
675
+    public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int {
676
+        $query = $this->dbConn->getQueryBuilder();
677
+        $query->select($query->func()->count('id', 'num_messages'))
678
+            ->from('comments')
679
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
680
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
681
+            ->andWhere($query->expr()->gt('id', $query->createNamedParameter($lastRead)));
682
+
683
+        if ($verb !== '') {
684
+            $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
685
+        }
686
+
687
+        $result = $query->execute();
688
+        $data = $result->fetch();
689
+        $result->closeCursor();
690
+
691
+        return (int) ($data['num_messages'] ?? 0);
692
+    }
693
+
694
+    /**
695
+     * @param string $objectType
696
+     * @param string $objectId
697
+     * @param \DateTime $beforeDate
698
+     * @param string $verb
699
+     * @return int
700
+     * @since 21.0.0
701
+     */
702
+    public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int {
703
+        $query = $this->dbConn->getQueryBuilder();
704
+        $query->select('id')
705
+            ->from('comments')
706
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
707
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
708
+            ->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATE)))
709
+            ->orderBy('creation_timestamp', 'desc');
710
+
711
+        if ($verb !== '') {
712
+            $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
713
+        }
714
+
715
+        $result = $query->execute();
716
+        $data = $result->fetch();
717
+        $result->closeCursor();
718
+
719
+        return (int) ($data['id'] ?? 0);
720
+    }
721
+
722
+    /**
723
+     * @param string $objectType
724
+     * @param string $objectId
725
+     * @param string $verb
726
+     * @param string $actorType
727
+     * @param string[] $actors
728
+     * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
729
+     * @psalm-return array<string, \DateTime>
730
+     * @since 21.0.0
731
+     */
732
+    public function getLastCommentDateByActor(
733
+        string $objectType,
734
+        string $objectId,
735
+        string $verb,
736
+        string $actorType,
737
+        array $actors
738
+    ): array {
739
+        $lastComments = [];
740
+
741
+        $query = $this->dbConn->getQueryBuilder();
742
+        $query->select('actor_id')
743
+            ->selectAlias($query->createFunction('MAX(' . $query->getColumnName('creation_timestamp') . ')'), 'last_comment')
744
+            ->from('comments')
745
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
746
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
747
+            ->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)))
748
+            ->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
749
+            ->andWhere($query->expr()->in('actor_id', $query->createNamedParameter($actors, IQueryBuilder::PARAM_STR_ARRAY)))
750
+            ->groupBy('actor_id');
751
+
752
+        $result = $query->execute();
753
+        while ($row = $result->fetch()) {
754
+            $lastComments[$row['actor_id']] = $this->timeFactory->getDateTime($row['last_comment']);
755
+        }
756
+        $result->closeCursor();
757
+
758
+        return $lastComments;
759
+    }
760
+
761
+    /**
762
+     * Get the number of unread comments for all files in a folder
763
+     *
764
+     * @param int $folderId
765
+     * @param IUser $user
766
+     * @return array [$fileId => $unreadCount]
767
+     */
768
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) {
769
+        $qb = $this->dbConn->getQueryBuilder();
770
+
771
+        $query = $qb->select('f.fileid')
772
+            ->addSelect($qb->func()->count('c.id', 'num_ids'))
773
+            ->from('filecache', 'f')
774
+            ->leftJoin('f', 'comments', 'c', $qb->expr()->andX(
775
+                $qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT)),
776
+                $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files'))
777
+            ))
778
+            ->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX(
779
+                $qb->expr()->eq('c.object_id', 'm.object_id'),
780
+                $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files'))
781
+            ))
782
+            ->where(
783
+                $qb->expr()->andX(
784
+                    $qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)),
785
+                    $qb->expr()->orX(
786
+                        $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')),
787
+                        $qb->expr()->isNull('c.object_type')
788
+                    ),
789
+                    $qb->expr()->orX(
790
+                        $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')),
791
+                        $qb->expr()->isNull('m.object_type')
792
+                    ),
793
+                    $qb->expr()->orX(
794
+                        $qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID())),
795
+                        $qb->expr()->isNull('m.user_id')
796
+                    ),
797
+                    $qb->expr()->orX(
798
+                        $qb->expr()->gt('c.creation_timestamp', 'm.marker_datetime'),
799
+                        $qb->expr()->isNull('m.marker_datetime')
800
+                    )
801
+                )
802
+            )->groupBy('f.fileid');
803
+
804
+        $resultStatement = $query->execute();
805
+
806
+        $results = [];
807
+        while ($row = $resultStatement->fetch()) {
808
+            $results[$row['fileid']] = (int) $row['num_ids'];
809
+        }
810
+        $resultStatement->closeCursor();
811
+        return $results;
812
+    }
813
+
814
+    /**
815
+     * creates a new comment and returns it. At this point of time, it is not
816
+     * saved in the used data storage. Use save() after setting other fields
817
+     * of the comment (e.g. message or verb).
818
+     *
819
+     * @param string $actorType the actor type (e.g. 'users')
820
+     * @param string $actorId a user id
821
+     * @param string $objectType the object type the comment is attached to
822
+     * @param string $objectId the object id the comment is attached to
823
+     * @return IComment
824
+     * @since 9.0.0
825
+     */
826
+    public function create($actorType, $actorId, $objectType, $objectId) {
827
+        $comment = new Comment();
828
+        $comment
829
+            ->setActor($actorType, $actorId)
830
+            ->setObject($objectType, $objectId);
831
+        return $comment;
832
+    }
833
+
834
+    /**
835
+     * permanently deletes the comment specified by the ID
836
+     *
837
+     * When the comment has child comments, their parent ID will be changed to
838
+     * the parent ID of the item that is to be deleted.
839
+     *
840
+     * @param string $id
841
+     * @return bool
842
+     * @throws \InvalidArgumentException
843
+     * @since 9.0.0
844
+     */
845
+    public function delete($id) {
846
+        if (!is_string($id)) {
847
+            throw new \InvalidArgumentException('Parameter must be string');
848
+        }
849
+
850
+        try {
851
+            $comment = $this->get($id);
852
+        } catch (\Exception $e) {
853
+            // Ignore exceptions, we just don't fire a hook then
854
+            $comment = null;
855
+        }
856
+
857
+        $qb = $this->dbConn->getQueryBuilder();
858
+        $query = $qb->delete('comments')
859
+            ->where($qb->expr()->eq('id', $qb->createParameter('id')))
860
+            ->setParameter('id', $id);
861
+
862
+        try {
863
+            $affectedRows = $query->execute();
864
+            $this->uncache($id);
865
+        } catch (DriverException $e) {
866
+            $this->logger->error($e->getMessage(), [
867
+                'exception' => $e,
868
+                'app' => 'core_comments',
869
+            ]);
870
+            return false;
871
+        }
872
+
873
+        if ($affectedRows > 0 && $comment instanceof IComment) {
874
+            $this->sendEvent(CommentsEvent::EVENT_DELETE, $comment);
875
+        }
876
+
877
+        return ($affectedRows > 0);
878
+    }
879
+
880
+    /**
881
+     * saves the comment permanently
882
+     *
883
+     * if the supplied comment has an empty ID, a new entry comment will be
884
+     * saved and the instance updated with the new ID.
885
+     *
886
+     * Otherwise, an existing comment will be updated.
887
+     *
888
+     * Throws NotFoundException when a comment that is to be updated does not
889
+     * exist anymore at this point of time.
890
+     *
891
+     * @param IComment $comment
892
+     * @return bool
893
+     * @throws NotFoundException
894
+     * @since 9.0.0
895
+     */
896
+    public function save(IComment $comment) {
897
+        if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') {
898
+            $result = $this->insert($comment);
899
+        } else {
900
+            $result = $this->update($comment);
901
+        }
902
+
903
+        if ($result && !!$comment->getParentId()) {
904
+            $this->updateChildrenInformation(
905
+                $comment->getParentId(),
906
+                $comment->getCreationDateTime()
907
+            );
908
+            $this->cache($comment);
909
+        }
910
+
911
+        return $result;
912
+    }
913
+
914
+    /**
915
+     * inserts the provided comment in the database
916
+     *
917
+     * @param IComment $comment
918
+     * @return bool
919
+     */
920
+    protected function insert(IComment $comment): bool {
921
+        try {
922
+            $result = $this->insertQuery($comment, true);
923
+        } catch (InvalidFieldNameException $e) {
924
+            // The reference id field was only added in Nextcloud 19.
925
+            // In order to not cause too long waiting times on the update,
926
+            // it was decided to only add it lazy, as it is also not a critical
927
+            // feature, but only helps to have a better experience while commenting.
928
+            // So in case the reference_id field is missing,
929
+            // we simply save the comment without that field.
930
+            $result = $this->insertQuery($comment, false);
931
+        }
932
+
933
+        return $result;
934
+    }
935
+
936
+    protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool {
937
+        $qb = $this->dbConn->getQueryBuilder();
938
+
939
+        $values = [
940
+            'parent_id' => $qb->createNamedParameter($comment->getParentId()),
941
+            'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
942
+            'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
943
+            'actor_type' => $qb->createNamedParameter($comment->getActorType()),
944
+            'actor_id' => $qb->createNamedParameter($comment->getActorId()),
945
+            'message' => $qb->createNamedParameter($comment->getMessage()),
946
+            'verb' => $qb->createNamedParameter($comment->getVerb()),
947
+            'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
948
+            'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
949
+            'object_type' => $qb->createNamedParameter($comment->getObjectType()),
950
+            'object_id' => $qb->createNamedParameter($comment->getObjectId()),
951
+        ];
952
+
953
+        if ($tryWritingReferenceId) {
954
+            $values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId());
955
+        }
956
+
957
+        $affectedRows = $qb->insert('comments')
958
+            ->values($values)
959
+            ->execute();
960
+
961
+        if ($affectedRows > 0) {
962
+            $comment->setId((string)$qb->getLastInsertId());
963
+            $this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
964
+        }
965
+
966
+        return $affectedRows > 0;
967
+    }
968
+
969
+    /**
970
+     * updates a Comment data row
971
+     *
972
+     * @param IComment $comment
973
+     * @return bool
974
+     * @throws NotFoundException
975
+     */
976
+    protected function update(IComment $comment) {
977
+        // for properly working preUpdate Events we need the old comments as is
978
+        // in the DB and overcome caching. Also avoid that outdated information stays.
979
+        $this->uncache($comment->getId());
980
+        $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
981
+        $this->uncache($comment->getId());
982
+
983
+        try {
984
+            $result = $this->updateQuery($comment, true);
985
+        } catch (InvalidFieldNameException $e) {
986
+            // See function insert() for explanation
987
+            $result = $this->updateQuery($comment, false);
988
+        }
989
+
990
+        $this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment);
991
+
992
+        return $result;
993
+    }
994
+
995
+    protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool {
996
+        $qb = $this->dbConn->getQueryBuilder();
997
+        $qb
998
+            ->update('comments')
999
+            ->set('parent_id', $qb->createNamedParameter($comment->getParentId()))
1000
+            ->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId()))
1001
+            ->set('children_count', $qb->createNamedParameter($comment->getChildrenCount()))
1002
+            ->set('actor_type', $qb->createNamedParameter($comment->getActorType()))
1003
+            ->set('actor_id', $qb->createNamedParameter($comment->getActorId()))
1004
+            ->set('message', $qb->createNamedParameter($comment->getMessage()))
1005
+            ->set('verb', $qb->createNamedParameter($comment->getVerb()))
1006
+            ->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'))
1007
+            ->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
1008
+            ->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
1009
+            ->set('object_id', $qb->createNamedParameter($comment->getObjectId()));
1010
+
1011
+        if ($tryWritingReferenceId) {
1012
+            $qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()));
1013
+        }
1014
+
1015
+        $affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())))
1016
+            ->execute();
1017
+
1018
+        if ($affectedRows === 0) {
1019
+            throw new NotFoundException('Comment to update does ceased to exist');
1020
+        }
1021
+
1022
+        return $affectedRows > 0;
1023
+    }
1024
+
1025
+    /**
1026
+     * removes references to specific actor (e.g. on user delete) of a comment.
1027
+     * The comment itself must not get lost/deleted.
1028
+     *
1029
+     * @param string $actorType the actor type (e.g. 'users')
1030
+     * @param string $actorId a user id
1031
+     * @return boolean
1032
+     * @since 9.0.0
1033
+     */
1034
+    public function deleteReferencesOfActor($actorType, $actorId) {
1035
+        $this->checkRoleParameters('Actor', $actorType, $actorId);
1036
+
1037
+        $qb = $this->dbConn->getQueryBuilder();
1038
+        $affectedRows = $qb
1039
+            ->update('comments')
1040
+            ->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1041
+            ->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1042
+            ->where($qb->expr()->eq('actor_type', $qb->createParameter('type')))
1043
+            ->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id')))
1044
+            ->setParameter('type', $actorType)
1045
+            ->setParameter('id', $actorId)
1046
+            ->execute();
1047
+
1048
+        $this->commentsCache = [];
1049
+
1050
+        return is_int($affectedRows);
1051
+    }
1052
+
1053
+    /**
1054
+     * deletes all comments made of a specific object (e.g. on file delete)
1055
+     *
1056
+     * @param string $objectType the object type (e.g. 'files')
1057
+     * @param string $objectId e.g. the file id
1058
+     * @return boolean
1059
+     * @since 9.0.0
1060
+     */
1061
+    public function deleteCommentsAtObject($objectType, $objectId) {
1062
+        $this->checkRoleParameters('Object', $objectType, $objectId);
1063
+
1064
+        $qb = $this->dbConn->getQueryBuilder();
1065
+        $affectedRows = $qb
1066
+            ->delete('comments')
1067
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
1068
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
1069
+            ->setParameter('type', $objectType)
1070
+            ->setParameter('id', $objectId)
1071
+            ->execute();
1072
+
1073
+        $this->commentsCache = [];
1074
+
1075
+        return is_int($affectedRows);
1076
+    }
1077
+
1078
+    /**
1079
+     * deletes the read markers for the specified user
1080
+     *
1081
+     * @param \OCP\IUser $user
1082
+     * @return bool
1083
+     * @since 9.0.0
1084
+     */
1085
+    public function deleteReadMarksFromUser(IUser $user) {
1086
+        $qb = $this->dbConn->getQueryBuilder();
1087
+        $query = $qb->delete('comments_read_markers')
1088
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1089
+            ->setParameter('user_id', $user->getUID());
1090
+
1091
+        try {
1092
+            $affectedRows = $query->execute();
1093
+        } catch (DriverException $e) {
1094
+            $this->logger->error($e->getMessage(), [
1095
+                'exception' => $e,
1096
+                'app' => 'core_comments',
1097
+            ]);
1098
+            return false;
1099
+        }
1100
+        return ($affectedRows > 0);
1101
+    }
1102
+
1103
+    /**
1104
+     * sets the read marker for a given file to the specified date for the
1105
+     * provided user
1106
+     *
1107
+     * @param string $objectType
1108
+     * @param string $objectId
1109
+     * @param \DateTime $dateTime
1110
+     * @param IUser $user
1111
+     * @since 9.0.0
1112
+     */
1113
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) {
1114
+        $this->checkRoleParameters('Object', $objectType, $objectId);
1115
+
1116
+        $qb = $this->dbConn->getQueryBuilder();
1117
+        $values = [
1118
+            'user_id' => $qb->createNamedParameter($user->getUID()),
1119
+            'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'),
1120
+            'object_type' => $qb->createNamedParameter($objectType),
1121
+            'object_id' => $qb->createNamedParameter($objectId),
1122
+        ];
1123
+
1124
+        // Strategy: try to update, if this does not return affected rows, do an insert.
1125
+        $affectedRows = $qb
1126
+            ->update('comments_read_markers')
1127
+            ->set('user_id', $values['user_id'])
1128
+            ->set('marker_datetime', $values['marker_datetime'])
1129
+            ->set('object_type', $values['object_type'])
1130
+            ->set('object_id', $values['object_id'])
1131
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1132
+            ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1133
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1134
+            ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1135
+            ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1136
+            ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1137
+            ->execute();
1138
+
1139
+        if ($affectedRows > 0) {
1140
+            return;
1141
+        }
1142
+
1143
+        $qb->insert('comments_read_markers')
1144
+            ->values($values)
1145
+            ->execute();
1146
+    }
1147
+
1148
+    /**
1149
+     * returns the read marker for a given file to the specified date for the
1150
+     * provided user. It returns null, when the marker is not present, i.e.
1151
+     * no comments were marked as read.
1152
+     *
1153
+     * @param string $objectType
1154
+     * @param string $objectId
1155
+     * @param IUser $user
1156
+     * @return \DateTime|null
1157
+     * @since 9.0.0
1158
+     */
1159
+    public function getReadMark($objectType, $objectId, IUser $user) {
1160
+        $qb = $this->dbConn->getQueryBuilder();
1161
+        $resultStatement = $qb->select('marker_datetime')
1162
+            ->from('comments_read_markers')
1163
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1164
+            ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1165
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1166
+            ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1167
+            ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1168
+            ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1169
+            ->execute();
1170
+
1171
+        $data = $resultStatement->fetch();
1172
+        $resultStatement->closeCursor();
1173
+        if (!$data || is_null($data['marker_datetime'])) {
1174
+            return null;
1175
+        }
1176
+
1177
+        return new \DateTime($data['marker_datetime']);
1178
+    }
1179
+
1180
+    /**
1181
+     * deletes the read markers on the specified object
1182
+     *
1183
+     * @param string $objectType
1184
+     * @param string $objectId
1185
+     * @return bool
1186
+     * @since 9.0.0
1187
+     */
1188
+    public function deleteReadMarksOnObject($objectType, $objectId) {
1189
+        $this->checkRoleParameters('Object', $objectType, $objectId);
1190
+
1191
+        $qb = $this->dbConn->getQueryBuilder();
1192
+        $query = $qb->delete('comments_read_markers')
1193
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1194
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1195
+            ->setParameter('object_type', $objectType)
1196
+            ->setParameter('object_id', $objectId);
1197
+
1198
+        try {
1199
+            $affectedRows = $query->execute();
1200
+        } catch (DriverException $e) {
1201
+            $this->logger->error($e->getMessage(), [
1202
+                'exception' => $e,
1203
+                'app' => 'core_comments',
1204
+            ]);
1205
+            return false;
1206
+        }
1207
+        return ($affectedRows > 0);
1208
+    }
1209
+
1210
+    /**
1211
+     * registers an Entity to the manager, so event notifications can be send
1212
+     * to consumers of the comments infrastructure
1213
+     *
1214
+     * @param \Closure $closure
1215
+     */
1216
+    public function registerEventHandler(\Closure $closure) {
1217
+        $this->eventHandlerClosures[] = $closure;
1218
+        $this->eventHandlers = [];
1219
+    }
1220
+
1221
+    /**
1222
+     * registers a method that resolves an ID to a display name for a given type
1223
+     *
1224
+     * @param string $type
1225
+     * @param \Closure $closure
1226
+     * @throws \OutOfBoundsException
1227
+     * @since 11.0.0
1228
+     *
1229
+     * Only one resolver shall be registered per type. Otherwise a
1230
+     * \OutOfBoundsException has to thrown.
1231
+     */
1232
+    public function registerDisplayNameResolver($type, \Closure $closure) {
1233
+        if (!is_string($type)) {
1234
+            throw new \InvalidArgumentException('String expected.');
1235
+        }
1236
+        if (isset($this->displayNameResolvers[$type])) {
1237
+            throw new \OutOfBoundsException('Displayname resolver for this type already registered');
1238
+        }
1239
+        $this->displayNameResolvers[$type] = $closure;
1240
+    }
1241
+
1242
+    /**
1243
+     * resolves a given ID of a given Type to a display name.
1244
+     *
1245
+     * @param string $type
1246
+     * @param string $id
1247
+     * @return string
1248
+     * @throws \OutOfBoundsException
1249
+     * @since 11.0.0
1250
+     *
1251
+     * If a provided type was not registered, an \OutOfBoundsException shall
1252
+     * be thrown. It is upon the resolver discretion what to return of the
1253
+     * provided ID is unknown. It must be ensured that a string is returned.
1254
+     */
1255
+    public function resolveDisplayName($type, $id) {
1256
+        if (!is_string($type)) {
1257
+            throw new \InvalidArgumentException('String expected.');
1258
+        }
1259
+        if (!isset($this->displayNameResolvers[$type])) {
1260
+            throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1261
+        }
1262
+        return (string)$this->displayNameResolvers[$type]($id);
1263
+    }
1264
+
1265
+    /**
1266
+     * returns valid, registered entities
1267
+     *
1268
+     * @return \OCP\Comments\ICommentsEventHandler[]
1269
+     */
1270
+    private function getEventHandlers() {
1271
+        if (!empty($this->eventHandlers)) {
1272
+            return $this->eventHandlers;
1273
+        }
1274
+
1275
+        $this->eventHandlers = [];
1276
+        foreach ($this->eventHandlerClosures as $name => $closure) {
1277
+            $entity = $closure();
1278
+            if (!($entity instanceof ICommentsEventHandler)) {
1279
+                throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface');
1280
+            }
1281
+            $this->eventHandlers[$name] = $entity;
1282
+        }
1283
+
1284
+        return $this->eventHandlers;
1285
+    }
1286
+
1287
+    /**
1288
+     * sends notifications to the registered entities
1289
+     *
1290
+     * @param $eventType
1291
+     * @param IComment $comment
1292
+     */
1293
+    private function sendEvent($eventType, IComment $comment) {
1294
+        $entities = $this->getEventHandlers();
1295
+        $event = new CommentsEvent($eventType, $comment);
1296
+        foreach ($entities as $entity) {
1297
+            $entity->handle($event);
1298
+        }
1299
+    }
1300
+
1301
+    /**
1302
+     * Load the Comments app into the page
1303
+     *
1304
+     * @since 21.0.0
1305
+     */
1306
+    public function load(): void {
1307
+        $this->initialStateService->provideInitialState(Application::APP_ID, 'max-message-length', IComment::MAX_MESSAGE_LENGTH);
1308
+        Util::addScript(Application::APP_ID, 'comments-app');
1309
+    }
1310 1310
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/CommentPropertiesPlugin.php 2 patches
Indentation   +136 added lines, -136 removed lines patch added patch discarded remove patch
@@ -32,140 +32,140 @@
 block discarded – undo
32 32
 use Sabre\DAV\ServerPlugin;
33 33
 
34 34
 class CommentPropertiesPlugin extends ServerPlugin {
35
-	public const PROPERTY_NAME_HREF = '{http://owncloud.org/ns}comments-href';
36
-	public const PROPERTY_NAME_COUNT = '{http://owncloud.org/ns}comments-count';
37
-	public const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread';
38
-
39
-	/** @var  \Sabre\DAV\Server */
40
-	protected $server;
41
-
42
-	/** @var ICommentsManager */
43
-	private $commentsManager;
44
-
45
-	/** @var IUserSession */
46
-	private $userSession;
47
-
48
-	private $cachedUnreadCount = [];
49
-
50
-	public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
51
-		$this->commentsManager = $commentsManager;
52
-		$this->userSession = $userSession;
53
-	}
54
-
55
-	/**
56
-	 * This initializes the plugin.
57
-	 *
58
-	 * This function is called by Sabre\DAV\Server, after
59
-	 * addPlugin is called.
60
-	 *
61
-	 * This method should set up the required event subscriptions.
62
-	 *
63
-	 * @param \Sabre\DAV\Server $server
64
-	 * @return void
65
-	 */
66
-	public function initialize(\Sabre\DAV\Server $server) {
67
-		$this->server = $server;
68
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
69
-	}
70
-
71
-	private function cacheDirectory(Directory $directory) {
72
-		$children = $directory->getChildren();
73
-
74
-		$ids = [];
75
-		foreach ($children as $child) {
76
-			if (!($child instanceof File || $child instanceof Directory)) {
77
-				continue;
78
-			}
79
-
80
-			$id = $child->getId();
81
-			if ($id === null) {
82
-				continue;
83
-			}
84
-
85
-			$ids[] = (string)$id;
86
-		}
87
-
88
-		$ids[] = (string) $directory->getId();
89
-		$unread = $this->commentsManager->getNumberOfUnreadCommentsForObjects('files', $ids, $this->userSession->getUser());
90
-
91
-		foreach ($unread as $id => $count) {
92
-			$this->cachedUnreadCount[(int)$id] = $count;
93
-		}
94
-	}
95
-
96
-	/**
97
-	 * Adds tags and favorites properties to the response,
98
-	 * if requested.
99
-	 *
100
-	 * @param PropFind $propFind
101
-	 * @param \Sabre\DAV\INode $node
102
-	 * @return void
103
-	 */
104
-	public function handleGetProperties(
105
-		PropFind $propFind,
106
-		\Sabre\DAV\INode $node
107
-	) {
108
-		if (!($node instanceof File) && !($node instanceof Directory)) {
109
-			return;
110
-		}
111
-
112
-		// need prefetch ?
113
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
114
-			&& $propFind->getDepth() !== 0
115
-			&& !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD))
116
-		) {
117
-			$this->cacheDirectory($node);
118
-		}
119
-
120
-		$propFind->handle(self::PROPERTY_NAME_COUNT, function () use ($node) {
121
-			return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
122
-		});
123
-
124
-		$propFind->handle(self::PROPERTY_NAME_HREF, function () use ($node) {
125
-			return $this->getCommentsLink($node);
126
-		});
127
-
128
-		$propFind->handle(self::PROPERTY_NAME_UNREAD, function () use ($node) {
129
-			if (isset($this->cachedUnreadCount[$node->getId()])) {
130
-				return $this->cachedUnreadCount[$node->getId()];
131
-			}
132
-			return $this->getUnreadCount($node);
133
-		});
134
-	}
135
-
136
-	/**
137
-	 * returns a reference to the comments node
138
-	 *
139
-	 * @param Node $node
140
-	 * @return mixed|string
141
-	 */
142
-	public function getCommentsLink(Node $node) {
143
-		$href = $this->server->getBaseUri();
144
-		$entryPoint = strpos($href, '/remote.php/');
145
-		if ($entryPoint === false) {
146
-			// in case we end up somewhere else, unexpectedly.
147
-			return null;
148
-		}
149
-		$commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
150
-		$href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
151
-		return $href;
152
-	}
153
-
154
-	/**
155
-	 * returns the number of unread comments for the currently logged in user
156
-	 * on the given file or directory node
157
-	 *
158
-	 * @param Node $node
159
-	 * @return Int|null
160
-	 */
161
-	public function getUnreadCount(Node $node) {
162
-		$user = $this->userSession->getUser();
163
-		if (is_null($user)) {
164
-			return null;
165
-		}
166
-
167
-		$lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
168
-
169
-		return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
170
-	}
35
+    public const PROPERTY_NAME_HREF = '{http://owncloud.org/ns}comments-href';
36
+    public const PROPERTY_NAME_COUNT = '{http://owncloud.org/ns}comments-count';
37
+    public const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread';
38
+
39
+    /** @var  \Sabre\DAV\Server */
40
+    protected $server;
41
+
42
+    /** @var ICommentsManager */
43
+    private $commentsManager;
44
+
45
+    /** @var IUserSession */
46
+    private $userSession;
47
+
48
+    private $cachedUnreadCount = [];
49
+
50
+    public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) {
51
+        $this->commentsManager = $commentsManager;
52
+        $this->userSession = $userSession;
53
+    }
54
+
55
+    /**
56
+     * This initializes the plugin.
57
+     *
58
+     * This function is called by Sabre\DAV\Server, after
59
+     * addPlugin is called.
60
+     *
61
+     * This method should set up the required event subscriptions.
62
+     *
63
+     * @param \Sabre\DAV\Server $server
64
+     * @return void
65
+     */
66
+    public function initialize(\Sabre\DAV\Server $server) {
67
+        $this->server = $server;
68
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
69
+    }
70
+
71
+    private function cacheDirectory(Directory $directory) {
72
+        $children = $directory->getChildren();
73
+
74
+        $ids = [];
75
+        foreach ($children as $child) {
76
+            if (!($child instanceof File || $child instanceof Directory)) {
77
+                continue;
78
+            }
79
+
80
+            $id = $child->getId();
81
+            if ($id === null) {
82
+                continue;
83
+            }
84
+
85
+            $ids[] = (string)$id;
86
+        }
87
+
88
+        $ids[] = (string) $directory->getId();
89
+        $unread = $this->commentsManager->getNumberOfUnreadCommentsForObjects('files', $ids, $this->userSession->getUser());
90
+
91
+        foreach ($unread as $id => $count) {
92
+            $this->cachedUnreadCount[(int)$id] = $count;
93
+        }
94
+    }
95
+
96
+    /**
97
+     * Adds tags and favorites properties to the response,
98
+     * if requested.
99
+     *
100
+     * @param PropFind $propFind
101
+     * @param \Sabre\DAV\INode $node
102
+     * @return void
103
+     */
104
+    public function handleGetProperties(
105
+        PropFind $propFind,
106
+        \Sabre\DAV\INode $node
107
+    ) {
108
+        if (!($node instanceof File) && !($node instanceof Directory)) {
109
+            return;
110
+        }
111
+
112
+        // need prefetch ?
113
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
114
+            && $propFind->getDepth() !== 0
115
+            && !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD))
116
+        ) {
117
+            $this->cacheDirectory($node);
118
+        }
119
+
120
+        $propFind->handle(self::PROPERTY_NAME_COUNT, function () use ($node) {
121
+            return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
122
+        });
123
+
124
+        $propFind->handle(self::PROPERTY_NAME_HREF, function () use ($node) {
125
+            return $this->getCommentsLink($node);
126
+        });
127
+
128
+        $propFind->handle(self::PROPERTY_NAME_UNREAD, function () use ($node) {
129
+            if (isset($this->cachedUnreadCount[$node->getId()])) {
130
+                return $this->cachedUnreadCount[$node->getId()];
131
+            }
132
+            return $this->getUnreadCount($node);
133
+        });
134
+    }
135
+
136
+    /**
137
+     * returns a reference to the comments node
138
+     *
139
+     * @param Node $node
140
+     * @return mixed|string
141
+     */
142
+    public function getCommentsLink(Node $node) {
143
+        $href = $this->server->getBaseUri();
144
+        $entryPoint = strpos($href, '/remote.php/');
145
+        if ($entryPoint === false) {
146
+            // in case we end up somewhere else, unexpectedly.
147
+            return null;
148
+        }
149
+        $commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
150
+        $href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
151
+        return $href;
152
+    }
153
+
154
+    /**
155
+     * returns the number of unread comments for the currently logged in user
156
+     * on the given file or directory node
157
+     *
158
+     * @param Node $node
159
+     * @return Int|null
160
+     */
161
+    public function getUnreadCount(Node $node) {
162
+        $user = $this->userSession->getUser();
163
+        if (is_null($user)) {
164
+            return null;
165
+        }
166
+
167
+        $lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
168
+
169
+        return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
170
+    }
171 171
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -82,14 +82,14 @@  discard block
 block discarded – undo
82 82
 				continue;
83 83
 			}
84 84
 
85
-			$ids[] = (string)$id;
85
+			$ids[] = (string) $id;
86 86
 		}
87 87
 
88 88
 		$ids[] = (string) $directory->getId();
89 89
 		$unread = $this->commentsManager->getNumberOfUnreadCommentsForObjects('files', $ids, $this->userSession->getUser());
90 90
 
91 91
 		foreach ($unread as $id => $count) {
92
-			$this->cachedUnreadCount[(int)$id] = $count;
92
+			$this->cachedUnreadCount[(int) $id] = $count;
93 93
 		}
94 94
 	}
95 95
 
@@ -117,15 +117,15 @@  discard block
 block discarded – undo
117 117
 			$this->cacheDirectory($node);
118 118
 		}
119 119
 
120
-		$propFind->handle(self::PROPERTY_NAME_COUNT, function () use ($node) {
121
-			return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId());
120
+		$propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) {
121
+			return $this->commentsManager->getNumberOfCommentsForObject('files', (string) $node->getId());
122 122
 		});
123 123
 
124
-		$propFind->handle(self::PROPERTY_NAME_HREF, function () use ($node) {
124
+		$propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) {
125 125
 			return $this->getCommentsLink($node);
126 126
 		});
127 127
 
128
-		$propFind->handle(self::PROPERTY_NAME_UNREAD, function () use ($node) {
128
+		$propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) {
129 129
 			if (isset($this->cachedUnreadCount[$node->getId()])) {
130 130
 				return $this->cachedUnreadCount[$node->getId()];
131 131
 			}
@@ -146,7 +146,7 @@  discard block
 block discarded – undo
146 146
 			// in case we end up somewhere else, unexpectedly.
147 147
 			return null;
148 148
 		}
149
-		$commentsPart = 'dav/comments/files/' . rawurldecode($node->getId());
149
+		$commentsPart = 'dav/comments/files/'.rawurldecode($node->getId());
150 150
 		$href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/'));
151 151
 		return $href;
152 152
 	}
@@ -164,8 +164,8 @@  discard block
 block discarded – undo
164 164
 			return null;
165 165
 		}
166 166
 
167
-		$lastRead = $this->commentsManager->getReadMark('files', (string)$node->getId(), $user);
167
+		$lastRead = $this->commentsManager->getReadMark('files', (string) $node->getId(), $user);
168 168
 
169
-		return $this->commentsManager->getNumberOfCommentsForObject('files', (string)$node->getId(), $lastRead);
169
+		return $this->commentsManager->getNumberOfCommentsForObject('files', (string) $node->getId(), $lastRead);
170 170
 	}
171 171
 }
Please login to merge, or discard this patch.