Completed
Push — master ( ef85ef...c39bc1 )
by Blizzz
59:43 queued 32:51
created
lib/private/Comments/Manager.php 2 patches
Indentation   +1016 added lines, -1016 removed lines patch added patch discarded remove patch
@@ -41,1020 +41,1020 @@
 block discarded – undo
41 41
 
42 42
 class Manager implements ICommentsManager {
43 43
 
44
-	/** @var  IDBConnection */
45
-	protected $dbConn;
46
-
47
-	/** @var  ILogger */
48
-	protected $logger;
49
-
50
-	/** @var IConfig */
51
-	protected $config;
52
-
53
-	/** @var IComment[] */
54
-	protected $commentsCache = [];
55
-
56
-	/** @var  \Closure[] */
57
-	protected $eventHandlerClosures = [];
58
-
59
-	/** @var  ICommentsEventHandler[] */
60
-	protected $eventHandlers = [];
61
-
62
-	/** @var \Closure[] */
63
-	protected $displayNameResolvers = [];
64
-
65
-	/**
66
-	 * Manager constructor.
67
-	 *
68
-	 * @param IDBConnection $dbConn
69
-	 * @param ILogger $logger
70
-	 * @param IConfig $config
71
-	 */
72
-	public function __construct(
73
-		IDBConnection $dbConn,
74
-		ILogger $logger,
75
-		IConfig $config
76
-	) {
77
-		$this->dbConn = $dbConn;
78
-		$this->logger = $logger;
79
-		$this->config = $config;
80
-	}
81
-
82
-	/**
83
-	 * converts data base data into PHP native, proper types as defined by
84
-	 * IComment interface.
85
-	 *
86
-	 * @param array $data
87
-	 * @return array
88
-	 */
89
-	protected function normalizeDatabaseData(array $data) {
90
-		$data['id'] = (string)$data['id'];
91
-		$data['parent_id'] = (string)$data['parent_id'];
92
-		$data['topmost_parent_id'] = (string)$data['topmost_parent_id'];
93
-		$data['creation_timestamp'] = new \DateTime($data['creation_timestamp']);
94
-		if (!is_null($data['latest_child_timestamp'])) {
95
-			$data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']);
96
-		}
97
-		$data['children_count'] = (int)$data['children_count'];
98
-		return $data;
99
-	}
100
-
101
-	/**
102
-	 * prepares a comment for an insert or update operation after making sure
103
-	 * all necessary fields have a value assigned.
104
-	 *
105
-	 * @param IComment $comment
106
-	 * @return IComment returns the same updated IComment instance as provided
107
-	 *                  by parameter for convenience
108
-	 * @throws \UnexpectedValueException
109
-	 */
110
-	protected function prepareCommentForDatabaseWrite(IComment $comment) {
111
-		if (!$comment->getActorType()
112
-			|| !$comment->getActorId()
113
-			|| !$comment->getObjectType()
114
-			|| !$comment->getObjectId()
115
-			|| !$comment->getVerb()
116
-		) {
117
-			throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving');
118
-		}
119
-
120
-		if ($comment->getId() === '') {
121
-			$comment->setChildrenCount(0);
122
-			$comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC')));
123
-			$comment->setLatestChildDateTime(null);
124
-		}
125
-
126
-		if (is_null($comment->getCreationDateTime())) {
127
-			$comment->setCreationDateTime(new \DateTime());
128
-		}
129
-
130
-		if ($comment->getParentId() !== '0') {
131
-			$comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId()));
132
-		} else {
133
-			$comment->setTopmostParentId('0');
134
-		}
135
-
136
-		$this->cache($comment);
137
-
138
-		return $comment;
139
-	}
140
-
141
-	/**
142
-	 * returns the topmost parent id of a given comment identified by ID
143
-	 *
144
-	 * @param string $id
145
-	 * @return string
146
-	 * @throws NotFoundException
147
-	 */
148
-	protected function determineTopmostParentId($id) {
149
-		$comment = $this->get($id);
150
-		if ($comment->getParentId() === '0') {
151
-			return $comment->getId();
152
-		} else {
153
-			return $this->determineTopmostParentId($comment->getId());
154
-		}
155
-	}
156
-
157
-	/**
158
-	 * updates child information of a comment
159
-	 *
160
-	 * @param string $id
161
-	 * @param \DateTime $cDateTime the date time of the most recent child
162
-	 * @throws NotFoundException
163
-	 */
164
-	protected function updateChildrenInformation($id, \DateTime $cDateTime) {
165
-		$qb = $this->dbConn->getQueryBuilder();
166
-		$query = $qb->select($qb->createFunction('COUNT(`id`)'))
167
-			->from('comments')
168
-			->where($qb->expr()->eq('parent_id', $qb->createParameter('id')))
169
-			->setParameter('id', $id);
170
-
171
-		$resultStatement = $query->execute();
172
-		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
173
-		$resultStatement->closeCursor();
174
-		$children = (int)$data[0];
175
-
176
-		$comment = $this->get($id);
177
-		$comment->setChildrenCount($children);
178
-		$comment->setLatestChildDateTime($cDateTime);
179
-		$this->save($comment);
180
-	}
181
-
182
-	/**
183
-	 * Tests whether actor or object type and id parameters are acceptable.
184
-	 * Throws exception if not.
185
-	 *
186
-	 * @param string $role
187
-	 * @param string $type
188
-	 * @param string $id
189
-	 * @throws \InvalidArgumentException
190
-	 */
191
-	protected function checkRoleParameters($role, $type, $id) {
192
-		if (
193
-			!is_string($type) || empty($type)
194
-			|| !is_string($id) || empty($id)
195
-		) {
196
-			throw new \InvalidArgumentException($role . ' parameters must be string and not empty');
197
-		}
198
-	}
199
-
200
-	/**
201
-	 * run-time caches a comment
202
-	 *
203
-	 * @param IComment $comment
204
-	 */
205
-	protected function cache(IComment $comment) {
206
-		$id = $comment->getId();
207
-		if (empty($id)) {
208
-			return;
209
-		}
210
-		$this->commentsCache[(string)$id] = $comment;
211
-	}
212
-
213
-	/**
214
-	 * removes an entry from the comments run time cache
215
-	 *
216
-	 * @param mixed $id the comment's id
217
-	 */
218
-	protected function uncache($id) {
219
-		$id = (string)$id;
220
-		if (isset($this->commentsCache[$id])) {
221
-			unset($this->commentsCache[$id]);
222
-		}
223
-	}
224
-
225
-	/**
226
-	 * returns a comment instance
227
-	 *
228
-	 * @param string $id the ID of the comment
229
-	 * @return IComment
230
-	 * @throws NotFoundException
231
-	 * @throws \InvalidArgumentException
232
-	 * @since 9.0.0
233
-	 */
234
-	public function get($id) {
235
-		if ((int)$id === 0) {
236
-			throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.');
237
-		}
238
-
239
-		if (isset($this->commentsCache[$id])) {
240
-			return $this->commentsCache[$id];
241
-		}
242
-
243
-		$qb = $this->dbConn->getQueryBuilder();
244
-		$resultStatement = $qb->select('*')
245
-			->from('comments')
246
-			->where($qb->expr()->eq('id', $qb->createParameter('id')))
247
-			->setParameter('id', $id, IQueryBuilder::PARAM_INT)
248
-			->execute();
249
-
250
-		$data = $resultStatement->fetch();
251
-		$resultStatement->closeCursor();
252
-		if (!$data) {
253
-			throw new NotFoundException();
254
-		}
255
-
256
-		$comment = new Comment($this->normalizeDatabaseData($data));
257
-		$this->cache($comment);
258
-		return $comment;
259
-	}
260
-
261
-	/**
262
-	 * returns the comment specified by the id and all it's child comments.
263
-	 * At this point of time, we do only support one level depth.
264
-	 *
265
-	 * @param string $id
266
-	 * @param int $limit max number of entries to return, 0 returns all
267
-	 * @param int $offset the start entry
268
-	 * @return array
269
-	 * @since 9.0.0
270
-	 *
271
-	 * The return array looks like this
272
-	 * [
273
-	 *   'comment' => IComment, // root comment
274
-	 *   'replies' =>
275
-	 *   [
276
-	 *     0 =>
277
-	 *     [
278
-	 *       'comment' => IComment,
279
-	 *       'replies' => []
280
-	 *     ]
281
-	 *     1 =>
282
-	 *     [
283
-	 *       'comment' => IComment,
284
-	 *       'replies'=> []
285
-	 *     ],
286
-	 *     …
287
-	 *   ]
288
-	 * ]
289
-	 */
290
-	public function getTree($id, $limit = 0, $offset = 0) {
291
-		$tree = [];
292
-		$tree['comment'] = $this->get($id);
293
-		$tree['replies'] = [];
294
-
295
-		$qb = $this->dbConn->getQueryBuilder();
296
-		$query = $qb->select('*')
297
-			->from('comments')
298
-			->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id')))
299
-			->orderBy('creation_timestamp', 'DESC')
300
-			->setParameter('id', $id);
301
-
302
-		if ($limit > 0) {
303
-			$query->setMaxResults($limit);
304
-		}
305
-		if ($offset > 0) {
306
-			$query->setFirstResult($offset);
307
-		}
308
-
309
-		$resultStatement = $query->execute();
310
-		while ($data = $resultStatement->fetch()) {
311
-			$comment = new Comment($this->normalizeDatabaseData($data));
312
-			$this->cache($comment);
313
-			$tree['replies'][] = [
314
-				'comment' => $comment,
315
-				'replies' => []
316
-			];
317
-		}
318
-		$resultStatement->closeCursor();
319
-
320
-		return $tree;
321
-	}
322
-
323
-	/**
324
-	 * returns comments for a specific object (e.g. a file).
325
-	 *
326
-	 * The sort order is always newest to oldest.
327
-	 *
328
-	 * @param string $objectType the object type, e.g. 'files'
329
-	 * @param string $objectId the id of the object
330
-	 * @param int $limit optional, number of maximum comments to be returned. if
331
-	 * not specified, all comments are returned.
332
-	 * @param int $offset optional, starting point
333
-	 * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
334
-	 * that may be returned
335
-	 * @return IComment[]
336
-	 * @since 9.0.0
337
-	 */
338
-	public function getForObject(
339
-		$objectType,
340
-		$objectId,
341
-		$limit = 0,
342
-		$offset = 0,
343
-		\DateTime $notOlderThan = null
344
-	) {
345
-		$comments = [];
346
-
347
-		$qb = $this->dbConn->getQueryBuilder();
348
-		$query = $qb->select('*')
349
-			->from('comments')
350
-			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
351
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
352
-			->orderBy('creation_timestamp', 'DESC')
353
-			->setParameter('type', $objectType)
354
-			->setParameter('id', $objectId);
355
-
356
-		if ($limit > 0) {
357
-			$query->setMaxResults($limit);
358
-		}
359
-		if ($offset > 0) {
360
-			$query->setFirstResult($offset);
361
-		}
362
-		if (!is_null($notOlderThan)) {
363
-			$query
364
-				->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
365
-				->setParameter('notOlderThan', $notOlderThan, 'datetime');
366
-		}
367
-
368
-		$resultStatement = $query->execute();
369
-		while ($data = $resultStatement->fetch()) {
370
-			$comment = new Comment($this->normalizeDatabaseData($data));
371
-			$this->cache($comment);
372
-			$comments[] = $comment;
373
-		}
374
-		$resultStatement->closeCursor();
375
-
376
-		return $comments;
377
-	}
378
-
379
-	/**
380
-	 * @param string $objectType the object type, e.g. 'files'
381
-	 * @param string $objectId the id of the object
382
-	 * @param int $lastKnownCommentId the last known comment (will be used as offset)
383
-	 * @param string $sortDirection direction of the comments (`asc` or `desc`)
384
-	 * @param int $limit optional, number of maximum comments to be returned. if
385
-	 * set to 0, all comments are returned.
386
-	 * @return IComment[]
387
-	 * @return array
388
-	 */
389
-	public function getForObjectSince(
390
-		string $objectType,
391
-		string $objectId,
392
-		int $lastKnownCommentId,
393
-		string $sortDirection = 'asc',
394
-		int $limit = 30
395
-	): array {
396
-		$comments = [];
397
-
398
-		$query = $this->dbConn->getQueryBuilder();
399
-		$query->select('*')
400
-			->from('comments')
401
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
402
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
403
-			->orderBy('creation_timestamp', $sortDirection === 'desc' ? 'DESC' : 'ASC')
404
-			->addOrderBy('id', $sortDirection === 'desc' ? 'DESC' : 'ASC');
405
-
406
-		if ($limit > 0) {
407
-			$query->setMaxResults($limit);
408
-		}
409
-
410
-		$lastKnownComment = $lastKnownCommentId > 0 ? $this->getLastKnownComment(
411
-			$objectType,
412
-			$objectId,
413
-			$lastKnownCommentId
414
-		) : null;
415
-		if ($lastKnownComment instanceof IComment) {
416
-			$lastKnownCommentDateTime = $lastKnownComment->getCreationDateTime();
417
-			if ($sortDirection === 'desc') {
418
-				$query->andWhere(
419
-					$query->expr()->orX(
420
-						$query->expr()->lt(
421
-							'creation_timestamp',
422
-							$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
423
-							IQueryBuilder::PARAM_DATE
424
-						),
425
-						$query->expr()->andX(
426
-							$query->expr()->eq(
427
-								'creation_timestamp',
428
-								$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
429
-								IQueryBuilder::PARAM_DATE
430
-							),
431
-							$query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId))
432
-						)
433
-					)
434
-				);
435
-			} else {
436
-				$query->andWhere(
437
-					$query->expr()->orX(
438
-						$query->expr()->gt(
439
-							'creation_timestamp',
440
-							$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
441
-							IQueryBuilder::PARAM_DATE
442
-						),
443
-						$query->expr()->andX(
444
-							$query->expr()->eq(
445
-								'creation_timestamp',
446
-								$query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
447
-								IQueryBuilder::PARAM_DATE
448
-							),
449
-							$query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId))
450
-						)
451
-					)
452
-				);
453
-			}
454
-		}
455
-
456
-		$resultStatement = $query->execute();
457
-		while ($data = $resultStatement->fetch()) {
458
-			$comment = new Comment($this->normalizeDatabaseData($data));
459
-			$this->cache($comment);
460
-			$comments[] = $comment;
461
-		}
462
-		$resultStatement->closeCursor();
463
-
464
-		return $comments;
465
-	}
466
-
467
-	/**
468
-	 * @param string $objectType the object type, e.g. 'files'
469
-	 * @param string $objectId the id of the object
470
-	 * @param int $id the comment to look for
471
-	 * @return Comment|null
472
-	 */
473
-	protected function getLastKnownComment(string $objectType,
474
-										   string $objectId,
475
-										   int $id) {
476
-		$query = $this->dbConn->getQueryBuilder();
477
-		$query->select('*')
478
-			->from('comments')
479
-			->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
480
-			->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
481
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
482
-
483
-		$result = $query->execute();
484
-		$row = $result->fetch();
485
-		$result->closeCursor();
486
-
487
-		if ($row) {
488
-			$comment = new Comment($this->normalizeDatabaseData($row));
489
-			$this->cache($comment);
490
-			return $comment;
491
-		}
492
-
493
-		return null;
494
-	}
495
-
496
-	/**
497
-	 * Search for comments with a given content
498
-	 *
499
-	 * @param string $search content to search for
500
-	 * @param string $objectType Limit the search by object type
501
-	 * @param string $objectId Limit the search by object id
502
-	 * @param string $verb Limit the verb of the comment
503
-	 * @param int $offset
504
-	 * @param int $limit
505
-	 * @return IComment[]
506
-	 */
507
-	public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array {
508
-		$query = $this->dbConn->getQueryBuilder();
509
-
510
-		$query->select('*')
511
-			->from('comments')
512
-			->where($query->expr()->iLike('message', $query->createNamedParameter(
513
-				'%' . $this->dbConn->escapeLikeParameter($search). '%'
514
-			)))
515
-			->orderBy('creation_timestamp', 'DESC')
516
-			->addOrderBy('id', 'DESC')
517
-			->setMaxResults($limit);
518
-
519
-		if ($objectType !== '') {
520
-			$query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType)));
521
-		}
522
-		if ($objectId !== '') {
523
-			$query->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)));
524
-		}
525
-		if ($verb !== '') {
526
-			$query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
527
-		}
528
-		if ($offset !== 0) {
529
-			$query->setFirstResult($offset);
530
-		}
531
-
532
-		$comments = [];
533
-		$result = $query->execute();
534
-		while ($data = $result->fetch()) {
535
-			$comment = new Comment($this->normalizeDatabaseData($data));
536
-			$this->cache($comment);
537
-			$comments[] = $comment;
538
-		}
539
-		$result->closeCursor();
540
-
541
-		return $comments;
542
-	}
543
-
544
-	/**
545
-	 * @param $objectType string the object type, e.g. 'files'
546
-	 * @param $objectId string the id of the object
547
-	 * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
548
-	 * that may be returned
549
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
550
-	 * @return Int
551
-	 * @since 9.0.0
552
-	 */
553
-	public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '') {
554
-		$qb = $this->dbConn->getQueryBuilder();
555
-		$query = $qb->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
556
-			->from('comments')
557
-			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
558
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
559
-			->setParameter('type', $objectType)
560
-			->setParameter('id', $objectId);
561
-
562
-		if (!is_null($notOlderThan)) {
563
-			$query
564
-				->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
565
-				->setParameter('notOlderThan', $notOlderThan, 'datetime');
566
-		}
567
-
568
-		if ($verb !== '') {
569
-			$query->andWhere($qb->expr()->eq('verb', $qb->createNamedParameter($verb)));
570
-		}
571
-
572
-		$resultStatement = $query->execute();
573
-		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
574
-		$resultStatement->closeCursor();
575
-		return (int)$data[0];
576
-	}
577
-
578
-	/**
579
-	 * Get the number of unread comments for all files in a folder
580
-	 *
581
-	 * @param int $folderId
582
-	 * @param IUser $user
583
-	 * @return array [$fileId => $unreadCount]
584
-	 */
585
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) {
586
-		$qb = $this->dbConn->getQueryBuilder();
587
-		$query = $qb->select('f.fileid')
588
-			->selectAlias(
589
-				$qb->createFunction('COUNT(' . $qb->getColumnName('c.id') . ')'),
590
-				'num_ids'
591
-			)
592
-			->from('comments', 'c')
593
-			->innerJoin('c', 'filecache', 'f', $qb->expr()->andX(
594
-				$qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')),
595
-				$qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT))
596
-			))
597
-			->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX(
598
-				$qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')),
599
-				$qb->expr()->eq('m.object_id', 'c.object_id'),
600
-				$qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID()))
601
-			))
602
-			->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)))
603
-			->andWhere($qb->expr()->orX(
604
-				$qb->expr()->gt('c.creation_timestamp', 'marker_datetime'),
605
-				$qb->expr()->isNull('marker_datetime')
606
-			))
607
-			->groupBy('f.fileid');
608
-
609
-		$resultStatement = $query->execute();
610
-
611
-		$results = [];
612
-		while ($row = $resultStatement->fetch()) {
613
-			$results[$row['fileid']] = (int) $row['num_ids'];
614
-		}
615
-		$resultStatement->closeCursor();
616
-		return $results;
617
-	}
618
-
619
-	/**
620
-	 * creates a new comment and returns it. At this point of time, it is not
621
-	 * saved in the used data storage. Use save() after setting other fields
622
-	 * of the comment (e.g. message or verb).
623
-	 *
624
-	 * @param string $actorType the actor type (e.g. 'users')
625
-	 * @param string $actorId a user id
626
-	 * @param string $objectType the object type the comment is attached to
627
-	 * @param string $objectId the object id the comment is attached to
628
-	 * @return IComment
629
-	 * @since 9.0.0
630
-	 */
631
-	public function create($actorType, $actorId, $objectType, $objectId) {
632
-		$comment = new Comment();
633
-		$comment
634
-			->setActor($actorType, $actorId)
635
-			->setObject($objectType, $objectId);
636
-		return $comment;
637
-	}
638
-
639
-	/**
640
-	 * permanently deletes the comment specified by the ID
641
-	 *
642
-	 * When the comment has child comments, their parent ID will be changed to
643
-	 * the parent ID of the item that is to be deleted.
644
-	 *
645
-	 * @param string $id
646
-	 * @return bool
647
-	 * @throws \InvalidArgumentException
648
-	 * @since 9.0.0
649
-	 */
650
-	public function delete($id) {
651
-		if (!is_string($id)) {
652
-			throw new \InvalidArgumentException('Parameter must be string');
653
-		}
654
-
655
-		try {
656
-			$comment = $this->get($id);
657
-		} catch (\Exception $e) {
658
-			// Ignore exceptions, we just don't fire a hook then
659
-			$comment = null;
660
-		}
661
-
662
-		$qb = $this->dbConn->getQueryBuilder();
663
-		$query = $qb->delete('comments')
664
-			->where($qb->expr()->eq('id', $qb->createParameter('id')))
665
-			->setParameter('id', $id);
666
-
667
-		try {
668
-			$affectedRows = $query->execute();
669
-			$this->uncache($id);
670
-		} catch (DriverException $e) {
671
-			$this->logger->logException($e, ['app' => 'core_comments']);
672
-			return false;
673
-		}
674
-
675
-		if ($affectedRows > 0 && $comment instanceof IComment) {
676
-			$this->sendEvent(CommentsEvent::EVENT_DELETE, $comment);
677
-		}
678
-
679
-		return ($affectedRows > 0);
680
-	}
681
-
682
-	/**
683
-	 * saves the comment permanently
684
-	 *
685
-	 * if the supplied comment has an empty ID, a new entry comment will be
686
-	 * saved and the instance updated with the new ID.
687
-	 *
688
-	 * Otherwise, an existing comment will be updated.
689
-	 *
690
-	 * Throws NotFoundException when a comment that is to be updated does not
691
-	 * exist anymore at this point of time.
692
-	 *
693
-	 * @param IComment $comment
694
-	 * @return bool
695
-	 * @throws NotFoundException
696
-	 * @since 9.0.0
697
-	 */
698
-	public function save(IComment $comment) {
699
-		if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') {
700
-			$result = $this->insert($comment);
701
-		} else {
702
-			$result = $this->update($comment);
703
-		}
704
-
705
-		if ($result && !!$comment->getParentId()) {
706
-			$this->updateChildrenInformation(
707
-				$comment->getParentId(),
708
-				$comment->getCreationDateTime()
709
-			);
710
-			$this->cache($comment);
711
-		}
712
-
713
-		return $result;
714
-	}
715
-
716
-	/**
717
-	 * inserts the provided comment in the database
718
-	 *
719
-	 * @param IComment $comment
720
-	 * @return bool
721
-	 */
722
-	protected function insert(IComment &$comment) {
723
-		$qb = $this->dbConn->getQueryBuilder();
724
-		$affectedRows = $qb
725
-			->insert('comments')
726
-			->values([
727
-				'parent_id' => $qb->createNamedParameter($comment->getParentId()),
728
-				'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
729
-				'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
730
-				'actor_type' => $qb->createNamedParameter($comment->getActorType()),
731
-				'actor_id' => $qb->createNamedParameter($comment->getActorId()),
732
-				'message' => $qb->createNamedParameter($comment->getMessage()),
733
-				'verb' => $qb->createNamedParameter($comment->getVerb()),
734
-				'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
735
-				'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
736
-				'object_type' => $qb->createNamedParameter($comment->getObjectType()),
737
-				'object_id' => $qb->createNamedParameter($comment->getObjectId()),
738
-			])
739
-			->execute();
740
-
741
-		if ($affectedRows > 0) {
742
-			$comment->setId((string)$qb->getLastInsertId());
743
-			$this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
744
-		}
745
-
746
-		return $affectedRows > 0;
747
-	}
748
-
749
-	/**
750
-	 * updates a Comment data row
751
-	 *
752
-	 * @param IComment $comment
753
-	 * @return bool
754
-	 * @throws NotFoundException
755
-	 */
756
-	protected function update(IComment $comment) {
757
-		// for properly working preUpdate Events we need the old comments as is
758
-		// in the DB and overcome caching. Also avoid that outdated information stays.
759
-		$this->uncache($comment->getId());
760
-		$this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
761
-		$this->uncache($comment->getId());
762
-
763
-		$qb = $this->dbConn->getQueryBuilder();
764
-		$affectedRows = $qb
765
-			->update('comments')
766
-			->set('parent_id', $qb->createNamedParameter($comment->getParentId()))
767
-			->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId()))
768
-			->set('children_count', $qb->createNamedParameter($comment->getChildrenCount()))
769
-			->set('actor_type', $qb->createNamedParameter($comment->getActorType()))
770
-			->set('actor_id', $qb->createNamedParameter($comment->getActorId()))
771
-			->set('message', $qb->createNamedParameter($comment->getMessage()))
772
-			->set('verb', $qb->createNamedParameter($comment->getVerb()))
773
-			->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'))
774
-			->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
775
-			->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
776
-			->set('object_id', $qb->createNamedParameter($comment->getObjectId()))
777
-			->where($qb->expr()->eq('id', $qb->createParameter('id')))
778
-			->setParameter('id', $comment->getId())
779
-			->execute();
780
-
781
-		if ($affectedRows === 0) {
782
-			throw new NotFoundException('Comment to update does ceased to exist');
783
-		}
784
-
785
-		$this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment);
786
-
787
-		return $affectedRows > 0;
788
-	}
789
-
790
-	/**
791
-	 * removes references to specific actor (e.g. on user delete) of a comment.
792
-	 * The comment itself must not get lost/deleted.
793
-	 *
794
-	 * @param string $actorType the actor type (e.g. 'users')
795
-	 * @param string $actorId a user id
796
-	 * @return boolean
797
-	 * @since 9.0.0
798
-	 */
799
-	public function deleteReferencesOfActor($actorType, $actorId) {
800
-		$this->checkRoleParameters('Actor', $actorType, $actorId);
801
-
802
-		$qb = $this->dbConn->getQueryBuilder();
803
-		$affectedRows = $qb
804
-			->update('comments')
805
-			->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
806
-			->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
807
-			->where($qb->expr()->eq('actor_type', $qb->createParameter('type')))
808
-			->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id')))
809
-			->setParameter('type', $actorType)
810
-			->setParameter('id', $actorId)
811
-			->execute();
812
-
813
-		$this->commentsCache = [];
814
-
815
-		return is_int($affectedRows);
816
-	}
817
-
818
-	/**
819
-	 * deletes all comments made of a specific object (e.g. on file delete)
820
-	 *
821
-	 * @param string $objectType the object type (e.g. 'files')
822
-	 * @param string $objectId e.g. the file id
823
-	 * @return boolean
824
-	 * @since 9.0.0
825
-	 */
826
-	public function deleteCommentsAtObject($objectType, $objectId) {
827
-		$this->checkRoleParameters('Object', $objectType, $objectId);
828
-
829
-		$qb = $this->dbConn->getQueryBuilder();
830
-		$affectedRows = $qb
831
-			->delete('comments')
832
-			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
833
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
834
-			->setParameter('type', $objectType)
835
-			->setParameter('id', $objectId)
836
-			->execute();
837
-
838
-		$this->commentsCache = [];
839
-
840
-		return is_int($affectedRows);
841
-	}
842
-
843
-	/**
844
-	 * deletes the read markers for the specified user
845
-	 *
846
-	 * @param \OCP\IUser $user
847
-	 * @return bool
848
-	 * @since 9.0.0
849
-	 */
850
-	public function deleteReadMarksFromUser(IUser $user) {
851
-		$qb = $this->dbConn->getQueryBuilder();
852
-		$query = $qb->delete('comments_read_markers')
853
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
854
-			->setParameter('user_id', $user->getUID());
855
-
856
-		try {
857
-			$affectedRows = $query->execute();
858
-		} catch (DriverException $e) {
859
-			$this->logger->logException($e, ['app' => 'core_comments']);
860
-			return false;
861
-		}
862
-		return ($affectedRows > 0);
863
-	}
864
-
865
-	/**
866
-	 * sets the read marker for a given file to the specified date for the
867
-	 * provided user
868
-	 *
869
-	 * @param string $objectType
870
-	 * @param string $objectId
871
-	 * @param \DateTime $dateTime
872
-	 * @param IUser $user
873
-	 * @since 9.0.0
874
-	 * @suppress SqlInjectionChecker
875
-	 */
876
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) {
877
-		$this->checkRoleParameters('Object', $objectType, $objectId);
878
-
879
-		$qb = $this->dbConn->getQueryBuilder();
880
-		$values = [
881
-			'user_id' => $qb->createNamedParameter($user->getUID()),
882
-			'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'),
883
-			'object_type' => $qb->createNamedParameter($objectType),
884
-			'object_id' => $qb->createNamedParameter($objectId),
885
-		];
886
-
887
-		// Strategy: try to update, if this does not return affected rows, do an insert.
888
-		$affectedRows = $qb
889
-			->update('comments_read_markers')
890
-			->set('user_id', $values['user_id'])
891
-			->set('marker_datetime', $values['marker_datetime'])
892
-			->set('object_type', $values['object_type'])
893
-			->set('object_id', $values['object_id'])
894
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
895
-			->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
896
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
897
-			->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
898
-			->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
899
-			->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
900
-			->execute();
901
-
902
-		if ($affectedRows > 0) {
903
-			return;
904
-		}
905
-
906
-		$qb->insert('comments_read_markers')
907
-			->values($values)
908
-			->execute();
909
-	}
910
-
911
-	/**
912
-	 * returns the read marker for a given file to the specified date for the
913
-	 * provided user. It returns null, when the marker is not present, i.e.
914
-	 * no comments were marked as read.
915
-	 *
916
-	 * @param string $objectType
917
-	 * @param string $objectId
918
-	 * @param IUser $user
919
-	 * @return \DateTime|null
920
-	 * @since 9.0.0
921
-	 */
922
-	public function getReadMark($objectType, $objectId, IUser $user) {
923
-		$qb = $this->dbConn->getQueryBuilder();
924
-		$resultStatement = $qb->select('marker_datetime')
925
-			->from('comments_read_markers')
926
-			->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
927
-			->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
928
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
929
-			->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
930
-			->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
931
-			->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
932
-			->execute();
933
-
934
-		$data = $resultStatement->fetch();
935
-		$resultStatement->closeCursor();
936
-		if (!$data || is_null($data['marker_datetime'])) {
937
-			return null;
938
-		}
939
-
940
-		return new \DateTime($data['marker_datetime']);
941
-	}
942
-
943
-	/**
944
-	 * deletes the read markers on the specified object
945
-	 *
946
-	 * @param string $objectType
947
-	 * @param string $objectId
948
-	 * @return bool
949
-	 * @since 9.0.0
950
-	 */
951
-	public function deleteReadMarksOnObject($objectType, $objectId) {
952
-		$this->checkRoleParameters('Object', $objectType, $objectId);
953
-
954
-		$qb = $this->dbConn->getQueryBuilder();
955
-		$query = $qb->delete('comments_read_markers')
956
-			->where($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
957
-			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
958
-			->setParameter('object_type', $objectType)
959
-			->setParameter('object_id', $objectId);
960
-
961
-		try {
962
-			$affectedRows = $query->execute();
963
-		} catch (DriverException $e) {
964
-			$this->logger->logException($e, ['app' => 'core_comments']);
965
-			return false;
966
-		}
967
-		return ($affectedRows > 0);
968
-	}
969
-
970
-	/**
971
-	 * registers an Entity to the manager, so event notifications can be send
972
-	 * to consumers of the comments infrastructure
973
-	 *
974
-	 * @param \Closure $closure
975
-	 */
976
-	public function registerEventHandler(\Closure $closure) {
977
-		$this->eventHandlerClosures[] = $closure;
978
-		$this->eventHandlers = [];
979
-	}
980
-
981
-	/**
982
-	 * registers a method that resolves an ID to a display name for a given type
983
-	 *
984
-	 * @param string $type
985
-	 * @param \Closure $closure
986
-	 * @throws \OutOfBoundsException
987
-	 * @since 11.0.0
988
-	 *
989
-	 * Only one resolver shall be registered per type. Otherwise a
990
-	 * \OutOfBoundsException has to thrown.
991
-	 */
992
-	public function registerDisplayNameResolver($type, \Closure $closure) {
993
-		if (!is_string($type)) {
994
-			throw new \InvalidArgumentException('String expected.');
995
-		}
996
-		if (isset($this->displayNameResolvers[$type])) {
997
-			throw new \OutOfBoundsException('Displayname resolver for this type already registered');
998
-		}
999
-		$this->displayNameResolvers[$type] = $closure;
1000
-	}
1001
-
1002
-	/**
1003
-	 * resolves a given ID of a given Type to a display name.
1004
-	 *
1005
-	 * @param string $type
1006
-	 * @param string $id
1007
-	 * @return string
1008
-	 * @throws \OutOfBoundsException
1009
-	 * @since 11.0.0
1010
-	 *
1011
-	 * If a provided type was not registered, an \OutOfBoundsException shall
1012
-	 * be thrown. It is upon the resolver discretion what to return of the
1013
-	 * provided ID is unknown. It must be ensured that a string is returned.
1014
-	 */
1015
-	public function resolveDisplayName($type, $id) {
1016
-		if (!is_string($type)) {
1017
-			throw new \InvalidArgumentException('String expected.');
1018
-		}
1019
-		if (!isset($this->displayNameResolvers[$type])) {
1020
-			throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1021
-		}
1022
-		return (string)$this->displayNameResolvers[$type]($id);
1023
-	}
1024
-
1025
-	/**
1026
-	 * returns valid, registered entities
1027
-	 *
1028
-	 * @return \OCP\Comments\ICommentsEventHandler[]
1029
-	 */
1030
-	private function getEventHandlers() {
1031
-		if (!empty($this->eventHandlers)) {
1032
-			return $this->eventHandlers;
1033
-		}
1034
-
1035
-		$this->eventHandlers = [];
1036
-		foreach ($this->eventHandlerClosures as $name => $closure) {
1037
-			$entity = $closure();
1038
-			if (!($entity instanceof ICommentsEventHandler)) {
1039
-				throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface');
1040
-			}
1041
-			$this->eventHandlers[$name] = $entity;
1042
-		}
1043
-
1044
-		return $this->eventHandlers;
1045
-	}
1046
-
1047
-	/**
1048
-	 * sends notifications to the registered entities
1049
-	 *
1050
-	 * @param $eventType
1051
-	 * @param IComment $comment
1052
-	 */
1053
-	private function sendEvent($eventType, IComment $comment) {
1054
-		$entities = $this->getEventHandlers();
1055
-		$event = new CommentsEvent($eventType, $comment);
1056
-		foreach ($entities as $entity) {
1057
-			$entity->handle($event);
1058
-		}
1059
-	}
44
+    /** @var  IDBConnection */
45
+    protected $dbConn;
46
+
47
+    /** @var  ILogger */
48
+    protected $logger;
49
+
50
+    /** @var IConfig */
51
+    protected $config;
52
+
53
+    /** @var IComment[] */
54
+    protected $commentsCache = [];
55
+
56
+    /** @var  \Closure[] */
57
+    protected $eventHandlerClosures = [];
58
+
59
+    /** @var  ICommentsEventHandler[] */
60
+    protected $eventHandlers = [];
61
+
62
+    /** @var \Closure[] */
63
+    protected $displayNameResolvers = [];
64
+
65
+    /**
66
+     * Manager constructor.
67
+     *
68
+     * @param IDBConnection $dbConn
69
+     * @param ILogger $logger
70
+     * @param IConfig $config
71
+     */
72
+    public function __construct(
73
+        IDBConnection $dbConn,
74
+        ILogger $logger,
75
+        IConfig $config
76
+    ) {
77
+        $this->dbConn = $dbConn;
78
+        $this->logger = $logger;
79
+        $this->config = $config;
80
+    }
81
+
82
+    /**
83
+     * converts data base data into PHP native, proper types as defined by
84
+     * IComment interface.
85
+     *
86
+     * @param array $data
87
+     * @return array
88
+     */
89
+    protected function normalizeDatabaseData(array $data) {
90
+        $data['id'] = (string)$data['id'];
91
+        $data['parent_id'] = (string)$data['parent_id'];
92
+        $data['topmost_parent_id'] = (string)$data['topmost_parent_id'];
93
+        $data['creation_timestamp'] = new \DateTime($data['creation_timestamp']);
94
+        if (!is_null($data['latest_child_timestamp'])) {
95
+            $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']);
96
+        }
97
+        $data['children_count'] = (int)$data['children_count'];
98
+        return $data;
99
+    }
100
+
101
+    /**
102
+     * prepares a comment for an insert or update operation after making sure
103
+     * all necessary fields have a value assigned.
104
+     *
105
+     * @param IComment $comment
106
+     * @return IComment returns the same updated IComment instance as provided
107
+     *                  by parameter for convenience
108
+     * @throws \UnexpectedValueException
109
+     */
110
+    protected function prepareCommentForDatabaseWrite(IComment $comment) {
111
+        if (!$comment->getActorType()
112
+            || !$comment->getActorId()
113
+            || !$comment->getObjectType()
114
+            || !$comment->getObjectId()
115
+            || !$comment->getVerb()
116
+        ) {
117
+            throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving');
118
+        }
119
+
120
+        if ($comment->getId() === '') {
121
+            $comment->setChildrenCount(0);
122
+            $comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC')));
123
+            $comment->setLatestChildDateTime(null);
124
+        }
125
+
126
+        if (is_null($comment->getCreationDateTime())) {
127
+            $comment->setCreationDateTime(new \DateTime());
128
+        }
129
+
130
+        if ($comment->getParentId() !== '0') {
131
+            $comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId()));
132
+        } else {
133
+            $comment->setTopmostParentId('0');
134
+        }
135
+
136
+        $this->cache($comment);
137
+
138
+        return $comment;
139
+    }
140
+
141
+    /**
142
+     * returns the topmost parent id of a given comment identified by ID
143
+     *
144
+     * @param string $id
145
+     * @return string
146
+     * @throws NotFoundException
147
+     */
148
+    protected function determineTopmostParentId($id) {
149
+        $comment = $this->get($id);
150
+        if ($comment->getParentId() === '0') {
151
+            return $comment->getId();
152
+        } else {
153
+            return $this->determineTopmostParentId($comment->getId());
154
+        }
155
+    }
156
+
157
+    /**
158
+     * updates child information of a comment
159
+     *
160
+     * @param string $id
161
+     * @param \DateTime $cDateTime the date time of the most recent child
162
+     * @throws NotFoundException
163
+     */
164
+    protected function updateChildrenInformation($id, \DateTime $cDateTime) {
165
+        $qb = $this->dbConn->getQueryBuilder();
166
+        $query = $qb->select($qb->createFunction('COUNT(`id`)'))
167
+            ->from('comments')
168
+            ->where($qb->expr()->eq('parent_id', $qb->createParameter('id')))
169
+            ->setParameter('id', $id);
170
+
171
+        $resultStatement = $query->execute();
172
+        $data = $resultStatement->fetch(\PDO::FETCH_NUM);
173
+        $resultStatement->closeCursor();
174
+        $children = (int)$data[0];
175
+
176
+        $comment = $this->get($id);
177
+        $comment->setChildrenCount($children);
178
+        $comment->setLatestChildDateTime($cDateTime);
179
+        $this->save($comment);
180
+    }
181
+
182
+    /**
183
+     * Tests whether actor or object type and id parameters are acceptable.
184
+     * Throws exception if not.
185
+     *
186
+     * @param string $role
187
+     * @param string $type
188
+     * @param string $id
189
+     * @throws \InvalidArgumentException
190
+     */
191
+    protected function checkRoleParameters($role, $type, $id) {
192
+        if (
193
+            !is_string($type) || empty($type)
194
+            || !is_string($id) || empty($id)
195
+        ) {
196
+            throw new \InvalidArgumentException($role . ' parameters must be string and not empty');
197
+        }
198
+    }
199
+
200
+    /**
201
+     * run-time caches a comment
202
+     *
203
+     * @param IComment $comment
204
+     */
205
+    protected function cache(IComment $comment) {
206
+        $id = $comment->getId();
207
+        if (empty($id)) {
208
+            return;
209
+        }
210
+        $this->commentsCache[(string)$id] = $comment;
211
+    }
212
+
213
+    /**
214
+     * removes an entry from the comments run time cache
215
+     *
216
+     * @param mixed $id the comment's id
217
+     */
218
+    protected function uncache($id) {
219
+        $id = (string)$id;
220
+        if (isset($this->commentsCache[$id])) {
221
+            unset($this->commentsCache[$id]);
222
+        }
223
+    }
224
+
225
+    /**
226
+     * returns a comment instance
227
+     *
228
+     * @param string $id the ID of the comment
229
+     * @return IComment
230
+     * @throws NotFoundException
231
+     * @throws \InvalidArgumentException
232
+     * @since 9.0.0
233
+     */
234
+    public function get($id) {
235
+        if ((int)$id === 0) {
236
+            throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.');
237
+        }
238
+
239
+        if (isset($this->commentsCache[$id])) {
240
+            return $this->commentsCache[$id];
241
+        }
242
+
243
+        $qb = $this->dbConn->getQueryBuilder();
244
+        $resultStatement = $qb->select('*')
245
+            ->from('comments')
246
+            ->where($qb->expr()->eq('id', $qb->createParameter('id')))
247
+            ->setParameter('id', $id, IQueryBuilder::PARAM_INT)
248
+            ->execute();
249
+
250
+        $data = $resultStatement->fetch();
251
+        $resultStatement->closeCursor();
252
+        if (!$data) {
253
+            throw new NotFoundException();
254
+        }
255
+
256
+        $comment = new Comment($this->normalizeDatabaseData($data));
257
+        $this->cache($comment);
258
+        return $comment;
259
+    }
260
+
261
+    /**
262
+     * returns the comment specified by the id and all it's child comments.
263
+     * At this point of time, we do only support one level depth.
264
+     *
265
+     * @param string $id
266
+     * @param int $limit max number of entries to return, 0 returns all
267
+     * @param int $offset the start entry
268
+     * @return array
269
+     * @since 9.0.0
270
+     *
271
+     * The return array looks like this
272
+     * [
273
+     *   'comment' => IComment, // root comment
274
+     *   'replies' =>
275
+     *   [
276
+     *     0 =>
277
+     *     [
278
+     *       'comment' => IComment,
279
+     *       'replies' => []
280
+     *     ]
281
+     *     1 =>
282
+     *     [
283
+     *       'comment' => IComment,
284
+     *       'replies'=> []
285
+     *     ],
286
+     *     …
287
+     *   ]
288
+     * ]
289
+     */
290
+    public function getTree($id, $limit = 0, $offset = 0) {
291
+        $tree = [];
292
+        $tree['comment'] = $this->get($id);
293
+        $tree['replies'] = [];
294
+
295
+        $qb = $this->dbConn->getQueryBuilder();
296
+        $query = $qb->select('*')
297
+            ->from('comments')
298
+            ->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id')))
299
+            ->orderBy('creation_timestamp', 'DESC')
300
+            ->setParameter('id', $id);
301
+
302
+        if ($limit > 0) {
303
+            $query->setMaxResults($limit);
304
+        }
305
+        if ($offset > 0) {
306
+            $query->setFirstResult($offset);
307
+        }
308
+
309
+        $resultStatement = $query->execute();
310
+        while ($data = $resultStatement->fetch()) {
311
+            $comment = new Comment($this->normalizeDatabaseData($data));
312
+            $this->cache($comment);
313
+            $tree['replies'][] = [
314
+                'comment' => $comment,
315
+                'replies' => []
316
+            ];
317
+        }
318
+        $resultStatement->closeCursor();
319
+
320
+        return $tree;
321
+    }
322
+
323
+    /**
324
+     * returns comments for a specific object (e.g. a file).
325
+     *
326
+     * The sort order is always newest to oldest.
327
+     *
328
+     * @param string $objectType the object type, e.g. 'files'
329
+     * @param string $objectId the id of the object
330
+     * @param int $limit optional, number of maximum comments to be returned. if
331
+     * not specified, all comments are returned.
332
+     * @param int $offset optional, starting point
333
+     * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
334
+     * that may be returned
335
+     * @return IComment[]
336
+     * @since 9.0.0
337
+     */
338
+    public function getForObject(
339
+        $objectType,
340
+        $objectId,
341
+        $limit = 0,
342
+        $offset = 0,
343
+        \DateTime $notOlderThan = null
344
+    ) {
345
+        $comments = [];
346
+
347
+        $qb = $this->dbConn->getQueryBuilder();
348
+        $query = $qb->select('*')
349
+            ->from('comments')
350
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
351
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
352
+            ->orderBy('creation_timestamp', 'DESC')
353
+            ->setParameter('type', $objectType)
354
+            ->setParameter('id', $objectId);
355
+
356
+        if ($limit > 0) {
357
+            $query->setMaxResults($limit);
358
+        }
359
+        if ($offset > 0) {
360
+            $query->setFirstResult($offset);
361
+        }
362
+        if (!is_null($notOlderThan)) {
363
+            $query
364
+                ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
365
+                ->setParameter('notOlderThan', $notOlderThan, 'datetime');
366
+        }
367
+
368
+        $resultStatement = $query->execute();
369
+        while ($data = $resultStatement->fetch()) {
370
+            $comment = new Comment($this->normalizeDatabaseData($data));
371
+            $this->cache($comment);
372
+            $comments[] = $comment;
373
+        }
374
+        $resultStatement->closeCursor();
375
+
376
+        return $comments;
377
+    }
378
+
379
+    /**
380
+     * @param string $objectType the object type, e.g. 'files'
381
+     * @param string $objectId the id of the object
382
+     * @param int $lastKnownCommentId the last known comment (will be used as offset)
383
+     * @param string $sortDirection direction of the comments (`asc` or `desc`)
384
+     * @param int $limit optional, number of maximum comments to be returned. if
385
+     * set to 0, all comments are returned.
386
+     * @return IComment[]
387
+     * @return array
388
+     */
389
+    public function getForObjectSince(
390
+        string $objectType,
391
+        string $objectId,
392
+        int $lastKnownCommentId,
393
+        string $sortDirection = 'asc',
394
+        int $limit = 30
395
+    ): array {
396
+        $comments = [];
397
+
398
+        $query = $this->dbConn->getQueryBuilder();
399
+        $query->select('*')
400
+            ->from('comments')
401
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
402
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
403
+            ->orderBy('creation_timestamp', $sortDirection === 'desc' ? 'DESC' : 'ASC')
404
+            ->addOrderBy('id', $sortDirection === 'desc' ? 'DESC' : 'ASC');
405
+
406
+        if ($limit > 0) {
407
+            $query->setMaxResults($limit);
408
+        }
409
+
410
+        $lastKnownComment = $lastKnownCommentId > 0 ? $this->getLastKnownComment(
411
+            $objectType,
412
+            $objectId,
413
+            $lastKnownCommentId
414
+        ) : null;
415
+        if ($lastKnownComment instanceof IComment) {
416
+            $lastKnownCommentDateTime = $lastKnownComment->getCreationDateTime();
417
+            if ($sortDirection === 'desc') {
418
+                $query->andWhere(
419
+                    $query->expr()->orX(
420
+                        $query->expr()->lt(
421
+                            'creation_timestamp',
422
+                            $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
423
+                            IQueryBuilder::PARAM_DATE
424
+                        ),
425
+                        $query->expr()->andX(
426
+                            $query->expr()->eq(
427
+                                'creation_timestamp',
428
+                                $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
429
+                                IQueryBuilder::PARAM_DATE
430
+                            ),
431
+                            $query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId))
432
+                        )
433
+                    )
434
+                );
435
+            } else {
436
+                $query->andWhere(
437
+                    $query->expr()->orX(
438
+                        $query->expr()->gt(
439
+                            'creation_timestamp',
440
+                            $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
441
+                            IQueryBuilder::PARAM_DATE
442
+                        ),
443
+                        $query->expr()->andX(
444
+                            $query->expr()->eq(
445
+                                'creation_timestamp',
446
+                                $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE),
447
+                                IQueryBuilder::PARAM_DATE
448
+                            ),
449
+                            $query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId))
450
+                        )
451
+                    )
452
+                );
453
+            }
454
+        }
455
+
456
+        $resultStatement = $query->execute();
457
+        while ($data = $resultStatement->fetch()) {
458
+            $comment = new Comment($this->normalizeDatabaseData($data));
459
+            $this->cache($comment);
460
+            $comments[] = $comment;
461
+        }
462
+        $resultStatement->closeCursor();
463
+
464
+        return $comments;
465
+    }
466
+
467
+    /**
468
+     * @param string $objectType the object type, e.g. 'files'
469
+     * @param string $objectId the id of the object
470
+     * @param int $id the comment to look for
471
+     * @return Comment|null
472
+     */
473
+    protected function getLastKnownComment(string $objectType,
474
+                                            string $objectId,
475
+                                            int $id) {
476
+        $query = $this->dbConn->getQueryBuilder();
477
+        $query->select('*')
478
+            ->from('comments')
479
+            ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType)))
480
+            ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)))
481
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
482
+
483
+        $result = $query->execute();
484
+        $row = $result->fetch();
485
+        $result->closeCursor();
486
+
487
+        if ($row) {
488
+            $comment = new Comment($this->normalizeDatabaseData($row));
489
+            $this->cache($comment);
490
+            return $comment;
491
+        }
492
+
493
+        return null;
494
+    }
495
+
496
+    /**
497
+     * Search for comments with a given content
498
+     *
499
+     * @param string $search content to search for
500
+     * @param string $objectType Limit the search by object type
501
+     * @param string $objectId Limit the search by object id
502
+     * @param string $verb Limit the verb of the comment
503
+     * @param int $offset
504
+     * @param int $limit
505
+     * @return IComment[]
506
+     */
507
+    public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array {
508
+        $query = $this->dbConn->getQueryBuilder();
509
+
510
+        $query->select('*')
511
+            ->from('comments')
512
+            ->where($query->expr()->iLike('message', $query->createNamedParameter(
513
+                '%' . $this->dbConn->escapeLikeParameter($search). '%'
514
+            )))
515
+            ->orderBy('creation_timestamp', 'DESC')
516
+            ->addOrderBy('id', 'DESC')
517
+            ->setMaxResults($limit);
518
+
519
+        if ($objectType !== '') {
520
+            $query->andWhere($query->expr()->eq('object_type', $query->createNamedParameter($objectType)));
521
+        }
522
+        if ($objectId !== '') {
523
+            $query->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId)));
524
+        }
525
+        if ($verb !== '') {
526
+            $query->andWhere($query->expr()->eq('verb', $query->createNamedParameter($verb)));
527
+        }
528
+        if ($offset !== 0) {
529
+            $query->setFirstResult($offset);
530
+        }
531
+
532
+        $comments = [];
533
+        $result = $query->execute();
534
+        while ($data = $result->fetch()) {
535
+            $comment = new Comment($this->normalizeDatabaseData($data));
536
+            $this->cache($comment);
537
+            $comments[] = $comment;
538
+        }
539
+        $result->closeCursor();
540
+
541
+        return $comments;
542
+    }
543
+
544
+    /**
545
+     * @param $objectType string the object type, e.g. 'files'
546
+     * @param $objectId string the id of the object
547
+     * @param \DateTime $notOlderThan optional, timestamp of the oldest comments
548
+     * that may be returned
549
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
550
+     * @return Int
551
+     * @since 9.0.0
552
+     */
553
+    public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '') {
554
+        $qb = $this->dbConn->getQueryBuilder();
555
+        $query = $qb->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
556
+            ->from('comments')
557
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
558
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
559
+            ->setParameter('type', $objectType)
560
+            ->setParameter('id', $objectId);
561
+
562
+        if (!is_null($notOlderThan)) {
563
+            $query
564
+                ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan')))
565
+                ->setParameter('notOlderThan', $notOlderThan, 'datetime');
566
+        }
567
+
568
+        if ($verb !== '') {
569
+            $query->andWhere($qb->expr()->eq('verb', $qb->createNamedParameter($verb)));
570
+        }
571
+
572
+        $resultStatement = $query->execute();
573
+        $data = $resultStatement->fetch(\PDO::FETCH_NUM);
574
+        $resultStatement->closeCursor();
575
+        return (int)$data[0];
576
+    }
577
+
578
+    /**
579
+     * Get the number of unread comments for all files in a folder
580
+     *
581
+     * @param int $folderId
582
+     * @param IUser $user
583
+     * @return array [$fileId => $unreadCount]
584
+     */
585
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) {
586
+        $qb = $this->dbConn->getQueryBuilder();
587
+        $query = $qb->select('f.fileid')
588
+            ->selectAlias(
589
+                $qb->createFunction('COUNT(' . $qb->getColumnName('c.id') . ')'),
590
+                'num_ids'
591
+            )
592
+            ->from('comments', 'c')
593
+            ->innerJoin('c', 'filecache', 'f', $qb->expr()->andX(
594
+                $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')),
595
+                $qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT))
596
+            ))
597
+            ->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX(
598
+                $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')),
599
+                $qb->expr()->eq('m.object_id', 'c.object_id'),
600
+                $qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID()))
601
+            ))
602
+            ->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)))
603
+            ->andWhere($qb->expr()->orX(
604
+                $qb->expr()->gt('c.creation_timestamp', 'marker_datetime'),
605
+                $qb->expr()->isNull('marker_datetime')
606
+            ))
607
+            ->groupBy('f.fileid');
608
+
609
+        $resultStatement = $query->execute();
610
+
611
+        $results = [];
612
+        while ($row = $resultStatement->fetch()) {
613
+            $results[$row['fileid']] = (int) $row['num_ids'];
614
+        }
615
+        $resultStatement->closeCursor();
616
+        return $results;
617
+    }
618
+
619
+    /**
620
+     * creates a new comment and returns it. At this point of time, it is not
621
+     * saved in the used data storage. Use save() after setting other fields
622
+     * of the comment (e.g. message or verb).
623
+     *
624
+     * @param string $actorType the actor type (e.g. 'users')
625
+     * @param string $actorId a user id
626
+     * @param string $objectType the object type the comment is attached to
627
+     * @param string $objectId the object id the comment is attached to
628
+     * @return IComment
629
+     * @since 9.0.0
630
+     */
631
+    public function create($actorType, $actorId, $objectType, $objectId) {
632
+        $comment = new Comment();
633
+        $comment
634
+            ->setActor($actorType, $actorId)
635
+            ->setObject($objectType, $objectId);
636
+        return $comment;
637
+    }
638
+
639
+    /**
640
+     * permanently deletes the comment specified by the ID
641
+     *
642
+     * When the comment has child comments, their parent ID will be changed to
643
+     * the parent ID of the item that is to be deleted.
644
+     *
645
+     * @param string $id
646
+     * @return bool
647
+     * @throws \InvalidArgumentException
648
+     * @since 9.0.0
649
+     */
650
+    public function delete($id) {
651
+        if (!is_string($id)) {
652
+            throw new \InvalidArgumentException('Parameter must be string');
653
+        }
654
+
655
+        try {
656
+            $comment = $this->get($id);
657
+        } catch (\Exception $e) {
658
+            // Ignore exceptions, we just don't fire a hook then
659
+            $comment = null;
660
+        }
661
+
662
+        $qb = $this->dbConn->getQueryBuilder();
663
+        $query = $qb->delete('comments')
664
+            ->where($qb->expr()->eq('id', $qb->createParameter('id')))
665
+            ->setParameter('id', $id);
666
+
667
+        try {
668
+            $affectedRows = $query->execute();
669
+            $this->uncache($id);
670
+        } catch (DriverException $e) {
671
+            $this->logger->logException($e, ['app' => 'core_comments']);
672
+            return false;
673
+        }
674
+
675
+        if ($affectedRows > 0 && $comment instanceof IComment) {
676
+            $this->sendEvent(CommentsEvent::EVENT_DELETE, $comment);
677
+        }
678
+
679
+        return ($affectedRows > 0);
680
+    }
681
+
682
+    /**
683
+     * saves the comment permanently
684
+     *
685
+     * if the supplied comment has an empty ID, a new entry comment will be
686
+     * saved and the instance updated with the new ID.
687
+     *
688
+     * Otherwise, an existing comment will be updated.
689
+     *
690
+     * Throws NotFoundException when a comment that is to be updated does not
691
+     * exist anymore at this point of time.
692
+     *
693
+     * @param IComment $comment
694
+     * @return bool
695
+     * @throws NotFoundException
696
+     * @since 9.0.0
697
+     */
698
+    public function save(IComment $comment) {
699
+        if ($this->prepareCommentForDatabaseWrite($comment)->getId() === '') {
700
+            $result = $this->insert($comment);
701
+        } else {
702
+            $result = $this->update($comment);
703
+        }
704
+
705
+        if ($result && !!$comment->getParentId()) {
706
+            $this->updateChildrenInformation(
707
+                $comment->getParentId(),
708
+                $comment->getCreationDateTime()
709
+            );
710
+            $this->cache($comment);
711
+        }
712
+
713
+        return $result;
714
+    }
715
+
716
+    /**
717
+     * inserts the provided comment in the database
718
+     *
719
+     * @param IComment $comment
720
+     * @return bool
721
+     */
722
+    protected function insert(IComment &$comment) {
723
+        $qb = $this->dbConn->getQueryBuilder();
724
+        $affectedRows = $qb
725
+            ->insert('comments')
726
+            ->values([
727
+                'parent_id' => $qb->createNamedParameter($comment->getParentId()),
728
+                'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()),
729
+                'children_count' => $qb->createNamedParameter($comment->getChildrenCount()),
730
+                'actor_type' => $qb->createNamedParameter($comment->getActorType()),
731
+                'actor_id' => $qb->createNamedParameter($comment->getActorId()),
732
+                'message' => $qb->createNamedParameter($comment->getMessage()),
733
+                'verb' => $qb->createNamedParameter($comment->getVerb()),
734
+                'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'),
735
+                'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'),
736
+                'object_type' => $qb->createNamedParameter($comment->getObjectType()),
737
+                'object_id' => $qb->createNamedParameter($comment->getObjectId()),
738
+            ])
739
+            ->execute();
740
+
741
+        if ($affectedRows > 0) {
742
+            $comment->setId((string)$qb->getLastInsertId());
743
+            $this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
744
+        }
745
+
746
+        return $affectedRows > 0;
747
+    }
748
+
749
+    /**
750
+     * updates a Comment data row
751
+     *
752
+     * @param IComment $comment
753
+     * @return bool
754
+     * @throws NotFoundException
755
+     */
756
+    protected function update(IComment $comment) {
757
+        // for properly working preUpdate Events we need the old comments as is
758
+        // in the DB and overcome caching. Also avoid that outdated information stays.
759
+        $this->uncache($comment->getId());
760
+        $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
761
+        $this->uncache($comment->getId());
762
+
763
+        $qb = $this->dbConn->getQueryBuilder();
764
+        $affectedRows = $qb
765
+            ->update('comments')
766
+            ->set('parent_id', $qb->createNamedParameter($comment->getParentId()))
767
+            ->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId()))
768
+            ->set('children_count', $qb->createNamedParameter($comment->getChildrenCount()))
769
+            ->set('actor_type', $qb->createNamedParameter($comment->getActorType()))
770
+            ->set('actor_id', $qb->createNamedParameter($comment->getActorId()))
771
+            ->set('message', $qb->createNamedParameter($comment->getMessage()))
772
+            ->set('verb', $qb->createNamedParameter($comment->getVerb()))
773
+            ->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'))
774
+            ->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
775
+            ->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
776
+            ->set('object_id', $qb->createNamedParameter($comment->getObjectId()))
777
+            ->where($qb->expr()->eq('id', $qb->createParameter('id')))
778
+            ->setParameter('id', $comment->getId())
779
+            ->execute();
780
+
781
+        if ($affectedRows === 0) {
782
+            throw new NotFoundException('Comment to update does ceased to exist');
783
+        }
784
+
785
+        $this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment);
786
+
787
+        return $affectedRows > 0;
788
+    }
789
+
790
+    /**
791
+     * removes references to specific actor (e.g. on user delete) of a comment.
792
+     * The comment itself must not get lost/deleted.
793
+     *
794
+     * @param string $actorType the actor type (e.g. 'users')
795
+     * @param string $actorId a user id
796
+     * @return boolean
797
+     * @since 9.0.0
798
+     */
799
+    public function deleteReferencesOfActor($actorType, $actorId) {
800
+        $this->checkRoleParameters('Actor', $actorType, $actorId);
801
+
802
+        $qb = $this->dbConn->getQueryBuilder();
803
+        $affectedRows = $qb
804
+            ->update('comments')
805
+            ->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
806
+            ->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER))
807
+            ->where($qb->expr()->eq('actor_type', $qb->createParameter('type')))
808
+            ->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id')))
809
+            ->setParameter('type', $actorType)
810
+            ->setParameter('id', $actorId)
811
+            ->execute();
812
+
813
+        $this->commentsCache = [];
814
+
815
+        return is_int($affectedRows);
816
+    }
817
+
818
+    /**
819
+     * deletes all comments made of a specific object (e.g. on file delete)
820
+     *
821
+     * @param string $objectType the object type (e.g. 'files')
822
+     * @param string $objectId e.g. the file id
823
+     * @return boolean
824
+     * @since 9.0.0
825
+     */
826
+    public function deleteCommentsAtObject($objectType, $objectId) {
827
+        $this->checkRoleParameters('Object', $objectType, $objectId);
828
+
829
+        $qb = $this->dbConn->getQueryBuilder();
830
+        $affectedRows = $qb
831
+            ->delete('comments')
832
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
833
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
834
+            ->setParameter('type', $objectType)
835
+            ->setParameter('id', $objectId)
836
+            ->execute();
837
+
838
+        $this->commentsCache = [];
839
+
840
+        return is_int($affectedRows);
841
+    }
842
+
843
+    /**
844
+     * deletes the read markers for the specified user
845
+     *
846
+     * @param \OCP\IUser $user
847
+     * @return bool
848
+     * @since 9.0.0
849
+     */
850
+    public function deleteReadMarksFromUser(IUser $user) {
851
+        $qb = $this->dbConn->getQueryBuilder();
852
+        $query = $qb->delete('comments_read_markers')
853
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
854
+            ->setParameter('user_id', $user->getUID());
855
+
856
+        try {
857
+            $affectedRows = $query->execute();
858
+        } catch (DriverException $e) {
859
+            $this->logger->logException($e, ['app' => 'core_comments']);
860
+            return false;
861
+        }
862
+        return ($affectedRows > 0);
863
+    }
864
+
865
+    /**
866
+     * sets the read marker for a given file to the specified date for the
867
+     * provided user
868
+     *
869
+     * @param string $objectType
870
+     * @param string $objectId
871
+     * @param \DateTime $dateTime
872
+     * @param IUser $user
873
+     * @since 9.0.0
874
+     * @suppress SqlInjectionChecker
875
+     */
876
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) {
877
+        $this->checkRoleParameters('Object', $objectType, $objectId);
878
+
879
+        $qb = $this->dbConn->getQueryBuilder();
880
+        $values = [
881
+            'user_id' => $qb->createNamedParameter($user->getUID()),
882
+            'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'),
883
+            'object_type' => $qb->createNamedParameter($objectType),
884
+            'object_id' => $qb->createNamedParameter($objectId),
885
+        ];
886
+
887
+        // Strategy: try to update, if this does not return affected rows, do an insert.
888
+        $affectedRows = $qb
889
+            ->update('comments_read_markers')
890
+            ->set('user_id', $values['user_id'])
891
+            ->set('marker_datetime', $values['marker_datetime'])
892
+            ->set('object_type', $values['object_type'])
893
+            ->set('object_id', $values['object_id'])
894
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
895
+            ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
896
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
897
+            ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
898
+            ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
899
+            ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
900
+            ->execute();
901
+
902
+        if ($affectedRows > 0) {
903
+            return;
904
+        }
905
+
906
+        $qb->insert('comments_read_markers')
907
+            ->values($values)
908
+            ->execute();
909
+    }
910
+
911
+    /**
912
+     * returns the read marker for a given file to the specified date for the
913
+     * provided user. It returns null, when the marker is not present, i.e.
914
+     * no comments were marked as read.
915
+     *
916
+     * @param string $objectType
917
+     * @param string $objectId
918
+     * @param IUser $user
919
+     * @return \DateTime|null
920
+     * @since 9.0.0
921
+     */
922
+    public function getReadMark($objectType, $objectId, IUser $user) {
923
+        $qb = $this->dbConn->getQueryBuilder();
924
+        $resultStatement = $qb->select('marker_datetime')
925
+            ->from('comments_read_markers')
926
+            ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')))
927
+            ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
928
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
929
+            ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR)
930
+            ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR)
931
+            ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR)
932
+            ->execute();
933
+
934
+        $data = $resultStatement->fetch();
935
+        $resultStatement->closeCursor();
936
+        if (!$data || is_null($data['marker_datetime'])) {
937
+            return null;
938
+        }
939
+
940
+        return new \DateTime($data['marker_datetime']);
941
+    }
942
+
943
+    /**
944
+     * deletes the read markers on the specified object
945
+     *
946
+     * @param string $objectType
947
+     * @param string $objectId
948
+     * @return bool
949
+     * @since 9.0.0
950
+     */
951
+    public function deleteReadMarksOnObject($objectType, $objectId) {
952
+        $this->checkRoleParameters('Object', $objectType, $objectId);
953
+
954
+        $qb = $this->dbConn->getQueryBuilder();
955
+        $query = $qb->delete('comments_read_markers')
956
+            ->where($qb->expr()->eq('object_type', $qb->createParameter('object_type')))
957
+            ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id')))
958
+            ->setParameter('object_type', $objectType)
959
+            ->setParameter('object_id', $objectId);
960
+
961
+        try {
962
+            $affectedRows = $query->execute();
963
+        } catch (DriverException $e) {
964
+            $this->logger->logException($e, ['app' => 'core_comments']);
965
+            return false;
966
+        }
967
+        return ($affectedRows > 0);
968
+    }
969
+
970
+    /**
971
+     * registers an Entity to the manager, so event notifications can be send
972
+     * to consumers of the comments infrastructure
973
+     *
974
+     * @param \Closure $closure
975
+     */
976
+    public function registerEventHandler(\Closure $closure) {
977
+        $this->eventHandlerClosures[] = $closure;
978
+        $this->eventHandlers = [];
979
+    }
980
+
981
+    /**
982
+     * registers a method that resolves an ID to a display name for a given type
983
+     *
984
+     * @param string $type
985
+     * @param \Closure $closure
986
+     * @throws \OutOfBoundsException
987
+     * @since 11.0.0
988
+     *
989
+     * Only one resolver shall be registered per type. Otherwise a
990
+     * \OutOfBoundsException has to thrown.
991
+     */
992
+    public function registerDisplayNameResolver($type, \Closure $closure) {
993
+        if (!is_string($type)) {
994
+            throw new \InvalidArgumentException('String expected.');
995
+        }
996
+        if (isset($this->displayNameResolvers[$type])) {
997
+            throw new \OutOfBoundsException('Displayname resolver for this type already registered');
998
+        }
999
+        $this->displayNameResolvers[$type] = $closure;
1000
+    }
1001
+
1002
+    /**
1003
+     * resolves a given ID of a given Type to a display name.
1004
+     *
1005
+     * @param string $type
1006
+     * @param string $id
1007
+     * @return string
1008
+     * @throws \OutOfBoundsException
1009
+     * @since 11.0.0
1010
+     *
1011
+     * If a provided type was not registered, an \OutOfBoundsException shall
1012
+     * be thrown. It is upon the resolver discretion what to return of the
1013
+     * provided ID is unknown. It must be ensured that a string is returned.
1014
+     */
1015
+    public function resolveDisplayName($type, $id) {
1016
+        if (!is_string($type)) {
1017
+            throw new \InvalidArgumentException('String expected.');
1018
+        }
1019
+        if (!isset($this->displayNameResolvers[$type])) {
1020
+            throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1021
+        }
1022
+        return (string)$this->displayNameResolvers[$type]($id);
1023
+    }
1024
+
1025
+    /**
1026
+     * returns valid, registered entities
1027
+     *
1028
+     * @return \OCP\Comments\ICommentsEventHandler[]
1029
+     */
1030
+    private function getEventHandlers() {
1031
+        if (!empty($this->eventHandlers)) {
1032
+            return $this->eventHandlers;
1033
+        }
1034
+
1035
+        $this->eventHandlers = [];
1036
+        foreach ($this->eventHandlerClosures as $name => $closure) {
1037
+            $entity = $closure();
1038
+            if (!($entity instanceof ICommentsEventHandler)) {
1039
+                throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface');
1040
+            }
1041
+            $this->eventHandlers[$name] = $entity;
1042
+        }
1043
+
1044
+        return $this->eventHandlers;
1045
+    }
1046
+
1047
+    /**
1048
+     * sends notifications to the registered entities
1049
+     *
1050
+     * @param $eventType
1051
+     * @param IComment $comment
1052
+     */
1053
+    private function sendEvent($eventType, IComment $comment) {
1054
+        $entities = $this->getEventHandlers();
1055
+        $event = new CommentsEvent($eventType, $comment);
1056
+        foreach ($entities as $entity) {
1057
+            $entity->handle($event);
1058
+        }
1059
+    }
1060 1060
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -87,14 +87,14 @@  discard block
 block discarded – undo
