Merge pull request #48612 from nextcloud/fix/activity-log-for-favorites-in-dav

add activity logging for favorites in dav
pull/49832/head
Julius Knorr 2024-12-12 15:18:47 +07:00 committed by GitHub
commit cba556d641
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 164 additions and 114 deletions

@ -136,7 +136,7 @@ class ServerFactory {
));
if ($this->userSession->isLoggedIn()) {
$server->addPlugin(new TagsPlugin($objectTree, $this->tagManager));
$server->addPlugin(new TagsPlugin($objectTree, $this->tagManager, $this->eventDispatcher, $this->userSession));
$server->addPlugin(new SharesPlugin(
$objectTree,
$this->userSession,

@ -27,8 +27,10 @@ namespace OCA\DAV\Connector\Sabre;
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ITagManager;
use OCP\ITags;
use OCP\IUserSession;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
@ -67,6 +69,8 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
public function __construct(
private \Sabre\DAV\Tree $tree,
private ITagManager $tagManager,
private IEventDispatcher $eventDispatcher,
private IUserSession $userSession,
) {
$this->tagger = null;
$this->cachedTags = [];
@ -251,7 +255,7 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin {
return true;
});
$propPatch->handle(self::FAVORITE_PROPERTYNAME, function ($favState) use ($node) {
$propPatch->handle(self::FAVORITE_PROPERTYNAME, function ($favState) use ($node, $path) {
if ((int)$favState === 1 || $favState === 'true') {
$this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE);
} else {

@ -295,7 +295,7 @@ class Server {
}
$this->server->addPlugin(
new TagsPlugin(
$this->server->tree, \OC::$server->getTagManager()
$this->server->tree, \OC::$server->getTagManager(), \OC::$server->get(IEventDispatcher::class), \OC::$server->get(IUserSession::class)
)
);

@ -13,8 +13,11 @@ use OCA\DAV\Connector\Sabre\Node;
use OCA\DAV\Connector\Sabre\TagList;
use OCA\DAV\Connector\Sabre\TagsPlugin;
use OCA\DAV\Upload\UploadFile;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ITagManager;
use OCP\ITags;
use OCP\IUser;
use OCP\IUserSession;
use Sabre\DAV\Tree;
class TagsPluginTest extends \Test\TestCase {
@ -42,6 +45,16 @@ class TagsPluginTest extends \Test\TestCase {
*/
private $tagger;
/**
* @var IEventDispatcher
*/
private $eventDispatcher;
/**
* @var IUserSession
*/
private $userSession;
/**
* @var TagsPlugin
*/
@ -59,11 +72,24 @@ class TagsPluginTest extends \Test\TestCase {
$this->tagManager = $this->getMockBuilder(ITagManager::class)
->disableOriginalConstructor()
->getMock();
$this->eventDispatcher = $this->getMockBuilder(IEventDispatcher::class)
->disableOriginalConstructor()
->getMock();
$user = $this->createMock(IUser::class);
/**
* @var IUserSession
*/
$this->userSession = $this->createMock(IUserSession::class);
$this->userSession->expects($this->any())
->method('getUser')
->withAnyParameters()
->willReturn($user);
$this->tagManager->expects($this->any())
->method('load')
->with('files')
->willReturn($this->tagger);
$this->plugin = new TagsPlugin($this->tree, $this->tagManager);
$this->plugin = new TagsPlugin($this->tree, $this->tagManager, $this->eventDispatcher, $this->userSession);
$this->plugin->initialize($this->server);
}

@ -60,6 +60,8 @@ return array(
'OCA\\Files\\Helper' => $baseDir . '/../lib/Helper.php',
'OCA\\Files\\Listener\\LoadSearchPluginsListener' => $baseDir . '/../lib/Listener/LoadSearchPluginsListener.php',
'OCA\\Files\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files\\Listener\\NodeAddedToFavoriteListener' => $baseDir . '/../lib/Listener/NodeAddedToFavoriteListener.php',
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => $baseDir . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php',
'OCA\\Files\\Listener\\SyncLivePhotosListener' => $baseDir . '/../lib/Listener/SyncLivePhotosListener.php',
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',

@ -75,6 +75,8 @@ class ComposerStaticInitFiles
'OCA\\Files\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
'OCA\\Files\\Listener\\LoadSearchPluginsListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSearchPluginsListener.php',
'OCA\\Files\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files\\Listener\\NodeAddedToFavoriteListener' => __DIR__ . '/..' . '/../lib/Listener/NodeAddedToFavoriteListener.php',
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => __DIR__ . '/..' . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php',
'OCA\\Files\\Listener\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listener/SyncLivePhotosListener.php',
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',

@ -18,6 +18,8 @@ use OCA\Files\Event\LoadSearchPlugins;
use OCA\Files\Event\LoadSidebar;
use OCA\Files\Listener\LoadSearchPluginsListener;
use OCA\Files\Listener\LoadSidebarListener;
use OCA\Files\Listener\NodeAddedToFavoriteListener;
use OCA\Files\Listener\NodeRemovedFromFavoriteListener;
use OCA\Files\Listener\RenderReferenceEventListener;
use OCA\Files\Listener\SyncLivePhotosListener;
use OCA\Files\Notification\Notifier;
@ -33,12 +35,13 @@ use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\Collaboration\Resources\IProviderManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Events\Node\BeforeNodeCopiedEvent;
use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
use OCP\Files\Events\Node\NodeCopiedEvent;
use OCP\Files\Events\NodeAddedToFavorite;
use OCP\Files\Events\NodeRemovedFromFavorite;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use OCP\IL10N;
@ -96,7 +99,6 @@ class Application extends App implements IBootstrap {
$c->get(IActivityManager::class),
$c->get(ITagManager::class)->load(self::APP_ID),
$server->getUserFolder(),
$c->get(IEventDispatcher::class),
);
});
@ -116,7 +118,8 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(BeforeNodeCopiedEvent::class, SyncLivePhotosListener::class);
$context->registerEventListener(NodeCopiedEvent::class, SyncLivePhotosListener::class);
$context->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class);
$context->registerEventListener(NodeAddedToFavorite::class, NodeAddedToFavoriteListener::class);
$context->registerEventListener(NodeRemovedFromFavorite::class, NodeRemovedFromFavoriteListener::class);
$context->registerSearchProvider(FilesSearchProvider::class);
$context->registerNotifierService(Notifier::class);

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Listener;
use OCA\Files\Activity\FavoriteProvider;
use OCP\Activity\IManager as IActivityManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\NodeAddedToFavorite;
/** @template-implements IEventListener<NodeAddedToFavorite> */
class NodeAddedToFavoriteListener implements IEventListener {
public function __construct(
private IActivityManager $activityManager,
) {
}
public function handle(Event $event):void {
if (!($event instanceof NodeAddedToFavorite)) {
return;
}
$activityEvent = $this->activityManager->generateEvent();
try {
$activityEvent->setApp('files')
->setObject('files', $event->getFileId(), $event->getPath())
->setType('favorite')
->setAuthor($event->getUser()->getUID())
->setAffectedUser($event->getUser()->getUID())
->setTimestamp(time())
->setSubject(
FavoriteProvider::SUBJECT_ADDED,
['id' => $event->getFileId(), 'path' => $event->getPath()]
);
$this->activityManager->publish($activityEvent);
} catch (\InvalidArgumentException $e) {
} catch (\BadMethodCallException $e) {
}
}
}

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Listener;
use OCA\Files\Activity\FavoriteProvider;
use OCP\Activity\IManager as IActivityManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\NodeRemovedFromFavorite;
/** @template-implements IEventListener<NodeRemovedFromFavorite> */
class NodeRemovedFromFavoriteListener implements IEventListener {
public function __construct(
private IActivityManager $activityManager,
) {
}
public function handle(Event $event):void {
if (!($event instanceof NodeRemovedFromFavorite)) {
return;
}
$activityEvent = $this->activityManager->generateEvent();
try {
$activityEvent->setApp('files')
->setObject('files', $event->getFileId(), $event->getPath())
->setType('favorite')
->setAuthor($event->getUser()->getUID())
->setAffectedUser($event->getUser()->getUID())
->setTimestamp(time())
->setSubject(
FavoriteProvider::SUBJECT_REMOVED,
['id' => $event->getFileId(), 'path' => $event->getPath()]
);
$this->activityManager->publish($activityEvent);
} catch (\InvalidArgumentException $e) {
} catch (\BadMethodCallException $e) {
}
}
}

@ -7,15 +7,10 @@
*/
namespace OCA\Files\Service;
use OCA\Files\Activity\FavoriteProvider;
use OCP\Activity\IManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\NodeAddedToFavorite;
use OCP\Files\Events\NodeRemovedFromFavorite;
use OCP\Files\Folder;
use OCP\Files\NotFoundException;
use OCP\ITags;
use OCP\IUser;
use OCP\IUserSession;
/**
@ -28,7 +23,6 @@ class TagService {
private IManager $activityManager,
private ?ITags $tagger,
private ?Folder $homeFolder,
private IEventDispatcher $dispatcher,
) {
}
@ -60,16 +54,10 @@ class TagService {
$newTags = array_diff($tags, $currentTags);
foreach ($newTags as $tag) {
if ($tag === ITags::TAG_FAVORITE) {
$this->addActivity(true, $fileId, $path);
}
$this->tagger->tagAs($fileId, $tag);
}
$deletedTags = array_diff($currentTags, $tags);
foreach ($deletedTags as $tag) {
if ($tag === ITags::TAG_FAVORITE) {
$this->addActivity(false, $fileId, $path);
}
$this->tagger->unTag($fileId, $tag);
}
@ -77,40 +65,4 @@ class TagService {
// list is up to date, in case of concurrent changes ?
return $tags;
}
/**
* @param bool $addToFavorite
* @param int $fileId
* @param string $path
*/
protected function addActivity($addToFavorite, $fileId, $path) {
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return;
}
if ($addToFavorite) {
$event = new NodeAddedToFavorite($user, $fileId, $path);
} else {
$event = new NodeRemovedFromFavorite($user, $fileId, $path);
}
$this->dispatcher->dispatchTyped($event);
$event = $this->activityManager->generateEvent();
try {
$event->setApp('files')
->setObject('files', $fileId, $path)
->setType('favorite')
->setAuthor($user->getUID())
->setAffectedUser($user->getUID())
->setTimestamp(time())
->setSubject(
$addToFavorite ? FavoriteProvider::SUBJECT_ADDED : FavoriteProvider::SUBJECT_REMOVED,
['id' => $fileId, 'path' => $path]
);
$this->activityManager->publish($event);
} catch (\InvalidArgumentException $e) {
} catch (\BadMethodCallException $e) {
}
}
}

@ -9,7 +9,6 @@ namespace OCA\Files\Tests\Service;
use OCA\Files\Service\TagService;
use OCP\Activity\IManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\Files\NotFoundException;
use OCP\ITags;
@ -41,9 +40,6 @@ class TagServiceTest extends \Test\TestCase {
*/
private $root;
/** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
private $dispatcher;
/**
* @var TagService|\PHPUnit\Framework\MockObject\MockObject
*/
@ -72,7 +68,6 @@ class TagServiceTest extends \Test\TestCase {
->willReturn($user);
$this->root = \OC::$server->getUserFolder();
$this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->tagger = \OC::$server->getTagManager()->load('files');
$this->tagService = $this->getTagService(['addActivity']);
@ -89,7 +84,6 @@ class TagServiceTest extends \Test\TestCase {
$this->activityManager,
$this->tagger,
$this->root,
$this->dispatcher,
])
->setMethods($methods)
->getMock();
@ -107,9 +101,6 @@ class TagServiceTest extends \Test\TestCase {
$tag1 = 'tag1';
$tag2 = 'tag2';
$this->tagService->expects($this->never())
->method('addActivity');
$subdir = $this->root->newFolder('subdir');
$testFile = $subdir->newFile('test.txt');
$testFile->putContent('test contents');
@ -141,27 +132,6 @@ class TagServiceTest extends \Test\TestCase {
}
$this->assertTrue($caught);
$subdir->delete();
}
public function testFavoriteActivity(): void {
$subdir = $this->root->newFolder('subdir');
$file = $subdir->newFile('test.txt');
$this->tagService->expects($this->exactly(2))
->method('addActivity')
->withConsecutive(
[true, $file->getId(), 'subdir/test.txt'],
[false, $file->getId(), 'subdir/test.txt']
);
// set tags
$this->tagService->updateFileTags('subdir/test.txt', [ITags::TAG_FAVORITE]);
// remove tag
$this->tagService->updateFileTags('subdir/test.txt', []);
$subdir->delete();
}
}

@ -11,6 +11,7 @@ use OC\Tagging\TagMapper;
use OCP\Db\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\EventDispatcher\IEventListener;
use OCP\IDBConnection;
use OCP\ITagManager;
@ -23,16 +24,14 @@ use Psr\Log\LoggerInterface;
* @template-implements IEventListener<UserDeletedEvent>
*/
class TagManager implements ITagManager, IEventListener {
private TagMapper $mapper;
private IUserSession $userSession;
private IDBConnection $connection;
private LoggerInterface $logger;
public function __construct(TagMapper $mapper, IUserSession $userSession, IDBConnection $connection, LoggerInterface $logger) {
$this->mapper = $mapper;
$this->userSession = $userSession;
$this->connection = $connection;
$this->logger = $logger;
public function __construct(
private TagMapper $mapper,
private IUserSession $userSession,
private IDBConnection $connection,
private LoggerInterface $logger,
private IEventDispatcher $dispatcher,
) {
}
/**
@ -57,7 +56,7 @@ class TagManager implements ITagManager, IEventListener {
}
$userId = $this->userSession->getUser()->getUId();
}
return new Tags($this->mapper, $userId, $type, $this->logger, $this->connection, $defaultTags);
return new Tags($this->mapper, $userId, $type, $this->logger, $this->connection, $this->dispatcher, $this->userSession, $defaultTags);
}
/**

@ -11,8 +11,12 @@ use OC\Tagging\Tag;
use OC\Tagging\TagMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\NodeAddedToFavorite;
use OCP\Files\Events\NodeRemovedFromFavorite;
use OCP\IDBConnection;
use OCP\ITags;
use OCP\IUserSession;
use OCP\Share_Backend;
use Psr\Log\LoggerInterface;
@ -21,10 +25,6 @@ class Tags implements ITags {
* Used for storing objectid/categoryname pairs while rescanning.
*/
private static array $relations = [];
private string $type;
private string $user;
private IDBConnection $db;
private LoggerInterface $logger;
private array $tags = [];
/**
@ -38,11 +38,6 @@ class Tags implements ITags {
*/
private array $owners = [];
/**
* The Mapper we are using to communicate our Tag objects to the database.
*/
private TagMapper $mapper;
/**
* The sharing backend for objects of $this->type. Required if
* $this->includeShared === true to determine ownership of items.
@ -62,14 +57,18 @@ class Tags implements ITags {
*
* since 20.0.0 $includeShared isn't used anymore
*/
public function __construct(TagMapper $mapper, string $user, string $type, LoggerInterface $logger, IDBConnection $connection, array $defaultTags = []) {
$this->mapper = $mapper;
$this->user = $user;
$this->type = $type;
public function __construct(
private TagMapper $mapper,
private string $user,
private string $type,
private LoggerInterface $logger,
private IDBConnection $db,
private IEventDispatcher $dispatcher,
private IUserSession $userSession,
array $defaultTags = [],
) {
$this->owners = [$this->user];
$this->tags = $this->mapper->loadTags($this->owners, $this->type);
$this->db = $connection;
$this->logger = $logger;
if (count($defaultTags) > 0 && count($this->tags) === 0) {
$this->addMultiple($defaultTags, true);
@ -502,7 +501,7 @@ class Tags implements ITags {
* @param string $tag The id or name of the tag
* @return boolean Returns false on error.
*/
public function tagAs($objid, $tag) {
public function tagAs($objid, $tag, string $path = '') {
if (is_string($tag) && !is_numeric($tag)) {
$tag = trim($tag);
if ($tag === '') {
@ -532,6 +531,9 @@ class Tags implements ITags {
]);
return false;
}
if ($tag === ITags::TAG_FAVORITE) {
$this->dispatcher->dispatchTyped(new NodeAddedToFavorite($this->userSession->getUser(), $objid, $path));
}
return true;
}
@ -542,7 +544,7 @@ class Tags implements ITags {
* @param string $tag The id or name of the tag
* @return boolean
*/
public function unTag($objid, $tag) {
public function unTag($objid, $tag, string $path = '') {
if (is_string($tag) && !is_numeric($tag)) {
$tag = trim($tag);
if ($tag === '') {
@ -569,6 +571,9 @@ class Tags implements ITags {
]);
return false;
}
if ($tag === ITags::TAG_FAVORITE) {
$this->dispatcher->dispatchTyped(new NodeRemovedFromFavorite($this->userSession->getUser(), $objid, $path));
}
return true;
}

@ -7,6 +7,7 @@
namespace Test;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserSession;
@ -48,7 +49,7 @@ class TagsTest extends \Test\TestCase {
$this->objectType = $this->getUniqueID('type_');
$this->tagMapper = new \OC\Tagging\TagMapper(\OC::$server->get(IDBConnection::class));
$this->tagMgr = new \OC\TagManager($this->tagMapper, $this->userSession, \OC::$server->get(IDBConnection::class), \OC::$server->get(LoggerInterface::class));
$this->tagMgr = new \OC\TagManager($this->tagMapper, $this->userSession, \OC::$server->get(IDBConnection::class), \OC::$server->get(LoggerInterface::class), \OC::$server->get(IEventDispatcher::class));
}
protected function tearDown(): void {
@ -65,7 +66,7 @@ class TagsTest extends \Test\TestCase {
->expects($this->any())
->method('getUser')
->willReturn(null);
$this->tagMgr = new \OC\TagManager($this->tagMapper, $this->userSession, \OC::$server->getDatabaseConnection(), \OC::$server->get(LoggerInterface::class));
$this->tagMgr = new \OC\TagManager($this->tagMapper, $this->userSession, \OC::$server->getDatabaseConnection(), \OC::$server->get(LoggerInterface::class), \OC::$server->get(IEventDispatcher::class));
$this->assertNull($this->tagMgr->load($this->objectType));
}