From 653325f25415e109ebe24daccbffc8d419fae22e Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 15 Apr 2016 15:54:20 +0200 Subject: Move \OC\Comments to PSR-4 --- lib/private/Comments/Comment.php | 382 ++++++++++++++++ lib/private/Comments/Manager.php | 753 ++++++++++++++++++++++++++++++++ lib/private/Comments/ManagerFactory.php | 62 +++ lib/private/comments/comment.php | 382 ---------------- lib/private/comments/manager.php | 753 -------------------------------- lib/private/comments/managerfactory.php | 62 --- 6 files changed, 1197 insertions(+), 1197 deletions(-) create mode 100644 lib/private/Comments/Comment.php create mode 100644 lib/private/Comments/Manager.php create mode 100644 lib/private/Comments/ManagerFactory.php delete mode 100644 lib/private/comments/comment.php delete mode 100644 lib/private/comments/manager.php delete mode 100644 lib/private/comments/managerfactory.php (limited to 'lib/private') diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php new file mode 100644 index 00000000000..60663d61578 --- /dev/null +++ b/lib/private/Comments/Comment.php @@ -0,0 +1,382 @@ + + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @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 + * + */ +namespace OC\Comments; + +use OCP\Comments\IComment; +use OCP\Comments\IllegalIDChangeException; +use OCP\Comments\MessageTooLongException; + +class Comment implements IComment { + + protected $data = [ + 'id' => '', + 'parentId' => '0', + 'topmostParentId' => '0', + 'childrenCount' => '0', + 'message' => '', + 'verb' => '', + 'actorType' => '', + 'actorId' => '', + 'objectType' => '', + 'objectId' => '', + 'creationDT' => null, + 'latestChildDT' => null, + ]; + + /** + * Comment constructor. + * + * @param [] $data optional, array with keys according to column names from + * the comments database scheme + */ + public function __construct(array $data = null) { + if(is_array($data)) { + $this->fromArray($data); + } + } + + /** + * returns the ID of the comment + * + * It may return an empty string, if the comment was not stored. + * It is expected that the concrete Comment implementation gives an ID + * by itself (e.g. after saving). + * + * @return string + * @since 9.0.0 + */ + public function getId() { + return $this->data['id']; + } + + /** + * sets the ID of the comment and returns itself + * + * It is only allowed to set the ID only, if the current id is an empty + * string (which means it is not stored in a database, storage or whatever + * the concrete implementation does), or vice versa. Changing a given ID is + * not permitted and must result in an IllegalIDChangeException. + * + * @param string $id + * @return IComment + * @throws IllegalIDChangeException + * @since 9.0.0 + */ + public function setId($id) { + if(!is_string($id)) { + throw new \InvalidArgumentException('String expected.'); + } + + $id = trim($id); + if($this->data['id'] === '' || ($this->data['id'] !== '' && $id === '')) { + $this->data['id'] = $id; + return $this; + } + + throw new IllegalIDChangeException('Not allowed to assign a new ID to an already saved comment.'); + } + + /** + * returns the parent ID of the comment + * + * @return string + * @since 9.0.0 + */ + public function getParentId() { + return $this->data['parentId']; + } + + /** + * sets the parent ID and returns itself + * + * @param string $parentId + * @return IComment + * @since 9.0.0 + */ + public function setParentId($parentId) { + if(!is_string($parentId)) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['parentId'] = trim($parentId); + return $this; + } + + /** + * returns the topmost parent ID of the comment + * + * @return string + * @since 9.0.0 + */ + public function getTopmostParentId() { + return $this->data['topmostParentId']; + } + + + /** + * sets the topmost parent ID and returns itself + * + * @param string $id + * @return IComment + * @since 9.0.0 + */ + public function setTopmostParentId($id) { + if(!is_string($id)) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['topmostParentId'] = trim($id); + return $this; + } + + /** + * returns the number of children + * + * @return int + * @since 9.0.0 + */ + public function getChildrenCount() { + return $this->data['childrenCount']; + } + + /** + * sets the number of children + * + * @param int $count + * @return IComment + * @since 9.0.0 + */ + public function setChildrenCount($count) { + if(!is_int($count)) { + throw new \InvalidArgumentException('Integer expected.'); + } + $this->data['childrenCount'] = $count; + return $this; + } + + /** + * returns the message of the comment + * + * @return string + * @since 9.0.0 + */ + public function getMessage() { + return $this->data['message']; + } + + /** + * sets the message of the comment and returns itself + * + * @param string $message + * @return IComment + * @throws MessageTooLongException + * @since 9.0.0 + */ + public function setMessage($message) { + if(!is_string($message)) { + throw new \InvalidArgumentException('String expected.'); + } + $message = trim($message); + if(mb_strlen($message, 'UTF-8') > IComment::MAX_MESSAGE_LENGTH) { + throw new MessageTooLongException('Comment message must not exceed ' . IComment::MAX_MESSAGE_LENGTH . ' characters'); + } + $this->data['message'] = $message; + return $this; + } + + /** + * returns the verb of the comment + * + * @return string + * @since 9.0.0 + */ + public function getVerb() { + return $this->data['verb']; + } + + /** + * sets the verb of the comment, e.g. 'comment' or 'like' + * + * @param string $verb + * @return IComment + * @since 9.0.0 + */ + public function setVerb($verb) { + if(!is_string($verb) || !trim($verb)) { + throw new \InvalidArgumentException('Non-empty String expected.'); + } + $this->data['verb'] = trim($verb); + return $this; + } + + /** + * returns the actor type + * + * @return string + * @since 9.0.0 + */ + public function getActorType() { + return $this->data['actorType']; + } + + /** + * returns the actor ID + * + * @return string + * @since 9.0.0 + */ + public function getActorId() { + return $this->data['actorId']; + } + + /** + * sets (overwrites) the actor type and id + * + * @param string $actorType e.g. 'users' + * @param string $actorId e.g. 'zombie234' + * @return IComment + * @since 9.0.0 + */ + public function setActor($actorType, $actorId) { + if( + !is_string($actorType) || !trim($actorType) + || !is_string($actorId) || !trim($actorId) + ) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['actorType'] = trim($actorType); + $this->data['actorId'] = trim($actorId); + return $this; + } + + /** + * returns the creation date of the comment. + * + * If not explicitly set, it shall default to the time of initialization. + * + * @return \DateTime + * @since 9.0.0 + */ + public function getCreationDateTime() { + return $this->data['creationDT']; + } + + /** + * sets the creation date of the comment and returns itself + * + * @param \DateTime $timestamp + * @return IComment + * @since 9.0.0 + */ + public function setCreationDateTime(\DateTime $timestamp) { + $this->data['creationDT'] = $timestamp; + return $this; + } + + /** + * returns the DateTime of the most recent child, if set, otherwise null + * + * @return \DateTime|null + * @since 9.0.0 + */ + public function getLatestChildDateTime() { + return $this->data['latestChildDT']; + } + + /** + * sets the date of the most recent child + * + * @param \DateTime $dateTime + * @return IComment + * @since 9.0.0 + */ + public function setLatestChildDateTime(\DateTime $dateTime = null) { + $this->data['latestChildDT'] = $dateTime; + return $this; + } + + /** + * returns the object type the comment is attached to + * + * @return string + * @since 9.0.0 + */ + public function getObjectType() { + return $this->data['objectType']; + } + + /** + * returns the object id the comment is attached to + * + * @return string + * @since 9.0.0 + */ + public function getObjectId() { + return $this->data['objectId']; + } + + /** + * sets (overwrites) the object of the comment + * + * @param string $objectType e.g. 'files' + * @param string $objectId e.g. '16435' + * @return IComment + * @since 9.0.0 + */ + public function setObject($objectType, $objectId) { + if( + !is_string($objectType) || !trim($objectType) + || !is_string($objectId) || !trim($objectId) + ) { + throw new \InvalidArgumentException('String expected.'); + } + $this->data['objectType'] = trim($objectType); + $this->data['objectId'] = trim($objectId); + return $this; + } + + /** + * sets the comment data based on an array with keys as taken from the + * database. + * + * @param [] $data + * @return IComment + */ + protected function fromArray($data) { + foreach(array_keys($data) as $key) { + // translate DB keys to internal setter names + $setter = 'set' . implode('', array_map('ucfirst', explode('_', $key))); + $setter = str_replace('Timestamp', 'DateTime', $setter); + + if(method_exists($this, $setter)) { + $this->$setter($data[$key]); + } + } + + foreach(['actor', 'object'] as $role) { + if(isset($data[$role . '_type']) && isset($data[$role . '_id'])) { + $setter = 'set' . ucfirst($role); + $this->$setter($data[$role . '_type'], $data[$role . '_id']); + } + } + + return $this; + } +} diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php new file mode 100644 index 00000000000..b986c8c51f9 --- /dev/null +++ b/lib/private/Comments/Manager.php @@ -0,0 +1,753 @@ + + * @author Joas Schilling + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @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 + * + */ +namespace OC\Comments; + +use Doctrine\DBAL\Exception\DriverException; +use OCP\Comments\CommentsEvent; +use OCP\Comments\IComment; +use OCP\Comments\ICommentsManager; +use OCP\Comments\NotFoundException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUser; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +class Manager implements ICommentsManager { + + /** @var IDBConnection */ + protected $dbConn; + + /** @var ILogger */ + protected $logger; + + /** @var IConfig */ + protected $config; + + /** @var EventDispatcherInterface */ + protected $dispatcher; + + /** @var IComment[] */ + protected $commentsCache = []; + + /** + * Manager constructor. + * + * @param IDBConnection $dbConn + * @param ILogger $logger + * @param IConfig $config + * @param EventDispatcherInterface $dispatcher + */ + public function __construct( + IDBConnection $dbConn, + ILogger $logger, + IConfig $config, + EventDispatcherInterface $dispatcher + ) { + $this->dbConn = $dbConn; + $this->logger = $logger; + $this->config = $config; + $this->dispatcher = $dispatcher; + } + + /** + * converts data base data into PHP native, proper types as defined by + * IComment interface. + * + * @param array $data + * @return array + */ + protected function normalizeDatabaseData(array $data) { + $data['id'] = strval($data['id']); + $data['parent_id'] = strval($data['parent_id']); + $data['topmost_parent_id'] = strval($data['topmost_parent_id']); + $data['creation_timestamp'] = new \DateTime($data['creation_timestamp']); + if (!is_null($data['latest_child_timestamp'])) { + $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']); + } + $data['children_count'] = intval($data['children_count']); + return $data; + } + + /** + * prepares a comment for an insert or update operation after making sure + * all necessary fields have a value assigned. + * + * @param IComment $comment + * @return IComment returns the same updated IComment instance as provided + * by parameter for convenience + * @throws \UnexpectedValueException + */ + protected function prepareCommentForDatabaseWrite(IComment $comment) { + if( !$comment->getActorType() + || !$comment->getActorId() + || !$comment->getObjectType() + || !$comment->getObjectId() + || !$comment->getVerb() + ) { + throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); + } + + 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())) { + $comment->setCreationDateTime(new \DateTime()); + } + + if($comment->getParentId() !== '0') { + $comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId())); + } else { + $comment->setTopmostParentId('0'); + } + + $this->cache($comment); + + return $comment; + } + + /** + * returns the topmost parent id of a given comment identified by ID + * + * @param string $id + * @return string + * @throws NotFoundException + */ + protected function determineTopmostParentId($id) { + $comment = $this->get($id); + if($comment->getParentId() === '0') { + return $comment->getId(); + } else { + return $this->determineTopmostParentId($comment->getId()); + } + } + + /** + * updates child information of a comment + * + * @param string $id + * @param \DateTime $cDateTime the date time of the most recent child + * @throws NotFoundException + */ + protected function updateChildrenInformation($id, \DateTime $cDateTime) { + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select($qb->createFunction('COUNT(`id`)')) + ->from('comments') + ->where($qb->expr()->eq('parent_id', $qb->createParameter('id'))) + ->setParameter('id', $id); + + $resultStatement = $query->execute(); + $data = $resultStatement->fetch(\PDO::FETCH_NUM); + $resultStatement->closeCursor(); + $children = intval($data[0]); + + $comment = $this->get($id); + $comment->setChildrenCount($children); + $comment->setLatestChildDateTime($cDateTime); + $this->save($comment); + } + + /** + * Tests whether actor or object type and id parameters are acceptable. + * Throws exception if not. + * + * @param string $role + * @param string $type + * @param string $id + * @throws \InvalidArgumentException + */ + protected function checkRoleParameters($role, $type, $id) { + if( + !is_string($type) || empty($type) + || !is_string($id) || empty($id) + ) { + throw new \InvalidArgumentException($role . ' parameters must be string and not empty'); + } + } + + /** + * run-time caches a comment + * + * @param IComment $comment + */ + protected function cache(IComment $comment) { + $id = $comment->getId(); + if(empty($id)) { + return; + } + $this->commentsCache[strval($id)] = $comment; + } + + /** + * removes an entry from the comments run time cache + * + * @param mixed $id the comment's id + */ + protected function uncache($id) { + $id = strval($id); + if (isset($this->commentsCache[$id])) { + unset($this->commentsCache[$id]); + } + } + + /** + * 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) { + if(intval($id) === 0) { + throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.'); + } + + if(isset($this->commentsCache[$id])) { + return $this->commentsCache[$id]; + } + + $qb = $this->dbConn->getQueryBuilder(); + $resultStatement = $qb->select('*') + ->from('comments') + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) + ->setParameter('id', $id, IQueryBuilder::PARAM_INT) + ->execute(); + + $data = $resultStatement->fetch(); + $resultStatement->closeCursor(); + if(!$data) { + throw new NotFoundException(); + } + + $comment = new Comment($this->normalizeDatabaseData($data)); + $this->cache($comment); + return $comment; + } + + /** + * 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'=> [] + * ], + * … + * ] + * ] + */ + public function getTree($id, $limit = 0, $offset = 0) { + $tree = []; + $tree['comment'] = $this->get($id); + $tree['replies'] = []; + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select('*') + ->from('comments') + ->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id'))) + ->orderBy('creation_timestamp', 'DESC') + ->setParameter('id', $id); + + if($limit > 0) { + $query->setMaxResults($limit); + } + if($offset > 0) { + $query->setFirstResult($offset); + } + + $resultStatement = $query->execute(); + while($data = $resultStatement->fetch()) { + $comment = new Comment($this->normalizeDatabaseData($data)); + $this->cache($comment); + $tree['replies'][] = [ + 'comment' => $comment, + 'replies' => [] + ]; + } + $resultStatement->closeCursor(); + + return $tree; + } + + /** + * returns comments for a specific object (e.g. a file). + * + * The sort order is always newest to oldest. + * + * @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. + * @param int $offset optional, starting point + * @param \DateTime $notOlderThan optional, timestamp of the oldest comments + * that may be returned + * @return IComment[] + * @since 9.0.0 + */ + public function getForObject( + $objectType, + $objectId, + $limit = 0, + $offset = 0, + \DateTime $notOlderThan = null + ) { + $comments = []; + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select('*') + ->from('comments') + ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) + ->orderBy('creation_timestamp', 'DESC') + ->setParameter('type', $objectType) + ->setParameter('id', $objectId); + + if($limit > 0) { + $query->setMaxResults($limit); + } + if($offset > 0) { + $query->setFirstResult($offset); + } + if(!is_null($notOlderThan)) { + $query + ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan'))) + ->setParameter('notOlderThan', $notOlderThan, 'datetime'); + } + + $resultStatement = $query->execute(); + while($data = $resultStatement->fetch()) { + $comment = new Comment($this->normalizeDatabaseData($data)); + $this->cache($comment); + $comments[] = $comment; + } + $resultStatement->closeCursor(); + + return $comments; + } + + /** + * @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 + * @return Int + * @since 9.0.0 + */ + public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null) { + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->select($qb->createFunction('COUNT(`id`)')) + ->from('comments') + ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) + ->setParameter('type', $objectType) + ->setParameter('id', $objectId); + + if(!is_null($notOlderThan)) { + $query + ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan'))) + ->setParameter('notOlderThan', $notOlderThan, 'datetime'); + } + + $resultStatement = $query->execute(); + $data = $resultStatement->fetch(\PDO::FETCH_NUM); + $resultStatement->closeCursor(); + return intval($data[0]); + } + + /** + * creates a new comment and returns it. At this point of time, it is not + * saved in the used data storage. Use save() after setting other fields + * of the comment (e.g. message or verb). + * + * @param string $actorType the actor type (e.g. 'users') + * @param string $actorId a user id + * @param string $objectType the object type the comment is attached to + * @param string $objectId the object id the comment is attached to + * @return IComment + * @since 9.0.0 + */ + public function create($actorType, $actorId, $objectType, $objectId) { + $comment = new Comment(); + $comment + ->setActor($actorType, $actorId) + ->setObject($objectType, $objectId); + return $comment; + } + + /** + * permanently deletes the comment specified by the ID + * + * When the comment has child comments, their parent ID will be changed to + * the parent ID of the item that is to be deleted. + * + * @param string $id + * @return bool + * @throws \InvalidArgumentException + * @since 9.0.0 + */ + public function delete($id) { + if(!is_string($id)) { + throw new \InvalidArgumentException('Parameter must be string'); + } + + try { + $comment = $this->get($id); + } catch (\Exception $e) { + // Ignore exceptions, we just don't fire a hook then + $comment = null; + } + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->delete('comments') + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) + ->setParameter('id', $id); + + try { + $affectedRows = $query->execute(); + $this->uncache($id); + } catch (DriverException $e) { + $this->logger->logException($e, ['app' => 'core_comments']); + return false; + } + + if ($affectedRows > 0 && $comment instanceof IComment) { + $this->dispatcher->dispatch(CommentsEvent::EVENT_DELETE, new CommentsEvent( + CommentsEvent::EVENT_DELETE, + $comment + )); + } + + return ($affectedRows > 0); + } + + /** + * saves the comment permanently + * + * if the supplied comment has an empty ID, a new entry comment will be + * saved and the instance updated with the new ID. + * + * Otherwise, an existing comment will be updated. + * + * Throws NotFoundException when a comment that is to be updated does not + * exist anymore at this point of time. + * + * @param IComment $comment + * @return bool + * @throws NotFoundException + * @since 9.0.0 + */ + public function save(IComment $comment) { + if($this->prepareCommentForDatabaseWrite($comment)->getId() === '') { + $result = $this->insert($comment); + } else { + $result = $this->update($comment); + } + + if($result && !!$comment->getParentId()) { + $this->updateChildrenInformation( + $comment->getParentId(), + $comment->getCreationDateTime() + ); + $this->cache($comment); + } + + return $result; + } + + /** + * inserts the provided comment in the database + * + * @param IComment $comment + * @return bool + */ + protected function insert(IComment &$comment) { + $qb = $this->dbConn->getQueryBuilder(); + $affectedRows = $qb + ->insert('comments') + ->values([ + 'parent_id' => $qb->createNamedParameter($comment->getParentId()), + 'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()), + 'children_count' => $qb->createNamedParameter($comment->getChildrenCount()), + 'actor_type' => $qb->createNamedParameter($comment->getActorType()), + 'actor_id' => $qb->createNamedParameter($comment->getActorId()), + 'message' => $qb->createNamedParameter($comment->getMessage()), + 'verb' => $qb->createNamedParameter($comment->getVerb()), + 'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'), + 'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'), + 'object_type' => $qb->createNamedParameter($comment->getObjectType()), + 'object_id' => $qb->createNamedParameter($comment->getObjectId()), + ]) + ->execute(); + + if ($affectedRows > 0) { + $comment->setId(strval($qb->getLastInsertId())); + } + + $this->dispatcher->dispatch(CommentsEvent::EVENT_ADD, new CommentsEvent( + CommentsEvent::EVENT_ADD, + $comment + )); + + return $affectedRows > 0; + } + + /** + * updates a Comment data row + * + * @param IComment $comment + * @return bool + * @throws NotFoundException + */ + protected function update(IComment $comment) { + $qb = $this->dbConn->getQueryBuilder(); + $affectedRows = $qb + ->update('comments') + ->set('parent_id', $qb->createNamedParameter($comment->getParentId())) + ->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId())) + ->set('children_count', $qb->createNamedParameter($comment->getChildrenCount())) + ->set('actor_type', $qb->createNamedParameter($comment->getActorType())) + ->set('actor_id', $qb->createNamedParameter($comment->getActorId())) + ->set('message', $qb->createNamedParameter($comment->getMessage())) + ->set('verb', $qb->createNamedParameter($comment->getVerb())) + ->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())) + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) + ->setParameter('id', $comment->getId()) + ->execute(); + + if($affectedRows === 0) { + throw new NotFoundException('Comment to update does ceased to exist'); + } + + $this->dispatcher->dispatch(CommentsEvent::EVENT_UPDATE, new CommentsEvent( + CommentsEvent::EVENT_UPDATE, + $comment + )); + + return $affectedRows > 0; + } + + /** + * removes references to specific actor (e.g. on user delete) of a comment. + * The comment itself must not get lost/deleted. + * + * @param string $actorType the actor type (e.g. 'users') + * @param string $actorId a user id + * @return boolean + * @since 9.0.0 + */ + public function deleteReferencesOfActor($actorType, $actorId) { + $this->checkRoleParameters('Actor', $actorType, $actorId); + + $qb = $this->dbConn->getQueryBuilder(); + $affectedRows = $qb + ->update('comments') + ->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER)) + ->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER)) + ->where($qb->expr()->eq('actor_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id'))) + ->setParameter('type', $actorType) + ->setParameter('id', $actorId) + ->execute(); + + $this->commentsCache = []; + + return is_int($affectedRows); + } + + /** + * deletes all comments made of a specific object (e.g. on file delete) + * + * @param string $objectType the object type (e.g. 'files') + * @param string $objectId e.g. the file id + * @return boolean + * @since 9.0.0 + */ + public function deleteCommentsAtObject($objectType, $objectId) { + $this->checkRoleParameters('Object', $objectType, $objectId); + + $qb = $this->dbConn->getQueryBuilder(); + $affectedRows = $qb + ->delete('comments') + ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) + ->setParameter('type', $objectType) + ->setParameter('id', $objectId) + ->execute(); + + $this->commentsCache = []; + + return is_int($affectedRows); + } + + /** + * deletes the read markers for the specified user + * + * @param \OCP\IUser $user + * @return bool + * @since 9.0.0 + */ + public function deleteReadMarksFromUser(IUser $user) { + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->delete('comments_read_markers') + ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) + ->setParameter('user_id', $user->getUID()); + + try { + $affectedRows = $query->execute(); + } catch (DriverException $e) { + $this->logger->logException($e, ['app' => 'core_comments']); + return false; + } + return ($affectedRows > 0); + } + + /** + * sets the read marker for a given file to the specified date for the + * provided user + * + * @param string $objectType + * @param string $objectId + * @param \DateTime $dateTime + * @param IUser $user + * @since 9.0.0 + */ + public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) { + $this->checkRoleParameters('Object', $objectType, $objectId); + + $qb = $this->dbConn->getQueryBuilder(); + $values = [ + 'user_id' => $qb->createNamedParameter($user->getUID()), + 'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'), + 'object_type' => $qb->createNamedParameter($objectType), + 'object_id' => $qb->createNamedParameter($objectId), + ]; + + // Strategy: try to update, if this does not return affected rows, do an insert. + $affectedRows = $qb + ->update('comments_read_markers') + ->set('user_id', $values['user_id']) + ->set('marker_datetime', $values['marker_datetime']) + ->set('object_type', $values['object_type']) + ->set('object_id', $values['object_id']) + ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) + ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) + ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR) + ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR) + ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR) + ->execute(); + + if ($affectedRows > 0) { + return; + } + + $qb->insert('comments_read_markers') + ->values($values) + ->execute(); + } + + /** + * returns the read marker for a given file to the specified date for the + * provided user. It returns null, when the marker is not present, i.e. + * no comments were marked as read. + * + * @param string $objectType + * @param string $objectId + * @param IUser $user + * @return \DateTime|null + * @since 9.0.0 + */ + public function getReadMark($objectType, $objectId, IUser $user) { + $qb = $this->dbConn->getQueryBuilder(); + $resultStatement = $qb->select('marker_datetime') + ->from('comments_read_markers') + ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) + ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) + ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR) + ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR) + ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR) + ->execute(); + + $data = $resultStatement->fetch(); + $resultStatement->closeCursor(); + if(!$data || is_null($data['marker_datetime'])) { + return null; + } + + return new \DateTime($data['marker_datetime']); + } + + /** + * deletes the read markers on the specified object + * + * @param string $objectType + * @param string $objectId + * @return bool + * @since 9.0.0 + */ + public function deleteReadMarksOnObject($objectType, $objectId) { + $this->checkRoleParameters('Object', $objectType, $objectId); + + $qb = $this->dbConn->getQueryBuilder(); + $query = $qb->delete('comments_read_markers') + ->where($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) + ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) + ->setParameter('object_type', $objectType) + ->setParameter('object_id', $objectId); + + try { + $affectedRows = $query->execute(); + } catch (DriverException $e) { + $this->logger->logException($e, ['app' => 'core_comments']); + return false; + } + return ($affectedRows > 0); + } +} diff --git a/lib/private/Comments/ManagerFactory.php b/lib/private/Comments/ManagerFactory.php new file mode 100644 index 00000000000..044463d1e96 --- /dev/null +++ b/lib/private/Comments/ManagerFactory.php @@ -0,0 +1,62 @@ + + * @author Joas Schilling + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @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 + * + */ +namespace OC\Comments; + +use OCP\Comments\ICommentsManager; +use OCP\Comments\ICommentsManagerFactory; +use OCP\IServerContainer; + +class ManagerFactory implements ICommentsManagerFactory { + + /** + * Server container + * + * @var IServerContainer + */ + private $serverContainer; + + /** + * Constructor for the comments manager factory + * + * @param IServerContainer $serverContainer server container + */ + public function __construct(IServerContainer $serverContainer) { + $this->serverContainer = $serverContainer; + } + + /** + * creates and returns an instance of the ICommentsManager + * + * @return ICommentsManager + * @since 9.0.0 + */ + public function getManager() { + return new Manager( + $this->serverContainer->getDatabaseConnection(), + $this->serverContainer->getLogger(), + $this->serverContainer->getConfig(), + $this->serverContainer->getEventDispatcher() + ); + } +} diff --git a/lib/private/comments/comment.php b/lib/private/comments/comment.php deleted file mode 100644 index 60663d61578..00000000000 --- a/lib/private/comments/comment.php +++ /dev/null @@ -1,382 +0,0 @@ - - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @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 - * - */ -namespace OC\Comments; - -use OCP\Comments\IComment; -use OCP\Comments\IllegalIDChangeException; -use OCP\Comments\MessageTooLongException; - -class Comment implements IComment { - - protected $data = [ - 'id' => '', - 'parentId' => '0', - 'topmostParentId' => '0', - 'childrenCount' => '0', - 'message' => '', - 'verb' => '', - 'actorType' => '', - 'actorId' => '', - 'objectType' => '', - 'objectId' => '', - 'creationDT' => null, - 'latestChildDT' => null, - ]; - - /** - * Comment constructor. - * - * @param [] $data optional, array with keys according to column names from - * the comments database scheme - */ - public function __construct(array $data = null) { - if(is_array($data)) { - $this->fromArray($data); - } - } - - /** - * returns the ID of the comment - * - * It may return an empty string, if the comment was not stored. - * It is expected that the concrete Comment implementation gives an ID - * by itself (e.g. after saving). - * - * @return string - * @since 9.0.0 - */ - public function getId() { - return $this->data['id']; - } - - /** - * sets the ID of the comment and returns itself - * - * It is only allowed to set the ID only, if the current id is an empty - * string (which means it is not stored in a database, storage or whatever - * the concrete implementation does), or vice versa. Changing a given ID is - * not permitted and must result in an IllegalIDChangeException. - * - * @param string $id - * @return IComment - * @throws IllegalIDChangeException - * @since 9.0.0 - */ - public function setId($id) { - if(!is_string($id)) { - throw new \InvalidArgumentException('String expected.'); - } - - $id = trim($id); - if($this->data['id'] === '' || ($this->data['id'] !== '' && $id === '')) { - $this->data['id'] = $id; - return $this; - } - - throw new IllegalIDChangeException('Not allowed to assign a new ID to an already saved comment.'); - } - - /** - * returns the parent ID of the comment - * - * @return string - * @since 9.0.0 - */ - public function getParentId() { - return $this->data['parentId']; - } - - /** - * sets the parent ID and returns itself - * - * @param string $parentId - * @return IComment - * @since 9.0.0 - */ - public function setParentId($parentId) { - if(!is_string($parentId)) { - throw new \InvalidArgumentException('String expected.'); - } - $this->data['parentId'] = trim($parentId); - return $this; - } - - /** - * returns the topmost parent ID of the comment - * - * @return string - * @since 9.0.0 - */ - public function getTopmostParentId() { - return $this->data['topmostParentId']; - } - - - /** - * sets the topmost parent ID and returns itself - * - * @param string $id - * @return IComment - * @since 9.0.0 - */ - public function setTopmostParentId($id) { - if(!is_string($id)) { - throw new \InvalidArgumentException('String expected.'); - } - $this->data['topmostParentId'] = trim($id); - return $this; - } - - /** - * returns the number of children - * - * @return int - * @since 9.0.0 - */ - public function getChildrenCount() { - return $this->data['childrenCount']; - } - - /** - * sets the number of children - * - * @param int $count - * @return IComment - * @since 9.0.0 - */ - public function setChildrenCount($count) { - if(!is_int($count)) { - throw new \InvalidArgumentException('Integer expected.'); - } - $this->data['childrenCount'] = $count; - return $this; - } - - /** - * returns the message of the comment - * - * @return string - * @since 9.0.0 - */ - public function getMessage() { - return $this->data['message']; - } - - /** - * sets the message of the comment and returns itself - * - * @param string $message - * @return IComment - * @throws MessageTooLongException - * @since 9.0.0 - */ - public function setMessage($message) { - if(!is_string($message)) { - throw new \InvalidArgumentException('String expected.'); - } - $message = trim($message); - if(mb_strlen($message, 'UTF-8') > IComment::MAX_MESSAGE_LENGTH) { - throw new MessageTooLongException('Comment message must not exceed ' . IComment::MAX_MESSAGE_LENGTH . ' characters'); - } - $this->data['message'] = $message; - return $this; - } - - /** - * returns the verb of the comment - * - * @return string - * @since 9.0.0 - */ - public function getVerb() { - return $this->data['verb']; - } - - /** - * sets the verb of the comment, e.g. 'comment' or 'like' - * - * @param string $verb - * @return IComment - * @since 9.0.0 - */ - public function setVerb($verb) { - if(!is_string($verb) || !trim($verb)) { - throw new \InvalidArgumentException('Non-empty String expected.'); - } - $this->data['verb'] = trim($verb); - return $this; - } - - /** - * returns the actor type - * - * @return string - * @since 9.0.0 - */ - public function getActorType() { - return $this->data['actorType']; - } - - /** - * returns the actor ID - * - * @return string - * @since 9.0.0 - */ - public function getActorId() { - return $this->data['actorId']; - } - - /** - * sets (overwrites) the actor type and id - * - * @param string $actorType e.g. 'users' - * @param string $actorId e.g. 'zombie234' - * @return IComment - * @since 9.0.0 - */ - public function setActor($actorType, $actorId) { - if( - !is_string($actorType) || !trim($actorType) - || !is_string($actorId) || !trim($actorId) - ) { - throw new \InvalidArgumentException('String expected.'); - } - $this->data['actorType'] = trim($actorType); - $this->data['actorId'] = trim($actorId); - return $this; - } - - /** - * returns the creation date of the comment. - * - * If not explicitly set, it shall default to the time of initialization. - * - * @return \DateTime - * @since 9.0.0 - */ - public function getCreationDateTime() { - return $this->data['creationDT']; - } - - /** - * sets the creation date of the comment and returns itself - * - * @param \DateTime $timestamp - * @return IComment - * @since 9.0.0 - */ - public function setCreationDateTime(\DateTime $timestamp) { - $this->data['creationDT'] = $timestamp; - return $this; - } - - /** - * returns the DateTime of the most recent child, if set, otherwise null - * - * @return \DateTime|null - * @since 9.0.0 - */ - public function getLatestChildDateTime() { - return $this->data['latestChildDT']; - } - - /** - * sets the date of the most recent child - * - * @param \DateTime $dateTime - * @return IComment - * @since 9.0.0 - */ - public function setLatestChildDateTime(\DateTime $dateTime = null) { - $this->data['latestChildDT'] = $dateTime; - return $this; - } - - /** - * returns the object type the comment is attached to - * - * @return string - * @since 9.0.0 - */ - public function getObjectType() { - return $this->data['objectType']; - } - - /** - * returns the object id the comment is attached to - * - * @return string - * @since 9.0.0 - */ - public function getObjectId() { - return $this->data['objectId']; - } - - /** - * sets (overwrites) the object of the comment - * - * @param string $objectType e.g. 'files' - * @param string $objectId e.g. '16435' - * @return IComment - * @since 9.0.0 - */ - public function setObject($objectType, $objectId) { - if( - !is_string($objectType) || !trim($objectType) - || !is_string($objectId) || !trim($objectId) - ) { - throw new \InvalidArgumentException('String expected.'); - } - $this->data['objectType'] = trim($objectType); - $this->data['objectId'] = trim($objectId); - return $this; - } - - /** - * sets the comment data based on an array with keys as taken from the - * database. - * - * @param [] $data - * @return IComment - */ - protected function fromArray($data) { - foreach(array_keys($data) as $key) { - // translate DB keys to internal setter names - $setter = 'set' . implode('', array_map('ucfirst', explode('_', $key))); - $setter = str_replace('Timestamp', 'DateTime', $setter); - - if(method_exists($this, $setter)) { - $this->$setter($data[$key]); - } - } - - foreach(['actor', 'object'] as $role) { - if(isset($data[$role . '_type']) && isset($data[$role . '_id'])) { - $setter = 'set' . ucfirst($role); - $this->$setter($data[$role . '_type'], $data[$role . '_id']); - } - } - - return $this; - } -} diff --git a/lib/private/comments/manager.php b/lib/private/comments/manager.php deleted file mode 100644 index b986c8c51f9..00000000000 --- a/lib/private/comments/manager.php +++ /dev/null @@ -1,753 +0,0 @@ - - * @author Joas Schilling - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @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 - * - */ -namespace OC\Comments; - -use Doctrine\DBAL\Exception\DriverException; -use OCP\Comments\CommentsEvent; -use OCP\Comments\IComment; -use OCP\Comments\ICommentsManager; -use OCP\Comments\NotFoundException; -use OCP\DB\QueryBuilder\IQueryBuilder; -use OCP\IDBConnection; -use OCP\IConfig; -use OCP\ILogger; -use OCP\IUser; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; - -class Manager implements ICommentsManager { - - /** @var IDBConnection */ - protected $dbConn; - - /** @var ILogger */ - protected $logger; - - /** @var IConfig */ - protected $config; - - /** @var EventDispatcherInterface */ - protected $dispatcher; - - /** @var IComment[] */ - protected $commentsCache = []; - - /** - * Manager constructor. - * - * @param IDBConnection $dbConn - * @param ILogger $logger - * @param IConfig $config - * @param EventDispatcherInterface $dispatcher - */ - public function __construct( - IDBConnection $dbConn, - ILogger $logger, - IConfig $config, - EventDispatcherInterface $dispatcher - ) { - $this->dbConn = $dbConn; - $this->logger = $logger; - $this->config = $config; - $this->dispatcher = $dispatcher; - } - - /** - * converts data base data into PHP native, proper types as defined by - * IComment interface. - * - * @param array $data - * @return array - */ - protected function normalizeDatabaseData(array $data) { - $data['id'] = strval($data['id']); - $data['parent_id'] = strval($data['parent_id']); - $data['topmost_parent_id'] = strval($data['topmost_parent_id']); - $data['creation_timestamp'] = new \DateTime($data['creation_timestamp']); - if (!is_null($data['latest_child_timestamp'])) { - $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']); - } - $data['children_count'] = intval($data['children_count']); - return $data; - } - - /** - * prepares a comment for an insert or update operation after making sure - * all necessary fields have a value assigned. - * - * @param IComment $comment - * @return IComment returns the same updated IComment instance as provided - * by parameter for convenience - * @throws \UnexpectedValueException - */ - protected function prepareCommentForDatabaseWrite(IComment $comment) { - if( !$comment->getActorType() - || !$comment->getActorId() - || !$comment->getObjectType() - || !$comment->getObjectId() - || !$comment->getVerb() - ) { - throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); - } - - 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())) { - $comment->setCreationDateTime(new \DateTime()); - } - - if($comment->getParentId() !== '0') { - $comment->setTopmostParentId($this->determineTopmostParentId($comment->getParentId())); - } else { - $comment->setTopmostParentId('0'); - } - - $this->cache($comment); - - return $comment; - } - - /** - * returns the topmost parent id of a given comment identified by ID - * - * @param string $id - * @return string - * @throws NotFoundException - */ - protected function determineTopmostParentId($id) { - $comment = $this->get($id); - if($comment->getParentId() === '0') { - return $comment->getId(); - } else { - return $this->determineTopmostParentId($comment->getId()); - } - } - - /** - * updates child information of a comment - * - * @param string $id - * @param \DateTime $cDateTime the date time of the most recent child - * @throws NotFoundException - */ - protected function updateChildrenInformation($id, \DateTime $cDateTime) { - $qb = $this->dbConn->getQueryBuilder(); - $query = $qb->select($qb->createFunction('COUNT(`id`)')) - ->from('comments') - ->where($qb->expr()->eq('parent_id', $qb->createParameter('id'))) - ->setParameter('id', $id); - - $resultStatement = $query->execute(); - $data = $resultStatement->fetch(\PDO::FETCH_NUM); - $resultStatement->closeCursor(); - $children = intval($data[0]); - - $comment = $this->get($id); - $comment->setChildrenCount($children); - $comment->setLatestChildDateTime($cDateTime); - $this->save($comment); - } - - /** - * Tests whether actor or object type and id parameters are acceptable. - * Throws exception if not. - * - * @param string $role - * @param string $type - * @param string $id - * @throws \InvalidArgumentException - */ - protected function checkRoleParameters($role, $type, $id) { - if( - !is_string($type) || empty($type) - || !is_string($id) || empty($id) - ) { - throw new \InvalidArgumentException($role . ' parameters must be string and not empty'); - } - } - - /** - * run-time caches a comment - * - * @param IComment $comment - */ - protected function cache(IComment $comment) { - $id = $comment->getId(); - if(empty($id)) { - return; - } - $this->commentsCache[strval($id)] = $comment; - } - - /** - * removes an entry from the comments run time cache - * - * @param mixed $id the comment's id - */ - protected function uncache($id) { - $id = strval($id); - if (isset($this->commentsCache[$id])) { - unset($this->commentsCache[$id]); - } - } - - /** - * 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) { - if(intval($id) === 0) { - throw new \InvalidArgumentException('IDs must be translatable to a number in this implementation.'); - } - - if(isset($this->commentsCache[$id])) { - return $this->commentsCache[$id]; - } - - $qb = $this->dbConn->getQueryBuilder(); - $resultStatement = $qb->select('*') - ->from('comments') - ->where($qb->expr()->eq('id', $qb->createParameter('id'))) - ->setParameter('id', $id, IQueryBuilder::PARAM_INT) - ->execute(); - - $data = $resultStatement->fetch(); - $resultStatement->closeCursor(); - if(!$data) { - throw new NotFoundException(); - } - - $comment = new Comment($this->normalizeDatabaseData($data)); - $this->cache($comment); - return $comment; - } - - /** - * 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'=> [] - * ], - * … - * ] - * ] - */ - public function getTree($id, $limit = 0, $offset = 0) { - $tree = []; - $tree['comment'] = $this->get($id); - $tree['replies'] = []; - - $qb = $this->dbConn->getQueryBuilder(); - $query = $qb->select('*') - ->from('comments') - ->where($qb->expr()->eq('topmost_parent_id', $qb->createParameter('id'))) - ->orderBy('creation_timestamp', 'DESC') - ->setParameter('id', $id); - - if($limit > 0) { - $query->setMaxResults($limit); - } - if($offset > 0) { - $query->setFirstResult($offset); - } - - $resultStatement = $query->execute(); - while($data = $resultStatement->fetch()) { - $comment = new Comment($this->normalizeDatabaseData($data)); - $this->cache($comment); - $tree['replies'][] = [ - 'comment' => $comment, - 'replies' => [] - ]; - } - $resultStatement->closeCursor(); - - return $tree; - } - - /** - * returns comments for a specific object (e.g. a file). - * - * The sort order is always newest to oldest. - * - * @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. - * @param int $offset optional, starting point - * @param \DateTime $notOlderThan optional, timestamp of the oldest comments - * that may be returned - * @return IComment[] - * @since 9.0.0 - */ - public function getForObject( - $objectType, - $objectId, - $limit = 0, - $offset = 0, - \DateTime $notOlderThan = null - ) { - $comments = []; - - $qb = $this->dbConn->getQueryBuilder(); - $query = $qb->select('*') - ->from('comments') - ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) - ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) - ->orderBy('creation_timestamp', 'DESC') - ->setParameter('type', $objectType) - ->setParameter('id', $objectId); - - if($limit > 0) { - $query->setMaxResults($limit); - } - if($offset > 0) { - $query->setFirstResult($offset); - } - if(!is_null($notOlderThan)) { - $query - ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan'))) - ->setParameter('notOlderThan', $notOlderThan, 'datetime'); - } - - $resultStatement = $query->execute(); - while($data = $resultStatement->fetch()) { - $comment = new Comment($this->normalizeDatabaseData($data)); - $this->cache($comment); - $comments[] = $comment; - } - $resultStatement->closeCursor(); - - return $comments; - } - - /** - * @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 - * @return Int - * @since 9.0.0 - */ - public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null) { - $qb = $this->dbConn->getQueryBuilder(); - $query = $qb->select($qb->createFunction('COUNT(`id`)')) - ->from('comments') - ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) - ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) - ->setParameter('type', $objectType) - ->setParameter('id', $objectId); - - if(!is_null($notOlderThan)) { - $query - ->andWhere($qb->expr()->gt('creation_timestamp', $qb->createParameter('notOlderThan'))) - ->setParameter('notOlderThan', $notOlderThan, 'datetime'); - } - - $resultStatement = $query->execute(); - $data = $resultStatement->fetch(\PDO::FETCH_NUM); - $resultStatement->closeCursor(); - return intval($data[0]); - } - - /** - * creates a new comment and returns it. At this point of time, it is not - * saved in the used data storage. Use save() after setting other fields - * of the comment (e.g. message or verb). - * - * @param string $actorType the actor type (e.g. 'users') - * @param string $actorId a user id - * @param string $objectType the object type the comment is attached to - * @param string $objectId the object id the comment is attached to - * @return IComment - * @since 9.0.0 - */ - public function create($actorType, $actorId, $objectType, $objectId) { - $comment = new Comment(); - $comment - ->setActor($actorType, $actorId) - ->setObject($objectType, $objectId); - return $comment; - } - - /** - * permanently deletes the comment specified by the ID - * - * When the comment has child comments, their parent ID will be changed to - * the parent ID of the item that is to be deleted. - * - * @param string $id - * @return bool - * @throws \InvalidArgumentException - * @since 9.0.0 - */ - public function delete($id) { - if(!is_string($id)) { - throw new \InvalidArgumentException('Parameter must be string'); - } - - try { - $comment = $this->get($id); - } catch (\Exception $e) { - // Ignore exceptions, we just don't fire a hook then - $comment = null; - } - - $qb = $this->dbConn->getQueryBuilder(); - $query = $qb->delete('comments') - ->where($qb->expr()->eq('id', $qb->createParameter('id'))) - ->setParameter('id', $id); - - try { - $affectedRows = $query->execute(); - $this->uncache($id); - } catch (DriverException $e) { - $this->logger->logException($e, ['app' => 'core_comments']); - return false; - } - - if ($affectedRows > 0 && $comment instanceof IComment) { - $this->dispatcher->dispatch(CommentsEvent::EVENT_DELETE, new CommentsEvent( - CommentsEvent::EVENT_DELETE, - $comment - )); - } - - return ($affectedRows > 0); - } - - /** - * saves the comment permanently - * - * if the supplied comment has an empty ID, a new entry comment will be - * saved and the instance updated with the new ID. - * - * Otherwise, an existing comment will be updated. - * - * Throws NotFoundException when a comment that is to be updated does not - * exist anymore at this point of time. - * - * @param IComment $comment - * @return bool - * @throws NotFoundException - * @since 9.0.0 - */ - public function save(IComment $comment) { - if($this->prepareCommentForDatabaseWrite($comment)->getId() === '') { - $result = $this->insert($comment); - } else { - $result = $this->update($comment); - } - - if($result && !!$comment->getParentId()) { - $this->updateChildrenInformation( - $comment->getParentId(), - $comment->getCreationDateTime() - ); - $this->cache($comment); - } - - return $result; - } - - /** - * inserts the provided comment in the database - * - * @param IComment $comment - * @return bool - */ - protected function insert(IComment &$comment) { - $qb = $this->dbConn->getQueryBuilder(); - $affectedRows = $qb - ->insert('comments') - ->values([ - 'parent_id' => $qb->createNamedParameter($comment->getParentId()), - 'topmost_parent_id' => $qb->createNamedParameter($comment->getTopmostParentId()), - 'children_count' => $qb->createNamedParameter($comment->getChildrenCount()), - 'actor_type' => $qb->createNamedParameter($comment->getActorType()), - 'actor_id' => $qb->createNamedParameter($comment->getActorId()), - 'message' => $qb->createNamedParameter($comment->getMessage()), - 'verb' => $qb->createNamedParameter($comment->getVerb()), - 'creation_timestamp' => $qb->createNamedParameter($comment->getCreationDateTime(), 'datetime'), - 'latest_child_timestamp' => $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'), - 'object_type' => $qb->createNamedParameter($comment->getObjectType()), - 'object_id' => $qb->createNamedParameter($comment->getObjectId()), - ]) - ->execute(); - - if ($affectedRows > 0) { - $comment->setId(strval($qb->getLastInsertId())); - } - - $this->dispatcher->dispatch(CommentsEvent::EVENT_ADD, new CommentsEvent( - CommentsEvent::EVENT_ADD, - $comment - )); - - return $affectedRows > 0; - } - - /** - * updates a Comment data row - * - * @param IComment $comment - * @return bool - * @throws NotFoundException - */ - protected function update(IComment $comment) { - $qb = $this->dbConn->getQueryBuilder(); - $affectedRows = $qb - ->update('comments') - ->set('parent_id', $qb->createNamedParameter($comment->getParentId())) - ->set('topmost_parent_id', $qb->createNamedParameter($comment->getTopmostParentId())) - ->set('children_count', $qb->createNamedParameter($comment->getChildrenCount())) - ->set('actor_type', $qb->createNamedParameter($comment->getActorType())) - ->set('actor_id', $qb->createNamedParameter($comment->getActorId())) - ->set('message', $qb->createNamedParameter($comment->getMessage())) - ->set('verb', $qb->createNamedParameter($comment->getVerb())) - ->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())) - ->where($qb->expr()->eq('id', $qb->createParameter('id'))) - ->setParameter('id', $comment->getId()) - ->execute(); - - if($affectedRows === 0) { - throw new NotFoundException('Comment to update does ceased to exist'); - } - - $this->dispatcher->dispatch(CommentsEvent::EVENT_UPDATE, new CommentsEvent( - CommentsEvent::EVENT_UPDATE, - $comment - )); - - return $affectedRows > 0; - } - - /** - * removes references to specific actor (e.g. on user delete) of a comment. - * The comment itself must not get lost/deleted. - * - * @param string $actorType the actor type (e.g. 'users') - * @param string $actorId a user id - * @return boolean - * @since 9.0.0 - */ - public function deleteReferencesOfActor($actorType, $actorId) { - $this->checkRoleParameters('Actor', $actorType, $actorId); - - $qb = $this->dbConn->getQueryBuilder(); - $affectedRows = $qb - ->update('comments') - ->set('actor_type', $qb->createNamedParameter(ICommentsManager::DELETED_USER)) - ->set('actor_id', $qb->createNamedParameter(ICommentsManager::DELETED_USER)) - ->where($qb->expr()->eq('actor_type', $qb->createParameter('type'))) - ->andWhere($qb->expr()->eq('actor_id', $qb->createParameter('id'))) - ->setParameter('type', $actorType) - ->setParameter('id', $actorId) - ->execute(); - - $this->commentsCache = []; - - return is_int($affectedRows); - } - - /** - * deletes all comments made of a specific object (e.g. on file delete) - * - * @param string $objectType the object type (e.g. 'files') - * @param string $objectId e.g. the file id - * @return boolean - * @since 9.0.0 - */ - public function deleteCommentsAtObject($objectType, $objectId) { - $this->checkRoleParameters('Object', $objectType, $objectId); - - $qb = $this->dbConn->getQueryBuilder(); - $affectedRows = $qb - ->delete('comments') - ->where($qb->expr()->eq('object_type', $qb->createParameter('type'))) - ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('id'))) - ->setParameter('type', $objectType) - ->setParameter('id', $objectId) - ->execute(); - - $this->commentsCache = []; - - return is_int($affectedRows); - } - - /** - * deletes the read markers for the specified user - * - * @param \OCP\IUser $user - * @return bool - * @since 9.0.0 - */ - public function deleteReadMarksFromUser(IUser $user) { - $qb = $this->dbConn->getQueryBuilder(); - $query = $qb->delete('comments_read_markers') - ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) - ->setParameter('user_id', $user->getUID()); - - try { - $affectedRows = $query->execute(); - } catch (DriverException $e) { - $this->logger->logException($e, ['app' => 'core_comments']); - return false; - } - return ($affectedRows > 0); - } - - /** - * sets the read marker for a given file to the specified date for the - * provided user - * - * @param string $objectType - * @param string $objectId - * @param \DateTime $dateTime - * @param IUser $user - * @since 9.0.0 - */ - public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) { - $this->checkRoleParameters('Object', $objectType, $objectId); - - $qb = $this->dbConn->getQueryBuilder(); - $values = [ - 'user_id' => $qb->createNamedParameter($user->getUID()), - 'marker_datetime' => $qb->createNamedParameter($dateTime, 'datetime'), - 'object_type' => $qb->createNamedParameter($objectType), - 'object_id' => $qb->createNamedParameter($objectId), - ]; - - // Strategy: try to update, if this does not return affected rows, do an insert. - $affectedRows = $qb - ->update('comments_read_markers') - ->set('user_id', $values['user_id']) - ->set('marker_datetime', $values['marker_datetime']) - ->set('object_type', $values['object_type']) - ->set('object_id', $values['object_id']) - ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) - ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) - ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) - ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR) - ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR) - ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR) - ->execute(); - - if ($affectedRows > 0) { - return; - } - - $qb->insert('comments_read_markers') - ->values($values) - ->execute(); - } - - /** - * returns the read marker for a given file to the specified date for the - * provided user. It returns null, when the marker is not present, i.e. - * no comments were marked as read. - * - * @param string $objectType - * @param string $objectId - * @param IUser $user - * @return \DateTime|null - * @since 9.0.0 - */ - public function getReadMark($objectType, $objectId, IUser $user) { - $qb = $this->dbConn->getQueryBuilder(); - $resultStatement = $qb->select('marker_datetime') - ->from('comments_read_markers') - ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) - ->andWhere($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) - ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) - ->setParameter('user_id', $user->getUID(), IQueryBuilder::PARAM_STR) - ->setParameter('object_type', $objectType, IQueryBuilder::PARAM_STR) - ->setParameter('object_id', $objectId, IQueryBuilder::PARAM_STR) - ->execute(); - - $data = $resultStatement->fetch(); - $resultStatement->closeCursor(); - if(!$data || is_null($data['marker_datetime'])) { - return null; - } - - return new \DateTime($data['marker_datetime']); - } - - /** - * deletes the read markers on the specified object - * - * @param string $objectType - * @param string $objectId - * @return bool - * @since 9.0.0 - */ - public function deleteReadMarksOnObject($objectType, $objectId) { - $this->checkRoleParameters('Object', $objectType, $objectId); - - $qb = $this->dbConn->getQueryBuilder(); - $query = $qb->delete('comments_read_markers') - ->where($qb->expr()->eq('object_type', $qb->createParameter('object_type'))) - ->andWhere($qb->expr()->eq('object_id', $qb->createParameter('object_id'))) - ->setParameter('object_type', $objectType) - ->setParameter('object_id', $objectId); - - try { - $affectedRows = $query->execute(); - } catch (DriverException $e) { - $this->logger->logException($e, ['app' => 'core_comments']); - return false; - } - return ($affectedRows > 0); - } -} diff --git a/lib/private/comments/managerfactory.php b/lib/private/comments/managerfactory.php deleted file mode 100644 index 044463d1e96..00000000000 --- a/lib/private/comments/managerfactory.php +++ /dev/null @@ -1,62 +0,0 @@ - - * @author Joas Schilling - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @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 - * - */ -namespace OC\Comments; - -use OCP\Comments\ICommentsManager; -use OCP\Comments\ICommentsManagerFactory; -use OCP\IServerContainer; - -class ManagerFactory implements ICommentsManagerFactory { - - /** - * Server container - * - * @var IServerContainer - */ - private $serverContainer; - - /** - * Constructor for the comments manager factory - * - * @param IServerContainer $serverContainer server container - */ - public function __construct(IServerContainer $serverContainer) { - $this->serverContainer = $serverContainer; - } - - /** - * creates and returns an instance of the ICommentsManager - * - * @return ICommentsManager - * @since 9.0.0 - */ - public function getManager() { - return new Manager( - $this->serverContainer->getDatabaseConnection(), - $this->serverContainer->getLogger(), - $this->serverContainer->getConfig(), - $this->serverContainer->getEventDispatcher() - ); - } -} -- cgit v1.2.3