87 87
 	 * @return array
88 88
 	 */
89 89
 	protected function normalizeDatabaseData(array $data) {
90
-		$data['id'] = (string)$data['id'];
91
-		$data['parent_id'] = (string)$data['parent_id'];
92
-		$data['topmost_parent_id'] = (string)$data['topmost_parent_id'];
90
+		$data['id'] = (string) $data['id'];
91
+		$data['parent_id'] = (string) $data['parent_id'];
92
+		$data['topmost_parent_id'] = (string) $data['topmost_parent_id'];
93 93
 		$data['creation_timestamp'] = new \DateTime($data['creation_timestamp']);
94 94
 		if (!is_null($data['latest_child_timestamp'])) {
95 95
 			$data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']);
96 96
 		}
97
-		$data['children_count'] = (int)$data['children_count'];
97
+		$data['children_count'] = (int) $data['children_count'];
98 98
 		return $data;
99 99
 	}
100 100
 
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 		$resultStatement = $query->execute();
172 172
 		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
173 173
 		$resultStatement->closeCursor();
174
-		$children = (int)$data[0];
174
+		$children = (int) $data[0];
175 175
 
176 176
 		$comment = $this->get($id);
177 177
 		$comment->setChildrenCount($children);
@@ -193,7 +193,7 @@  discard block
 block discarded – undo
