Passed
Push — master ( 49854f...eadce7 )
by Roeland
42:39 queued 29:44
created
lib/private/Comments/Manager.php 2 patches
Indentation   +1220 added lines, -1220 removed lines patch added patch discarded remove patch
@@ -47,1224 +47,1224 @@
 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
629
-	 * @param string $objectId
630
-	 * @param int $lastRead
631
-	 * @param string $verb
632
-	 * @return int
633
-	 * @since 21.0.0
634
-	 */
635
-	public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int {
636
-		$query = $this->dbConn->getQueryBuilder();
637
-		$query->select($query->func()->count('id', 'num_messages'))
638
-			->from('comments')
639
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
640
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
641
-			->andWhere($query->expr()->gt('id', $query->createNamedParameter($lastRead)));
642
-
643
-		if ($verb !== '') {
644
-			$query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
645
-		}
646
-
647
-		$result = $query->execute();
648
-		$data = $result->fetch();
649
-		$result->closeCursor();
650
-
651
-		return (int) ($data['num_messages'] ?? 0);
652
-	}
653
-
654
-	/**
655
-	 * @param string $objectType
656
-	 * @param string $objectId
657
-	 * @param \DateTime $beforeDate
658
-	 * @param string $verb
659
-	 * @return int
660
-	 * @since 21.0.0
661
-	 */
662
-	public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int {
663
-		$query = $this->dbConn->getQueryBuilder();
664
-		$query->select('id')
665
-			->from('comments')
666
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
667
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
668
-			->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATE)))
669
-			->orderBy('creation_timestamp', 'desc');
670
-
671
-		if ($verb !== '') {
672
-			$query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
673
-		}
674
-
675
-		$result = $query->execute();
676
-		$data = $result->fetch();
677
-		$result->closeCursor();
678
-
679
-		return (int) ($data['id'] ?? 0);
680
-	}
681
-
682
-	/**
683
-	 * @param string $objectType
684
-	 * @param string $objectId
685
-	 * @param string $verb
686
-	 * @param string $actorType
687
-	 * @param string[] $actors
688
-	 * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
689
-	 * @psalm-return array<string, \DateTime>
690
-	 * @since 21.0.0
691
-	 */
692
-	public function getLastCommentDateByActor(
693
-		string $objectType,
694
-		string $objectId,
695
-		string $verb,
696
-		string $actorType,
697
-		array $actors
698
-	): array {
699
-		$lastComments = [];
700
-
701
-		$query = $this->dbConn->getQueryBuilder();
702
-		$query->select('actor_id')
703
-			->selectAlias($query->createFunction('MAX(' . $query->getColumnName('creation_timestamp') . ')'), 'last_comment')
704
-			->from('comments')
705
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
706
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
707
-			->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)))
708
-			->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
709
-			->andWhere($query->expr()->in('actor_id', $query->createNamedParameter($actors, IQueryBuilder::PARAM_STR_ARRAY)))
710
-			->groupBy('actor_id');
711
-
712
-		$result = $query->execute();
713
-		while ($row = $result->fetch()) {
714
-			$lastComments[$row['actor_id']] = $this->timeFactory->getDateTime($row['last_comment']);
715
-		}
716
-		$result->closeCursor();
717
-
718
-		return $lastComments;
719
-	}
720
-
721
-	/**
722
-	 * Get the number of unread comments for all files in a folder
723
-	 *
724
-	 * @param int $folderId
725
-	 * @param IUser $user
726
-	 * @return array [$fileId => $unreadCount]
727
-	 */
728
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) {
729
-		$qb = $this->dbConn->getQueryBuilder();
730
-
731
-		$query = $qb->select('f.fileid')
732
-			->addSelect($qb->func()->count('c.id', 'num_ids'))
733
-			->from('filecache', 'f')
734
-			->leftJoin('f', 'comments', 'c', $qb->expr()->andX(
735
-				$qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT)),
736
-				$qb->expr()->eq('c.object_type', $qb->createNamedParameter('files'))
737
-			))
738
-			->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX(
739
-				$qb->expr()->eq('c.object_id', 'm.object_id'),
740
-				$qb->expr()->eq('m.object_type', $qb->createNamedParameter('files'))
741
-			))
742
-			->where(
743
-				$qb->expr()->andX(
744
-					$qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)),
745
-					$qb->expr()->orX(
746
-						$qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')),
747
-						$qb->expr()->isNull('c.object_type')
748
-					),
749
-					$qb->expr()->orX(
750
-						$qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')),
751
-						$qb->expr()->isNull('m.object_type')
752
-					),
753
-					$qb->expr()->orX(
754
-						$qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID())),
755
-						$qb->expr()->isNull('m.user_id')
756
-					),
757
-					$qb->expr()->orX(
758
-						$qb->expr()->gt('c.creation_timestamp', 'm.marker_datetime'),
759
-						$qb->expr()->isNull('m.marker_datetime')
760
-					)
761
-				)
762
-			)->groupBy('f.fileid');
763
-
764
-		$resultStatement = $query->execute();
765
-
766
-		$results = [];
767
-		while ($row = $resultStatement->fetch()) {
768
-			$results[$row['fileid']] = (int) $row['num_ids'];
769
-		}
770
-		$resultStatement->closeCursor();
771
-		return $results;
772
-	}
773
-
774
-	/**
775
-	 * creates a new comment and returns it. At this point of time, it is not
776
-	 * saved in the used data storage. Use save() after setting other fields
777
-	 * of the comment (e.g. message or verb).
778
-	 *
779
-	 * @param string $actorType the actor type (e.g. 'users')
780
-	 * @param string $actorId a user id
781
-	 * @param string $objectType the object type the comment is attached to
782
-	 * @param string $objectId the object id the comment is attached to
783
-	 * @return IComment
784
-	 * @since 9.0.0
785
-	 */
786
-	public function create($actorType, $actorId, $objectType, $objectId) {
787
-		$comment = new Comment();
788
-		$comment
789
-			->setActor($actorType, $actorId)
790
-			->setObject($objectType, $objectId);
791
-		return $comment;
792
-	}
793
-
794
-	/**
795
-	 * permanently deletes the comment specified by the ID
796
-	 *
797
-	 * When the comment has child comments, their parent ID will be changed to
798
-	 * the parent ID of the item that is to be deleted.
799
-	 *
800
-	 * @param string $id
801
-	 * @return bool
802
-	 * @throws \InvalidArgumentException
803
-	 * @since 9.0.0
804
-	 */
805
-	public function delete($id) {
806
-		if (!is_string($id)) {
807
-			throw new \InvalidArgumentException('Parameter must be string');
808
-		}
809
-
810
-		try {
811
-			$comment = $this->get($id);
812
-		} catch (\Exception $e) {
813
-			// Ignore exceptions, we just don't fire a hook then
814
-			$comment = null;
815
-		}
816
-
817
-		$qb = $this->dbConn->getQueryBuilder();
818
-		$query = $qb->delete('comments')
819
-			->where($qb->expr()->eq('id', $qb->createParameter('id')))
820
-			->setParameter('id', $id);
821
-
822
-		try {
823
-			$affectedRows = $query->execute();
824
-			$this->uncache($id);
825
-		} catch (DriverException $e) {
826
-			$this->logger->error($e->getMessage(), [
827
-				'exception' => $e,
828
-				'app' => 'core_comments',
829
-			]);
830
-			return false;
831
-		}
832
-
833
-		if ($affectedRows > 0 && $comment instanceof IComment) {
834
-			$this->sendEvent(CommentsEvent::EVENT_DELETE, $comment);
835
-		}
836
-
837
-		return ($affectedRows > 0);
838
-	}
839
-
840
-	/**
841
-	 * saves the comment permanently
842
-	 *
843
-	 * if the supplied comment has an empty ID, a new entry comment will be
844
-	 * saved and the instance updated with the new ID.
845
-	 *
846
-	 * Otherwise, an existing comment will be updated.
847
-	 *
848
-	 * Throws NotFoundException when a comment that is to be updated does not
849
-	 * exist anymore at this point of time.
850
-	 *
851
-	 * @param IComment $comment
852
-	 * @return bool
853
-	 * @throws NotFoundException
854
-	 * @since 9.0.0
855
-	 */
856
-	public function save(IComment $comment) {
857
-		if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') {
858
-			$result = $this->insert($comment);
859
-		} else {
860
-			$result = $this->update($comment);
861
-		}
862
-
863
-		if ($result && !!$comment->getParentId()) {
864
-			$this->updateChildrenInformation(
865
-				$comment->getParentId(),
866
-				$comment->getCreationDateTime()
867
-			);
868
-			$this->cache($comment);
869
-		}
870
-
871
-		return $result;
872
-	}
873
-
874
-	/**
875
-	 * inserts the provided comment in the database
876
-	 *
877
-	 * @param IComment $comment
878
-	 * @return bool
879
-	 */
880
-	protected function insert(IComment $comment): bool {
881
-		try {
882
-			$result = $this->insertQuery($comment, true);
883
-		} catch (InvalidFieldNameException $e) {
884
-			// The reference id field was only added in Nextcloud 19.
885
-			// In order to not cause too long waiting times on the update,
886
-			// it was decided to only add it lazy, as it is also not a critical
887
-			// feature, but only helps to have a better experience while commenting.
888
-			// So in case the reference_id field is missing,
889
-			// we simply save the comment without that field.
890
-			$result = $this->insertQuery($comment, false);
891
-		}
892
-
893
-		return $result;
894
-	}
895
-
896
-	protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool {
897
-		$qb = $this->dbConn->getQueryBuilder();
898
-
899
-		$values = [
900
-			'parent_id' => $qb->createNamedParameter($comment->getParentId()),
901
-			'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
902
-			'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
903
-			'actor_type' => $qb->createNamedParameter($comment->getActorType()),
904
-			'actor_id' => $qb->createNamedParameter($comment->getActorId()),
905
-			'message' => $qb->createNamedParameter($comment->getMessage()),
906
-			'verb' => $qb->createNamedParameter($comment->getVerb()),
907
-			'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
908
-			'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
909
-			'object_type' => $qb->createNamedParameter($comment->getObjectType()),
910
-			'object_id' => $qb->createNamedParameter($comment->getObjectId()),
911
-		];
912
-
913
-		if ($tryWritingReferenceId) {
914
-			$values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId());
915
-		}
916
-
917
-		$affectedRows = $qb->insert('comments')
918
-			->values($values)
919
-			->execute();
920
-
921
-		if ($affectedRows > 0) {
922
-			$comment->setId((string)$qb->getLastInsertId());
923
-			$this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
924
-		}
925
-
926
-		return $affectedRows > 0;
927
-	}
928
-
929
-	/**
930
-	 * updates a Comment data row
931
-	 *
932
-	 * @param IComment $comment
933
-	 * @return bool
934
-	 * @throws NotFoundException
935
-	 */
936
-	protected function update(IComment $comment) {
937
-		// for properly working preUpdate Events we need the old comments as is
938
-		// in the DB and overcome caching. Also avoid that outdated information stays.
939
-		$this->uncache($comment->getId());
940
-		$this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
941
-		$this->uncache($comment->getId());
942
-
943
-		try {
944
-			$result = $this->updateQuery($comment, true);
945
-		} catch (InvalidFieldNameException $e) {
946
-			// See function insert() for explanation
947
-			$result = $this->updateQuery($comment, false);
948
-		}
949
-
950
-		$this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment);
951
-
952
-		return $result;
953
-	}
954
-
955
-	protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool {
956
-		$qb = $this->dbConn->getQueryBuilder();
957
-		$qb
958
-			->update('comments')
959
-			->set('parent_id', $qb->createNamedParameter($comment->getParentId()))
960
-			->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId()))
961
-			->set('children_count', $qb->createNamedParameter($comment->getChildrenCount()))
962
-			->set('actor_type', $qb->createNamedParameter($comment->getActorType()))
963
-			->set('actor_id', $qb->createNamedParameter($comment->getActorId()))
964
-			->set('message', $qb->createNamedParameter($comment->getMessage()))
965
-			->set('verb', $qb->createNamedParameter($comment->getVerb()))
966
-			->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'))
967
-			->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
968
-			->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
969
-			->set('object_id', $qb->createNamedParameter($comment->getObjectId()));
970
-
971
-		if ($tryWritingReferenceId) {
972
-			$qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()));
973
-		}
974
-
975
-		$affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())))
976
-			->execute();
977
-
978
-		if ($affectedRows === 0) {
979
-			throw new NotFoundException('Comment to update does ceased to exist');
980
-		}
981
-
982
-		return $affectedRows > 0;
983
-	}
984
-
985
-	/**
986
-	 * removes references to specific actor (e.g. on user delete) of a comment.
987
-	 * The comment itself must not get lost/deleted.
988
-	 *
989
-	 * @param string $actorType the actor type (e.g. 'users')
990
-	 * @param string $actorId a user id
991
-	 * @return boolean
992
-	 * @since 9.0.0
993
-	 */
994
-	public function deleteReferencesOfActor($actorType, $actorId) {
995
-		$this->checkRoleParameters('Actor', $actorType, $actorId);
996
-
997
-		$qb = $this->dbConn->getQueryBuilder();
998
-		$affectedRows = $qb
999
-			->update('comments')
1000
-			->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1001
-			->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1002
-			->where($qb->expr()->eq('actor_type', $qb->createParameter('type')))
1003
-			->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id')))
1004
-			->setParameter('type', $actorType)
1005
-			->setParameter('id', $actorId)
1006
-			->execute();
1007
-
1008
-		$this->commentsCache = [];
1009
-
1010
-		return is_int($affectedRows);
1011
-	}
1012
-
1013
-	/**
1014
-	 * deletes all comments made of a specific object (e.g. on file delete)
1015
-	 *
1016
-	 * @param string $objectType the object type (e.g. 'files')
1017
-	 * @param string $objectId e.g. the file id
1018
-	 * @return boolean
1019
-	 * @since 9.0.0
1020
-	 */
1021
-	public function deleteCommentsAtObject($objectType, $objectId) {
1022
-		$this->checkRoleParameters('Object', $objectType, $objectId);
1023
-
1024
-		$qb = $this->dbConn->getQueryBuilder();
1025
-		$affectedRows = $qb
1026
-			->delete('comments')
1027
-			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
1028
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
1029
-			->setParameter('type', $objectType)
1030
-			->setParameter('id', $objectId)
1031
-			->execute();
1032
-
1033
-		$this->commentsCache = [];
1034
-
1035
-		return is_int($affectedRows);
1036
-	}
1037
-
1038
-	/**
1039
-	 * deletes the read markers for the specified user
1040
-	 *
1041
-	 * @param \OCP\IUser $user
1042
-	 * @return bool
1043
-	 * @since 9.0.0
1044
-	 */
1045
-	public function deleteReadMarksFromUser(IUser $user) {
1046
-		$qb = $this->dbConn->getQueryBuilder();
1047
-		$query = $qb->delete('comments_read_markers')
1048
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1049
-			->setParameter('user_id', $user->getUID());
1050
-
1051
-		try {
1052
-			$affectedRows = $query->execute();
1053
-		} catch (DriverException $e) {
1054
-			$this->logger->error($e->getMessage(), [
1055
-				'exception' => $e,
1056
-				'app' => 'core_comments',
1057
-			]);
1058
-			return false;
1059
-		}
1060
-		return ($affectedRows > 0);
1061
-	}
1062
-
1063
-	/**
1064
-	 * sets the read marker for a given file to the specified date for the
1065
-	 * provided user
1066
-	 *
1067
-	 * @param string $objectType
1068
-	 * @param string $objectId
1069
-	 * @param \DateTime $dateTime
1070
-	 * @param IUser $user
1071
-	 * @since 9.0.0
1072
-	 */
1073
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) {
1074
-		$this->checkRoleParameters('Object', $objectType, $objectId);
1075
-
1076
-		$qb = $this->dbConn->getQueryBuilder();
1077
-		$values = [
1078
-			'user_id' => $qb->createNamedParameter($user->getUID()),
1079
-			'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'),
1080
-			'object_type' => $qb->createNamedParameter($objectType),
1081
-			'object_id' => $qb->createNamedParameter($objectId),
1082
-		];
1083
-
1084
-		// Strategy: try to update, if this does not return affected rows, do an insert.
1085
-		$affectedRows = $qb
1086
-			->update('comments_read_markers')
1087
-			->set('user_id', $values['user_id'])
1088
-			->set('marker_datetime', $values['marker_datetime'])
1089
-			->set('object_type', $values['object_type'])
1090
-			->set('object_id', $values['object_id'])
1091
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1092
-			->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1093
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1094
-			->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1095
-			->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1096
-			->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1097
-			->execute();
1098
-
1099
-		if ($affectedRows > 0) {
1100
-			return;
1101
-		}
1102
-
1103
-		$qb->insert('comments_read_markers')
1104
-			->values($values)
1105
-			->execute();
1106
-	}
1107
-
1108
-	/**
1109
-	 * returns the read marker for a given file to the specified date for the
1110
-	 * provided user. It returns null, when the marker is not present, i.e.
1111
-	 * no comments were marked as read.
1112
-	 *
1113
-	 * @param string $objectType
1114
-	 * @param string $objectId
1115
-	 * @param IUser $user
1116
-	 * @return \DateTime|null
1117
-	 * @since 9.0.0
1118
-	 */
1119
-	public function getReadMark($objectType, $objectId, IUser $user) {
1120
-		$qb = $this->dbConn->getQueryBuilder();
1121
-		$resultStatement = $qb->select('marker_datetime')
1122
-			->from('comments_read_markers')
1123
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1124
-			->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1125
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1126
-			->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1127
-			->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1128
-			->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1129
-			->execute();
1130
-
1131
-		$data = $resultStatement->fetch();
1132
-		$resultStatement->closeCursor();
1133
-		if (!$data || is_null($data['marker_datetime'])) {
1134
-			return null;
1135
-		}
1136
-
1137
-		return new \DateTime($data['marker_datetime']);
1138
-	}
1139
-
1140
-	/**
1141
-	 * deletes the read markers on the specified object
1142
-	 *
1143
-	 * @param string $objectType
1144
-	 * @param string $objectId
1145
-	 * @return bool
1146
-	 * @since 9.0.0
1147
-	 */
1148
-	public function deleteReadMarksOnObject($objectType, $objectId) {
1149
-		$this->checkRoleParameters('Object', $objectType, $objectId);
1150
-
1151
-		$qb = $this->dbConn->getQueryBuilder();
1152
-		$query = $qb->delete('comments_read_markers')
1153
-			->where($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1154
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1155
-			->setParameter('object_type', $objectType)
1156
-			->setParameter('object_id', $objectId);
1157
-
1158
-		try {
1159
-			$affectedRows = $query->execute();
1160
-		} catch (DriverException $e) {
1161
-			$this->logger->error($e->getMessage(), [
1162
-				'exception' => $e,
1163
-				'app' => 'core_comments',
1164
-			]);
1165
-			return false;
1166
-		}
1167
-		return ($affectedRows > 0);
1168
-	}
1169
-
1170
-	/**
1171
-	 * registers an Entity to the manager, so event notifications can be send
1172
-	 * to consumers of the comments infrastructure
1173
-	 *
1174
-	 * @param \Closure $closure
1175
-	 */
1176
-	public function registerEventHandler(\Closure $closure) {
1177
-		$this->eventHandlerClosures[] = $closure;
1178
-		$this->eventHandlers = [];
1179
-	}
1180
-
1181
-	/**
1182
-	 * registers a method that resolves an ID to a display name for a given type
1183
-	 *
1184
-	 * @param string $type
1185
-	 * @param \Closure $closure
1186
-	 * @throws \OutOfBoundsException
1187
-	 * @since 11.0.0
1188
-	 *
1189
-	 * Only one resolver shall be registered per type. Otherwise a
1190
-	 * \OutOfBoundsException has to thrown.
1191
-	 */
1192
-	public function registerDisplayNameResolver($type, \Closure $closure) {
1193
-		if (!is_string($type)) {
1194
-			throw new \InvalidArgumentException('String expected.');
1195
-		}
1196
-		if (isset($this->displayNameResolvers[$type])) {
1197
-			throw new \OutOfBoundsException('Displayname resolver for this type already registered');
1198
-		}
1199
-		$this->displayNameResolvers[$type] = $closure;
1200
-	}
1201
-
1202
-	/**
1203
-	 * resolves a given ID of a given Type to a display name.
1204
-	 *
1205
-	 * @param string $type
1206
-	 * @param string $id
1207
-	 * @return string
1208
-	 * @throws \OutOfBoundsException
1209
-	 * @since 11.0.0
1210
-	 *
1211
-	 * If a provided type was not registered, an \OutOfBoundsException shall
1212
-	 * be thrown. It is upon the resolver discretion what to return of the
1213
-	 * provided ID is unknown. It must be ensured that a string is returned.
1214
-	 */
1215
-	public function resolveDisplayName($type, $id) {
1216
-		if (!is_string($type)) {
1217
-			throw new \InvalidArgumentException('String expected.');
1218
-		}
1219
-		if (!isset($this->displayNameResolvers[$type])) {
1220
-			throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1221
-		}
1222
-		return (string)$this->displayNameResolvers[$type]($id);
1223
-	}
1224
-
1225
-	/**
1226
-	 * returns valid, registered entities
1227
-	 *
1228
-	 * @return \OCP\Comments\ICommentsEventHandler[]
1229
-	 */
1230
-	private function getEventHandlers() {
1231
-		if (!empty($this->eventHandlers)) {
1232
-			return $this->eventHandlers;
1233
-		}
1234
-
1235
-		$this->eventHandlers = [];
1236
-		foreach ($this->eventHandlerClosures as $name => $closure) {
1237
-			$entity = $closure();
1238
-			if (!($entity instanceof ICommentsEventHandler)) {
1239
-				throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface');
1240
-			}
1241
-			$this->eventHandlers[$name] = $entity;
1242
-		}
1243
-
1244
-		return $this->eventHandlers;
1245
-	}
1246
-
1247
-	/**
1248
-	 * sends notifications to the registered entities
1249
-	 *
1250
-	 * @param $eventType
1251
-	 * @param IComment $comment
1252
-	 */
1253
-	private function sendEvent($eventType, IComment $comment) {
1254
-		$entities = $this->getEventHandlers();
1255
-		$event = new CommentsEvent($eventType, $comment);
1256
-		foreach ($entities as $entity) {
1257
-			$entity->handle($event);
1258
-		}
1259
-	}
1260
-
1261
-	/**
1262
-	 * Load the Comments app into the page
1263
-	 *
1264
-	 * @since 21.0.0
1265
-	 */
1266
-	public function load(): void {
1267
-		$this->initialStateService->provideInitialState(Application::APP_ID, 'max-message-length', IComment::MAX_MESSAGE_LENGTH);
1268
-		Util::addScript(Application::APP_ID, 'comments-app');
1269
-	}
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
629
+     * @param string $objectId
630
+     * @param int $lastRead
631
+     * @param string $verb
632
+     * @return int
633
+     * @since 21.0.0
634
+     */
635
+    public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int {
636
+        $query = $this->dbConn->getQueryBuilder();
637
+        $query->select($query->func()->count('id', 'num_messages'))
638
+            ->from('comments')
639
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
640
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
641
+            ->andWhere($query->expr()->gt('id', $query->createNamedParameter($lastRead)));
642
+
643
+        if ($verb !== '') {
644
+            $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
645
+        }
646
+
647
+        $result = $query->execute();
648
+        $data = $result->fetch();
649
+        $result->closeCursor();
650
+
651
+        return (int) ($data['num_messages'] ?? 0);
652
+    }
653
+
654
+    /**
655
+     * @param string $objectType
656
+     * @param string $objectId
657
+     * @param \DateTime $beforeDate
658
+     * @param string $verb
659
+     * @return int
660
+     * @since 21.0.0
661
+     */
662
+    public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int {
663
+        $query = $this->dbConn->getQueryBuilder();
664
+        $query->select('id')
665
+            ->from('comments')
666
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
667
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
668
+            ->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATE)))
669
+            ->orderBy('creation_timestamp', 'desc');
670
+
671
+        if ($verb !== '') {
672
+            $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
673
+        }
674
+
675
+        $result = $query->execute();
676
+        $data = $result->fetch();
677
+        $result->closeCursor();
678
+
679
+        return (int) ($data['id'] ?? 0);
680
+    }
681
+
682
+    /**
683
+     * @param string $objectType
684
+     * @param string $objectId
685
+     * @param string $verb
686
+     * @param string $actorType
687
+     * @param string[] $actors
688
+     * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
689
+     * @psalm-return array<string, \DateTime>
690
+     * @since 21.0.0
691
+     */
692
+    public function getLastCommentDateByActor(
693
+        string $objectType,
694
+        string $objectId,
695
+        string $verb,
696
+        string $actorType,
697
+        array $actors
698
+    ): array {
699
+        $lastComments = [];
700
+
701
+        $query = $this->dbConn->getQueryBuilder();
702
+        $query->select('actor_id')
703
+            ->selectAlias($query->createFunction('MAX(' . $query->getColumnName('creation_timestamp') . ')'), 'last_comment')
704
+            ->from('comments')
705
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
706
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
707
+            ->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)))
708
+            ->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType)))
709
+            ->andWhere($query->expr()->in('actor_id', $query->createNamedParameter($actors, IQueryBuilder::PARAM_STR_ARRAY)))
710
+            ->groupBy('actor_id');
711
+
712
+        $result = $query->execute();
713
+        while ($row = $result->fetch()) {
714
+            $lastComments[$row['actor_id']] = $this->timeFactory->getDateTime($row['last_comment']);
715
+        }
716
+        $result->closeCursor();
717
+
718
+        return $lastComments;
719
+    }
720
+
721
+    /**
722
+     * Get the number of unread comments for all files in a folder
723
+     *
724
+     * @param int $folderId
725
+     * @param IUser $user
726
+     * @return array [$fileId => $unreadCount]
727
+     */
728
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) {
729
+        $qb = $this->dbConn->getQueryBuilder();
730
+
731
+        $query = $qb->select('f.fileid')
732
+            ->addSelect($qb->func()->count('c.id', 'num_ids'))
733
+            ->from('filecache', 'f')
734
+            ->leftJoin('f', 'comments', 'c', $qb->expr()->andX(
735
+                $qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT)),
736
+                $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files'))
737
+            ))
738
+            ->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX(
739
+                $qb->expr()->eq('c.object_id', 'm.object_id'),
740
+                $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files'))
741
+            ))
742
+            ->where(
743
+                $qb->expr()->andX(
744
+                    $qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)),
745
+                    $qb->expr()->orX(
746
+                        $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')),
747
+                        $qb->expr()->isNull('c.object_type')
748
+                    ),
749
+                    $qb->expr()->orX(
750
+                        $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')),
751
+                        $qb->expr()->isNull('m.object_type')
752
+                    ),
753
+                    $qb->expr()->orX(
754
+                        $qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID())),
755
+                        $qb->expr()->isNull('m.user_id')
756
+                    ),
757
+                    $qb->expr()->orX(
758
+                        $qb->expr()->gt('c.creation_timestamp', 'm.marker_datetime'),
759
+                        $qb->expr()->isNull('m.marker_datetime')
760
+                    )
761
+                )
762
+            )->groupBy('f.fileid');
763
+
764
+        $resultStatement = $query->execute();
765
+
766
+        $results = [];
767
+        while ($row = $resultStatement->fetch()) {
768
+            $results[$row['fileid']] = (int) $row['num_ids'];
769
+        }
770
+        $resultStatement->closeCursor();
771
+        return $results;
772
+    }
773
+
774
+    /**
775
+     * creates a new comment and returns it. At this point of time, it is not
776
+     * saved in the used data storage. Use save() after setting other fields
777
+     * of the comment (e.g. message or verb).
778
+     *
779
+     * @param string $actorType the actor type (e.g. 'users')
780
+     * @param string $actorId a user id
781
+     * @param string $objectType the object type the comment is attached to
782
+     * @param string $objectId the object id the comment is attached to
783
+     * @return IComment
784
+     * @since 9.0.0
785
+     */
786
+    public function create($actorType, $actorId, $objectType, $objectId) {
787
+        $comment = new Comment();
788
+        $comment
789
+            ->setActor($actorType, $actorId)
790
+            ->setObject($objectType, $objectId);
791
+        return $comment;
792
+    }
793
+
794
+    /**
795
+     * permanently deletes the comment specified by the ID
796
+     *
797
+     * When the comment has child comments, their parent ID will be changed to
798
+     * the parent ID of the item that is to be deleted.
799
+     *
800
+     * @param string $id
801
+     * @return bool
802
+     * @throws \InvalidArgumentException
803
+     * @since 9.0.0
804
+     */
805
+    public function delete($id) {
806
+        if (!is_string($id)) {
807
+            throw new \InvalidArgumentException('Parameter must be string');
808
+        }
809
+
810
+        try {
811
+            $comment = $this->get($id);
812
+        } catch (\Exception $e) {
813
+            // Ignore exceptions, we just don't fire a hook then
814
+            $comment = null;
815
+        }
816
+
817
+        $qb = $this->dbConn->getQueryBuilder();
818
+        $query = $qb->delete('comments')
819
+            ->where($qb->expr()->eq('id', $qb->createParameter('id')))
820
+            ->setParameter('id', $id);
821
+
822
+        try {
823
+            $affectedRows = $query->execute();
824
+            $this->uncache($id);
825
+        } catch (DriverException $e) {
826
+            $this->logger->error($e->getMessage(), [
827
+                'exception' => $e,
828
+                'app' => 'core_comments',
829
+            ]);
830
+            return false;
831
+        }
832
+
833
+        if ($affectedRows > 0 && $comment instanceof IComment) {
834
+            $this->sendEvent(CommentsEvent::EVENT_DELETE, $comment);
835
+        }
836
+
837
+        return ($affectedRows > 0);
838
+    }
839
+
840
+    /**
841
+     * saves the comment permanently
842
+     *
843
+     * if the supplied comment has an empty ID, a new entry comment will be
844
+     * saved and the instance updated with the new ID.
845
+     *
846
+     * Otherwise, an existing comment will be updated.
847
+     *
848
+     * Throws NotFoundException when a comment that is to be updated does not
849
+     * exist anymore at this point of time.
850
+     *
851
+     * @param IComment $comment
852
+     * @return bool
853
+     * @throws NotFoundException
854
+     * @since 9.0.0
855
+     */
856
+    public function save(IComment $comment) {
857
+        if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') {
858
+            $result = $this->insert($comment);
859
+        } else {
860
+            $result = $this->update($comment);
861
+        }
862
+
863
+        if ($result && !!$comment->getParentId()) {
864
+            $this->updateChildrenInformation(
865
+                $comment->getParentId(),
866
+                $comment->getCreationDateTime()
867
+            );
868
+            $this->cache($comment);
869
+        }
870
+
871
+        return $result;
872
+    }
873
+
874
+    /**
875
+     * inserts the provided comment in the database
876
+     *
877
+     * @param IComment $comment
878
+     * @return bool
879
+     */
880
+    protected function insert(IComment $comment): bool {
881
+        try {
882
+            $result = $this->insertQuery($comment, true);
883
+        } catch (InvalidFieldNameException $e) {
884
+            // The reference id field was only added in Nextcloud 19.
885
+            // In order to not cause too long waiting times on the update,
886
+            // it was decided to only add it lazy, as it is also not a critical
887
+            // feature, but only helps to have a better experience while commenting.
888
+            // So in case the reference_id field is missing,
889
+            // we simply save the comment without that field.
890
+            $result = $this->insertQuery($comment, false);
891
+        }
892
+
893
+        return $result;
894
+    }
895
+
896
+    protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool {
897
+        $qb = $this->dbConn->getQueryBuilder();
898
+
899
+        $values = [
900
+            'parent_id' => $qb->createNamedParameter($comment->getParentId()),
901
+            'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
902
+            'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
903
+            'actor_type' => $qb->createNamedParameter($comment->getActorType()),
904
+            'actor_id' => $qb->createNamedParameter($comment->getActorId()),
905
+            'message' => $qb->createNamedParameter($comment->getMessage()),
906
+            'verb' => $qb->createNamedParameter($comment->getVerb()),
907
+            'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
908
+            'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
909
+            'object_type' => $qb->createNamedParameter($comment->getObjectType()),
910
+            'object_id' => $qb->createNamedParameter($comment->getObjectId()),
911
+        ];
912
+
913
+        if ($tryWritingReferenceId) {
914
+            $values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId());
915
+        }
916
+
917
+        $affectedRows = $qb->insert('comments')
918
+            ->values($values)
919
+            ->execute();
920
+
921
+        if ($affectedRows > 0) {
922
+            $comment->setId((string)$qb->getLastInsertId());
923
+            $this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
924
+        }
925
+
926
+        return $affectedRows > 0;
927
+    }
928
+
929
+    /**
930
+     * updates a Comment data row
931
+     *
932
+     * @param IComment $comment
933
+     * @return bool
934
+     * @throws NotFoundException
935
+     */
936
+    protected function update(IComment $comment) {
937
+        // for properly working preUpdate Events we need the old comments as is
938
+        // in the DB and overcome caching. Also avoid that outdated information stays.
939
+        $this->uncache($comment->getId());
940
+        $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
941
+        $this->uncache($comment->getId());
942
+
943
+        try {
944
+            $result = $this->updateQuery($comment, true);
945
+        } catch (InvalidFieldNameException $e) {
946
+            // See function insert() for explanation
947
+            $result = $this->updateQuery($comment, false);
948
+        }
949
+
950
+        $this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment);
951
+
952
+        return $result;
953
+    }
954
+
955
+    protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool {
956
+        $qb = $this->dbConn->getQueryBuilder();
957
+        $qb
958
+            ->update('comments')
959
+            ->set('parent_id', $qb->createNamedParameter($comment->getParentId()))
960
+            ->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId()))
961
+            ->set('children_count', $qb->createNamedParameter($comment->getChildrenCount()))
962
+            ->set('actor_type', $qb->createNamedParameter($comment->getActorType()))
963
+            ->set('actor_id', $qb->createNamedParameter($comment->getActorId()))
964
+            ->set('message', $qb->createNamedParameter($comment->getMessage()))
965
+            ->set('verb', $qb->createNamedParameter($comment->getVerb()))
966
+            ->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'))
967
+            ->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
968
+            ->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
969
+            ->set('object_id', $qb->createNamedParameter($comment->getObjectId()));
970
+
971
+        if ($tryWritingReferenceId) {
972
+            $qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()));
973
+        }
974
+
975
+        $affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())))
976
+            ->execute();
977
+
978
+        if ($affectedRows === 0) {
979
+            throw new NotFoundException('Comment to update does ceased to exist');
980
+        }
981
+
982
+        return $affectedRows > 0;
983
+    }
984
+
985
+    /**
986
+     * removes references to specific actor (e.g. on user delete) of a comment.
987
+     * The comment itself must not get lost/deleted.
988
+     *
989
+     * @param string $actorType the actor type (e.g. 'users')
990
+     * @param string $actorId a user id
991
+     * @return boolean
992
+     * @since 9.0.0
993
+     */
994
+    public function deleteReferencesOfActor($actorType, $actorId) {
995
+        $this->checkRoleParameters('Actor', $actorType, $actorId);
996
+
997
+        $qb = $this->dbConn->getQueryBuilder();
998
+        $affectedRows = $qb
999
+            ->update('comments')
1000
+            ->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1001
+            ->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
1002
+            ->where($qb->expr()->eq('actor_type', $qb->createParameter('type')))
1003
+            ->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id')))
1004
+            ->setParameter('type', $actorType)
1005
+            ->setParameter('id', $actorId)
1006
+            ->execute();
1007
+
1008
+        $this->commentsCache = [];
1009
+
1010
+        return is_int($affectedRows);
1011
+    }
1012
+
1013
+    /**
1014
+     * deletes all comments made of a specific object (e.g. on file delete)
1015
+     *
1016
+     * @param string $objectType the object type (e.g. 'files')
1017
+     * @param string $objectId e.g. the file id
1018
+     * @return boolean
1019
+     * @since 9.0.0
1020
+     */
1021
+    public function deleteCommentsAtObject($objectType, $objectId) {
1022
+        $this->checkRoleParameters('Object', $objectType, $objectId);
1023
+
1024
+        $qb = $this->dbConn->getQueryBuilder();
1025
+        $affectedRows = $qb
1026
+            ->delete('comments')
1027
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
1028
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
1029
+            ->setParameter('type', $objectType)
1030
+            ->setParameter('id', $objectId)
1031
+            ->execute();
1032
+
1033
+        $this->commentsCache = [];
1034
+
1035
+        return is_int($affectedRows);
1036
+    }
1037
+
1038
+    /**
1039
+     * deletes the read markers for the specified user
1040
+     *
1041
+     * @param \OCP\IUser $user
1042
+     * @return bool
1043
+     * @since 9.0.0
1044
+     */
1045
+    public function deleteReadMarksFromUser(IUser $user) {
1046
+        $qb = $this->dbConn->getQueryBuilder();
1047
+        $query = $qb->delete('comments_read_markers')
1048
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1049
+            ->setParameter('user_id', $user->getUID());
1050
+
1051
+        try {
1052
+            $affectedRows = $query->execute();
1053
+        } catch (DriverException $e) {
1054
+            $this->logger->error($e->getMessage(), [
1055
+                'exception' => $e,
1056
+                'app' => 'core_comments',
1057
+            ]);
1058
+            return false;
1059
+        }
1060
+        return ($affectedRows > 0);
1061
+    }
1062
+
1063
+    /**
1064
+     * sets the read marker for a given file to the specified date for the
1065
+     * provided user
1066
+     *
1067
+     * @param string $objectType
1068
+     * @param string $objectId
1069
+     * @param \DateTime $dateTime
1070
+     * @param IUser $user
1071
+     * @since 9.0.0
1072
+     */
1073
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) {
1074
+        $this->checkRoleParameters('Object', $objectType, $objectId);
1075
+
1076
+        $qb = $this->dbConn->getQueryBuilder();
1077
+        $values = [
1078
+            'user_id' => $qb->createNamedParameter($user->getUID()),
1079
+            'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'),
1080
+            'object_type' => $qb->createNamedParameter($objectType),
1081
+            'object_id' => $qb->createNamedParameter($objectId),
1082
+        ];
1083
+
1084
+        // Strategy: try to update, if this does not return affected rows, do an insert.
1085
+        $affectedRows = $qb
1086
+            ->update('comments_read_markers')
1087
+            ->set('user_id', $values['user_id'])
1088
+            ->set('marker_datetime', $values['marker_datetime'])
1089
+            ->set('object_type', $values['object_type'])
1090
+            ->set('object_id', $values['object_id'])
1091
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1092
+            ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1093
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1094
+            ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1095
+            ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1096
+            ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1097
+            ->execute();
1098
+
1099
+        if ($affectedRows > 0) {
1100
+            return;
1101
+        }
1102
+
1103
+        $qb->insert('comments_read_markers')
1104
+            ->values($values)
1105
+            ->execute();
1106
+    }
1107
+
1108
+    /**
1109
+     * returns the read marker for a given file to the specified date for the
1110
+     * provided user. It returns null, when the marker is not present, i.e.
1111
+     * no comments were marked as read.
1112
+     *
1113
+     * @param string $objectType
1114
+     * @param string $objectId
1115
+     * @param IUser $user
1116
+     * @return \DateTime|null
1117
+     * @since 9.0.0
1118
+     */
1119
+    public function getReadMark($objectType, $objectId, IUser $user) {
1120
+        $qb = $this->dbConn->getQueryBuilder();
1121
+        $resultStatement = $qb->select('marker_datetime')
1122
+            ->from('comments_read_markers')
1123
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
1124
+            ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1125
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1126
+            ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
1127
+            ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
1128
+            ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
1129
+            ->execute();
1130
+
1131
+        $data = $resultStatement->fetch();
1132
+        $resultStatement->closeCursor();
1133
+        if (!$data || is_null($data['marker_datetime'])) {
1134
+            return null;
1135
+        }
1136
+
1137
+        return new \DateTime($data['marker_datetime']);
1138
+    }
1139
+
1140
+    /**
1141
+     * deletes the read markers on the specified object
1142
+     *
1143
+     * @param string $objectType
1144
+     * @param string $objectId
1145
+     * @return bool
1146
+     * @since 9.0.0
1147
+     */
1148
+    public function deleteReadMarksOnObject($objectType, $objectId) {
1149
+        $this->checkRoleParameters('Object', $objectType, $objectId);
1150
+
1151
+        $qb = $this->dbConn->getQueryBuilder();
1152
+        $query = $qb->delete('comments_read_markers')
1153
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
1154
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
1155
+            ->setParameter('object_type', $objectType)
1156
+            ->setParameter('object_id', $objectId);
1157
+
1158
+        try {
1159
+            $affectedRows = $query->execute();
1160
+        } catch (DriverException $e) {
1161
+            $this->logger->error($e->getMessage(), [
1162
+                'exception' => $e,
1163
+                'app' => 'core_comments',
1164
+            ]);
1165
+            return false;
1166
+        }
1167
+        return ($affectedRows > 0);
1168
+    }
1169
+
1170
+    /**
1171
+     * registers an Entity to the manager, so event notifications can be send
1172
+     * to consumers of the comments infrastructure
1173
+     *
1174
+     * @param \Closure $closure
1175
+     */
1176
+    public function registerEventHandler(\Closure $closure) {
1177
+        $this->eventHandlerClosures[] = $closure;
1178
+        $this->eventHandlers = [];
1179
+    }
1180
+
1181
+    /**
1182
+     * registers a method that resolves an ID to a display name for a given type
1183
+     *
1184
+     * @param string $type
1185
+     * @param \Closure $closure
1186
+     * @throws \OutOfBoundsException
1187
+     * @since 11.0.0
1188
+     *
1189
+     * Only one resolver shall be registered per type. Otherwise a
1190
+     * \OutOfBoundsException has to thrown.
1191
+     */
1192
+    public function registerDisplayNameResolver($type, \Closure $closure) {
1193
+        if (!is_string($type)) {
1194
+            throw new \InvalidArgumentException('String expected.');
1195
+        }
1196
+        if (isset($this->displayNameResolvers[$type])) {
1197
+            throw new \OutOfBoundsException('Displayname resolver for this type already registered');
1198
+        }
1199
+        $this->displayNameResolvers[$type] = $closure;
1200
+    }
1201
+
1202
+    /**
1203
+     * resolves a given ID of a given Type to a display name.
1204
+     *
1205
+     * @param string $type
1206
+     * @param string $id
1207
+     * @return string
1208
+     * @throws \OutOfBoundsException
1209
+     * @since 11.0.0
1210
+     *
1211
+     * If a provided type was not registered, an \OutOfBoundsException shall
1212
+     * be thrown. It is upon the resolver discretion what to return of the
1213
+     * provided ID is unknown. It must be ensured that a string is returned.
1214
+     */
1215
+    public function resolveDisplayName($type, $id) {
1216
+        if (!is_string($type)) {
1217
+            throw new \InvalidArgumentException('String expected.');
1218
+        }
1219
+        if (!isset($this->displayNameResolvers[$type])) {
1220
+            throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1221
+        }
1222
+        return (string)$this->displayNameResolvers[$type]($id);
1223
+    }
1224
+
1225
+    /**
1226
+     * returns valid, registered entities
1227
+     *
1228
+     * @return \OCP\Comments\ICommentsEventHandler[]
1229
+     */
1230
+    private function getEventHandlers() {
1231
+        if (!empty($this->eventHandlers)) {
1232
+            return $this->eventHandlers;
1233
+        }
1234
+
1235
+        $this->eventHandlers = [];
1236
+        foreach ($this->eventHandlerClosures as $name => $closure) {
1237
+            $entity = $closure();
1238
+            if (!($entity instanceof ICommentsEventHandler)) {
1239
+                throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface');
1240
+            }
1241
+            $this->eventHandlers[$name] = $entity;
1242
+        }
1243
+
1244
+        return $this->eventHandlers;
1245
+    }
1246
+
1247
+    /**
1248
+     * sends notifications to the registered entities
1249
+     *
1250
+     * @param $eventType
1251
+     * @param IComment $comment
1252
+     */
1253
+    private function sendEvent($eventType, IComment $comment) {
1254
+        $entities = $this->getEventHandlers();
1255
+        $event = new CommentsEvent($eventType, $comment);
1256
+        foreach ($entities as $entity) {
1257
+            $entity->handle($event);
1258
+        }
1259
+    }
1260
+
1261
+    /**
1262
+     * Load the Comments app into the page
1263
+     *
1264
+     * @since 21.0.0
1265
+     */
1266
+    public function load(): void {
1267
+        $this->initialStateService->provideInitialState(Application::APP_ID, 'max-message-length', IComment::MAX_MESSAGE_LENGTH);
1268
+        Util::addScript(Application::APP_ID, 'comments-app');
1269
+    }
1270 1270
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -94,14 +94,14 @@  discard block
 block discarded – undo
