summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2015-12-09 17:44:40 +0100
committerThomas Müller <thomas.mueller@tmit.eu>2015-12-09 17:44:40 +0100
commitea7fe672e8ef75d7fffc7b9d90eccb82e8e7ad11 (patch)
tree3da359d415131f806e858dd8c4b8a3206a6b5cbf
parentd86b4f34a5f3c847ac43257d934cc3edd1c2d7d0 (diff)
parent81d763be4224adeb5676c620d76b3e4ec407187f (diff)
downloadnextcloud-server-ea7fe672e8ef75d7fffc7b9d90eccb82e8e7ad11.tar.gz
nextcloud-server-ea7fe672e8ef75d7fffc7b9d90eccb82e8e7ad11.zip
Merge pull request #20775 from owncloud/comments-manager
add icommentsmanger and icomment implementation
-rw-r--r--config/config.sample.php13
-rw-r--r--lib/private/comments/comment.php357
-rw-r--r--lib/private/comments/manager.php549
-rw-r--r--lib/private/comments/managerfactory.php23
-rw-r--r--lib/private/server.php14
-rw-r--r--lib/private/user/user.php2
-rw-r--r--lib/public/comments/icomment.php21
-rw-r--r--lib/public/comments/icommentsmanager.php22
-rw-r--r--lib/public/comments/icommentsmanagerfactory.php23
-rw-r--r--lib/public/iservercontainer.php6
-rw-r--r--tests/lib/comments/comment.php110
-rw-r--r--tests/lib/comments/fakefactory.php13
-rw-r--r--tests/lib/comments/fakemanager.php33
-rw-r--r--tests/lib/comments/manager.php564
-rw-r--r--tests/lib/server.php13
15 files changed, 1756 insertions, 7 deletions
diff --git a/config/config.sample.php b/config/config.sample.php
index c3abe3a2b87..9b10792944f 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -815,6 +815,19 @@ $CONFIG = array(
'enforce_home_folder_naming_rule' => true,
/**
+ * Comments
+ *
+ * Global settings for the Comments infrastructure
+ */
+
+/**
+ * Replaces the default Comments Manager Factory. This can be utilized if an
+ * own or 3rdParty CommentsManager should be used that – for instance – uses the
+ * filesystem instead of the database to keep the comments.
+ */
+'comments.managerFactory' => '\OC\Comments\ManagerFactory',
+
+/**
* Maintenance
*
* These options are for halting user activity when you are performing server
diff --git a/lib/private/comments/comment.php b/lib/private/comments/comment.php
new file mode 100644
index 00000000000..219e7ec8e4b
--- /dev/null
+++ b/lib/private/comments/comment.php
@@ -0,0 +1,357 @@
+<?php
+
+namespace OC\Comments;
+
+use OCP\Comments\IComment;
+use OCP\Comments\IllegalIDChangeException;
+
+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
+ * @since 9.0.0
+ */
+ public function setMessage($message) {
+ if(!is_string($message)) {
+ throw new \InvalidArgumentException('String expected.');
+ }
+ $this->data['message'] = trim($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. 'user'
+ * @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 timestamp of the most recent child
+ *
+ * @return int
+ * @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. 'file'
+ * @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' . str_replace('_', '', ucwords($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..09e59f28370
--- /dev/null
+++ b/lib/private/comments/manager.php
@@ -0,0 +1,549 @@
+<?php
+
+namespace OC\Comments;
+
+use Doctrine\DBAL\Exception\DriverException;
+use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\NotFoundException;
+use OCP\IDBConnection;
+use OCP\ILogger;
+
+class Manager implements ICommentsManager {
+
+ /** @var IDBConnection */
+ protected $dbConn;
+
+ /** @var ILogger */
+ protected $logger;
+
+ /** @var IComment[] */
+ protected $commentsCache = [];
+
+ public function __construct(
+ IDBConnection $dbConn,
+ ILogger $logger
+ ) {
+ $this->dbConn = $dbConn;
+ $this->logger = $logger;
+ }
+
+ /**
+ * 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']);
+ $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, \PDO::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
+ * @return Int
+ * @since 9.0.0
+ */
+ public function getNumberOfCommentsForObject($objectType, $objectId) {
+ $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);
+
+ $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. 'user')
+ * @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');
+ }
+
+ $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;
+ }
+ return ($affectedRows > 0);
+ }
+
+ /**
+ * saves the comment permanently and returns it
+ *
+ * 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()));
+ }
+
+ 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');
+ }
+
+ 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. 'user')
+ * @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. 'file')
+ * @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);
+ }
+}
diff --git a/lib/private/comments/managerfactory.php b/lib/private/comments/managerfactory.php
new file mode 100644
index 00000000000..41978d0cf4b
--- /dev/null
+++ b/lib/private/comments/managerfactory.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace OC\Comments;
+
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\ICommentsManagerFactory;
+
+
+class ManagerFactory implements ICommentsManagerFactory {
+
+ /**
+ * creates and returns an instance of the ICommentsManager
+ *
+ * @return ICommentsManager
+ * @since 9.0.0
+ */
+ public function getManager() {
+ return new Manager(
+ \OC::$server->getDatabaseConnection(),
+ \OC::$server->getLogger()
+ );
+ }
+}
diff --git a/lib/private/server.php b/lib/private/server.php
index 6692e6f6bbf..8439500706d 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -528,6 +528,13 @@ class Server extends SimpleContainer implements IServerContainer {
});
return $manager;
});
+ $this->registerService('CommentsManager', function(Server $c) {
+ $config = $c->getConfig();
+ $factoryClass = $config->getSystemValue('comments.managerFactory', '\OC\Comments\ManagerFactory');
+ /** @var \OCP\Comments\ICommentsManagerFactory $factory */
+ $factory = new $factoryClass();
+ return $factory->getManager();
+ });
$this->registerService('EventDispatcher', function() {
return new EventDispatcher();
});
@@ -1122,6 +1129,13 @@ class Server extends SimpleContainer implements IServerContainer {
}
/**
+ * @return \OCP\Comments\ICommentsManager
+ */
+ public function getCommentsManager() {
+ return $this->query('CommentsManager');
+ }
+
+ /**
* @return \OC\IntegrityCheck\Checker
*/
public function getIntegrityCodeChecker() {
diff --git a/lib/private/user/user.php b/lib/private/user/user.php
index d827097ee39..6c89dd06f77 100644
--- a/lib/private/user/user.php
+++ b/lib/private/user/user.php
@@ -189,6 +189,8 @@ class User implements IUser {
// Delete the users entry in the storage table
\OC\Files\Cache\Storage::remove('home::' . $this->uid);
+
+ \OC::$server->getCommentsManager()->deleteReferencesOfActor('user', $this->uid);
}
if ($this->emitter) {
diff --git a/lib/public/comments/icomment.php b/lib/public/comments/icomment.php
index c8f407624a0..7924ec8d5f6 100644
--- a/lib/public/comments/icomment.php
+++ b/lib/public/comments/icomment.php
@@ -5,7 +5,7 @@ namespace OCP\Comments;
/**
* Interface IComment
*
- * This class represents a comment and offers methods for modification.
+ * This class represents a comment
*
* @package OCP\Comments
* @since 9.0.0
@@ -49,7 +49,6 @@ interface IComment {
/**
* sets the parent ID and returns itself
- *
* @param string $parentId
* @return IComment
* @since 9.0.0
@@ -57,6 +56,24 @@ interface IComment {
public function setParentId($parentId);
/**
+ * returns the topmost parent ID of the comment
+ *
+ * @return string
+ * @since 9.0.0
+ */
+ public function getTopmostParentId();
+
+
+ /**
+ * sets the topmost parent ID and returns itself
+ *
+ * @param string $id
+ * @return IComment
+ * @since 9.0.0
+ */
+ public function setTopmostParentId($id);
+
+ /**
* returns the number of children
*
* @return int
diff --git a/lib/public/comments/icommentsmanager.php b/lib/public/comments/icommentsmanager.php
index ebf7a34bf27..7626ffd6351 100644
--- a/lib/public/comments/icommentsmanager.php
+++ b/lib/public/comments/icommentsmanager.php
@@ -13,6 +13,17 @@ namespace OCP\Comments;
interface ICommentsManager {
/**
+ * @const DELETED_USER type and id for a user that has been deleted
+ * @see deleteReferencesOfActor
+ * @since 9.0.0
+ *
+ * To be used as replacement for user type actors in deleteReferencesOfActor().
+ *
+ * User interfaces shall show "Deleted user" as display name, if needed.
+ */
+ const DELETED_USER = 'deleted_user';
+
+ /**
* returns a comment instance
*
* @param string $id the ID of the comment
@@ -28,7 +39,7 @@ interface ICommentsManager {
* @param string $id
* @param int $limit max number of entries to return, 0 returns all
* @param int $offset the start entry
- * @return []
+ * @return array
* @since 9.0.0
*
* The return array looks like this
@@ -73,7 +84,6 @@ interface ICommentsManager {
* @param \DateTime $notOlderThan optional, timestamp of the oldest comments
* that may be returned
* @return IComment[]
- * @throws NotFoundException in case the requested type or id is not present
* @since 9.0.0
*/
public function getForObject(
@@ -88,7 +98,6 @@ interface ICommentsManager {
* @param $objectType string the object type, e.g. 'files'
* @param $objectId string the id of the object
* @return Int
- * @throws NotFoundException in case the requested type or id is not present
* @since 9.0.0
*/
public function getNumberOfCommentsForObject($objectType, $objectId);
@@ -130,17 +139,20 @@ interface ICommentsManager {
* Throws NotFoundException when a comment that is to be updated does not
* exist anymore at this point of time.
*
- * @param IComment
+ * @param IComment $comment
* @return bool
* @throws NotFoundException
* @since 9.0.0
*/
- public function save(&$comment);
+ public function save(IComment $comment);
/**
* removes references to specific actor (e.g. on user delete) of a comment.
* The comment itself must not get lost/deleted.
*
+ * A 'user' type actor (type and id) should get replaced by the
+ * value of the DELETED_USER constant of this interface.
+ *
* @param string $actorType the actor type (e.g. 'user')
* @param string $actorId a user id
* @return boolean
diff --git a/lib/public/comments/icommentsmanagerfactory.php b/lib/public/comments/icommentsmanagerfactory.php
new file mode 100644
index 00000000000..6718dd39ba0
--- /dev/null
+++ b/lib/public/comments/icommentsmanagerfactory.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace OCP\Comments;
+
+/**
+ * Interface ICommentsManagerFactory
+ *
+ * This class is responsible for instantiating and returning an ICommentsManager
+ * instance.
+ *
+ * @package OCP\Comments
+ * @since 9.0.0
+ */
+interface ICommentsManagerFactory {
+
+ /**
+ * creates and returns an instance of the ICommentsManager
+ *
+ * @return ICommentsManager
+ * @since 9.0.0
+ */
+ public function getManager();
+}
diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php
index 7cb2672254b..267e5dc4d31 100644
--- a/lib/public/iservercontainer.php
+++ b/lib/public/iservercontainer.php
@@ -472,6 +472,12 @@ interface IServerContainer {
public function getNotificationManager();
/**
+ * @return \OCP\Comments\ICommentsManager
+ * @since 9.0.0
+ */
+ public function getCommentsManager();
+
+ /**
* Returns the system-tag manager
*
* @return \OCP\SystemTag\ISystemTagManager
diff --git a/tests/lib/comments/comment.php b/tests/lib/comments/comment.php
new file mode 100644
index 00000000000..ae7e913eaab
--- /dev/null
+++ b/tests/lib/comments/comment.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Test\Comments;
+
+use Test\TestCase;
+
+class Test_Comments_Comment extends TestCase
+{
+
+ public function testSettersValidInput() {
+ $comment = new \OC\Comments\Comment();
+
+ $id = 'comment23';
+ $parentId = 'comment11.5';
+ $childrenCount = 6;
+ $message = 'I like to comment comment';
+ $verb = 'comment';
+ $actor = ['type' => 'user', 'id' => 'alice'];
+ $creationDT = new \DateTime();
+ $latestChildDT = new \DateTime('yesterday');
+ $object = ['type' => 'file', 'id' => 'file64'];
+
+ $comment
+ ->setId($id)
+ ->setParentId($parentId)
+ ->setChildrenCount($childrenCount)
+ ->setMessage($message)
+ ->setVerb($verb)
+ ->setActor($actor['type'], $actor['id'])
+ ->setCreationDateTime($creationDT)
+ ->setLatestChildDateTime($latestChildDT)
+ ->setObject($object['type'], $object['id']);
+
+ $this->assertSame($id, $comment->getId());
+ $this->assertSame($parentId, $comment->getParentId());
+ $this->assertSame($childrenCount, $comment->getChildrenCount());
+ $this->assertSame($message, $comment->getMessage());
+ $this->assertSame($verb, $comment->getVerb());
+ $this->assertSame($actor['type'], $comment->getActorType());
+ $this->assertSame($actor['id'], $comment->getActorId());
+ $this->assertSame($creationDT, $comment->getCreationDateTime());
+ $this->assertSame($latestChildDT, $comment->getLatestChildDateTime());
+ $this->assertSame($object['type'], $comment->getObjectType());
+ $this->assertSame($object['id'], $comment->getObjectId());
+ }
+
+ /**
+ * @expectedException \OCP\Comments\IllegalIDChangeException
+ */
+ public function testSetIdIllegalInput() {
+ $comment = new \OC\Comments\Comment();
+
+ $comment->setId('c23');
+ $comment->setId('c17');
+ }
+
+ public function testResetId() {
+ $comment = new \OC\Comments\Comment();
+ $comment->setId('c23');
+ $comment->setId('');
+ }
+
+ public function simpleSetterProvider() {
+ return [
+ ['Id', true],
+ ['ParentId', true],
+ ['Message', true],
+ ['Verb', true],
+ ['Verb', ''],
+ ['ChildrenCount', true],
+ ];
+ }
+
+ /**
+ * @dataProvider simpleSetterProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSimpleSetterInvalidInput($field, $input) {
+ $comment = new \OC\Comments\Comment();
+ $setter = 'set' . $field;
+
+ $comment->$setter($input);
+ }
+
+ public function roleSetterProvider() {
+ return [
+ ['Actor', true, true],
+ ['Actor', 'user', true],
+ ['Actor', true, 'alice'],
+ ['Actor', ' ', ' '],
+ ['Object', true, true],
+ ['Object', 'file', true],
+ ['Object', true, 'file64'],
+ ['Object', ' ', ' '],
+ ];
+ }
+
+ /**
+ * @dataProvider roleSetterProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetRoleInvalidInput($role, $type, $id){
+ $comment = new \OC\Comments\Comment();
+ $setter = 'set' . $role;
+ $comment->$setter($type, $id);
+ }
+
+
+
+}
diff --git a/tests/lib/comments/fakefactory.php b/tests/lib/comments/fakefactory.php
new file mode 100644
index 00000000000..837bcb10585
--- /dev/null
+++ b/tests/lib/comments/fakefactory.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Test\Comments;
+
+/**
+ * Class FakeFactory
+ */
+class FakeFactory implements \OCP\Comments\ICommentsManagerFactory {
+
+ public function getManager() {
+ return new FakeManager();
+ }
+}
diff --git a/tests/lib/comments/fakemanager.php b/tests/lib/comments/fakemanager.php
new file mode 100644
index 00000000000..e5cf58dda4f
--- /dev/null
+++ b/tests/lib/comments/fakemanager.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Test\Comments;
+
+/**
+ * Class FakeManager
+ */
+class FakeManager implements \OCP\Comments\ICommentsManager {
+
+ public function get($id) {}
+
+ public function getTree($id, $limit = 0, $offset = 0) {}
+
+ public function getForObject(
+ $objectType,
+ $objectId,
+ $limit = 0,
+ $offset = 0,
+ \DateTime $notOlderThan = null
+ ) {}
+
+ public function getNumberOfCommentsForObject($objectType, $objectId) {}
+
+ public function create($actorType, $actorId, $objectType, $objectId) {}
+
+ public function delete($id) {}
+
+ public function save(\OCP\Comments\IComment $comment) {}
+
+ public function deleteReferencesOfActor($actorType, $actorId) {}
+
+ public function deleteCommentsAtObject($objectType, $objectId) {}
+}
diff --git a/tests/lib/comments/manager.php b/tests/lib/comments/manager.php
new file mode 100644
index 00000000000..248de683253
--- /dev/null
+++ b/tests/lib/comments/manager.php
@@ -0,0 +1,564 @@
+<?php
+
+namespace Test\Comments;
+
+use OCP\Comments\ICommentsManager;
+use Test\TestCase;
+
+/**
+ * Class Test_Comments_Manager
+ *
+ * @group DB
+ */
+class Test_Comments_Manager extends TestCase
+{
+
+ public function setUp() {
+ parent::setUp();
+
+ $sql = \OC::$server->getDatabaseConnection()->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*comments`');
+ \OC::$server->getDatabaseConnection()->prepare($sql)->execute();
+ }
+
+ protected function addDatabaseEntry($parentId, $topmostParentId, $creationDT = null, $latestChildDT = null) {
+ if(is_null($creationDT)) {
+ $creationDT = new \DateTime();
+ }
+ if(is_null($latestChildDT)) {
+ $latestChildDT = new \DateTime('yesterday');
+ }
+
+ $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
+ $qb
+ ->insert('comments')
+ ->values([
+ 'parent_id' => $qb->createNamedParameter($parentId),
+ 'topmost_parent_id' => $qb->createNamedParameter($topmostParentId),
+ 'children_count' => $qb->createNamedParameter(2),
+ 'actor_type' => $qb->createNamedParameter('user'),
+ 'actor_id' => $qb->createNamedParameter('alice'),
+ 'message' => $qb->createNamedParameter('nice one'),
+ 'verb' => $qb->createNamedParameter('comment'),
+ 'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
+ 'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
+ 'object_type' => $qb->createNamedParameter('file'),
+ 'object_id' => $qb->createNamedParameter('file64'),
+ ])
+ ->execute();
+
+ return $qb->getLastInsertId();
+ }
+
+ protected function getManager() {
+ $factory = new \OC\Comments\ManagerFactory();
+ return $factory->getManager();
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testGetCommentNotFound() {
+ $manager = $this->getManager();
+ $manager->get('22');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetCommentNotFoundInvalidInput() {
+ $manager = $this->getManager();
+ $manager->get('unexisting22');
+ }
+
+ public function testGetComment() {
+ $manager = $this->getManager();
+
+ $creationDT = new \DateTime();
+ $latestChildDT = new \DateTime('yesterday');
+
+ $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
+ $qb
+ ->insert('comments')
+ ->values([
+ 'parent_id' => $qb->createNamedParameter('2'),
+ 'topmost_parent_id' => $qb->createNamedParameter('1'),
+ 'children_count' => $qb->createNamedParameter(2),
+ 'actor_type' => $qb->createNamedParameter('user'),
+ 'actor_id' => $qb->createNamedParameter('alice'),
+ 'message' => $qb->createNamedParameter('nice one'),
+ 'verb' => $qb->createNamedParameter('comment'),
+ 'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'),
+ 'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'),
+ 'object_type' => $qb->createNamedParameter('file'),
+ 'object_id' => $qb->createNamedParameter('file64'),
+ ])
+ ->execute();
+
+ $id = strval($qb->getLastInsertId());
+
+ $comment = $manager->get($id);
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getId(), $id);
+ $this->assertSame($comment->getParentId(), '2');
+ $this->assertSame($comment->getTopmostParentId(), '1');
+ $this->assertSame($comment->getChildrenCount(), 2);
+ $this->assertSame($comment->getActorType(), 'user');
+ $this->assertSame($comment->getActorId(), 'alice');
+ $this->assertSame($comment->getMessage(), 'nice one');
+ $this->assertSame($comment->getVerb(), 'comment');
+ $this->assertSame($comment->getObjectType(), 'file');
+ $this->assertSame($comment->getObjectId(), 'file64');
+ $this->assertEquals($comment->getCreationDateTime(), $creationDT);
+ $this->assertEquals($comment->getLatestChildDateTime(), $latestChildDT);
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testGetTreeNotFound() {
+ $manager = $this->getManager();
+ $manager->getTree('22');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetTreeNotFoundInvalidIpnut() {
+ $manager = $this->getManager();
+ $manager->getTree('unexisting22');
+ }
+
+ public function testGetTree() {
+ $headId = $this->addDatabaseEntry(0, 0);
+
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
+ $id = $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
+
+ $manager = $this->getManager();
+ $tree = $manager->getTree($headId);
+
+ // Verifying the root comment
+ $this->assertTrue(isset($tree['comment']));
+ $this->assertTrue($tree['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($tree['comment']->getId(), strval($headId));
+ $this->assertTrue(isset($tree['replies']));
+ $this->assertSame(count($tree['replies']), 3);
+
+ // one level deep
+ foreach($tree['replies'] as $reply) {
+ $this->assertTrue($reply['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($reply['comment']->getId(), strval($id));
+ $this->assertSame(count($reply['replies']), 0);
+ $id--;
+ }
+ }
+
+ public function testGetTreeNoReplies() {
+ $id = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+ $tree = $manager->getTree($id);
+
+ // Verifying the root comment
+ $this->assertTrue(isset($tree['comment']));
+ $this->assertTrue($tree['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($tree['comment']->getId(), strval($id));
+ $this->assertTrue(isset($tree['replies']));
+ $this->assertSame(count($tree['replies']), 0);
+
+ // one level deep
+ foreach($tree['replies'] as $reply) {
+ throw new \Exception('This ain`t happen');
+ }
+ }
+
+ public function testGetTreeWithLimitAndOffset() {
+ $headId = $this->addDatabaseEntry(0, 0);
+
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours'));
+ $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour'));
+ $idToVerify = $this->addDatabaseEntry($headId, $headId, new \DateTime());
+
+ $manager = $this->getManager();
+
+ for ($offset = 0; $offset < 3; $offset += 2) {
+ $tree = $manager->getTree(strval($headId), 2, $offset);
+
+ // Verifying the root comment
+ $this->assertTrue(isset($tree['comment']));
+ $this->assertTrue($tree['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($tree['comment']->getId(), strval($headId));
+ $this->assertTrue(isset($tree['replies']));
+ $this->assertSame(count($tree['replies']), 2);
+
+ // one level deep
+ foreach ($tree['replies'] as $reply) {
+ $this->assertTrue($reply['comment'] instanceof \OCP\Comments\IComment);
+ $this->assertSame($reply['comment']->getId(), strval($idToVerify));
+ $this->assertSame(count($reply['replies']), 0);
+ $idToVerify--;
+ }
+ }
+ }
+
+ public function testGetForObject() {
+ $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+ $comments = $manager->getForObject('file', 'file64');
+
+ $this->assertTrue(is_array($comments));
+ $this->assertSame(count($comments), 1);
+ $this->assertTrue($comments[0] instanceof \OCP\Comments\IComment);
+ $this->assertSame($comments[0]->getMessage(), 'nice one');
+ }
+
+ public function testGetForObjectWithLimitAndOffset() {
+ $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-5 hours'));
+ $this->addDatabaseEntry(1, 1, new \DateTime('-4 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-1 hours'));
+ $idToVerify = $this->addDatabaseEntry(3, 1, new \DateTime());
+
+ $manager = $this->getManager();
+ $offset = 0;
+ do {
+ $comments = $manager->getForObject('file', 'file64', 3, $offset);
+
+ $this->assertTrue(is_array($comments));
+ foreach($comments as $comment) {
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getMessage(), 'nice one');
+ $this->assertSame($comment->getId(), strval($idToVerify));
+ $idToVerify--;
+ }
+ $offset += 3;
+ } while(count($comments) > 0);
+ }
+
+ public function testGetForObjectWithDateTimeConstraint() {
+ $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-5 hours'));
+ $id1 = $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours'));
+ $id2 = $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours'));
+
+ $manager = $this->getManager();
+ $comments = $manager->getForObject('file', 'file64', 0, 0, new \DateTime('-4 hours'));
+
+ $this->assertSame(count($comments), 2);
+ $this->assertSame($comments[0]->getId(), strval($id2));
+ $this->assertSame($comments[1]->getId(), strval($id1));
+ }
+
+ public function testGetForObjectWithLimitAndOffsetAndDateTimeConstraint() {
+ $this->addDatabaseEntry(0, 0, new \DateTime('-7 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours'));
+ $this->addDatabaseEntry(1, 1, new \DateTime('-5 hours'));
+ $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours'));
+ $this->addDatabaseEntry(2, 2, new \DateTime('-1 hours'));
+ $idToVerify = $this->addDatabaseEntry(3, 1, new \DateTime());
+
+ $manager = $this->getManager();
+ $offset = 0;
+ do {
+ $comments = $manager->getForObject('file', 'file64', 3, $offset, new \DateTime('-4 hours'));
+
+ $this->assertTrue(is_array($comments));
+ foreach($comments as $comment) {
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getMessage(), 'nice one');
+ $this->assertSame($comment->getId(), strval($idToVerify));
+ $this->assertTrue(intval($comment->getId()) >= 4);
+ $idToVerify--;
+ }
+ $offset += 3;
+ } while(count($comments) > 0);
+ }
+
+ public function testGetNumberOfCommentsForObject() {
+ for($i = 1; $i < 5; $i++) {
+ $this->addDatabaseEntry(0, 0);
+ }
+
+ $manager = $this->getManager();
+
+ $amount = $manager->getNumberOfCommentsForObject('untype', '00');
+ $this->assertSame($amount, 0);
+
+ $amount = $manager->getNumberOfCommentsForObject('file', 'file64');
+ $this->assertSame($amount, 4);
+ }
+
+ public function invalidCreateArgsProvider() {
+ return [
+ ['', 'aId-1', 'oType-1', 'oId-1'],
+ ['aType-1', '', 'oType-1', 'oId-1'],
+ ['aType-1', 'aId-1', '', 'oId-1'],
+ ['aType-1', 'aId-1', 'oType-1', ''],
+ [1, 'aId-1', 'oType-1', 'oId-1'],
+ ['aType-1', 1, 'oType-1', 'oId-1'],
+ ['aType-1', 'aId-1', 1, 'oId-1'],
+ ['aType-1', 'aId-1', 'oType-1', 1],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidCreateArgsProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testCreateCommentInvalidArguments($aType, $aId, $oType, $oId) {
+ $manager = $this->getManager();
+ $manager->create($aType, $aId, $oType, $oId);
+ }
+
+ public function testCreateComment() {
+ $actorType = 'bot';
+ $actorId = 'bob';
+ $objectType = 'weather';
+ $objectId = 'bielefeld';
+
+ $comment = $this->getManager()->create($actorType, $actorId, $objectType, $objectId);
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $this->assertSame($comment->getActorType(), $actorType);
+ $this->assertSame($comment->getActorId(), $actorId);
+ $this->assertSame($comment->getObjectType(), $objectType);
+ $this->assertSame($comment->getObjectId(), $objectId);
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testDelete() {
+ $manager = $this->getManager();
+
+ $done = $manager->delete('404');
+ $this->assertFalse($done);
+
+ $done = $manager->delete('%');
+ $this->assertFalse($done);
+
+ $done = $manager->delete('');
+ $this->assertFalse($done);
+
+ $id = strval($this->addDatabaseEntry(0, 0));
+ $comment = $manager->get($id);
+ $this->assertTrue($comment instanceof \OCP\Comments\IComment);
+ $done = $manager->delete($id);
+ $this->assertTrue($done);
+ $manager->get($id);
+ }
+
+ public function testSaveNew() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setMessage('very beautiful, I am impressed!')
+ ->setVerb('comment');
+
+ $saveSuccessful = $manager->save($comment);
+ $this->assertTrue($saveSuccessful);
+ $this->assertTrue($comment->getId() !== '');
+ $this->assertTrue($comment->getId() !== '0');
+ $this->assertTrue(!is_null($comment->getCreationDateTime()));
+
+ $loadedComment = $manager->get($comment->getId());
+ $this->assertSame($comment->getMessage(), $loadedComment->getMessage());
+ $this->assertEquals($comment->getCreationDateTime(), $loadedComment->getCreationDateTime());
+ }
+
+ public function testSaveUpdate() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setMessage('very beautiful, I am impressed!')
+ ->setVerb('comment');
+
+ $manager->save($comment);
+
+ $comment->setMessage('very beautiful, I am really so much impressed!');
+ $manager->save($comment);
+
+ $loadedComment = $manager->get($comment->getId());
+ $this->assertSame($comment->getMessage(), $loadedComment->getMessage());
+ }
+
+ /**
+ * @expectedException \OCP\Comments\NotFoundException
+ */
+ public function testSaveUpdateException() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setMessage('very beautiful, I am impressed!')
+ ->setVerb('comment');
+
+ $manager->save($comment);
+
+ $manager->delete($comment->getId());
+ $comment->setMessage('very beautiful, I am really so much impressed!');
+ $manager->save($comment);
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testSaveIncomplete() {
+ $manager = $this->getManager();
+ $comment = new \OC\Comments\Comment();
+ $comment->setMessage('from no one to nothing');
+ $manager->save($comment);
+ }
+
+ public function testSaveAsChild() {
+ $id = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+
+ for($i = 0; $i < 3; $i++) {
+ $comment = new \OC\Comments\Comment();
+ $comment
+ ->setActor('user', 'alice')
+ ->setObject('file', 'file64')
+ ->setParentId(strval($id))
+ ->setMessage('full ack')
+ ->setVerb('comment')
+ // setting the creation time avoids using sleep() while making sure to test with different timestamps
+ ->setCreationDateTime(new \DateTime('+' . $i . ' minutes'));
+
+ $manager->save($comment);
+
+ $this->assertSame($comment->getTopmostParentId(), strval($id));
+ $parentComment = $manager->get(strval($id));
+ $this->assertSame($parentComment->getChildrenCount(), $i + 1);
+ $this->assertEquals($parentComment->getLatestChildDateTime(), $comment->getCreationDateTime());
+ }
+ }
+
+ public function invalidActorArgsProvider() {
+ return
+ [
+ ['', ''],
+ [1, 'alice'],
+ ['user', 1],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidActorArgsProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testDeleteReferencesOfActorInvalidInput($type, $id) {
+ $manager = $this->getManager();
+ $manager->deleteReferencesOfActor($type, $id);
+ }
+
+ public function testDeleteReferencesOfActor() {
+ $ids = [];
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+
+ // just to make sure they are really set, with correct actor data
+ $comment = $manager->get(strval($ids[1]));
+ $this->assertSame($comment->getActorType(), 'user');
+ $this->assertSame($comment->getActorId(), 'alice');
+
+ $wasSuccessful = $manager->deleteReferencesOfActor('user', 'alice');
+ $this->assertTrue($wasSuccessful);
+
+ foreach($ids as $id) {
+ $comment = $manager->get(strval($id));
+ $this->assertSame($comment->getActorType(), ICommentsManager::DELETED_USER);
+ $this->assertSame($comment->getActorId(), ICommentsManager::DELETED_USER);
+ }
+
+ // actor info is gone from DB, but when database interaction is alright,
+ // we still expect to get true back
+ $wasSuccessful = $manager->deleteReferencesOfActor('user', 'alice');
+ $this->assertTrue($wasSuccessful);
+ }
+
+ public function testDeleteReferencesOfActorWithUserManagement() {
+ $user = \OC::$server->getUserManager()->createUser('xenia', '123456');
+ $this->assertTrue($user instanceof \OCP\IUser);
+
+ $manager = \OC::$server->getCommentsManager();
+ $comment = $manager->create('user', $user->getUID(), 'file', 'file64');
+ $comment
+ ->setMessage('Most important comment I ever left on the Internet.')
+ ->setVerb('comment');
+ $status = $manager->save($comment);
+ $this->assertTrue($status);
+
+ $commentID = $comment->getId();
+ $user->delete();
+
+ $comment = $manager->get($commentID);
+ $this->assertSame($comment->getActorType(), \OCP\Comments\ICommentsManager::DELETED_USER);
+ $this->assertSame($comment->getActorId(), \OCP\Comments\ICommentsManager::DELETED_USER);
+ }
+
+ public function invalidObjectArgsProvider() {
+ return
+ [
+ ['', ''],
+ [1, 'file64'],
+ ['file', 1],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidObjectArgsProvider
+ * @expectedException \InvalidArgumentException
+ */
+ public function testDeleteCommentsAtObjectInvalidInput($type, $id) {
+ $manager = $this->getManager();
+ $manager->deleteCommentsAtObject($type, $id);
+ }
+
+ public function testDeleteCommentsAtObject() {
+ $ids = [];
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+ $ids[] = $this->addDatabaseEntry(0, 0);
+
+ $manager = $this->getManager();
+
+ // just to make sure they are really set, with correct actor data
+ $comment = $manager->get(strval($ids[1]));
+ $this->assertSame($comment->getObjectType(), 'file');
+ $this->assertSame($comment->getObjectId(), 'file64');
+
+ $wasSuccessful = $manager->deleteCommentsAtObject('file', 'file64');
+ $this->assertTrue($wasSuccessful);
+
+ $verified = 0;
+ foreach($ids as $id) {
+ try {
+ $manager->get(strval($id));
+ } catch (\OCP\Comments\NotFoundException $e) {
+ $verified++;
+ }
+ }
+ $this->assertSame($verified, 3);
+
+ // actor info is gone from DB, but when database interaction is alright,
+ // we still expect to get true back
+ $wasSuccessful = $manager->deleteCommentsAtObject('file', 'file64');
+ $this->assertTrue($wasSuccessful);
+ }
+
+}
diff --git a/tests/lib/server.php b/tests/lib/server.php
index b72bef82036..6b569e77dd9 100644
--- a/tests/lib/server.php
+++ b/tests/lib/server.php
@@ -61,6 +61,7 @@ class Server extends \Test\TestCase {
['CapabilitiesManager', '\OC\CapabilitiesManager'],
['ContactsManager', '\OC\ContactsManager'],
['ContactsManager', '\OCP\Contacts\IManager'],
+ ['CommentsManager', '\OCP\Comments\ICommentsManager'],
['Crypto', '\OC\Security\Crypto'],
['Crypto', '\OCP\Security\ICrypto'],
['CryptoWrapper', '\OC\Session\CryptoWrapper'],
@@ -173,4 +174,16 @@ class Server extends \Test\TestCase {
$this->assertInstanceOf('\OC_EventSource', $this->server->createEventSource(), 'service returned by "createEventSource" did not return the right class');
$this->assertInstanceOf('\OCP\IEventSource', $this->server->createEventSource(), 'service returned by "createEventSource" did not return the right class');
}
+
+ public function testOverwriteDefaultCommentsManager() {
+ $config = $this->server->getConfig();
+ $defaultManagerFactory = $config->getSystemValue('comments.managerFactory', '\OC\Comments\ManagerFactory');
+
+ $config->setSystemValue('comments.managerFactory', '\Test\Comments\FakeFactory');
+
+ $manager = $this->server->getCommentsManager();
+ $this->assertInstanceOf('\OCP\Comments\ICommentsManager', $manager);
+
+ $config->setSystemValue('comments.managerFactory', $defaultManagerFactory);
+ }
}