193 193
 			!is_string($type) || empty($type)
194 194
 			|| !is_string($id) || empty($id)
195 195
 		) {
196
-			throw new \InvalidArgumentException($role . ' parameters must be string and not empty');
196
+			throw new \InvalidArgumentException($role.' parameters must be string and not empty');
197 197
 		}
198 198
 	}
199 199
 
@@ -207,7 +207,7 @@  discard block
 block discarded – undo
207 207
 		if (empty($id)) {
208 208
 			return;
209 209
 		}
210
-		$this->commentsCache[(string)$id] = $comment;
210
+		$this->commentsCache[(string) $id] = $comment;
211 211
 	}
212 212
 
213 213
 	/**
@@ -216,7 +216,7 @@  discard block
 block discarded – undo
216 216
 	 * @param mixed $id the comment's id
217 217
 	 */
218 218
 	protected function uncache($id) {
219
-		$id = (string)$id;
219
+		$id = (string) $id;
220 220
 		if (isset($this->commentsCache[$id])) {
221 221
 			unset($this->commentsCache[$id]);
222 222
 		}
@@ -232,7 +232,7 @@  discard block
 block discarded – undo
232 232
 	 * @since 9.0.0
233 233
 	 */
234 234
 	public function get($id) {
235
-		if ((int)$id === 0) {
235
+		if ((int) $id === 0) {
236 236
 			throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.');
237 237
 		}
238 238
 
@@ -510,7 +510,7 @@  discard block
 block discarded – undo
510 510
 		$query->select('*')
511 511
 			->from('comments')
512 512
 			->where($query->expr()->iLike('message', $query->createNamedParameter(
513
-				'%' . $this->dbConn->escapeLikeParameter($search). '%'
513
+				'%'.$this->dbConn->escapeLikeParameter($search).'%'
514 514
 			)))
515 515
 			->orderBy('creation_timestamp', 'DESC')
516 516
 			->addOrderBy('id', 'DESC')
@@ -552,7 +552,7 @@  discard block
 block discarded – undo
552 552
 	 */
553 553
 	public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '') {
554 554
 		$qb = $this->dbConn->getQueryBuilder();
555
-		$query = $qb->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
555
+		$query = $qb->select($qb->createFunction('COUNT('.$qb->getColumnName('id').')'))
556 556
 			->from('comments')
557 557
 			->where($qb->expr()->eq('object_type', $qb->createParameter('type')))
558 558
 			->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id')))
@@ -572,7 +572,7 @@  discard block
 block discarded – undo
572 572
 		$resultStatement = $query->execute();
573 573
 		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
574 574
 		$resultStatement->closeCursor();
575
-		return (int)$data[0];
575
+		return (int) $data[0];
576 576
 	}
577 577
 
578 578
 	/**
@@ -586,7 +586,7 @@  discard block
 block discarded – undo
586 586
 		$qb = $this->dbConn->getQueryBuilder();
587 587
 		$query = $qb->select('f.fileid')
588 588
 			->selectAlias(
589
-				$qb->createFunction('COUNT(' . $qb->getColumnName('c.id') . ')'),
589
+				$qb->createFunction('COUNT('.$qb->getColumnName('c.id').')'),
590 590
 				'num_ids'
591 591
 			)
592 592
 			->from('comments', 'c')
@@ -739,7 +739,7 @@  discard block
 block discarded – undo
739 739
 			->execute();
740 740
 
741 741
 		if ($affectedRows > 0) {
742
-			$comment->setId((string)$qb->getLastInsertId());
742
+			$comment->setId((string) $qb->getLastInsertId());
743 743
 			$this->sendEvent(CommentsEvent::EVENT_ADD, $comment);
744 744
 		}
745 745
 
@@ -1019,7 +1019,7 @@  discard block
 block discarded – undo
1019 1019
 		if (!isset($this->displayNameResolvers[$type])) {
1020 1020
 			throw new \OutOfBoundsException('No Displayname resolver for this type registered');
1021 1021
 		}
1022
-		return (string)$this->displayNameResolvers[$type]($id);
1022
+		return (string) $this->displayNameResolvers[$type]($id);
1023 1023
 	}
1024 1024
 
1025 1025
 	/**
Please login to merge, or discard this patch.
lib/public/Comments/ICommentsManager.php 1 patch
Indentation   +264 added lines, -264 removed lines patch added patch discarded remove patch
@@ -38,288 +38,288 @@
 block discarded – undo
38 38
  */
39 39
 interface ICommentsManager {
40 40
 
41
-	/**
42
-	 * @const DELETED_USER type and id for a user that has been deleted
43
-	 * @see deleteReferencesOfActor
44
-	 * @since 9.0.0
45
-	 *
46
-	 * To be used as replacement for user type actors in deleteReferencesOfActor().
47
-	 *
48
-	 * User interfaces shall show "Deleted user" as display name, if needed.
49
-	 */
50
-	const DELETED_USER = 'deleted_users';
41
+    /**
42
+     * @const DELETED_USER type and id for a user that has been deleted
43
+     * @see deleteReferencesOfActor
44
+     * @since 9.0.0
45
+     *
46
+     * To be used as replacement for user type actors in deleteReferencesOfActor().
47
+     *
48
+     * User interfaces shall show "Deleted user" as display name, if needed.
49
+     */
50
+    const DELETED_USER = 'deleted_users';
51 51
 
52
-	/**
53
-	 * returns a comment instance
54
-	 *
55
-	 * @param string $id the ID of the comment
56
-	 * @return IComment
57
-	 * @throws NotFoundException
58
-	 * @since 9.0.0
59
-	 */
60
-	public function get($id);
52
+    /**
53
+     * returns a comment instance
54
+     *
55
+     * @param string $id the ID of the comment
56
+     * @return IComment
57
+     * @throws NotFoundException
58
+     * @since 9.0.0
59
+     */
60
+    public function get($id);
61 61
 
62
-	/**
63
-	 * returns the comment specified by the id and all it's child comments
64
-	 *
65
-	 * @param string $id
66
-	 * @param int $limit max number of entries to return, 0 returns all
67
-	 * @param int $offset the start entry
68
-	 * @return array
69
-	 * @since 9.0.0
70
-	 *
71
-	 * The return array looks like this
72
-	 * [
73
-	 * 	 'comment' => IComment, // root comment
74
-	 *   'replies' =>
75
-	 *   [
76
-	 *     0 =>
77
-	 *     [
78
-	 *       'comment' => IComment,
79
-	 *       'replies' =>
80
-	 *       [
81
-	 *         0 =>
82
-	 *         [
83
-	 *           'comment' => IComment,
84
-	 *           'replies' => [ … ]
85
-	 *         ],
86
-	 *         …
87
-	 *       ]
88
-	 *     ]
89
-	 *     1 =>
90
-	 *     [
91
-	 *       'comment' => IComment,
92
-	 *       'replies'=> [ … ]
93
-	 *     ],
94
-	 *     …
95
-	 *   ]
96
-	 * ]
97
-	 */
98
-	public function getTree($id, $limit = 0, $offset = 0);
62
+    /**
63
+     * returns the comment specified by the id and all it's child comments
64
+     *
65
+     * @param string $id
66
+     * @param int $limit max number of entries to return, 0 returns all
67
+     * @param int $offset the start entry
68
+     * @return array
69
+     * @since 9.0.0
70
+     *
71
+     * The return array looks like this
72
+     * [
73
+     * 	 'comment' => IComment, // root comment
74
+     *   'replies' =>
75
+     *   [
76
+     *     0 =>
77
+     *     [
78
+     *       'comment' => IComment,
79
+     *       'replies' =>
80
+     *       [
81
+     *         0 =>
82
+     *         [
83
+     *           'comment' => IComment,
84
+     *           'replies' => [ … ]
85
+     *         ],
86
+     *         …
87
+     *       ]
88
+     *     ]
89
+     *     1 =>
90
+     *     [
91
+     *       'comment' => IComment,
92
+     *       'replies'=> [ … ]
93
+     *     ],
94
+     *     …
95
+     *   ]
96
+     * ]
97
+     */
98
+    public function getTree($id, $limit = 0, $offset = 0);
99 99
 
100
-	/**
101
-	 * returns comments for a specific object (e.g. a file).
102
-	 *
103
-	 * The sort order is always newest to oldest.
104
-	 *
105
-	 * @param string $objectType the object type, e.g. 'files'
106
-	 * @param string $objectId the id of the object
107
-	 * @param int $limit optional, number of maximum comments to be returned. if
108
-	 * not specified, all comments are returned.
109
-	 * @param int $offset optional, starting point
110
-	 * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
111
-	 * that may be returned
112
-	 * @return IComment[]
113
-	 * @since 9.0.0
114
-	 */
115
-	public function getForObject(
116
-			$objectType,
117
-			$objectId,
118
-			$limit = 0,
119
-			$offset = 0,
120
-			\DateTime $notOlderThan = null
121
-	);
100
+    /**
101
+     * returns comments for a specific object (e.g. a file).
102
+     *
103
+     * The sort order is always newest to oldest.
104
+     *
105
+     * @param string $objectType the object type, e.g. 'files'
106
+     * @param string $objectId the id of the object
107
+     * @param int $limit optional, number of maximum comments to be returned. if
108
+     * not specified, all comments are returned.
109
+     * @param int $offset optional, starting point
110
+     * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
111
+     * that may be returned
112
+     * @return IComment[]
113
+     * @since 9.0.0
114
+     */
115
+    public function getForObject(
116
+            $objectType,
117
+            $objectId,
118
+            $limit = 0,
119
+            $offset = 0,
120
+            \DateTime $notOlderThan = null
121
+    );
122 122
 
123
-	/**
124
-	 * @param string $objectType the object type, e.g. 'files'
125
-	 * @param string $objectId the id of the object
126
-	 * @param int $lastKnownCommentId the last known comment (will be used as offset)
127
-	 * @param string $sortDirection direction of the comments (`asc` or `desc`)
128
-	 * @param int $limit optional, number of maximum comments to be returned. if
129
-	 * set to 0, all comments are returned.
130
-	 * @return IComment[]
131
-	 * @since 14.0.0
132
-	 */
133
-	public function getForObjectSince(
134
-		string $objectType,
135
-		string $objectId,
136
-		int $lastKnownCommentId,
137
-		string $sortDirection = 'asc',
138
-		int $limit = 30
139
-	): array;
123
+    /**
124
+     * @param string $objectType the object type, e.g. 'files'
125
+     * @param string $objectId the id of the object
126
+     * @param int $lastKnownCommentId the last known comment (will be used as offset)
127
+     * @param string $sortDirection direction of the comments (`asc` or `desc`)
128
+     * @param int $limit optional, number of maximum comments to be returned. if
129
+     * set to 0, all comments are returned.
130
+     * @return IComment[]
131
+     * @since 14.0.0
132
+     */
133
+    public function getForObjectSince(
134
+        string $objectType,
135
+        string $objectId,
136
+        int $lastKnownCommentId,
137
+        string $sortDirection = 'asc',
138
+        int $limit = 30
139
+    ): array;
140 140
 
141
-	/**
142
-	 * Search for comments with a given content
143
-	 *
144
-	 * @param string $search content to search for
145
-	 * @param string $objectType Limit the search by object type
146
-	 * @param string $objectId Limit the search by object id
147
-	 * @param string $verb Limit the verb of the comment
148
-	 * @param int $offset
149
-	 * @param int $limit
150
-	 * @return IComment[]
151
-	 * @since 14.0.0
152
-	 */
153
-	public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array;
141
+    /**
142
+     * Search for comments with a given content
143
+     *
144
+     * @param string $search content to search for
145
+     * @param string $objectType Limit the search by object type
146
+     * @param string $objectId Limit the search by object id
147
+     * @param string $verb Limit the verb of the comment
148
+     * @param int $offset
149
+     * @param int $limit
150
+     * @return IComment[]
151
+     * @since 14.0.0
152
+     */
153
+    public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array;
154 154
 
155
-	/**
156
-	 * @param $objectType string the object type, e.g. 'files'
157
-	 * @param $objectId string the id of the object
158
-	 * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
159
-	 * that may be returned
160
-	 * @param string $verb Limit the verb of the comment - Added in 14.0.0
161
-	 * @return Int
162
-	 * @since 9.0.0
163
-	 */
164
-	public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '');
155
+    /**
156
+     * @param $objectType string the object type, e.g. 'files'
157
+     * @param $objectId string the id of the object
158
+     * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments
159
+     * that may be returned
160
+     * @param string $verb Limit the verb of the comment - Added in 14.0.0
161
+     * @return Int
162
+     * @since 9.0.0
163
+     */
164
+    public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '');
165 165
 
166
-	/**
167
-	 * Get the number of unread comments for all files in a folder
168
-	 *
169
-	 * @param int $folderId
170
-	 * @param IUser $user
171
-	 * @return array [$fileId => $unreadCount]
172
-	 * @since 12.0.0
173
-	 */
174
-	public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
166
+    /**
167
+     * Get the number of unread comments for all files in a folder
168
+     *
169
+     * @param int $folderId
170
+     * @param IUser $user
171
+     * @return array [$fileId => $unreadCount]
172
+     * @since 12.0.0
173
+     */
174
+    public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user);
175 175
 
