This commit is contained in:
Carl Schwan 2026-04-02 22:54:47 +00:00 committed by GitHub
commit a8d99a73f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 409 additions and 346 deletions

View file

@ -10,7 +10,6 @@ return array(
'OCA\\AdminAudit\\Actions\\Action' => $baseDir . '/../lib/Actions/Action.php',
'OCA\\AdminAudit\\Actions\\Files' => $baseDir . '/../lib/Actions/Files.php',
'OCA\\AdminAudit\\Actions\\Sharing' => $baseDir . '/../lib/Actions/Sharing.php',
'OCA\\AdminAudit\\Actions\\TagManagement' => $baseDir . '/../lib/Actions/TagManagement.php',
'OCA\\AdminAudit\\Actions\\Trashbin' => $baseDir . '/../lib/Actions/Trashbin.php',
'OCA\\AdminAudit\\Actions\\Versions' => $baseDir . '/../lib/Actions/Versions.php',
'OCA\\AdminAudit\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
@ -26,5 +25,6 @@ return array(
'OCA\\AdminAudit\\Listener\\GroupManagementEventListener' => $baseDir . '/../lib/Listener/GroupManagementEventListener.php',
'OCA\\AdminAudit\\Listener\\SecurityEventListener' => $baseDir . '/../lib/Listener/SecurityEventListener.php',
'OCA\\AdminAudit\\Listener\\SharingEventListener' => $baseDir . '/../lib/Listener/SharingEventListener.php',
'OCA\\AdminAudit\\Listener\\TagEventListener' => $baseDir . '/../lib/Listener/TagEventListener.php',
'OCA\\AdminAudit\\Listener\\UserManagementEventListener' => $baseDir . '/../lib/Listener/UserManagementEventListener.php',
);

View file

@ -25,7 +25,6 @@ class ComposerStaticInitAdminAudit
'OCA\\AdminAudit\\Actions\\Action' => __DIR__ . '/..' . '/../lib/Actions/Action.php',
'OCA\\AdminAudit\\Actions\\Files' => __DIR__ . '/..' . '/../lib/Actions/Files.php',
'OCA\\AdminAudit\\Actions\\Sharing' => __DIR__ . '/..' . '/../lib/Actions/Sharing.php',
'OCA\\AdminAudit\\Actions\\TagManagement' => __DIR__ . '/..' . '/../lib/Actions/TagManagement.php',
'OCA\\AdminAudit\\Actions\\Trashbin' => __DIR__ . '/..' . '/../lib/Actions/Trashbin.php',
'OCA\\AdminAudit\\Actions\\Versions' => __DIR__ . '/..' . '/../lib/Actions/Versions.php',
'OCA\\AdminAudit\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
@ -41,6 +40,7 @@ class ComposerStaticInitAdminAudit
'OCA\\AdminAudit\\Listener\\GroupManagementEventListener' => __DIR__ . '/..' . '/../lib/Listener/GroupManagementEventListener.php',
'OCA\\AdminAudit\\Listener\\SecurityEventListener' => __DIR__ . '/..' . '/../lib/Listener/SecurityEventListener.php',
'OCA\\AdminAudit\\Listener\\SharingEventListener' => __DIR__ . '/..' . '/../lib/Listener/SharingEventListener.php',
'OCA\\AdminAudit\\Listener\\TagEventListener' => __DIR__ . '/..' . '/../lib/Listener/TagEventListener.php',
'OCA\\AdminAudit\\Listener\\UserManagementEventListener' => __DIR__ . '/..' . '/../lib/Listener/UserManagementEventListener.php',
);

View file

@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\AdminAudit\Actions;
use OCP\SystemTag\ISystemTag;
class TagManagement extends Action {
/**
* @param ISystemTag $tag newly created tag
*/
public function createTag(ISystemTag $tag): void {
$this->log('System tag "%s" (%s, %s) created',
[
'name' => $tag->getName(),
'visibility' => $tag->isUserVisible() ? 'visible' : 'invisible',
'assignable' => $tag->isUserAssignable() ? 'user assignable' : 'system only',
],
['name', 'visibility', 'assignable']
);
}
}

View file