94 94
 	 * @return array
95 95
 	 */
96 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'];
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 100
 		$data['creation_timestamp'] = new \DateTime($data['creation_timestamp']);
101 101
 		if (!is_null($data['latest_child_timestamp'])) {
102 102
 			$data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']);
103 103
 		}
104
-		$data['children_count'] = (int)$data['children_count'];
104
+		$data['children_count'] = (int) $data['children_count'];
105 105
 		$data['reference_id'] = $data['reference_id'] ?? null;
106 106
 		return $data;
107 107
 	}
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
 		$resultStatement = $query->execute();
189 189
 		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
190 190
 		$resultStatement->closeCursor();
191
-		$children = (int)$data[0];
191
+		$children = (int) $data[0];
192 192
 
193 193
 		$comment = $this->get($id);
194 194
 		$comment->setChildrenCount($children);
@@ -210,7 +210,7 @@  discard block
 block discarded – undo
210 210
 			!is_string($type) || empty($type)
211 211
 			|| !is_string($id) || empty($id)
212 212
 		) {
213
-			throw new \InvalidArgumentException($role . ' parameters must be string and not empty');
213
+			throw new \InvalidArgumentException($role.' parameters must be string and not empty');
214 214
 		}
215 215
 	}
216 216
 
@@ -224,7 +224,7 @@  discard block
 block discarded – undo