176
-	/**
177
-	 * creates a new comment and returns it. At this point of time, it is not
178
-	 * saved in the used data storage. Use save() after setting other fields
179
-	 * of the comment (e.g. message or verb).
180
-	 *
181
-	 * @param string $actorType the actor type (e.g. 'users')
182
-	 * @param string $actorId a user id
183
-	 * @param string $objectType the object type the comment is attached to
184
-	 * @param string $objectId the object id the comment is attached to
185
-	 * @return IComment
186
-	 * @since 9.0.0
187
-	 */
188
-	public function create($actorType, $actorId, $objectType, $objectId);
176
+    /**
177
+     * creates a new comment and returns it. At this point of time, it is not
178
+     * saved in the used data storage. Use save() after setting other fields
179
+     * of the comment (e.g. message or verb).
180
+     *
181
+     * @param string $actorType the actor type (e.g. 'users')
182
+     * @param string $actorId a user id
183
+     * @param string $objectType the object type the comment is attached to
184
+     * @param string $objectId the object id the comment is attached to
185
+     * @return IComment
186
+     * @since 9.0.0
187
+     */
188
+    public function create($actorType, $actorId, $objectType, $objectId);
189 189
 
190
-	/**
191
-	 * permanently deletes the comment specified by the ID
192
-	 *
193
-	 * When the comment has child comments, their parent ID will be changed to
194
-	 * the parent ID of the item that is to be deleted.
195
-	 *
196
-	 * @param string $id
197
-	 * @return bool
198
-	 * @since 9.0.0
199
-	 */
200
-	public function delete($id);
190
+    /**
191
+     * permanently deletes the comment specified by the ID
192
+     *
193
+     * When the comment has child comments, their parent ID will be changed to
194
+     * the parent ID of the item that is to be deleted.
195
+     *
196
+     * @param string $id
197
+     * @return bool
198
+     * @since 9.0.0
199
+     */
200
+    public function delete($id);
201 201
 