@ -13,7 +13,6 @@ use OCA\AdminAudit\Actions\Auth;
use OCA\AdminAudit\Actions\Console;
use OCA\AdminAudit\Actions\Files;
use OCA\AdminAudit\Actions\Sharing;
use OCA\AdminAudit\Actions\TagManagement;
use OCA\AdminAudit\Actions\Trashbin;
use OCA\AdminAudit\Actions\Versions;
use OCA\AdminAudit\AuditLogger;
@ -27,6 +26,7 @@ use OCA\AdminAudit\Listener\FileEventListener;
use OCA\AdminAudit\Listener\GroupManagementEventListener;
use OCA\AdminAudit\Listener\SecurityEventListener;
use OCA\AdminAudit\Listener\SharingEventListener;
use OCA\AdminAudit\Listener\TagEventListener;
use OCA\AdminAudit\Listener\UserManagementEventListener;
use OCA\Files_Versions\Events\VersionRestoredEvent;
use OCP\App\Events\AppDisableEvent;
@ -60,7 +60,7 @@ use OCP\Preview\BeforePreviewFetchedEvent;
use OCP\Share;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\Events\ShareDeletedEvent;
use OCP\SystemTag\ManagerEvent;
use OCP\SystemTag\Events\TagCreatedEvent;
use OCP\User\Events\BeforeUserLoggedInEvent;
use OCP\User\Events\BeforeUserLoggedOutEvent;
use OCP\User\Events\PasswordUpdatedEvent;
@ -130,6 +130,9 @@ class Application extends App implements IBootstrap {
// Cache events
$context->registerEventListener(CacheEntryInsertedEvent::class, CacheEventListener::class);
$context->registerEventListener(CacheEntryRemovedEvent::class, CacheEventListener::class);
// System tag event
$context->registerEventListener(TagCreatedEvent::class, TagEventListener::class);
}
public function boot(IBootContext $context): void {
@ -153,7 +156,6 @@ class Application extends App implements IBootstrap {
$this->fileHooks($logger, $eventDispatcher);
$this->trashbinHooks($logger);
$this->versionsHooks($logger);
$this->tagHooks($logger, $eventDispatcher);
}
private function sharingLegacyHooks(IAuditLogger $logger): void {
@ -165,14 +167,6 @@ class Application extends App implements IBootstrap {
Util::connectHook(Share::class, 'share_link_access', $shareActions, 'shareAccessed');
}
private function tagHooks(IAuditLogger $logger,
IEventDispatcher $eventDispatcher): void {
$eventDispatcher->addListener(ManagerEvent::EVENT_CREATE, function (ManagerEvent $event) use ($logger): void {
$tagActions = new TagManagement($logger);
$tagActions->createTag($event->getTag());
});
}
private function fileHooks(IAuditLogger $logger, IEventDispatcher $eventDispatcher): void {
$fileActions = new Files($logger);

View file

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH
* SPDX-FileContributor: Carl Schwan
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\AdminAudit\Listener;
use OCA\AdminAudit\Actions\Action;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\SystemTag\Events\TagCreatedEvent;
/**
* @template-implements IEventListener<TagCreatedEvent>
*/
class TagEventListener extends Action implements IEventListener {
public function handle(Event $event): void {
if (!$event instanceof TagCreatedEvent) {
return;
}
$tag = $event->getTag();
$this->log('System tag "%s" (%s, %s) created',
[
'name' => $tag->getName(),
'visibility' => $tag->isUserVisible() ? 'visible' : 'invisible',
'assignable' => $tag->isUserAssignable() ? 'user assignable' : 'system only',
],
['name', 'visibility', 'assignable']
);
}
}

View file

@ -7,9 +7,9 @@ $baseDir = $vendorDir;
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCA\\SystemTags\\Activity\\Listener' => $baseDir . '/../lib/Activity/Listener.php',
'OCA\\SystemTags\\Activity\\Provider' => $baseDir . '/../lib/Activity/Provider.php',
'OCA\\SystemTags\\Activity\\Setting' => $baseDir . '/../lib/Activity/Setting.php',
'OCA\\SystemTags\\Activity\\TagListener' => $baseDir . '/../lib/Activity/TagListener.php',
'OCA\\SystemTags\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
'OCA\\SystemTags\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
'OCA\\SystemTags\\Command\\Files\\Add' => $baseDir . '/../lib/Command/Files/Add.php',

View file

@ -22,9 +22,9 @@ class ComposerStaticInitSystemTags
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCA\\SystemTags\\Activity\\Listener' => __DIR__ . '/..' . '/../lib/Activity/Listener.php',
'OCA\\SystemTags\\Activity\\Provider' => __DIR__ . '/..' . '/../lib/Activity/Provider.php',
'OCA\\SystemTags\\Activity\\Setting' => __DIR__ . '/..' . '/../lib/Activity/Setting.php',
'OCA\\SystemTags\\Activity\\TagListener' => __DIR__ . '/..' . '/../lib/Activity/TagListener.php',
'OCA\\SystemTags\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
'OCA\\SystemTags\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
'OCA\\SystemTags\\Command\\Files\\Add' => __DIR__ . '/..' . '/../lib/Command/Files/Add.php',

View file

@ -1,222 +0,0 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\SystemTags\Activity;
use OCP\Activity\IManager;
use OCP\App\IAppManager;
use OCP\Files\Config\IMountProviderCollection;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Share\IShareHelper;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ManagerEvent;
use OCP\SystemTag\MapperEvent;
use OCP\SystemTag\TagNotFoundException;
class Listener {
/**
* Listener constructor.
*
* @param IGroupManager $groupManager
* @param IManager $activityManager
* @param IUserSession $session
* @param IConfig $config
* @param ISystemTagManager $tagManager
* @param IAppManager $appManager
* @param IMountProviderCollection $mountCollection
* @param IRootFolder $rootFolder
* @param IShareHelper $shareHelper
*/
public function __construct(
protected IGroupManager $groupManager,
protected IManager $activityManager,
protected IUserSession $session,
protected IConfig $config,
protected ISystemTagManager $tagManager,
protected IAppManager $appManager,
protected IMountProviderCollection $mountCollection,
protected IRootFolder $rootFolder,
protected IShareHelper $shareHelper,
) {
}
/**
* @param ManagerEvent $event
*/
public function event(ManagerEvent $event) {
$actor = $this->session->getUser();
if ($actor instanceof IUser) {
$actor = $actor->getUID();
} else {
$actor = '';
}
$tag = $event->getTag();
$activity = $this->activityManager->generateEvent();
$activity->setApp('systemtags')
->setType('systemtags')
->setAuthor($actor)
->setObject('systemtag', (int)$tag->getId(), $tag->getName());
if ($event->getEvent() === ManagerEvent::EVENT_CREATE) {
$activity->setSubject(Provider::CREATE_TAG, [
$actor,
$this->prepareTagAsParameter($event->getTag()),
]);
} elseif ($event->getEvent() === ManagerEvent::EVENT_UPDATE) {
$activity->setSubject(Provider::UPDATE_TAG, [
$actor,
$this->prepareTagAsParameter($event->getTag()),
$this->prepareTagAsParameter($event->getTagBefore()),
]);
} elseif ($event->getEvent() === ManagerEvent::EVENT_DELETE) {
$activity->setSubject(Provider::DELETE_TAG, [
$actor,
$this->prepareTagAsParameter($event->getTag()),
]);
} else {
return;
}
$group = $this->groupManager->get('admin');
if ($group instanceof IGroup) {
foreach ($group->getUsers() as $user) {
$activity->setAffectedUser($user->getUID());
$this->activityManager->publish($activity);
}
}
if ($actor !== '' && ($event->getEvent() === ManagerEvent::EVENT_CREATE || $event->getEvent() === ManagerEvent::EVENT_UPDATE)) {
$this->updateLastUsedTags($actor, $event->getTag());
}
}
/**
* @param MapperEvent $event
*/
public function mapperEvent(MapperEvent $event) {
$tagIds = $event->getTags();
if ($event->getObjectType() !== 'files' || empty($tagIds)
|| !in_array($event->getEvent(), [MapperEvent::EVENT_ASSIGN, MapperEvent::EVENT_UNASSIGN])
|| !$this->appManager->isEnabledForAnyone('activity')) {
// System tags not for files, no tags, not (un-)assigning or no activity-app enabled (save the energy)
return;
}
try {
$tags = $this->tagManager->getTagsByIds($tagIds);
} catch (TagNotFoundException $e) {
// User assigned/unassigned a non-existing tag, ignore...
return;
}
if (empty($tags)) {
return;
}
// Get all mount point owners
$cache = $this->mountCollection->getMountCache();
$mounts = $cache->getMountsForFileId($event->getObjectId());
if (empty($mounts)) {
return;
}
$users = [];
foreach ($mounts as $mount) {
$owner = $mount->getUser()->getUID();
$ownerFolder = $this->rootFolder->getUserFolder($owner);
$nodes = $ownerFolder->getById($event->getObjectId());
if (!empty($nodes)) {
/** @var Node $node */
$node = array_shift($nodes);
$al = $this->shareHelper->getPathsForAccessList($node);
$users += $al['users'];
}
}
$actor = $this->session->getUser();
if ($actor instanceof IUser) {
$actor = $actor->getUID();
} else {
$actor = '';
}
$activity = $this->activityManager->generateEvent();
$activity->setApp('systemtags')
->setType('systemtags')
->setAuthor($actor)
->setObject($event->getObjectType(), (int)$event->getObjectId());
foreach ($users as $user => $path) {
$user = (string)$user; // numerical ids could be ints which are not accepted everywhere
$activity->setAffectedUser($user);
foreach ($tags as $tag) {
// don't publish activity for non-admins if tag is invisible
if (!$tag->isUserVisible() && !$this->groupManager->isAdmin($user)) {
continue;
}
if ($event->getEvent() === MapperEvent::EVENT_ASSIGN) {
$activity->setSubject(Provider::ASSIGN_TAG, [
$actor,
$path,
$this->prepareTagAsParameter($tag),
]);
} elseif ($event->getEvent() === MapperEvent::EVENT_UNASSIGN) {
$activity->setSubject(Provider::UNASSIGN_TAG, [
$actor,
$path,
$this->prepareTagAsParameter($tag),
]);
}
$this->activityManager->publish($activity);
}
}
if ($actor !== '' && $event->getEvent() === MapperEvent::EVENT_ASSIGN) {
foreach ($tags as $tag) {
$this->updateLastUsedTags($actor, $tag);
}
}
}
/**
* @param string $actor
* @param ISystemTag $tag
*/
protected function updateLastUsedTags($actor, ISystemTag $tag) {
$lastUsedTags = $this->config->getUserValue($actor, 'systemtags', 'last_used', '[]');
$lastUsedTags = json_decode($lastUsedTags, true);
array_unshift($lastUsedTags, $tag->getId());
$lastUsedTags = array_unique($lastUsedTags);
$lastUsedTags = array_slice($lastUsedTags, 0, 10);
$this->config->setUserValue($actor, 'systemtags', 'last_used', json_encode($lastUsedTags));
}
/**
* @param ISystemTag $tag
* @return string
*/
protected function prepareTagAsParameter(ISystemTag $tag) {
return json_encode([
'id' => $tag->getId(),
'name' => $tag->getName(),
'assignable' => $tag->isUserAssignable(),
'visible' => $tag->isUserVisible(),
]);
}
}

View file

@ -0,0 +1,212 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\SystemTags\Activity;
use OCP\Activity\IManager;
use OCP\App\IAppManager;
use OCP\Config\IUserConfig;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Config\IMountProviderCollection;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Share\IShareHelper;
use OCP\SystemTag\Events\AbstractTagEvent;
use OCP\SystemTag\Events\TagCreatedEvent;
use OCP\SystemTag\Events\TagDeletedEvent;
use OCP\SystemTag\Events\TagUpdatedEvent;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\TagAssignedEvent;
use OCP\SystemTag\TagNotFoundException;
use OCP\SystemTag\TagUnassignedEvent;
/**
* @template-implements IEventListener<TagAssignedEvent|TagUnassignedEvent|TagUpdatedEvent|TagCreatedEvent|TagDeletedEvent>
*/
class TagListener implements IEventListener {
public function __construct(
private readonly IGroupManager $groupManager,
private readonly IManager $activityManager,
private readonly IUserSession $session,
private readonly IUserConfig $userConfig,
private readonly ISystemTagManager $tagManager,
private readonly IAppManager $appManager,
private readonly IMountProviderCollection $mountCollection,
private readonly IRootFolder $rootFolder,
private readonly IShareHelper $shareHelper,
) {
}
public function handle(Event $event): void {
if ($event instanceof AbstractTagEvent) {
$this->handleManagerEvent($event);
} elseif ($event instanceof TagAssignedEvent || $event instanceof TagUnassignedEvent) {
$this->handleTagEvent($event);
}
}
public function handleManagerEvent(AbstractTagEvent $event): void {
$actor = $this->session->getUser();
if ($actor instanceof IUser) {
$actor = $actor->getUID();
} else {
$actor = '';
}
$tag = $event->getTag();
$activity = $this->activityManager->generateEvent();
$activity->setApp('systemtags')
->setType('systemtags')
->setAuthor($actor)
->setObject('systemtag', (int)$tag->getId(), $tag->getName());
if ($event instanceof TagCreatedEvent) {
$activity->setSubject(Provider::CREATE_TAG, [
$actor,
$this->prepareTagAsParameter($event->getTag()),
]);
} elseif ($event instanceof TagUpdatedEvent) {
$activity->setSubject(Provider::UPDATE_TAG, [
$actor,
$this->prepareTagAsParameter($event->getTag()),
$this->prepareTagAsParameter($event->getTagBefore()),
]);
} elseif ($event instanceof TagDeletedEvent) {
$activity->setSubject(Provider::DELETE_TAG, [
$actor,
$this->prepareTagAsParameter($event->getTag()),
]);
} else {
return;
}
$group = $this->groupManager->get('admin');
if ($group instanceof IGroup) {
foreach ($group->getUsers() as $user) {
$activity->setAffectedUser($user->getUID());
$this->activityManager->publish($activity);
}
}
if ($actor !== '' && ($event instanceof TagCreatedEvent || $event instanceof TagUpdatedEvent)) {
$this->updateLastUsedTags($actor, $event->getTag());
}
}
private function handleTagEvent(TagAssignedEvent|TagUnassignedEvent $event) {
$tagIds = $event->getTags();
if ($event->getObjectType() !== 'files' || empty($tagIds)
|| !$this->appManager->isEnabledForAnyone('activity')) {
// System tags not for files, no tags, not (un-)assigning or no activity-app enabled (save the energy)
return;
}
try {
$tags = $this->tagManager->getTagsByIds($tagIds);
} catch (TagNotFoundException $e) {
// User assigned/unassigned a non-existing tag, ignore...
return;
}
if (empty($tags)) {
return;
}
// Get all mount point owners
$cache = $this->mountCollection->getMountCache();
foreach ($event->getObjectIds() as $objectId) {
$mounts = $cache->getMountsForFileId((int)$objectId);
if (empty($mounts)) {
continue;
}
$users = [];
foreach ($mounts as $mount) {
$owner = $mount->getUser()->getUID();
$ownerFolder = $this->rootFolder->getUserFolder($owner);
$nodes = $ownerFolder->getById((int)$objectId);
if (!empty($nodes)) {
/** @var Node $node */
$node = array_shift($nodes);
$al = $this->shareHelper->getPathsForAccessList($node);
$users += $al['users'];
}
}
$actor = $this->session->getUser();
if ($actor instanceof IUser) {
$actor = $actor->getUID();
} else {
$actor = '';
}
$activity = $this->activityManager->generateEvent();
$activity->setApp('systemtags')
->setType('systemtags')
->setAuthor($actor)
->setObject($event->getObjectType(), (int)$objectId);
foreach ($users as $user => $path) {
$user = (string)$user; // numerical ids could be ints which are not accepted everywhere
$activity->setAffectedUser($user);
foreach ($tags as $tag) {
// don't publish activity for non-admins if tag is invisible
if (!$tag->isUserVisible() && !$this->groupManager->isAdmin($user)) {
continue;
}
if ($event instanceof TagAssignedEvent) {
$activity->setSubject(Provider::ASSIGN_TAG, [
$actor,
$path,
$this->prepareTagAsParameter($tag),
]);
} else {
$activity->setSubject(Provider::UNASSIGN_TAG, [
$actor,
$path,
$this->prepareTagAsParameter($tag),
]);
}
$this->activityManager->publish($activity);
}
}
if ($actor !== '' && $event instanceof TagAssignedEvent) {
foreach ($tags as $tag) {
$this->updateLastUsedTags($actor, $tag);
}
}
}
}
protected function updateLastUsedTags(string $actor, ISystemTag $tag): void {
$lastUsedTags = $this->userConfig->getValueArray($actor, 'systemtags', 'last_used');
array_unshift($lastUsedTags, $tag->getId());
$lastUsedTags = array_unique($lastUsedTags);
$lastUsedTags = array_slice($lastUsedTags, 0, 10);
$this->userConfig->setValueArray($actor, 'systemtags', 'last_used', $lastUsedTags);
}
protected function prepareTagAsParameter(ISystemTag $tag): string {
return json_encode([
'id' => $tag->getId(),
'name' => $tag->getName(),
'assignable' => $tag->isUserAssignable(),
'visible' => $tag->isUserVisible(),
]);
}
}

View file

@ -10,7 +10,7 @@ namespace OCA\SystemTags\AppInfo;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\SystemTags\Activity\Listener;
use OCA\SystemTags\Activity\TagListener;
use OCA\SystemTags\Capabilities;
use OCA\SystemTags\Listeners\BeforeSabrePubliclyLoadedListener;
use OCA\SystemTags\Listeners\BeforeTemplateRenderedListener;
@ -21,9 +21,11 @@ use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\BeforeSabrePubliclyLoadedEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\SystemTag\ManagerEvent;
use OCP\SystemTag\MapperEvent;
use OCP\SystemTag\Events\TagCreatedEvent;
use OCP\SystemTag\Events\TagDeletedEvent;
use OCP\SystemTag\Events\TagUpdatedEvent;
use OCP\SystemTag\TagAssignedEvent;
use OCP\SystemTag\TagUnassignedEvent;
class Application extends App implements IBootstrap {
public const APP_ID = 'systemtags';
@ -38,26 +40,15 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerEventListener(BeforeSabrePubliclyLoadedEvent::class, BeforeSabrePubliclyLoadedListener::class);
$context->registerEventListener(TagCreatedEvent::class, TagListener::class);
$context->registerEventListener(TagDeletedEvent::class, TagListener::class);
$context->registerEventListener(TagUpdatedEvent::class, TagListener::class);
$context->registerEventListener(TagAssignedEvent::class, TagListener::class);
$context->registerEventListener(TagUnassignedEvent::class, TagListener::class);
}
public function boot(IBootContext $context): void {
$context->injectFn(function (IEventDispatcher $dispatcher) use ($context): void {
$managerListener = function (ManagerEvent $event) use ($context): void {
/** @var Listener $listener */
$listener = $context->getServerContainer()->query(Listener::class);
$listener->event($event);
};
$dispatcher->addListener(ManagerEvent::EVENT_CREATE, $managerListener);
$dispatcher->addListener(ManagerEvent::EVENT_DELETE, $managerListener);
$dispatcher->addListener(ManagerEvent::EVENT_UPDATE, $managerListener);
$mapperListener = function (MapperEvent $event) use ($context): void {
/** @var Listener $listener */
$listener = $context->getServerContainer()->query(Listener::class);
$listener->mapperEvent($event);
};
$dispatcher->addListener(MapperEvent::EVENT_ASSIGN, $mapperListener);
$dispatcher->addListener(MapperEvent::EVENT_UNASSIGN, $mapperListener);
});
}
}

View file

@ -9,33 +9,23 @@ namespace OCA\SystemTags\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IConfig;
use OCP\Config\IUserConfig;
use OCP\IRequest;
use OCP\IUserSession;
class LastUsedController extends Controller {
/**
* @param string $appName
* @param IRequest $request
* @param IConfig $config
* @param IUserSession $userSession
*/
public function __construct(
$appName,
string $appName,
IRequest $request,
protected IConfig $config,
protected IUserSession $userSession,
protected readonly IUserConfig $config,
protected readonly IUserSession $userSession,
) {
parent::__construct($appName, $request);
}
#[NoAdminRequired]
public function getLastUsedTagIds() {
$lastUsed = $this->config->getUserValue($this->userSession->getUser()->getUID(), 'systemtags', 'last_used', '[]');
$tagIds = json_decode($lastUsed, true);
return new DataResponse(array_map(function ($id) {
return (string)$id;
}, $tagIds));
public function getLastUsedTagIds(): DataResponse {
$lastUsed = $this->config->getValueArray($this->userSession->getUser()->getUID(), 'systemtags', 'last_used');
return new DataResponse(array_map(static fn ($id): string => (string)$id, $lastUsed));
}
}

View file

@ -7,9 +7,6 @@
<code><![CDATA[Share::class]]></code>
<code><![CDATA[Share::class]]></code>
</DeprecatedClass>
<DeprecatedConstant>
<code><![CDATA[ManagerEvent::EVENT_CREATE]]></code>
</DeprecatedConstant>
<DeprecatedMethod>
<code><![CDATA[Util::connectHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', $trashActions, 'restore')]]></code>
<code><![CDATA[Util::connectHook('\OCP\Trashbin', 'preDelete', $trashActions, 'delete')]]></code>
@ -2248,46 +2245,6 @@
<code><![CDATA[getAppValue]]></code>
</DeprecatedMethod>
</file>
<file src="apps/systemtags/lib/Activity/Listener.php">
<DeprecatedConstant>
<code><![CDATA[ManagerEvent::EVENT_CREATE]]></code>
<code><![CDATA[ManagerEvent::EVENT_CREATE]]></code>
<code><![CDATA[ManagerEvent::EVENT_DELETE]]></code>
<code><![CDATA[ManagerEvent::EVENT_UPDATE]]></code>
<code><![CDATA[ManagerEvent::EVENT_UPDATE]]></code>
<code><![CDATA[MapperEvent::EVENT_ASSIGN]]></code>
<code><![CDATA[MapperEvent::EVENT_ASSIGN]]></code>
<code><![CDATA[MapperEvent::EVENT_ASSIGN]]></code>
<code><![CDATA[MapperEvent::EVENT_UNASSIGN]]></code>
<code><![CDATA[MapperEvent::EVENT_UNASSIGN]]></code>
</DeprecatedConstant>
<DeprecatedMethod>
<code><![CDATA[getUserValue]]></code>
<code><![CDATA[setUserValue]]></code>
</DeprecatedMethod>
<InvalidArgument>
<code><![CDATA[$event->getObjectId()]]></code>
<code><![CDATA[$event->getObjectId()]]></code>
</InvalidArgument>
</file>
<file src="apps/systemtags/lib/AppInfo/Application.php">
<DeprecatedConstant>
<code><![CDATA[ManagerEvent::EVENT_CREATE]]></code>
<code><![CDATA[ManagerEvent::EVENT_DELETE]]></code>
<code><![CDATA[ManagerEvent::EVENT_UPDATE]]></code>
<code><![CDATA[MapperEvent::EVENT_ASSIGN]]></code>
<code><![CDATA[MapperEvent::EVENT_UNASSIGN]]></code>
</DeprecatedConstant>
<DeprecatedMethod>
<code><![CDATA[query]]></code>
<code><![CDATA[query]]></code>
</DeprecatedMethod>
</file>
<file src="apps/systemtags/lib/Controller/LastUsedController.php">
<DeprecatedMethod>
<code><![CDATA[getUserValue]]></code>
</DeprecatedMethod>
</file>
<file src="apps/testing/lib/AlternativeHomeUserBackend.php">
<DeprecatedInterface>
<code><![CDATA[AlternativeHomeUserBackend]]></code>

View file

@ -891,6 +891,10 @@ return array(
'OCP\\Support\\Subscription\\IRegistry' => $baseDir . '/lib/public/Support/Subscription/IRegistry.php',
'OCP\\Support\\Subscription\\ISubscription' => $baseDir . '/lib/public/Support/Subscription/ISubscription.php',
'OCP\\Support\\Subscription\\ISupportedApps' => $baseDir . '/lib/public/Support/Subscription/ISupportedApps.php',
'OCP\\SystemTag\\Events\\AbstractTagEvent' => $baseDir . '/lib/public/SystemTag/Events/AbstractTagEvent.php',
'OCP\\SystemTag\\Events\\TagCreatedEvent' => $baseDir . '/lib/public/SystemTag/Events/TagCreatedEvent.php',
'OCP\\SystemTag\\Events\\TagDeletedEvent' => $baseDir . '/lib/public/SystemTag/Events/TagDeletedEvent.php',
'OCP\\SystemTag\\Events\\TagUpdatedEvent' => $baseDir . '/lib/public/SystemTag/Events/TagUpdatedEvent.php',
'OCP\\SystemTag\\ISystemTag' => $baseDir . '/lib/public/SystemTag/ISystemTag.php',
'OCP\\SystemTag\\ISystemTagManager' => $baseDir . '/lib/public/SystemTag/ISystemTagManager.php',
'OCP\\SystemTag\\ISystemTagManagerFactory' => $baseDir . '/lib/public/SystemTag/ISystemTagManagerFactory.php',

View file

@ -932,6 +932,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Support\\Subscription\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Support/Subscription/IRegistry.php',
'OCP\\Support\\Subscription\\ISubscription' => __DIR__ . '/../../..' . '/lib/public/Support/Subscription/ISubscription.php',
'OCP\\Support\\Subscription\\ISupportedApps' => __DIR__ . '/../../..' . '/lib/public/Support/Subscription/ISupportedApps.php',
'OCP\\SystemTag\\Events\\AbstractTagEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/Events/AbstractTagEvent.php',
'OCP\\SystemTag\\Events\\TagCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/Events/TagCreatedEvent.php',
'OCP\\SystemTag\\Events\\TagDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/Events/TagDeletedEvent.php',
'OCP\\SystemTag\\Events\\TagUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/SystemTag/Events/TagUpdatedEvent.php',
'OCP\\SystemTag\\ISystemTag' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ISystemTag.php',
'OCP\\SystemTag\\ISystemTagManager' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ISystemTagManager.php',
'OCP\\SystemTag\\ISystemTagManagerFactory' => __DIR__ . '/../../..' . '/lib/public/SystemTag/ISystemTagManagerFactory.php',

View file

@ -16,6 +16,9 @@ use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
use OCP\SystemTag\Events\TagCreatedEvent;
use OCP\SystemTag\Events\TagDeletedEvent;
use OCP\SystemTag\Events\TagUpdatedEvent;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ManagerEvent;
@ -209,6 +212,7 @@ class SystemTagManager implements ISystemTagManager {
$this->dispatcher->dispatch(ManagerEvent::EVENT_CREATE, new ManagerEvent(
ManagerEvent::EVENT_CREATE, $tag
));
$this->dispatcher->dispatchTyped(new TagCreatedEvent($tag));
return $tag;
}
@ -289,6 +293,7 @@ class SystemTagManager implements ISystemTagManager {
$this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent(
ManagerEvent::EVENT_UPDATE, $afterUpdate, $beforeUpdate
));
$this->dispatcher->dispatchTyped(new TagUpdatedEvent($afterUpdate, $beforeUpdate));
}
public function deleteTags($tagIds): void {
@ -331,6 +336,7 @@ class SystemTagManager implements ISystemTagManager {
$this->dispatcher->dispatch(ManagerEvent::EVENT_DELETE, new ManagerEvent(
ManagerEvent::EVENT_DELETE, $tag
));
$this->dispatcher->dispatchTyped(new TagDeletedEvent($tag));
}
if ($tagNotFoundException !== null) {

View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\SystemTag\Events;
use OCP\AppFramework\Attribute\Consumable;
use OCP\EventDispatcher\Event;
use OCP\SystemTag\ISystemTag;
/**
* Abstract event related to the lifecycle of a tag.
*
* @since 34.0.0
*/
#[Consumable(since: '34.0.0')]
abstract class AbstractTagEvent extends Event {
public function __construct(
private readonly ISystemTag $tag,
) {
}
/**
* @since 34.0.0
*/
public function getTag(): ISystemTag {
return $this->tag;
}
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\SystemTag\Events;
use OCP\AppFramework\Attribute\Consumable;
/**
* Event triggered when creating a new tag.
*
* @since 34.0.0
*/
#[Consumable(since: '34.0.0')]
class TagCreatedEvent extends AbstractTagEvent {
}

View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\SystemTag\Events;
use OCP\AppFramework\Attribute\Consumable;
/**
* Event triggered when deleting a new tag.
*
* @since 34.0.0
*/
#[Consumable(since: '34.0.0')]
class TagDeletedEvent extends AbstractTagEvent {
}

View file

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
* SPDX-FileContributor: Carl Schwan
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\SystemTag\Events;
use OCP\AppFramework\Attribute\Consumable;
use OCP\SystemTag\ISystemTag;
/**
* Event triggered when updated a tag.
*
* @since 34.0.0
*/
#[Consumable(since: '34.0.0')]
class TagUpdatedEvent extends AbstractTagEvent {
public function __construct(
ISystemTag $tag,
private readonly ISystemTag $beforeTag,
) {
parent::__construct($tag);
}
/**
* Return the tag state before it was updated.
*
* @since 34.0.0
*/
public function getTagBefore(): ISystemTag {
return $this->beforeTag;
}
}

View file

@ -14,6 +14,7 @@ use OCP\EventDispatcher\Event;
* Class ManagerEvent
*
* @since 9.0.0
* @deprecated 34.0.0
*/
class ManagerEvent extends Event {
/**

View file

@ -32,6 +32,7 @@
<file name="lib/public/DB/QueryBuilder/ITypedQueryBuilder.php"/>
<file name="lib/private/Share20/ShareHelper.php"/>
<file name="lib/public/Share/IShareHelper.php"/>
<file name="lib/public/SystemTag/Events/*.php"/>
<ignoreFiles>
<directory name="apps/**/composer"/>
<directory name="apps/**/tests"/>