diff options
Diffstat (limited to 'lib/private/Comments/Manager.php')
-rw-r--r-- | lib/private/Comments/Manager.php | 445 |
1 files changed, 193 insertions, 252 deletions
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 123e4f6988d..047fa361dad 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -1,35 +1,14 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Simounet <contact@simounet.net> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Comments; use Doctrine\DBAL\Exception\DriverException; -use Doctrine\DBAL\Exception\InvalidFieldNameException; +use OCA\DAV\Connector\Sabre\File; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\CommentsEvent; use OCP\Comments\IComment; @@ -37,58 +16,42 @@ use OCP\Comments\ICommentsEventHandler; use OCP\Comments\ICommentsManager; use OCP\Comments\NotFoundException; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\FileInfo; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; use OCP\IConfig; use OCP\IDBConnection; -use OCP\IUser; +use OCP\IEmojiHelper; use OCP\IInitialStateService; +use OCP\IUser; use OCP\PreConditionNotMetException; use OCP\Util; use Psr\Log\LoggerInterface; class Manager implements ICommentsManager { - - /** @var IDBConnection */ - protected $dbConn; - - /** @var LoggerInterface */ - protected $logger; - - /** @var IConfig */ - protected $config; - - /** @var ITimeFactory */ - protected $timeFactory; - - /** @var EmojiHelper */ - protected $emojiHelper; - - /** @var IInitialStateService */ - protected $initialStateService; - /** @var IComment[] */ - protected $commentsCache = []; + protected array $commentsCache = []; - /** @var \Closure[] */ - protected $eventHandlerClosures = []; + /** @var \Closure[] */ + protected array $eventHandlerClosures = []; - /** @var ICommentsEventHandler[] */ - protected $eventHandlers = []; + /** @var ICommentsEventHandler[] */ + protected array $eventHandlers = []; /** @var \Closure[] */ - protected $displayNameResolvers = []; - - public function __construct(IDBConnection $dbConn, - LoggerInterface $logger, - IConfig $config, - ITimeFactory $timeFactory, - EmojiHelper $emojiHelper, - IInitialStateService $initialStateService) { - $this->dbConn = $dbConn; - $this->logger = $logger; - $this->config = $config; - $this->timeFactory = $timeFactory; - $this->emojiHelper = $emojiHelper; - $this->initialStateService = $initialStateService; + protected array $displayNameResolvers = []; + + public function __construct( + protected IDBConnection $dbConn, + protected LoggerInterface $logger, + protected IConfig $config, + protected ITimeFactory $timeFactory, + protected IEmojiHelper $emojiHelper, + protected IInitialStateService $initialStateService, + protected IRootFolder $rootFolder, + protected IEventDispatcher $eventDispatcher, + ) { } /** @@ -96,9 +59,8 @@ class Manager implements ICommentsManager { * IComment interface. * * @param array $data - * @return array */ - protected function normalizeDatabaseData(array $data) { + protected function normalizeDatabaseData(array $data): array { $data['id'] = (string)$data['id']; $data['parent_id'] = (string)$data['parent_id']; $data['topmost_parent_id'] = (string)$data['topmost_parent_id']; @@ -106,30 +68,35 @@ class Manager implements ICommentsManager { if (!is_null($data['latest_child_timestamp'])) { $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']); } + if (!is_null($data['expire_date'])) { + $data['expire_date'] = new \DateTime($data['expire_date']); + } $data['children_count'] = (int)$data['children_count']; - $data['reference_id'] = $data['reference_id'] ?? null; + $data['reference_id'] = $data['reference_id']; + $data['meta_data'] = json_decode($data['meta_data'], true); if ($this->supportReactions()) { - $list = json_decode($data['reactions'], true); - // Ordering does not work on the database with group concat and Oracle, - // So we simply sort on the output. - if (is_array($list)) { - uasort($list, static function ($a, $b) { - if ($a === $b) { - return 0; - } - return ($a > $b) ? -1 : 1; - }); + if ($data['reactions'] !== null) { + $list = json_decode($data['reactions'], true); + // Ordering does not work on the database with group concat and Oracle, + // So we simply sort on the output. + if (is_array($list)) { + uasort($list, static function ($a, $b) { + if ($a === $b) { + return 0; + } + return ($a > $b) ? -1 : 1; + }); + $data['reactions'] = $list; + } else { + $data['reactions'] = []; + } + } else { + $data['reactions'] = []; } - $data['reactions'] = $list; } return $data; } - - /** - * @param array $data - * @return IComment - */ public function getCommentFromData(array $data): IComment { return new Comment($this->normalizeDatabaseData($data)); } @@ -143,7 +110,7 @@ class Manager implements ICommentsManager { * by parameter for convenience * @throws \UnexpectedValueException */ - protected function prepareCommentForDatabaseWrite(IComment $comment) { + protected function prepareCommentForDatabaseWrite(IComment $comment): IComment { if (!$comment->getActorType() || $comment->getActorId() === '' || !$comment->getObjectType() @@ -153,18 +120,19 @@ class Manager implements ICommentsManager { throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); } - if ($comment->getVerb() === 'reaction' && !$this->emojiHelper->isValidEmoji($comment->getMessage())) { + if ($comment->getVerb() === 'reaction' && !$this->emojiHelper->isValidSingleEmoji($comment->getMessage())) { // 4 characters: laptop + person + gender + skin color => "🧑🏽💻" is a single emoji from the picker throw new \UnexpectedValueException('Reactions can only be a single emoji'); } if ($comment->getId() === '') { $comment->setChildrenCount(0); - $comment->setLatestChildDateTime(new \DateTime('0000-00-00 00:00:00', new \DateTimeZone('UTC'))); $comment->setLatestChildDateTime(null); } - if (is_null($comment->getCreationDateTime())) { + try { + $comment->getCreationDateTime(); + } catch (\LogicException $e) { $comment->setCreationDateTime(new \DateTime()); } @@ -183,10 +151,9 @@ class Manager implements ICommentsManager { * returns the topmost parent id of a given comment identified by ID * * @param string $id - * @return string * @throws NotFoundException */ - protected function determineTopmostParentId($id) { + protected function determineTopmostParentId($id): string { $comment = $this->get($id); if ($comment->getParentId() === '0') { return $comment->getId(); @@ -202,7 +169,7 @@ class Manager implements ICommentsManager { * @param \DateTime $cDateTime the date time of the most recent child * @throws NotFoundException */ - protected function updateChildrenInformation($id, \DateTime $cDateTime) { + protected function updateChildrenInformation($id, \DateTime $cDateTime): void { $qb = $this->dbConn->getQueryBuilder(); $query = $qb->select($qb->func()->count('id')) ->from('comments') @@ -229,7 +196,7 @@ class Manager implements ICommentsManager { * @param string $id * @throws \InvalidArgumentException */ - protected function checkRoleParameters($role, $type, $id) { + protected function checkRoleParameters($role, $type, $id): void { if ( !is_string($type) || empty($type) || !is_string($id) || empty($id) @@ -240,10 +207,8 @@ class Manager implements ICommentsManager { /** * run-time caches a comment - * - * @param IComment $comment */ - protected function cache(IComment $comment) { + protected function cache(IComment $comment): void { $id = $comment->getId(); if (empty($id)) { return; @@ -256,7 +221,7 @@ class Manager implements ICommentsManager { * * @param mixed $id the comment's id */ - protected function uncache($id) { + protected function uncache($id): void { $id = (string)$id; if (isset($this->commentsCache[$id])) { unset($this->commentsCache[$id]); @@ -267,12 +232,11 @@ class Manager implements ICommentsManager { * returns a comment instance * * @param string $id the ID of the comment - * @return IComment * @throws NotFoundException * @throws \InvalidArgumentException * @since 9.0.0 */ - public function get($id) { + public function get($id): IComment { if ((int)$id === 0) { throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.'); } @@ -301,35 +265,9 @@ class Manager implements ICommentsManager { } /** - * returns the comment specified by the id and all it's child comments. - * At this point of time, we do only support one level depth. - * - * @param string $id - * @param int $limit max number of entries to return, 0 returns all - * @param int $offset the start entry - * @return array - * @since 9.0.0 - * - * The return array looks like this - * [ - * 'comment' => IComment, // root comment - * 'replies' => - * [ - * 0 => - * [ - * 'comment' => IComment, - * 'replies' => [] - * ] - * 1 => - * [ - * 'comment' => IComment, - * 'replies'=> [] - * ], - * … - * ] - * ] + * @inheritDoc */ - public function getTree($id, $limit = 0, $offset = 0) { + public function getTree($id, $limit = 0, $offset = 0): array { $tree = []; $tree['comment'] = $this->get($id); $tree['replies'] = []; @@ -370,11 +308,11 @@ class Manager implements ICommentsManager { * @param string $objectType the object type, e.g. 'files' * @param string $objectId the id of the object * @param int $limit optional, number of maximum comments to be returned. if - * not specified, all comments are returned. + * not specified, all comments are returned. * @param int $offset optional, starting point * @param \DateTime $notOlderThan optional, timestamp of the oldest comments - * that may be returned - * @return IComment[] + * that may be returned + * @return list<IComment> * @since 9.0.0 */ public function getForObject( @@ -382,7 +320,7 @@ class Manager implements ICommentsManager { $objectId, $limit = 0, $offset = 0, - \DateTime $notOlderThan = null + ?\DateTime $notOlderThan = null, ) { $comments = []; @@ -424,10 +362,10 @@ class Manager implements ICommentsManager { * @param int $lastKnownCommentId the last known comment (will be used as offset) * @param string $sortDirection direction of the comments (`asc` or `desc`) * @param int $limit optional, number of maximum comments to be returned. if - * set to 0, all comments are returned. + * set to 0, all comments are returned. * @param bool $includeLastKnown - * @return IComment[] - * @return array + * @param string $topmostParentId Limit the comments to a list of replies and its original root comment + * @return list<IComment> */ public function getForObjectSince( string $objectType, @@ -435,7 +373,8 @@ class Manager implements ICommentsManager { int $lastKnownCommentId, string $sortDirection = 'asc', int $limit = 30, - bool $includeLastKnown = false + bool $includeLastKnown = false, + string $topmostParentId = '', ): array { return $this->getCommentsWithVerbForObjectSinceComment( $objectType, @@ -444,7 +383,8 @@ class Manager implements ICommentsManager { $lastKnownCommentId, $sortDirection, $limit, - $includeLastKnown + $includeLastKnown, + $topmostParentId, ); } @@ -455,9 +395,10 @@ class Manager implements ICommentsManager { * @param int $lastKnownCommentId the last known comment (will be used as offset) * @param string $sortDirection direction of the comments (`asc` or `desc`) * @param int $limit optional, number of maximum comments to be returned. if - * set to 0, all comments are returned. + * set to 0, all comments are returned. * @param bool $includeLastKnown - * @return IComment[] + * @param string $topmostParentId Limit the comments to a list of replies and its original root comment + * @return list<IComment> */ public function getCommentsWithVerbForObjectSinceComment( string $objectType, @@ -466,7 +407,8 @@ class Manager implements ICommentsManager { int $lastKnownCommentId, string $sortDirection = 'asc', int $limit = 30, - bool $includeLastKnown = false + bool $includeLastKnown = false, + string $topmostParentId = '', ): array { $comments = []; @@ -486,6 +428,13 @@ class Manager implements ICommentsManager { $query->andWhere($query->expr()->in('verb', $query->createNamedParameter($verbs, IQueryBuilder::PARAM_STR_ARRAY))); } + if ($topmostParentId !== '') { + $query->andWhere($query->expr()->orX( + $query->expr()->eq('id', $query->createNamedParameter($topmostParentId)), + $query->expr()->eq('topmost_parent_id', $query->createNamedParameter($topmostParentId)), + )); + } + $lastKnownComment = $lastKnownCommentId > 0 ? $this->getLastKnownComment( $objectType, $objectId, @@ -503,14 +452,14 @@ class Manager implements ICommentsManager { $query->expr()->orX( $query->expr()->lt( 'creation_timestamp', - $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), - IQueryBuilder::PARAM_DATE + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME_MUTABLE), + IQueryBuilder::PARAM_DATETIME_MUTABLE ), $query->expr()->andX( $query->expr()->eq( 'creation_timestamp', - $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), - IQueryBuilder::PARAM_DATE + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME_MUTABLE), + IQueryBuilder::PARAM_DATETIME_MUTABLE ), $idComparison ) @@ -526,20 +475,36 @@ class Manager implements ICommentsManager { $query->expr()->orX( $query->expr()->gt( 'creation_timestamp', - $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), - IQueryBuilder::PARAM_DATE + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME_MUTABLE), + IQueryBuilder::PARAM_DATETIME_MUTABLE ), $query->expr()->andX( $query->expr()->eq( 'creation_timestamp', - $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATE), - IQueryBuilder::PARAM_DATE + $query->createNamedParameter($lastKnownCommentDateTime, IQueryBuilder::PARAM_DATETIME_MUTABLE), + IQueryBuilder::PARAM_DATETIME_MUTABLE ), $idComparison ) ) ); } + } elseif ($lastKnownCommentId > 0) { + // We didn't find the "$lastKnownComment" but we still use the ID as an offset. + // This is required as a fall-back for expired messages in talk and deleted comments in other apps. + if ($sortDirection === 'desc') { + if ($includeLastKnown) { + $query->andWhere($query->expr()->lte('id', $query->createNamedParameter($lastKnownCommentId))); + } else { + $query->andWhere($query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId))); + } + } else { + if ($includeLastKnown) { + $query->andWhere($query->expr()->gte('id', $query->createNamedParameter($lastKnownCommentId))); + } else { + $query->andWhere($query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId))); + } + } } $resultStatement = $query->execute(); @@ -557,11 +522,10 @@ class Manager implements ICommentsManager { * @param string $objectType the object type, e.g. 'files' * @param string $objectId the id of the object * @param int $id the comment to look for - * @return Comment|null */ protected function getLastKnownComment(string $objectType, - string $objectId, - int $id) { + string $objectId, + int $id): ?IComment { $query = $this->dbConn->getQueryBuilder(); $query->select('*') ->from('comments') @@ -591,12 +555,12 @@ class Manager implements ICommentsManager { * @param string $verb Limit the verb of the comment * @param int $offset * @param int $limit - * @return IComment[] + * @return list<IComment> */ public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array { $objectIds = []; if ($objectId) { - $objectIds[] = $objectIds; + $objectIds[] = $objectId; } return $this->searchForObjects($search, $objectType, $objectIds, $verb, $offset, $limit); } @@ -610,7 +574,7 @@ class Manager implements ICommentsManager { * @param string $verb Limit the verb of the comment * @param int $offset * @param int $limit - * @return IComment[] + * @return list<IComment> */ public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array { $query = $this->dbConn->getQueryBuilder(); @@ -623,7 +587,7 @@ class Manager implements ICommentsManager { if ($search !== '') { $query->where($query->expr()->iLike('message', $query->createNamedParameter( - '%' . $this->dbConn->escapeLikeParameter($search). '%' + '%' . $this->dbConn->escapeLikeParameter($search) . '%' ))); } @@ -656,12 +620,12 @@ class Manager implements ICommentsManager { * @param $objectType string the object type, e.g. 'files' * @param $objectId string the id of the object * @param \DateTime $notOlderThan optional, timestamp of the oldest comments - * that may be returned + * that may be returned * @param string $verb Limit the verb of the comment - Added in 14.0.0 * @return Int * @since 9.0.0 */ - public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null, $verb = '') { + public function getNumberOfCommentsForObject($objectType, $objectId, ?\DateTime $notOlderThan = null, $verb = '') { $qb = $this->dbConn->getQueryBuilder(); $query = $qb->select($qb->func()->count('id')) ->from('comments') @@ -723,7 +687,7 @@ class Manager implements ICommentsManager { $result = $query->executeQuery(); while ($row = $result->fetch()) { - $unreadComments[$row['object_id']] = (int) $row['num_comments']; + $unreadComments[$row['object_id']] = (int)$row['num_comments']; } $result->closeCursor(); } @@ -771,7 +735,7 @@ class Manager implements ICommentsManager { $data = $result->fetch(); $result->closeCursor(); - return (int) ($data['num_messages'] ?? 0); + return (int)($data['num_messages'] ?? 0); } /** @@ -788,7 +752,7 @@ class Manager implements ICommentsManager { ->from('comments') ->where($query->expr()->eq('object_type', $query->createNamedParameter($objectType))) ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))) - ->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATE))) + ->andWhere($query->expr()->lt('creation_timestamp', $query->createNamedParameter($beforeDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))) ->orderBy('creation_timestamp', 'desc'); if ($verb !== '') { @@ -799,7 +763,7 @@ class Manager implements ICommentsManager { $data = $result->fetch(); $result->closeCursor(); - return (int) ($data['id'] ?? 0); + return (int)($data['id'] ?? 0); } /** @@ -817,7 +781,7 @@ class Manager implements ICommentsManager { string $objectId, string $verb, string $actorType, - array $actors + array $actors, ): array { $lastComments = []; @@ -844,54 +808,25 @@ class Manager implements ICommentsManager { /** * Get the number of unread comments for all files in a folder * + * This is unused since 8bd39fccf411195839f2dadee085fad18ec52c23 + * * @param int $folderId * @param IUser $user * @return array [$fileId => $unreadCount] */ public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) { - $qb = $this->dbConn->getQueryBuilder(); - - $query = $qb->select('f.fileid') - ->addSelect($qb->func()->count('c.id', 'num_ids')) - ->from('filecache', 'f') - ->leftJoin('f', 'comments', 'c', $qb->expr()->andX( - $qb->expr()->eq('f.fileid', $qb->expr()->castColumn('c.object_id', IQueryBuilder::PARAM_INT)), - $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')) - )) - ->leftJoin('c', 'comments_read_markers', 'm', $qb->expr()->andX( - $qb->expr()->eq('c.object_id', 'm.object_id'), - $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')) - )) - ->where( - $qb->expr()->andX( - $qb->expr()->eq('f.parent', $qb->createNamedParameter($folderId)), - $qb->expr()->orX( - $qb->expr()->eq('c.object_type', $qb->createNamedParameter('files')), - $qb->expr()->isNull('c.object_type') - ), - $qb->expr()->orX( - $qb->expr()->eq('m.object_type', $qb->createNamedParameter('files')), - $qb->expr()->isNull('m.object_type') - ), - $qb->expr()->orX( - $qb->expr()->eq('m.user_id', $qb->createNamedParameter($user->getUID())), - $qb->expr()->isNull('m.user_id') - ), - $qb->expr()->orX( - $qb->expr()->gt('c.creation_timestamp', 'm.marker_datetime'), - $qb->expr()->isNull('m.marker_datetime') - ) - ) - )->groupBy('f.fileid'); - - $resultStatement = $query->execute(); - - $results = []; - while ($row = $resultStatement->fetch()) { - $results[$row['fileid']] = (int) $row['num_ids']; + $directory = $this->rootFolder->getFirstNodeById($folderId); + if (!$directory instanceof Folder) { + return []; } - $resultStatement->closeCursor(); - return $results; + $children = $directory->getDirectoryListing(); + $ids = array_map(fn (FileInfo $child) => (string)$child->getId(), $children); + + $ids[] = (string)$directory->getId(); + $counts = $this->getNumberOfUnreadCommentsForObjects('files', $ids, $user); + return array_filter($counts, function (int $count) { + return $count > 0; + }); } /** @@ -1023,6 +958,7 @@ class Manager implements ICommentsManager { ->select('message_id') ->from('reactions') ->where($qb->expr()->eq('parent_id', $qb->createNamedParameter($parentId))) + ->orderBy('message_id', 'DESC') ->executeQuery(); $commentIds = []; @@ -1074,7 +1010,7 @@ class Manager implements ICommentsManager { * @since 24.0.0 */ public function supportReactions(): bool { - return $this->dbConn->supports4ByteText(); + return $this->emojiHelper->doesPlatformSupportEmoji(); } /** @@ -1098,22 +1034,29 @@ class Manager implements ICommentsManager { if (!$commentIds) { return []; } - $query = $this->dbConn->getQueryBuilder(); + $chunks = array_chunk($commentIds, 500); + + $query = $this->dbConn->getQueryBuilder(); $query->select('*') ->from('comments') - ->where($query->expr()->in('id', $query->createNamedParameter($commentIds, IQueryBuilder::PARAM_STR_ARRAY))) + ->where($query->expr()->in('id', $query->createParameter('ids'))) ->orderBy('creation_timestamp', 'DESC') ->addOrderBy('id', 'DESC'); $comments = []; - $result = $query->executeQuery(); - while ($data = $result->fetch()) { - $comment = $this->getCommentFromData($data); - $this->cache($comment); - $comments[] = $comment; + foreach ($chunks as $ids) { + $query->setParameter('ids', $ids, IQueryBuilder::PARAM_STR_ARRAY); + + $result = $query->executeQuery(); + while ($data = $result->fetch()) { + $comment = $this->getCommentFromData($data); + $this->cache($comment); + $comments[] = $comment; + } + $result->closeCursor(); } - $result->closeCursor(); + return $comments; } @@ -1148,7 +1091,7 @@ class Manager implements ICommentsManager { $result = $this->update($comment); } - if ($result && !!$comment->getParentId()) { + if ($result && (bool)$comment->getParentId()) { $this->updateChildrenInformation( $comment->getParentId(), $comment->getCreationDateTime() @@ -1166,22 +1109,6 @@ class Manager implements ICommentsManager { * @return bool */ protected function insert(IComment $comment): bool { - try { - $result = $this->insertQuery($comment, true); - } catch (InvalidFieldNameException $e) { - // The reference id field was only added in Nextcloud 19. - // In order to not cause too long waiting times on the update, - // it was decided to only add it lazy, as it is also not a critical - // feature, but only helps to have a better experience while commenting. - // So in case the reference_id field is missing, - // we simply save the comment without that field. - $result = $this->insertQuery($comment, false); - } - - return $result; - } - - protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool { $qb = $this->dbConn->getQueryBuilder(); $values = [ @@ -1196,12 +1123,11 @@ class Manager implements ICommentsManager { 'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'), 'object_type' => $qb->createNamedParameter($comment->getObjectType()), 'object_id' => $qb->createNamedParameter($comment->getObjectId()), + 'expire_date' => $qb->createNamedParameter($comment->getExpireDate(), 'datetime'), + 'reference_id' => $qb->createNamedParameter($comment->getReferenceId()), + 'meta_data' => $qb->createNamedParameter(json_encode($comment->getMetaData())), ]; - if ($tryWritingReferenceId) { - $values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId()); - } - $affectedRows = $qb->insert('comments') ->values($values) ->execute(); @@ -1227,7 +1153,7 @@ class Manager implements ICommentsManager { ->andWhere($qb->expr()->eq('actor_id', $qb->createNamedParameter($reaction->getActorId()))) ->andWhere($qb->expr()->eq('reaction', $qb->createNamedParameter($reaction->getMessage()))); $result = $qb->executeQuery(); - $exists = (int) $result->fetchOne(); + $exists = (int)$result->fetchOne(); if (!$exists) { $qb = $this->dbConn->getQueryBuilder(); try { @@ -1251,8 +1177,6 @@ class Manager implements ICommentsManager { } private function sumReactions(string $parentId): void { - $qb = $this->dbConn->getQueryBuilder(); - $totalQuery = $this->dbConn->getQueryBuilder(); $totalQuery ->selectAlias( @@ -1266,9 +1190,10 @@ class Manager implements ICommentsManager { ) ->selectAlias($totalQuery->func()->count('id'), 'total') ->from('reactions', 'r') - ->where($totalQuery->expr()->eq('r.parent_id', $qb->createNamedParameter($parentId))) + ->where($totalQuery->expr()->eq('r.parent_id', $totalQuery->createNamedParameter($parentId))) ->groupBy('r.reaction') ->orderBy('total', 'DESC') + ->addOrderBy('r.reaction', 'ASC') ->setMaxResults(20); $jsonQuery = $this->dbConn->getQueryBuilder(); @@ -1283,9 +1208,10 @@ class Manager implements ICommentsManager { ) ->from($jsonQuery->createFunction('(' . $totalQuery->getSQL() . ')'), 'json'); + $qb = $this->dbConn->getQueryBuilder(); $qb ->update('comments') - ->set('reactions', $jsonQuery->createFunction('(' . $jsonQuery->getSQL() . ')')) + ->set('reactions', $qb->createFunction('(' . $jsonQuery->getSQL() . ')')) ->where($qb->expr()->eq('id', $qb->createNamedParameter($parentId))) ->executeStatement(); } @@ -1304,12 +1230,7 @@ class Manager implements ICommentsManager { $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId())); $this->uncache($comment->getId()); - try { - $result = $this->updateQuery($comment, true); - } catch (InvalidFieldNameException $e) { - // See function insert() for explanation - $result = $this->updateQuery($comment, false); - } + $result = $this->updateQuery($comment); if ($comment->getVerb() === 'reaction_deleted') { $this->deleteReaction($comment); @@ -1320,7 +1241,7 @@ class Manager implements ICommentsManager { return $result; } - protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool { + protected function updateQuery(IComment $comment): bool { $qb = $this->dbConn->getQueryBuilder(); $qb ->update('comments') @@ -1334,14 +1255,12 @@ class Manager implements ICommentsManager { ->set('creation_timestamp', $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime')) ->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime')) ->set('object_type', $qb->createNamedParameter($comment->getObjectType())) - ->set('object_id', $qb->createNamedParameter($comment->getObjectId())); - - if ($tryWritingReferenceId) { - $qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId())); - } - - $affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId()))) - ->execute(); + ->set('object_id', $qb->createNamedParameter($comment->getObjectId())) + ->set('expire_date', $qb->createNamedParameter($comment->getExpireDate(), 'datetime')) + ->set('reference_id', $qb->createNamedParameter($comment->getReferenceId())) + ->set('meta_data', $qb->createNamedParameter(json_encode($comment->getMetaData()))) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId()))); + $affectedRows = $qb->executeStatement(); if ($affectedRows === 0) { throw new NotFoundException('Comment to update does ceased to exist'); @@ -1624,6 +1543,7 @@ class Manager implements ICommentsManager { foreach ($entities as $entity) { $entity->handle($event); } + $this->eventDispatcher->dispatchTyped($event); } /** @@ -1635,4 +1555,25 @@ class Manager implements ICommentsManager { $this->initialStateService->provideInitialState('comments', 'max-message-length', IComment::MAX_MESSAGE_LENGTH); Util::addScript('comments', 'comments-app'); } + + /** + * @inheritDoc + */ + public function deleteCommentsExpiredAtObject(string $objectType, string $objectId = ''): bool { + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('comments') + ->where($qb->expr()->lte('expire_date', + $qb->createNamedParameter($this->timeFactory->getDateTime(), IQueryBuilder::PARAM_DATETIME_MUTABLE))) + ->andWhere($qb->expr()->eq('object_type', $qb->createNamedParameter($objectType))); + + if ($objectId !== '') { + $qb->andWhere($qb->expr()->eq('object_id', $qb->createNamedParameter($objectId))); + } + + $affectedRows = $qb->executeStatement(); + + $this->commentsCache = []; + + return $affectedRows > 0; + } } |