202
-	/**
203
-	 * saves the comment permanently
204
-	 *
205
-	 * if the supplied comment has an empty ID, a new entry comment will be
206
-	 * saved and the instance updated with the new ID.
207
-	 *
208
-	 * Otherwise, an existing comment will be updated.
209
-	 *
210
-	 * Throws NotFoundException when a comment that is to be updated does not
211
-	 * exist anymore at this point of time.
212
-	 *
213
-	 * @param IComment $comment
214
-	 * @return bool
215
-	 * @throws NotFoundException
216
-	 * @since 9.0.0
217
-	 */
218
-	public function save(IComment $comment);
202
+    /**
203
+     * saves the comment permanently
204
+     *
205
+     * if the supplied comment has an empty ID, a new entry comment will be
206
+     * saved and the instance updated with the new ID.
207
+     *
208
+     * Otherwise, an existing comment will be updated.
209
+     *
210
+     * Throws NotFoundException when a comment that is to be updated does not
211
+     * exist anymore at this point of time.
212
+     *
213
+     * @param IComment $comment
214
+     * @return bool
215
+     * @throws NotFoundException
216
+     * @since 9.0.0
217
+     */
218
+    public function save(IComment $comment);
219 219
 
220
-	/**
221
-	 * removes references to specific actor (e.g. on user delete) of a comment.
222
-	 * The comment itself must not get lost/deleted.
223
-	 *
224
-	 * A 'users' type actor (type and id) should get replaced by the
225
-	 * value of the DELETED_USER constant of this interface.
226
-	 *
227
-	 * @param string $actorType the actor type (e.g. 'users')
228
-	 * @param string $actorId a user id
229
-	 * @return boolean
230
-	 * @since 9.0.0
231
-	 */
232
-	public function deleteReferencesOfActor($actorType, $actorId);
220
+    /**
221
+     * removes references to specific actor (e.g. on user delete) of a comment.
222
+     * The comment itself must not get lost/deleted.
223
+     *
224
+     * A 'users' type actor (type and id) should get replaced by the
225
+     * value of the DELETED_USER constant of this interface.
226
+     *
227
+     * @param string $actorType the actor type (e.g. 'users')
228
+     * @param string $actorId a user id
229
+     * @return boolean
230
+     * @since 9.0.0
231
+     */
232
+    public function deleteReferencesOfActor($actorType, $actorId);
233 233
 