224 224
 		if (empty($id)) {
225 225
 			return;
226 226
 		}
227
-		$this->commentsCache[(string)$id] = $comment;
227
+		$this->commentsCache[(string) $id] = $comment;
228 228
 	}
229 229
 
230 230
 	/**
@@ -233,7 +233,7 @@  discard block
 block discarded – undo
233 233
 	 * @param mixed $id the comment's id
234 234
 	 */
235 235
 	protected function uncache($id) {
236
-		$id = (string)$id;
236
+		$id = (string) $id;
237 237
 		if (isset($this->commentsCache[$id])) {
238 238
 			unset($this->commentsCache[$id]);
239 239
 		}
@@ -249,7 +249,7 @@  discard block
 block discarded – undo
249 249
 	 * @since 9.0.0
250 250
 	 */
251 251
 	public function get($id) {
252
-		if ((int)$id === 0) {
252
+		if ((int) $id === 0) {
253 253
 			throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.');
254 254
 		}
255 255
 
@@ -559,7 +559,7 @@  discard block
 block discarded – undo
559 559
 		$query->select('*')
560 560
 			->from('comments')
561 561
 			->where($query->expr()->iLike('message', $query->createNamedParameter(
562
-				'%' . $this->dbConn->escapeLikeParameter($search). '%'
562
+				'%'.$this->dbConn->escapeLikeParameter($search).'%'
563 563
 			)))
564 564
 			->orderBy('creation_timestamp', 'DESC')
565 565
 			->addOrderBy('id', 'DESC')
@@ -621,7 +621,7 @@  discard block
 block discarded – undo
621 621
 		$resultStatement = $query->execute();
622 622
 		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
623 623
 		$resultStatement->closeCursor();
624
-		return (int)$data[0];
624
+		return (int) $data[0];
625 625
 	}
626 626
 
627 627
 	/**
@@ -700,7 +700,7 @@  discard block
 block discarded – undo
700 700
 
701 701
 		$query = $this->dbConn->getQueryBuilder();
702 702
 		$query->select('actor_id')
703
-			->selectAlias($query->createFunction('MAX(' . $query->getColumnName('creation_timestamp') . ')'), 'last_comment')
703
+			->selectAlias($query->createFunction('MAX('.$query->getColumnName('creation_timestamp').')'), 'last_comment')
704 704
 			->from('comments')
705 705
 			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
706 706
 			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
@@ -919,7 +919,7 @@  discard block
 block discarded – undo
919 919
 			->execute();
920 920
 
921 921
 		if ($affectedRows > 0) {
922
-			$comment->setId((string)$qb->getLastInsertId());
922
+			$comment->setId((string) $qb->getLastInsertId());
923 923
 			$this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
924 924
 		}
925 925
 
@@ -1219,7 +1219,7 @@  discard block
 block discarded – undo
1219 1219
 		if (!isset($this->displayNameResolvers[$type])) {
1220 1220
 			throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1221 1221
 		}
1222
-		return (string)$this->displayNameResolvers[$type]($id);
1222
+		return (string) $this->displayNameResolvers[$type]($id);
1223 1223
 	}
1224 1224
 
1225 1225
 	/**
Please login to merge, or discard this patch.
lib/private/Comments/ManagerFactory.php 1 patch
Indentation   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -31,29 +31,29 @@
 block discarded – undo
31 31
 
32 32
 class ManagerFactory implements ICommentsManagerFactory {
33 33
 
34
-	/**
35
-	 * Server container
36
-	 *
37
-	 * @var IServerContainer
38
-	 */
39
-	private $serverContainer;
34
+    /**
35
+     * Server container
36
+     *
37
+     * @var IServerContainer
38
+     */
39
+    private $serverContainer;
40 40
 
41
-	/**
42
-	 * Constructor for the comments manager factory
43
-	 *
44
-	 * @param IServerContainer $serverContainer server container
45
-	 */
46
-	public function __construct(IServerContainer $serverContainer) {
47
-		$this->serverContainer = $serverContainer;
48
-	}
41
+    /**
42
+     * Constructor for the comments manager factory
43
+     *
44
+     * @param IServerContainer $serverContainer server container
45
+     */
46
+    public function __construct(IServerContainer $serverContainer) {
47
+        $this->serverContainer = $serverContainer;
48
+    }
49 49
 
50
-	/**
51
-	 * creates and returns an instance of the ICommentsManager
52
-	 *
53
-	 * @return ICommentsManager
54
-	 * @since 9.0.0
55
-	 */
56
-	public function getManager() {
57
-		return $this->serverContainer->get(Manager::class);
58
-	}
50
+    /**
51
+     * creates and returns an instance of the ICommentsManager
52
+     *
53
+     * @return ICommentsManager
54
+     * @since 9.0.0
55
+     */
56
+    public function getManager() {
57
+        return $this->serverContainer->get(Manager::class);
58
+    }
59 59
 }
Please login to merge, or discard this patch.
lib/public/Comments/ICommentsManager.php 1 patch
Indentation   +320 added lines, -320 removed lines patch added patch discarded remove patch
@@ -39,348 +39,348 @@
 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
185
-	 * @param string $objectId
186
-	 * @param int $lastRead
187
-	 * @param string $verb
188
-	 * @return int
189
-	 * @since 21.0.0
190
-	 */
191
-	public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int;
183
+    /**
184
+     * @param string $objectType
185
+     * @param string $objectId
186
+     * @param int $lastRead
187
+     * @param string $verb
188
+     * @return int
189
+     * @since 21.0.0
190
+     */
191
+    public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int;
192 192
 
193
-	/**
194
-	 * @param string $objectType
195
-	 * @param string $objectId
196
-	 * @param \DateTime $beforeDate
197
-	 * @param string $verb
198
-	 * @return int
199
-	 * @since 21.0.0
200
-	 */
201
-	public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int;
193
+    /**
194
+     * @param string $objectType
195
+     * @param string $objectId
196
+     * @param \DateTime $beforeDate
197
+     * @param string $verb
198
+     * @return int
199
+     * @since 21.0.0
200
+     */
201
+    public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int;
202 202
 
203
-	/**
204
-	 * @param string $objectType
205
-	 * @param string $objectId
206
-	 * @param string $verb
207
-	 * @param string $actorType
208
-	 * @param string[] $actors
209
-	 * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
210
-	 * @psalm-return array<string, \DateTime>
211
-	 * @since 21.0.0
212
-	 */
213
-	public function getLastCommentDateByActor(
214
-		string $objectType,
215
-		string $objectId,
216
-		string $verb,
217
-		string $actorType,
218
-		array $actors
219
-	): array;
203
+    /**
204
+     * @param string $objectType
205
+     * @param string $objectId
206
+     * @param string $verb
207
+     * @param string $actorType
208
+     * @param string[] $actors
209
+     * @return \DateTime[] Map of "string actor" => "\DateTime most recent comment date"
210
+     * @psalm-return array<string, \DateTime>
211
+     * @since 21.0.0
212
+     */
213
+    public function getLastCommentDateByActor(
214
+        string $objectType,
215
+        string $objectId,
216
+        string $verb,
217
+        string $actorType,
218
+        array $actors
219
+    ): array;
220 220
 
221
-	/**
222
-	 * Get the number of unread comments for all files in a folder
223
-	 *
224
-	 * @param int $folderId
225
-	 * @param IUser $user
226
-	 * @return array [$fileId => $unreadCount]
227
-	 * @since 12.0.0
228
-	 */
229
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
221
+    /**
222
+     * Get the number of unread comments for all files in a folder
223
+     *
224
+     * @param int $folderId
225
+     * @param IUser $user
226
+     * @return array [$fileId => $unreadCount]
227
+     * @since 12.0.0
228
+     */
229
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
230 230
 
231
-	/**
232
-	 * creates a new comment and returns it. At this point of time, it is not
233
-	 * saved in the used data storage. Use save() after setting other fields
234
-	 * of the comment (e.g. message or verb).
235
-	 *
236
-	 * @param string $actorType the actor type (e.g. 'users')
237
-	 * @param string $actorId a user id
238
-	 * @param string $objectType the object type the comment is attached to
239
-	 * @param string $objectId the object id the comment is attached to
240
-	 * @return IComment
241
-	 * @since 9.0.0
242
-	 */
243
-	public function create($actorType, $actorId, $objectType, $objectId);
231
+    /**
232
+     * creates a new comment and returns it. At this point of time, it is not
233
+     * saved in the used data storage. Use save() after setting other fields
234
+     * of the comment (e.g. message or verb).
235
+     *
236
+     * @param string $actorType the actor type (e.g. 'users')
237
+     * @param string $actorId a user id
238
+     * @param string $objectType the object type the comment is attached to
239
+     * @param string $objectId the object id the comment is attached to
240
+     * @return IComment
241
+     * @since 9.0.0
242
+     */
243
+    public function create($actorType, $actorId, $objectType, $objectId);
244 244
 
245
-	/**
246
-	 * permanently deletes the comment specified by the ID
247
-	 *
248
-	 * When the comment has child comments, their parent ID will be changed to
249
-	 * the parent ID of the item that is to be deleted.
250
-	 *
251
-	 * @param string $id
252
-	 * @return bool
253
-	 * @since 9.0.0
254
-	 */
255
-	public function delete($id);
245
+    /**
246
+     * permanently deletes the comment specified by the ID
247
+     *
248
+     * When the comment has child comments, their parent ID will be changed to
249
+     * the parent ID of the item that is to be deleted.
250
+     *
251
+     * @param string $id
252
+     * @return bool
253
+     * @since 9.0.0
254
+     */
255
+    public function delete($id);
256 256
 
257
-	/**
258
-	 * saves the comment permanently
259
-	 *
260
-	 * if the supplied comment has an empty ID, a new entry comment will be
261
-	 * saved and the instance updated with the new ID.
262
-	 *
263
-	 * Otherwise, an existing comment will be updated.
264
-	 *
265
-	 * Throws NotFoundException when a comment that is to be updated does not
266
-	 * exist anymore at this point of time.
267
-	 *
268
-	 * @param IComment $comment
269
-	 * @return bool
270
-	 * @throws NotFoundException
271
-	 * @since 9.0.0
272
-	 */
273
-	public function save(IComment $comment);
257
+    /**
258
+     * saves the comment permanently
259
+     *
260
+     * if the supplied comment has an empty ID, a new entry comment will be
261
+     * saved and the instance updated with the new ID.
262
+     *
263
+     * Otherwise, an existing comment will be updated.
264
+     *
265
+     * Throws NotFoundException when a comment that is to be updated does not
266
+     * exist anymore at this point of time.
267
+     *
268
+     * @param IComment $comment
269
+     * @return bool
270
+     * @throws NotFoundException
271
+     * @since 9.0.0
272
+     */
273
+    public function save(IComment $comment);
274 274
 
275
-	/**
276
-	 * removes references to specific actor (e.g. on user delete) of a comment.
277
-	 * The comment itself must not get lost/deleted.
278
-	 *
279
-	 * A 'users' type actor (type and id) should get replaced by the
280
-	 * value of the DELETED_USER constant of this interface.
281
-	 *
282
-	 * @param string $actorType the actor type (e.g. 'users')
283
-	 * @param string $actorId a user id
284
-	 * @return boolean
285
-	 * @since 9.0.0
286
-	 */
287
-	public function deleteReferencesOfActor($actorType, $actorId);
275
+    /**
276
+     * removes references to specific actor (e.g. on user delete) of a comment.
277
+     * The comment itself must not get lost/deleted.
278
+     *
279
+     * A 'users' type actor (type and id) should get replaced by the
280
+     * value of the DELETED_USER constant of this interface.
281
+     *
282
+     * @param string $actorType the actor type (e.g. 'users')
283
+     * @param string $actorId a user id
284
+     * @return boolean
285
+     * @since 9.0.0
286
+     */
287
+    public function deleteReferencesOfActor($actorType, $actorId);
288 288
 
289
-	/**
290
-	 * deletes all comments made of a specific object (e.g. on file delete)
291
-	 *
292
-	 * @param string $objectType the object type (e.g. 'files')
293
-	 * @param string $objectId e.g. the file id
294
-	 * @return boolean
295
-	 * @since 9.0.0
296
-	 */
297
-	public function deleteCommentsAtObject($objectType, $objectId);
289
+    /**
290
+     * deletes all comments made of a specific object (e.g. on file delete)
291
+     *
292
+     * @param string $objectType the object type (e.g. 'files')
293
+     * @param string $objectId e.g. the file id
294
+     * @return boolean
295
+     * @since 9.0.0
296
+     */
297
+    public function deleteCommentsAtObject($objectType, $objectId);
298 298
 
299
-	/**
300
-	 * sets the read marker for a given file to the specified date for the
301
-	 * provided user
302
-	 *
303
-	 * @param string $objectType
304
-	 * @param string $objectId
305
-	 * @param \DateTime $dateTime
306
-	 * @param \OCP\IUser $user
307
-	 * @since 9.0.0
308
-	 */
309
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
299
+    /**
300
+     * sets the read marker for a given file to the specified date for the
301
+     * provided user
302
+     *
303
+     * @param string $objectType
304
+     * @param string $objectId
305
+     * @param \DateTime $dateTime
306
+     * @param \OCP\IUser $user
307
+     * @since 9.0.0
308
+     */
309
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
310 310
 
311
-	/**
312
-	 * returns the read marker for a given file to the specified date for the
313
-	 * provided user. It returns null, when the marker is not present, i.e.
314
-	 * no comments were marked as read.
315
-	 *
316
-	 * @param string $objectType
317
-	 * @param string $objectId
318
-	 * @param \OCP\IUser $user
319
-	 * @return \DateTime|null
320
-	 * @since 9.0.0
321
-	 */
322
-	public function getReadMark($objectType, $objectId, \OCP\IUser $user);
311
+    /**
312
+     * returns the read marker for a given file to the specified date for the
313
+     * provided user. It returns null, when the marker is not present, i.e.
314
+     * no comments were marked as read.
315
+     *
316
+     * @param string $objectType
317
+     * @param string $objectId
318
+     * @param \OCP\IUser $user
319
+     * @return \DateTime|null
320
+     * @since 9.0.0
321
+     */
322
+    public function getReadMark($objectType, $objectId, \OCP\IUser $user);
323 323
 
324
-	/**
325
-	 * deletes the read markers for the specified user
326
-	 *
327
-	 * @param \OCP\IUser $user
328
-	 * @return bool
329
-	 * @since 9.0.0
330
-	 */
331
-	public function deleteReadMarksFromUser(\OCP\IUser $user);
324
+    /**
325
+     * deletes the read markers for the specified user
326
+     *
327
+     * @param \OCP\IUser $user
328
+     * @return bool
329
+     * @since 9.0.0
330
+     */
331
+    public function deleteReadMarksFromUser(\OCP\IUser $user);
332 332
 
333
-	/**
334
-	 * deletes the read markers on the specified object
335
-	 *
336
-	 * @param string $objectType
337
-	 * @param string $objectId
338
-	 * @return bool
339
-	 * @since 9.0.0
340
-	 */
341
-	public function deleteReadMarksOnObject($objectType, $objectId);
333
+    /**
334
+     * deletes the read markers on the specified object
335
+     *
336
+     * @param string $objectType
337
+     * @param string $objectId
338
+     * @return bool
339
+     * @since 9.0.0
340
+     */
341
+    public function deleteReadMarksOnObject($objectType, $objectId);
342 342
 
343
-	/**
344
-	 * registers an Entity to the manager, so event notifications can be send
345
-	 * to consumers of the comments infrastructure
346
-	 *
347
-	 * @param \Closure $closure
348
-	 * @since 11.0.0
349
-	 */
350
-	public function registerEventHandler(\Closure $closure);
343
+    /**
344
+     * registers an Entity to the manager, so event notifications can be send
345
+     * to consumers of the comments infrastructure
346
+     *
347
+     * @param \Closure $closure
348
+     * @since 11.0.0
349
+     */
350
+    public function registerEventHandler(\Closure $closure);
351 351
 
352
-	/**
353
-	 * registers a method that resolves an ID to a display name for a given type
354
-	 *
355
-	 * @param string $type
356
-	 * @param \Closure $closure
357
-	 * @throws \OutOfBoundsException
358
-	 * @since 11.0.0
359
-	 *
360
-	 * Only one resolver shall be registered per type. Otherwise a
361
-	 * \OutOfBoundsException has to thrown.
362
-	 */
363
-	public function registerDisplayNameResolver($type, \Closure $closure);
352
+    /**
353
+     * registers a method that resolves an ID to a display name for a given type
354
+     *
355
+     * @param string $type
356
+     * @param \Closure $closure
357
+     * @throws \OutOfBoundsException
358
+     * @since 11.0.0
359
+     *
360
+     * Only one resolver shall be registered per type. Otherwise a
361
+     * \OutOfBoundsException has to thrown.
362
+     */
363
+    public function registerDisplayNameResolver($type, \Closure $closure);
364 364
 
365
-	/**
366
-	 * resolves a given ID of a given Type to a display name.
367
-	 *
368
-	 * @param string $type
369
-	 * @param string $id
370
-	 * @return string
371
-	 * @throws \OutOfBoundsException
372
-	 * @since 11.0.0
373
-	 *
374
-	 * If a provided type was not registered, an \OutOfBoundsException shall
375
-	 * be thrown. It is upon the resolver discretion what to return of the
376
-	 * provided ID is unknown. It must be ensured that a string is returned.
377
-	 */
378
-	public function resolveDisplayName($type, $id);
365
+    /**
366
+     * resolves a given ID of a given Type to a display name.
367
+     *
368
+     * @param string $type
369
+     * @param string $id
370
+     * @return string
371
+     * @throws \OutOfBoundsException
372
+     * @since 11.0.0
373
+     *
374
+     * If a provided type was not registered, an \OutOfBoundsException shall
375
+     * be thrown. It is upon the resolver discretion what to return of the
376
+     * provided ID is unknown. It must be ensured that a string is returned.
377
+     */
378
+    public function resolveDisplayName($type, $id);
379 379
 
380
-	/**
381
-	 * Load the Comments app into the page
382
-	 *
383
-	 * @since 21.0.0
384
-	 */
385
-	public function load(): void;
380
+    /**
381
+     * Load the Comments app into the page
382
+     *
383
+     * @since 21.0.0
384
+     */
385
+    public function load(): void;
386 386
 }
Please login to merge, or discard this patch.