@@ -2,7 +2,7 @@ | |||
/** | |||
* @author Arthur Schiwon <blizzz@owncloud.com> | |||
* | |||
* @copyright Copyright (c) 2015, ownCloud, Inc. | |||
* @copyright Copyright (c) 2016, ownCloud, Inc. | |||
* @license AGPL-3.0 | |||
* | |||
* This code is free software: you can redistribute it and/or modify | |||
@@ -88,7 +88,7 @@ class CommentsPlugin extends ServerPlugin { | |||
$this->server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; | |||
$this->server->xml->classMap['DateTime'] = function(Writer $writer, \DateTime $value) { | |||
$writer->write($value->format('Y-m-d H:m:i')); | |||
$writer->write($value->format('Y-m-d H:i:s')); | |||
}; | |||
$this->server->on('report', [$this, 'onReport']); |
@@ -0,0 +1,130 @@ | |||
<?php | |||
/** | |||
* @author Arthur Schiwon <blizzz@owncloud.com> | |||
* | |||
* @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 <http://www.gnu.org/licenses/> | |||
* | |||
*/ | |||
namespace OCA\DAV\Connector\Sabre; | |||
use OCP\Comments\ICommentsManager; | |||
use OCP\IUserSession; | |||
use Sabre\DAV\PropFind; | |||
use Sabre\DAV\ServerPlugin; | |||
class CommentPropertiesPlugin extends ServerPlugin { | |||
const PROPERTY_NAME_HREF = '{http://owncloud.org/ns}comments-href'; | |||
const PROPERTY_NAME_COUNT = '{http://owncloud.org/ns}comments-count'; | |||
const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread'; | |||
/** @var \Sabre\DAV\Server */ | |||
protected $server; | |||
/** @var ICommentsManager */ | |||
private $commentsManager; | |||
/** @var IUserSession */ | |||
private $userSession; | |||
public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) { | |||
$this->commentsManager = $commentsManager; | |||
$this->userSession = $userSession; | |||
} | |||
/** | |||
* This initializes the plugin. | |||
* | |||
* This function is called by Sabre\DAV\Server, after | |||
* addPlugin is called. | |||
* | |||
* This method should set up the required event subscriptions. | |||
* | |||
* @param \Sabre\DAV\Server $server | |||
* @return void | |||
*/ | |||
function initialize(\Sabre\DAV\Server $server) { | |||
$this->server = $server; | |||
$this->server->on('propFind', array($this, 'handleGetProperties')); | |||
} | |||
/** | |||
* Adds tags and favorites properties to the response, | |||
* if requested. | |||
* | |||
* @param PropFind $propFind | |||
* @param \Sabre\DAV\INode $node | |||
* @return void | |||
*/ | |||
public function handleGetProperties( | |||
PropFind $propFind, | |||
\Sabre\DAV\INode $node | |||
) { | |||
if (!($node instanceof File) && !($node instanceof Directory)) { | |||
return; | |||
} | |||
$propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) { | |||
return $this->commentsManager->getNumberOfCommentsForObject('files', strval($node->getId())); | |||
}); | |||
$propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) { | |||
return $this->getCommentsLink($node); | |||
}); | |||
$propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) { | |||
return $this->getUnreadCount($node); | |||
}); | |||
} | |||
/** | |||
* returns a reference to the comments node | |||
* | |||
* @param Node $node | |||
* @return mixed|string | |||
*/ | |||
public function getCommentsLink(Node $node) { | |||
$href = $this->server->getBaseUri(); | |||
$entryPoint = strrpos($href, '/webdav/'); | |||
if($entryPoint === false) { | |||
// in case we end up somewhere else, unexpectedly. | |||
return null; | |||
} | |||
$href = substr_replace($href, '/dav/', $entryPoint); | |||
$href .= 'comments/files/' . $node->getId(); | |||
return $href; | |||
} | |||
/** | |||
* returns the number of unread comments for the currently logged in user | |||
* on the given file or directory node | |||
* | |||
* @param Node $node | |||
* @return Int|null | |||
*/ | |||
public function getUnreadCount(Node $node) { | |||
$user = $this->userSession->getUser(); | |||
if(is_null($user)) { | |||
return null; | |||
} | |||
$lastRead = $this->commentsManager->getReadMark('files', strval($node->getId()), $user); | |||
return $this->commentsManager->getNumberOfCommentsForObject('files', strval($node->getId()), $lastRead); | |||
} | |||
} |
@@ -134,6 +134,7 @@ class ServerFactory { | |||
if($this->userSession->isLoggedIn()) { | |||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager)); | |||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin(\OC::$server->getCommentsManager(), $this->userSession)); | |||
// custom properties plugin must be the last one | |||
$server->addPlugin( | |||
new \Sabre\DAV\PropertyStorage\Plugin( |
@@ -0,0 +1,148 @@ | |||
<?php | |||
/** | |||
* @author Arthur Schiwon <blizzz@owncloud.com> | |||
* | |||
* @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 <http://www.gnu.org/licenses/> | |||
* | |||
*/ | |||
namespace OCA\DAV\Tests\Unit\Connector\Sabre; | |||
use \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin as CommentPropertiesPluginImplementation; | |||
class CommentsPropertiesPlugin extends \Test\TestCase { | |||
/** @var CommentPropertiesPluginImplementation */ | |||
protected $plugin; | |||
protected $commentsManager; | |||
protected $userSession; | |||
protected $server; | |||
public function setUp() { | |||
parent::setUp(); | |||
$this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); | |||
$this->userSession = $this->getMock('\OCP\IUserSession'); | |||
$this->server = $this->getMockBuilder('\Sabre\DAV\Server') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$this->plugin = new CommentPropertiesPluginImplementation($this->commentsManager, $this->userSession); | |||
$this->plugin->initialize($this->server); | |||
} | |||
public function nodeProvider() { | |||
$mocks = []; | |||
foreach(['\OCA\DAV\Connector\Sabre\File', '\OCA\DAV\Connector\Sabre\Directory', '\Sabre\DAV\INode'] as $class) { | |||
$mocks[] = $this->getMockBuilder($class) | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
} | |||
return [ | |||
[$mocks[0], true], | |||
[$mocks[1], true], | |||
[$mocks[2], false] | |||
]; | |||
} | |||
/** | |||
* @dataProvider nodeProvider | |||
* @param $node | |||
* @param $expectedSuccessful | |||
*/ | |||
public function testHandleGetProperties($node, $expectedSuccessful) { | |||
$propFind = $this->getMockBuilder('\Sabre\DAV\PropFind') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
if($expectedSuccessful) { | |||
$propFind->expects($this->exactly(3)) | |||
->method('handle'); | |||
} else { | |||
$propFind->expects($this->never()) | |||
->method('handle'); | |||
} | |||
$this->plugin->handleGetProperties($propFind, $node); | |||
} | |||
public function baseUriProvider() { | |||
return [ | |||
['owncloud/remote.php/webdav/', '4567', 'owncloud/remote.php/dav/comments/files/4567'], | |||
['owncloud/remote.php/wicked/', '4567', null] | |||
]; | |||
} | |||
/** | |||
* @dataProvider baseUriProvider | |||
* @param $baseUri | |||
* @param $fid | |||
* @param $expectedHref | |||
*/ | |||
public function testGetCommentsLink($baseUri, $fid, $expectedHref) { | |||
$node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$node->expects($this->any()) | |||
->method('getId') | |||
->will($this->returnValue($fid)); | |||
$this->server->expects($this->once()) | |||
->method('getBaseUri') | |||
->will($this->returnValue($baseUri)); | |||
$href = $this->plugin->getCommentsLink($node); | |||
$this->assertSame($expectedHref, $href); | |||
} | |||
public function userProvider() { | |||
return [ | |||
[$this->getMock('\OCP\IUser')], | |||
[null] | |||
]; | |||
} | |||
/** | |||
* @dataProvider userProvider | |||
* @param $user | |||
*/ | |||
public function testGetUnreadCount($user) { | |||
$node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$node->expects($this->any()) | |||
->method('getId') | |||
->will($this->returnValue('4567')); | |||
$this->userSession->expects($this->once()) | |||
->method('getUser') | |||
->will($this->returnValue($user)); | |||
$this->commentsManager->expects($this->any()) | |||
->method('getNumberOfCommentsForObject') | |||
->will($this->returnValue(42)); | |||
$unread = $this->plugin->getUnreadCount($node); | |||
if(is_null($user)) { | |||
$this->assertNull($unread); | |||
} else { | |||
$this->assertSame($unread, 42); | |||
} | |||
} | |||
} |
@@ -25,7 +25,9 @@ use OCP\Comments\IComment; | |||
use OCP\Comments\ICommentsManager; | |||
use OCP\Comments\NotFoundException; | |||
use OCP\IDBConnection; | |||
use OCP\IConfig; | |||
use OCP\ILogger; | |||
use OCP\IUser; | |||
class Manager implements ICommentsManager { | |||
@@ -38,12 +40,17 @@ class Manager implements ICommentsManager { | |||
/** @var IComment[] */ | |||
protected $commentsCache = []; | |||
/** @var IConfig */ | |||
protected $config; | |||
public function __construct( | |||
IDBConnection $dbConn, | |||
ILogger $logger | |||
ILogger $logger, | |||
IConfig $config | |||
) { | |||
$this->dbConn = $dbConn; | |||
$this->logger = $logger; | |||
$this->config = $config; | |||
} | |||
/** | |||
@@ -346,10 +353,12 @@ class Manager implements ICommentsManager { | |||
/** | |||
* @param $objectType string the object type, e.g. 'files' | |||
* @param $objectId string the id of the object | |||
* @param \DateTime $notOlderThan optional, timestamp of the oldest comments | |||
* that may be returned | |||
* @return Int | |||
* @since 9.0.0 | |||
*/ | |||
public function getNumberOfCommentsForObject($objectType, $objectId) { | |||
public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null) { | |||
$qb = $this->dbConn->getQueryBuilder(); | |||
$query = $qb->select($qb->createFunction('COUNT(`id`)')) | |||
->from('comments') | |||
@@ -358,6 +367,12 @@ class Manager implements ICommentsManager { | |||
->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(); | |||
@@ -566,4 +581,45 @@ class Manager implements ICommentsManager { | |||
return is_int($affectedRows); | |||
} | |||
/** | |||
* 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); | |||
$dateTime = $dateTime->format('Y-m-d H:i:s'); | |||
$this->config->setUserValue($user->getUID(), 'comments', 'marker.' . $objectType .'.'. $objectId, $dateTime); | |||
} | |||
/** | |||
* 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) { | |||
$this->checkRoleParameters('Object', $objectType, $objectId); | |||
$dateTime = $this->config->getUserValue( | |||
$user->getUID(), | |||
'comments', | |||
'marker.' . $objectType .'.'. $objectId, | |||
null | |||
); | |||
if(!is_null($dateTime)) { | |||
$dateTime = new \DateTime($dateTime); | |||
} | |||
return $dateTime; | |||
} | |||
} |
@@ -51,7 +51,8 @@ class ManagerFactory implements ICommentsManagerFactory { | |||
public function getManager() { | |||
return new Manager( | |||
$this->serverContainer->getDatabaseConnection(), | |||
$this->serverContainer->getLogger() | |||
$this->serverContainer->getLogger(), | |||
$this->serverContainer->getConfig() | |||
); | |||
} | |||
} |
@@ -115,10 +115,12 @@ interface ICommentsManager { | |||
/** | |||
* @param $objectType string the object type, e.g. 'files' | |||
* @param $objectId string the id of the object | |||
* @param \DateTime $notOlderThan optional, timestamp of the oldest comments | |||
* that may be returned | |||
* @return Int | |||
* @since 9.0.0 | |||
*/ | |||
public function getNumberOfCommentsForObject($objectType, $objectId); | |||
public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null); | |||
/** | |||
* creates a new comment and returns it. At this point of time, it is not | |||
@@ -188,4 +190,29 @@ interface ICommentsManager { | |||
*/ | |||
public function deleteCommentsAtObject($objectType, $objectId); | |||
/** | |||
* 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 \OCP\IUser $user | |||
* @since 9.0.0 | |||
*/ | |||
public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user); | |||
/** | |||
* 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 \OCP\IUser $user | |||
* @return \DateTime|null | |||
* @since 9.0.0 | |||
*/ | |||
public function getReadMark($objectType, $objectId, \OCP\IUser $user); | |||
} |
@@ -19,7 +19,7 @@ class FakeManager implements \OCP\Comments\ICommentsManager { | |||
\DateTime $notOlderThan = null | |||
) {} | |||
public function getNumberOfCommentsForObject($objectType, $objectId) {} | |||
public function getNumberOfCommentsForObject($objectType, $objectId, \DateTime $notOlderThan = null) {} | |||
public function create($actorType, $actorId, $objectType, $objectId) {} | |||
@@ -30,4 +30,8 @@ class FakeManager implements \OCP\Comments\ICommentsManager { | |||
public function deleteReferencesOfActor($actorType, $actorId) {} | |||
public function deleteCommentsAtObject($objectType, $objectId) {} | |||
public function setReadMark($objectType, $objectId, \DateTime $dateTime, \OCP\IUser $user) {} | |||
public function getReadMark($objectType, $objectId, \OCP\IUser $user) {} | |||
} |
@@ -561,4 +561,20 @@ class Test_Comments_Manager extends TestCase | |||
$this->assertTrue($wasSuccessful); | |||
} | |||
public function testSetMarkRead() { | |||
$user = $this->getMock('\OCP\IUser'); | |||
$user->expects($this->any()) | |||
->method('getUID') | |||
->will($this->returnValue('alice')); | |||
$dateTimeSet = new \DateTime(); | |||
$manager = $this->getManager(); | |||
$manager->setReadMark('files', '36', $dateTimeSet, $user); | |||
$dateTimeGet = $manager->getReadMark('files', '36', $user); | |||
$this->assertEquals($dateTimeGet, $dateTimeSet); | |||
} | |||
} |