234
-	/**
235
-	 * deletes all comments made of a specific object (e.g. on file delete)
236
-	 *
237
-	 * @param string $objectType the object type (e.g. 'files')
238
-	 * @param string $objectId e.g. the file id
239
-	 * @return boolean
240
-	 * @since 9.0.0
241
-	 */
242
-	public function deleteCommentsAtObject($objectType, $objectId);
234
+    /**
235
+     * deletes all comments made of a specific object (e.g. on file delete)
236
+     *
237
+     * @param string $objectType the object type (e.g. 'files')
238
+     * @param string $objectId e.g. the file id
239
+     * @return boolean
240
+     * @since 9.0.0
241
+     */
242
+    public function deleteCommentsAtObject($objectType, $objectId);
243 243
 
244
-	/**
245
-	 * sets the read marker for a given file to the specified date for the
246
-	 * provided user
247
-	 *
248
-	 * @param string $objectType
249
-	 * @param string $objectId
250
-	 * @param \DateTime $dateTime
251
-	 * @param \OCP\IUser $user
252
-	 * @since 9.0.0
253
-	 */
254
-	public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
244
+    /**
245
+     * sets the read marker for a given file to the specified date for the
246
+     * provided user
247
+     *
248
+     * @param string $objectType
249
+     * @param string $objectId
250
+     * @param \DateTime $dateTime
251
+     * @param \OCP\IUser $user
252
+     * @since 9.0.0
253
+     */
254
+    public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user);
255 255
 
256
-	/**
257
-	 * returns the read marker for a given file to the specified date for the
258
-	 * provided user. It returns null, when the marker is not present, i.e.
259
-	 * no comments were marked as read.
260
-	 *
261
-	 * @param string $objectType
262
-	 * @param string $objectId
263
-	 * @param \OCP\IUser $user
264
-	 * @return \DateTime|null
265
-	 * @since 9.0.0
266
-	 */
267
-	public function getReadMark($objectType, $objectId, \OCP\IUser $user);
256
+    /**
257
+     * returns the read marker for a given file to the specified date for the
258
+     * provided user. It returns null, when the marker is not present, i.e.
259
+     * no comments were marked as read.
260
+     *
261
+     * @param string $objectType
262
+     * @param string $objectId
263
+     * @param \OCP\IUser $user
264
+     * @return \DateTime|null
265
+     * @since 9.0.0
266
+     */
267
+    public function getReadMark($objectType, $objectId, \OCP\IUser $user);
268 268
 
269
-	/**
270
-	 * deletes the read markers for the specified user
271
-	 *
272
-	 * @param \OCP\IUser $user
273
-	 * @return bool
274
-	 * @since 9.0.0
275
-	 */
276
-	public function deleteReadMarksFromUser(\OCP\IUser $user);
269
+    /**
270
+     * deletes the read markers for the specified user
271
+     *
272
+     * @param \OCP\IUser $user
273
+     * @return bool
274
+     * @since 9.0.0
275
+     */
276
+    public function deleteReadMarksFromUser(\OCP\IUser $user);
277 277
 
278
-	/**
279
-	 * deletes the read markers on the specified object
280
-	 *
281
-	 * @param string $objectType
282
-	 * @param string $objectId
283
-	 * @return bool
284
-	 * @since 9.0.0
285
-	 */
286
-	public function deleteReadMarksOnObject($objectType, $objectId);
278
+    /**
279
+     * deletes the read markers on the specified object
280
+     *
281
+     * @param string $objectType
282
+     * @param string $objectId
283
+     * @return bool
284
+     * @since 9.0.0
285
+     */
286
+    public function deleteReadMarksOnObject($objectType, $objectId);
287 287
 
288
-	/**
289
-	 * registers an Entity to the manager, so event notifications can be send
290
-	 * to consumers of the comments infrastructure
291
-	 *
292
-	 * @param \Closure $closure
293
-	 * @since 11.0.0
294
-	 */
295
-	public function registerEventHandler(\Closure $closure);
288
+    /**
289
+     * registers an Entity to the manager, so event notifications can be send
290
+     * to consumers of the comments infrastructure
291
+     *
292
+     * @param \Closure $closure
293
+     * @since 11.0.0
294
+     */
295
+    public function registerEventHandler(\Closure $closure);
296 296
 
297
-	/**
298
-	 * registers a method that resolves an ID to a display name for a given type
299
-	 *
300
-	 * @param string $type
301
-	 * @param \Closure $closure
302
-	 * @throws \OutOfBoundsException
303
-	 * @since 11.0.0
304
-	 *
305
-	 * Only one resolver shall be registered per type. Otherwise a
306
-	 * \OutOfBoundsException has to thrown.
307
-	 */
308
-	public function registerDisplayNameResolver($type, \Closure $closure);
297
+    /**
298
+     * registers a method that resolves an ID to a display name for a given type
299
+     *
300
+     * @param string $type
301
+     * @param \Closure $closure
302
+     * @throws \OutOfBoundsException
303
+     * @since 11.0.0
304
+     *
305
+     * Only one resolver shall be registered per type. Otherwise a
306
+     * \OutOfBoundsException has to thrown.
307
+     */
308
+    public function registerDisplayNameResolver($type, \Closure $closure);
309 309
 
310
-	/**
311
-	 * resolves a given ID of a given Type to a display name.
312
-	 *
313
-	 * @param string $type
314
-	 * @param string $id
315
-	 * @return string
316
-	 * @throws \OutOfBoundsException
317
-	 * @since 11.0.0
318
-	 *
319
-	 * If a provided type was not registered, an \OutOfBoundsException shall
320
-	 * be thrown. It is upon the resolver discretion what to return of the
321
-	 * provided ID is unknown. It must be ensured that a string is returned.
322
-	 */
323
-	public function resolveDisplayName($type, $id);
310
+    /**
311
+     * resolves a given ID of a given Type to a display name.
312
+     *
313
+     * @param string $type
314
+     * @param string $id
315
+     * @return string
316
+     * @throws \OutOfBoundsException
317
+     * @since 11.0.0
318
+     *
319
+     * If a provided type was not registered, an \OutOfBoundsException shall
320
+     * be thrown. It is upon the resolver discretion what to return of the
321
+     * provided ID is unknown. It must be ensured that a string is returned.
322
+     */
323
+    public function resolveDisplayName($type, $id);
324 324
 
325 325
 }
Please login to merge, or discard this patch.