From 591613fce222d1902ac9d981ca6ae99e5aea23cc Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 1 Feb 2016 11:48:46 +0100 Subject: [PATCH 1/7] Dispatch some events when tags are un-/assigned --- lib/private/systemtag/managerfactory.php | 10 +- .../systemtag/systemtagobjectmapper.php | 35 +++++-- lib/public/systemtag/mapperevent.php | 93 +++++++++++++++++++ tests/lib/systemtag/systemtagmanagertest.php | 2 +- .../systemtag/systemtagobjectmappertest.php | 27 ++++-- 5 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 lib/public/systemtag/mapperevent.php diff --git a/lib/private/systemtag/managerfactory.php b/lib/private/systemtag/managerfactory.php index 7b7b9558b0..2939caacde 100644 --- a/lib/private/systemtag/managerfactory.php +++ b/lib/private/systemtag/managerfactory.php @@ -20,11 +20,10 @@ */ namespace OC\SystemTag; -use OCP\SystemTag\ISystemTagManagerFactory; -use OCP\SystemTag\ISystemTagManager; -use OC\SystemTag\SystemTagManager; -use OC\SystemTag\SystemTagObjectMapper; use OCP\IServerContainer; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagManagerFactory; +use OCP\SystemTag\ISystemTagObjectMapper; /** * Default factory class for system tag managers @@ -72,7 +71,8 @@ class ManagerFactory implements ISystemTagManagerFactory { public function getObjectMapper() { return new SystemTagObjectMapper( $this->serverContainer->getDatabaseConnection(), - $this->getManager() + $this->getManager(), + $this->serverContainer->getEventDispatcher() ); } } diff --git a/lib/private/systemtag/systemtagobjectmapper.php b/lib/private/systemtag/systemtagobjectmapper.php index 458b558ad9..1efb4f0f6e 100644 --- a/lib/private/systemtag/systemtagobjectmapper.php +++ b/lib/private/systemtag/systemtagobjectmapper.php @@ -28,31 +28,34 @@ use OCP\IDBConnection; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\SystemTag\MapperEvent; use OCP\SystemTag\TagNotFoundException; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class SystemTagObjectMapper implements ISystemTagObjectMapper { const RELATION_TABLE = 'systemtag_object_mapping'; - /** - * @var ISystemTagManager - */ - private $tagManager; + /** @var ISystemTagManager */ + protected $tagManager; - /** - * @var IDBConnection - */ - private $connection; + /** @var IDBConnection */ + protected $connection; + + /** @var EventDispatcherInterface */ + protected $dispatcher; /** * Constructor. * * @param IDBConnection $connection database connection * @param ISystemTagManager $tagManager system tag manager + * @param EventDispatcherInterface $dispatcher */ - public function __construct(IDBConnection $connection, ISystemTagManager $tagManager) { + public function __construct(IDBConnection $connection, ISystemTagManager $tagManager, EventDispatcherInterface $dispatcher) { $this->connection = $connection; $this->tagManager = $tagManager; + $this->dispatcher = $dispatcher; } /** @@ -143,6 +146,13 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper { // ignore existing relations } } + + $this->dispatcher->dispatch(MapperEvent::EVENT_ASSIGN, new MapperEvent( + MapperEvent::EVENT_ASSIGN, + $objectType, + $objId, + $tagIds + )); } /** @@ -164,6 +174,13 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper { ->setParameter('objecttype', $objectType) ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY) ->execute(); + + $this->dispatcher->dispatch(MapperEvent::EVENT_UNASSIGN, new MapperEvent( + MapperEvent::EVENT_UNASSIGN, + $objectType, + $objId, + $tagIds + )); } /** diff --git a/lib/public/systemtag/mapperevent.php b/lib/public/systemtag/mapperevent.php new file mode 100644 index 0000000000..e05a5ce09c --- /dev/null +++ b/lib/public/systemtag/mapperevent.php @@ -0,0 +1,93 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCP\SystemTag; + +use Symfony\Component\EventDispatcher\Event; + +/** + * Class MapperEvent + * + * @package OCP\SystemTag + * @since 9.0.0 + */ +class MapperEvent extends Event { + + const EVENT_ASSIGN = 'OCP\SystemTag\ISystemTagObjectMapper::assignTags'; + const EVENT_UNASSIGN = 'OCP\SystemTag\ISystemTagObjectMapper::unassignTags'; + + /** @var string */ + protected $event; + /** @var string */ + protected $objectType; + /** @var string */ + protected $objectId; + /** @var int[] */ + protected $tags; + + /** + * DispatcherEvent constructor. + * + * @param string $event + * @param string $objectType + * @param string $objectId + * @param int[] $tags + * @since 9.0.0 + */ + public function __construct($event, $objectType, $objectId, array $tags) { + $this->event = $event; + $this->objectType = $objectType; + $this->objectId = $objectId; + $this->tags = $tags; + } + + /** + * @return string + * @since 9.0.0 + */ + public function getEvent() { + return $this->event; + } + + /** + * @return string + * @since 9.0.0 + */ + public function getObjectType() { + return $this->objectType; + } + + /** + * @return string + * @since 9.0.0 + */ + public function getObjectId() { + return $this->objectId; + } + + /** + * @return int[] + * @since 9.0.0 + */ + public function getTags() { + return $this->tags; + } +} diff --git a/tests/lib/systemtag/systemtagmanagertest.php b/tests/lib/systemtag/systemtagmanagertest.php index 97c072f33f..f8d7117747 100644 --- a/tests/lib/systemtag/systemtagmanagertest.php +++ b/tests/lib/systemtag/systemtagmanagertest.php @@ -378,7 +378,7 @@ class SystemTagManagerTest extends TestCase { $tag1 = $this->tagManager->createTag('one', true, false); $tag2 = $this->tagManager->createTag('two', true, true); - $tagMapper = new SystemTagObjectMapper($this->connection, $this->tagManager); + $tagMapper = new SystemTagObjectMapper($this->connection, $this->tagManager, $this->dispatcher); $tagMapper->assignTags(1, 'testtype', $tag1->getId()); $tagMapper->assignTags(1, 'testtype', $tag2->getId()); diff --git a/tests/lib/systemtag/systemtagobjectmappertest.php b/tests/lib/systemtag/systemtagobjectmappertest.php index 4ea80c216e..5c8204f6a8 100644 --- a/tests/lib/systemtag/systemtagobjectmappertest.php +++ b/tests/lib/systemtag/systemtagobjectmappertest.php @@ -10,14 +10,15 @@ namespace Test\SystemTag; +use OC\SystemTag\SystemTag; use OC\SystemTag\SystemTagManager; use OC\SystemTag\SystemTagObjectMapper; -use \OCP\SystemTag\ISystemTag; -use \OCP\SystemTag\ISystemTagManager; -use \OCP\SystemTag\ISystemTagObjectMapper; -use \OCP\SystemTag\TagNotFoundException; -use \OCP\IDBConnection; -use \OC\SystemTag\SystemTag; +use OCP\IDBConnection; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; +use OCP\SystemTag\TagNotFoundException; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; /** @@ -43,6 +44,11 @@ class SystemTagObjectMapperTest extends TestCase { */ private $connection; + /** + * @var EventDispatcherInterface + */ + private $dispatcher; + /** * @var ISystemTag */ @@ -67,7 +73,14 @@ class SystemTagObjectMapperTest extends TestCase { $this->tagManager = $this->getMockBuilder('OCP\SystemTag\ISystemTagManager') ->getMock(); - $this->tagMapper = new SystemTagObjectMapper($this->connection, $this->tagManager); + $this->dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface') + ->getMock(); + + $this->tagMapper = new SystemTagObjectMapper( + $this->connection, + $this->tagManager, + $this->dispatcher + ); $this->tag1 = new SystemTag(1, 'testtag1', false, false); $this->tag2 = new SystemTag(2, 'testtag2', true, false); From d5126b1ad43377ebbb0f4fc2d7c43267fd5e6672 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 1 Feb 2016 12:19:48 +0100 Subject: [PATCH 2/7] Dispatch events when tags are added/updated/deleted --- lib/private/systemtag/managerfactory.php | 3 +- lib/private/systemtag/systemtagmanager.php | 72 ++++++++++++++--- lib/public/systemtag/managerevent.php | 85 ++++++++++++++++++++ tests/lib/systemtag/systemtagmanagertest.php | 15 +++- 4 files changed, 161 insertions(+), 14 deletions(-) create mode 100644 lib/public/systemtag/managerevent.php diff --git a/lib/private/systemtag/managerfactory.php b/lib/private/systemtag/managerfactory.php index 2939caacde..7ad4f92260 100644 --- a/lib/private/systemtag/managerfactory.php +++ b/lib/private/systemtag/managerfactory.php @@ -57,7 +57,8 @@ class ManagerFactory implements ISystemTagManagerFactory { */ public function getManager() { return new SystemTagManager( - $this->serverContainer->getDatabaseConnection() + $this->serverContainer->getDatabaseConnection(), + $this->serverContainer->getEventDispatcher() ); } diff --git a/lib/private/systemtag/systemtagmanager.php b/lib/private/systemtag/systemtagmanager.php index af912a7493..76a60a9132 100644 --- a/lib/private/systemtag/systemtagmanager.php +++ b/lib/private/systemtag/systemtagmanager.php @@ -26,17 +26,20 @@ use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ManagerEvent; use OCP\SystemTag\TagAlreadyExistsException; use OCP\SystemTag\TagNotFoundException; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class SystemTagManager implements ISystemTagManager { const TAG_TABLE = 'systemtag'; - /** - * @var IDBConnection - */ - private $connection; + /** @var IDBConnection */ + protected $connection; + + /** @var EventDispatcherInterface */ + protected $dispatcher; /** * Prepared query for selecting tags directly @@ -46,12 +49,14 @@ class SystemTagManager implements ISystemTagManager { private $selectTagQuery; /** - * Constructor. - * - * @param IDBConnection $connection database connection - */ - public function __construct(IDBConnection $connection) { + * Constructor. + * + * @param IDBConnection $connection database connection + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(IDBConnection $connection, EventDispatcherInterface $dispatcher) { $this->connection = $connection; + $this->dispatcher = $dispatcher; $query = $this->connection->getQueryBuilder(); $this->selectTagQuery = $query->select('*') @@ -190,14 +195,20 @@ class SystemTagManager implements ISystemTagManager { ); } - $tagId = $this->connection->lastInsertId('*PREFIX*' . self::TAG_TABLE); + $tagId = $query->getLastInsertId(); - return new SystemTag( + $tag = new SystemTag( (int)$tagId, $tagName, (bool)$userVisible, (bool)$userAssignable ); + + $this->dispatcher->dispatch(ManagerEvent::EVENT_CREATE, new ManagerEvent( + ManagerEvent::EVENT_CREATE, $tag + )); + + return $tag; } /** @@ -207,6 +218,22 @@ class SystemTagManager implements ISystemTagManager { $userVisible = (int)$userVisible; $userAssignable = (int)$userAssignable; + try { + $tags = $this->getTagsByIds($tagId); + } catch (TagNotFoundException $e) { + throw new TagNotFoundException( + 'Tag does not exist', 0, null, [$tagId] + ); + } + + $beforeUpdate = array_shift($tags); + $afterUpdate = new SystemTag( + (int) $tagId, + $tagName, + (bool) $userVisible, + (bool) $userAssignable + ); + $query = $this->connection->getQueryBuilder(); $query->update(self::TAG_TABLE) ->set('name', $query->createParameter('name')) @@ -231,6 +258,10 @@ class SystemTagManager implements ISystemTagManager { $e ); } + + $this->dispatcher->dispatch(ManagerEvent::EVENT_UPDATE, new ManagerEvent( + ManagerEvent::EVENT_UPDATE, $afterUpdate, $beforeUpdate + )); } /** @@ -242,10 +273,21 @@ class SystemTagManager implements ISystemTagManager { } $tagNotFoundException = null; + $tags = []; try { - $this->getTagsByIds($tagIds); + $tags = $this->getTagsByIds($tagIds); } catch (TagNotFoundException $e) { $tagNotFoundException = $e; + + // Get existing tag objects for the hooks later + $existingTags = array_diff($tagIds, $tagNotFoundException->getMissingTags()); + if (!empty($existingTags)) { + try { + $tags = $this->getTagsByIds($existingTags); + } catch (TagNotFoundException $e) { + // Ignore further errors... + } + } } // delete relationships first @@ -261,6 +303,12 @@ class SystemTagManager implements ISystemTagManager { ->setParameter('tagids', $tagIds, IQueryBuilder::PARAM_INT_ARRAY) ->execute(); + foreach ($tags as $tag) { + $this->dispatcher->dispatch(ManagerEvent::EVENT_DELETE, new ManagerEvent( + ManagerEvent::EVENT_DELETE, $tag + )); + } + if ($tagNotFoundException !== null) { throw new TagNotFoundException( 'Tag id(s) not found', 0, $tagNotFoundException, $tagNotFoundException->getMissingTags() diff --git a/lib/public/systemtag/managerevent.php b/lib/public/systemtag/managerevent.php new file mode 100644 index 0000000000..a0429fc9f6 --- /dev/null +++ b/lib/public/systemtag/managerevent.php @@ -0,0 +1,85 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCP\SystemTag; + +use Symfony\Component\EventDispatcher\Event; + +/** + * Class ManagerEvent + * + * @package OCP\SystemTag + * @since 9.0.0 + */ +class ManagerEvent extends Event { + + const EVENT_CREATE = 'OCP\SystemTag\ISystemTagManager::createTag'; + const EVENT_UPDATE = 'OCP\SystemTag\ISystemTagManager::updateTag'; + const EVENT_DELETE = 'OCP\SystemTag\ISystemTagManager::deleteTag'; + + /** @var string */ + protected $event; + /** @var ISystemTag */ + protected $tag; + /** @var ISystemTag */ + protected $beforeTag; + + /** + * DispatcherEvent constructor. + * + * @param string $event + * @param ISystemTag $tag + * @param ISystemTag $beforeTag + * @since 9.0.0 + */ + public function __construct($event, ISystemTag $tag, ISystemTag $beforeTag = null) { + $this->event = $event; + $this->tag = $tag; + $this->beforeTag = $beforeTag; + } + + /** + * @return string + * @since 9.0.0 + */ + public function getEvent() { + return $this->event; + } + + /** + * @return ISystemTag + * @since 9.0.0 + */ + public function getTag() { + return $this->tag; + } + + /** + * @return ISystemTag + * @since 9.0.0 + */ + public function getTagBefore() { + if ($this->event !== self::EVENT_UPDATE) { + throw new \BadMethodCallException('getTagBefore is only available on the update Event'); + } + return $this->beforeTag; + } +} diff --git a/tests/lib/systemtag/systemtagmanagertest.php b/tests/lib/systemtag/systemtagmanagertest.php index f8d7117747..64220205ad 100644 --- a/tests/lib/systemtag/systemtagmanagertest.php +++ b/tests/lib/systemtag/systemtagmanagertest.php @@ -15,6 +15,7 @@ use OC\SystemTag\SystemTagObjectMapper; use OCP\IDBConnection; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Test\TestCase; /** @@ -35,11 +36,23 @@ class SystemTagManagerTest extends TestCase { */ private $connection; + /** + * @var EventDispatcherInterface + */ + private $dispatcher; + public function setUp() { parent::setUp(); $this->connection = \OC::$server->getDatabaseConnection(); - $this->tagManager = new SystemTagManager($this->connection); + + $this->dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface') + ->getMock(); + + $this->tagManager = new SystemTagManager( + $this->connection, + $this->dispatcher + ); $this->pruneTagsTables(); } From aa3fdccb8ee272fbb59552fb903883bae2fbd05a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 1 Feb 2016 15:15:34 +0100 Subject: [PATCH 3/7] App needs to be of type logging to be able to have activities --- apps/systemtags/appinfo/info.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/systemtags/appinfo/info.xml b/apps/systemtags/appinfo/info.xml index d2f30e2c04..5da945db70 100644 --- a/apps/systemtags/appinfo/info.xml +++ b/apps/systemtags/appinfo/info.xml @@ -6,11 +6,14 @@ AGPL Vincent Petry - 0.1 + 0.2 user-systemtags + + + From dfe677fabb63cf9d61e060d1bbf5bcb170eeb52a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 1 Feb 2016 15:16:05 +0100 Subject: [PATCH 4/7] Add activities for admins when a tag is created/updated/deleted --- apps/systemtags/activity/extension.php | 273 +++++++++++++++++++++++++ apps/systemtags/activity/listener.php | 104 ++++++++++ apps/systemtags/appinfo/app.php | 25 +++ 3 files changed, 402 insertions(+) create mode 100644 apps/systemtags/activity/extension.php create mode 100644 apps/systemtags/activity/listener.php diff --git a/apps/systemtags/activity/extension.php b/apps/systemtags/activity/extension.php new file mode 100644 index 0000000000..b33b9046d3 --- /dev/null +++ b/apps/systemtags/activity/extension.php @@ -0,0 +1,273 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\SystemTags\Activity; + +use OCP\Activity\IExtension; +use OCP\Activity\IManager; +use OCP\IL10N; +use OCP\L10N\IFactory; + +/** + * Class Extension + * + * @package OCA\SystemTags\Activity + */ +class Extension implements IExtension { + const APP_NAME = 'systemtags'; + + const CREATE_TAG = 'create_tag'; + const UPDATE_TAG = 'update_tag'; + const DELETE_TAG = 'delete_tag'; + + /** @var IFactory */ + protected $languageFactory; + + /** @var IManager */ + protected $activityManager; + + /** + * @param IFactory $languageFactory + * @param IManager $activityManager + */ + public function __construct(IFactory $languageFactory, IManager $activityManager) { + $this->languageFactory = $languageFactory; + $this->activityManager = $activityManager; + } + + protected function getL10N($languageCode = null) { + return $this->languageFactory->get(self::APP_NAME, $languageCode); + } + + /** + * The extension can return an array of additional notification types. + * If no additional types are to be added false is to be returned + * + * @param string $languageCode + * @return array|false + */ + public function getNotificationTypes($languageCode) { + $l = $this->getL10N($languageCode); + + return array( + self::APP_NAME => (string) $l->t('System tags for a file have been modified'), + ); + } + + /** + * For a given method additional types to be displayed in the settings can be returned. + * In case no additional types are to be added false is to be returned. + * + * @param string $method + * @return array|false + */ + public function getDefaultTypes($method) { + return [self::APP_NAME]; + } + + /** + * A string naming the css class for the icon to be used can be returned. + * If no icon is known for the given type false is to be returned. + * + * @param string $type + * @return string|false + */ + public function getTypeIcon($type) { + switch ($type) { + case self::APP_NAME: + return false; + } + + return false; + } + + /** + * The extension can translate a given message to the requested languages. + * If no translation is available false is to be returned. + * + * @param string $app + * @param string $text + * @param array $params + * @param boolean $stripPath + * @param boolean $highlightParams + * @param string $languageCode + * @return string|false + */ + public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) { + if ($app !== self::APP_NAME) { + return false; + } + + $l = $this->getL10N($languageCode); + + /* + if ($this->activityManager->isFormattingFilteredObject()) { + $translation = $this->translateShort($text, $l, $params); + if ($translation !== false) { + return $translation; + } + } + */ + + return $this->translateLong($text, $l, $params); + } + + /** + * @param string $text + * @param IL10N $l + * @param array $params + * @return bool|string + */ + protected function translateLong($text, IL10N $l, array $params) { + + switch ($text) { + case self::CREATE_TAG: + $params[1] = $this->convertParameterToTag($params[1], $l); + return (string) $l->t('%1$s created system tag %2$s', $params); + case self::DELETE_TAG: + $params[1] = $this->convertParameterToTag($params[1], $l); + return (string) $l->t('%1$s deleted system tag %2$s', $params); + case self::UPDATE_TAG: + $params[1] = $this->convertParameterToTag($params[1], $l); + $params[2] = $this->convertParameterToTag($params[2], $l); + return (string) $l->t('%1$s updated system tag %3$s to %2$s', $params); + } + + return false; + } + + /** + * The extension can define the type of parameters for translation + * + * Currently known types are: + * * file => will strip away the path of the file and add a tooltip with it + * * username => will add the avatar of the user + * + * @param string $app + * @param string $text + * @return array|false + */ + public function getSpecialParameterList($app, $text) { + if ($app === self::APP_NAME) { + switch ($text) { + case self::CREATE_TAG: + case self::DELETE_TAG: + return array( + 0 => 'username', + //1 => 'systemtag description', + ); + case self::UPDATE_TAG: + return array( + 0 => 'username', + //1 => 'systemtag description', + //2 => 'systemtag description', + ); + } + } + + return false; + } + + /** + * The extension can define the parameter grouping by returning the index as integer. + * In case no grouping is required false is to be returned. + * + * @param array $activity + * @return integer|false + */ + public function getGroupParameter($activity) { + if ($activity['app'] === self::APP_NAME) { + switch ($activity['subject']) { + case self::CREATE_TAG: + // Group by system tag + // return 1; + } + } + + return false; + } + + /** + * The extension can define additional navigation entries. The array returned has to contain two keys 'top' + * and 'apps' which hold arrays with the relevant entries. + * If no further entries are to be added false is no be returned. + * + * @return array|false + */ + public function getNavigation() { + return false; + } + + /** + * The extension can check if a custom filter (given by a query string like filter=abc) is valid or not. + * + * @param string $filterValue + * @return boolean + */ + public function isFilterValid($filterValue) { + return false; + } + + /** + * The extension can filter the types based on the filter if required. + * In case no filter is to be applied false is to be returned unchanged. + * + * @param array $types + * @param string $filter + * @return array|false + */ + public function filterNotificationTypes($types, $filter) { + return false; + } + + /** + * For a given filter the extension can specify the sql query conditions including parameters for that query. + * In case the extension does not know the filter false is to be returned. + * The query condition and the parameters are to be returned as array with two elements. + * E.g. return array('`app` = ? and `message` like ?', array('mail', 'ownCloud%')); + * + * @param string $filter + * @return array|false + */ + public function getQueryForFilter($filter) { + return false; + } + + /** + * @param string $parameter + * @param IL10N $l + * @return string + */ + protected function convertParameterToTag($parameter, IL10N $l) { + if (preg_match('/^\\{\{\{(.*)\|\|\|(.*)\}\}\}\<\/parameter\>$/', $parameter, $matches)) { + switch ($matches[2]) { + case 'assignable': + return '' . $matches[1] . ''; + case 'not-assignable': + return '' . $l->t('%s (not-assignable)', $matches[1]) . ''; + case 'invisible': + return '' . $l->t('%s (invisible)', $matches[1]) . ''; + } + } + + return $parameter; + } +} diff --git a/apps/systemtags/activity/listener.php b/apps/systemtags/activity/listener.php new file mode 100644 index 0000000000..ce456afaef --- /dev/null +++ b/apps/systemtags/activity/listener.php @@ -0,0 +1,104 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\SystemTags\Activity; + +use OCP\Activity\IManager; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserSession; +use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ManagerEvent; + +class Listener { + protected $groupManager; + protected $activityManager; + protected $session; + + /** + * Listener constructor. + * + * @param IGroupManager $groupManager + * @param IManager $activityManager + * @param IUserSession $session + */ + public function __construct(IGroupManager $groupManager, IManager $activityManager, IUserSession $session) { + $this->groupManager = $groupManager; + $this->activityManager = $activityManager; + $this->session = $session; + } + + public function event(ManagerEvent $event) { + $actor = $this->session->getUser(); + if ($actor instanceof IUser) { + $actor = $actor->getUID(); + } else { + $actor = ''; + } + + $activity = $this->activityManager->generateEvent(); + $activity->setApp(Extension::APP_NAME) + ->setType(Extension::APP_NAME) + ->setAuthor($actor); + if ($event->getEvent() === ManagerEvent::EVENT_CREATE) { + $activity->setSubject(Extension::CREATE_TAG, [ + $actor, + $this->prepareTagAsParameter($event->getTag()), + ]); + } else if ($event->getEvent() === ManagerEvent::EVENT_UPDATE) { + $activity->setSubject(Extension::UPDATE_TAG, [ + $actor, + $this->prepareTagAsParameter($event->getTag()), + $this->prepareTagAsParameter($event->getTagBefore()), + ]); + } else if ($event->getEvent() === ManagerEvent::EVENT_DELETE) { + $activity->setSubject(Extension::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); + } + } + } + + /** + * @param ISystemTag $tag + * @return string + */ + protected function prepareTagAsParameter(ISystemTag $tag) { + if (!$tag->isUserVisible()) { + return '{{{' . $tag->getName() . '|||invisible}}}'; + } else if (!$tag->isUserAssignable()) { + return '{{{' . $tag->getName() . '|||not-assignable}}}'; + } else { + return '{{{' . $tag->getName() . '|||assignable}}}'; + } + } +} diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php index d3886993f8..c6fa21fa9b 100644 --- a/apps/systemtags/appinfo/app.php +++ b/apps/systemtags/appinfo/app.php @@ -19,6 +19,10 @@ * */ +use OCA\SystemTags\Activity\Extension; +use OCA\SystemTags\Activity\Listener; +use OCP\SystemTag\ManagerEvent; + $eventDispatcher = \OC::$server->getEventDispatcher(); $eventDispatcher->addListener( 'OCA\Files::loadAdditionalScripts', @@ -39,3 +43,24 @@ $eventDispatcher->addListener( \OCP\Util::addStyle('systemtags'); } ); + +$activityManager = \OC::$server->getActivityManager(); +$activityManager->registerExtension(function() { + return new Extension( + \OC::$server->getL10NFactory(), + \OC::$server->getActivityManager() + ); +}); + +$managerListener = function(ManagerEvent $event) use ($activityManager) { + $listener = new Listener( + \OC::$server->getGroupManager(), + $activityManager, + \OC::$server->getUserSession() + ); + $listener->event($event); +}; + +$eventDispatcher->addListener(ManagerEvent::EVENT_CREATE, $managerListener); +$eventDispatcher->addListener(ManagerEvent::EVENT_DELETE, $managerListener); +$eventDispatcher->addListener(ManagerEvent::EVENT_UPDATE, $managerListener); From 25cbc17becc4a47d67ad24ce12118a0d6b0aacea Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 1 Feb 2016 16:57:51 +0100 Subject: [PATCH 5/7] Add activities when a tag is (un-)assigned --- apps/systemtags/activity/extension.php | 47 +++++-- apps/systemtags/activity/listener.php | 121 +++++++++++++++++- apps/systemtags/appinfo/app.php | 23 ++-- .../dependencyinjection/dicontainer.php | 4 + 4 files changed, 175 insertions(+), 20 deletions(-) diff --git a/apps/systemtags/activity/extension.php b/apps/systemtags/activity/extension.php index b33b9046d3..aa61b0bb94 100644 --- a/apps/systemtags/activity/extension.php +++ b/apps/systemtags/activity/extension.php @@ -38,6 +38,9 @@ class Extension implements IExtension { const UPDATE_TAG = 'update_tag'; const DELETE_TAG = 'delete_tag'; + const ASSIGN_TAG = 'assign_tag'; + const UNASSIGN_TAG = 'unassign_tag'; + /** @var IFactory */ protected $languageFactory; @@ -118,18 +121,36 @@ class Extension implements IExtension { $l = $this->getL10N($languageCode); - /* if ($this->activityManager->isFormattingFilteredObject()) { $translation = $this->translateShort($text, $l, $params); if ($translation !== false) { return $translation; } } - */ return $this->translateLong($text, $l, $params); } + /** + * @param string $text + * @param IL10N $l + * @param array $params + * @return bool|string + */ + protected function translateShort($text, IL10N $l, array $params) { + + switch ($text) { + case self::ASSIGN_TAG: + $params[2] = $this->convertParameterToTag($params[2], $l); + return (string) $l->t('%1$s assigned system tag %3$s', $params); + case self::UNASSIGN_TAG: + $params[2] = $this->convertParameterToTag($params[2], $l); + return (string) $l->t('%1$s unassigned system tag %3$s', $params); + } + + return false; + } + /** * @param string $text * @param IL10N $l @@ -149,6 +170,12 @@ class Extension implements IExtension { $params[1] = $this->convertParameterToTag($params[1], $l); $params[2] = $this->convertParameterToTag($params[2], $l); return (string) $l->t('%1$s updated system tag %3$s to %2$s', $params); + case self::ASSIGN_TAG: + $params[2] = $this->convertParameterToTag($params[2], $l); + return (string) $l->t('%1$s assigned system tag %3$s to %2$s', $params); + case self::UNASSIGN_TAG: + $params[2] = $this->convertParameterToTag($params[2], $l); + return (string) $l->t('%1$s unassigned system tag %3$s from %2$s', $params); } return false; @@ -180,6 +207,14 @@ class Extension implements IExtension { //1 => 'systemtag description', //2 => 'systemtag description', ); + + case self::ASSIGN_TAG: + case self::UNASSIGN_TAG: + return array( + 0 => 'username', + 1 => 'file', + //2 => 'systemtag description', + ); } } @@ -194,14 +229,6 @@ class Extension implements IExtension { * @return integer|false */ public function getGroupParameter($activity) { - if ($activity['app'] === self::APP_NAME) { - switch ($activity['subject']) { - case self::CREATE_TAG: - // Group by system tag - // return 1; - } - } - return false; } diff --git a/apps/systemtags/activity/listener.php b/apps/systemtags/activity/listener.php index ce456afaef..3afbde7a66 100644 --- a/apps/systemtags/activity/listener.php +++ b/apps/systemtags/activity/listener.php @@ -22,17 +22,36 @@ 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\IGroup; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; +use OCP\Share; use OCP\SystemTag\ISystemTag; +use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ManagerEvent; +use OCP\SystemTag\MapperEvent; +use OCP\SystemTag\TagNotFoundException; class Listener { + /** @var IGroupManager */ protected $groupManager; + /** @var IManager */ protected $activityManager; + /** @var IUserSession */ protected $session; + /** @var \OCP\SystemTag\ISystemTagManager */ + protected $tagManager; + /** @var \OCP\App\IAppManager */ + protected $appManager; + /** @var \OCP\Files\Config\IMountProviderCollection */ + protected $mountCollection; + /** @var \OCP\Files\IRootFolder */ + protected $rootFolder; /** * Listener constructor. @@ -40,13 +59,30 @@ class Listener { * @param IGroupManager $groupManager * @param IManager $activityManager * @param IUserSession $session + * @param ISystemTagManager $tagManager + * @param IAppManager $appManager + * @param IMountProviderCollection $mountCollection + * @param IRootFolder $rootFolder */ - public function __construct(IGroupManager $groupManager, IManager $activityManager, IUserSession $session) { + public function __construct(IGroupManager $groupManager, + IManager $activityManager, + IUserSession $session, + ISystemTagManager $tagManager, + IAppManager $appManager, + IMountProviderCollection $mountCollection, + IRootFolder $rootFolder) { $this->groupManager = $groupManager; $this->activityManager = $activityManager; $this->session = $session; + $this->tagManager = $tagManager; + $this->appManager = $appManager; + $this->mountCollection = $mountCollection; + $this->rootFolder = $rootFolder; } + /** + * @param ManagerEvent $event + */ public function event(ManagerEvent $event) { $actor = $this->session->getUser(); if ($actor instanceof IUser) { @@ -88,6 +124,89 @@ class Listener { } } + /** + * @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->isInstalled('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); + $path = $node->getPath(); + if (strpos($path, '/' . $owner . '/files') === 0) { + $path = substr($path, strlen('/' . $owner . '/files')); + } + // Get all users that have access to the mount point + $users = array_merge($users, Share::getUsersSharingFile($path, $owner, true, true)); + } + } + + $actor = $this->session->getUser(); + if ($actor instanceof IUser) { + $actor = $actor->getUID(); + } else { + $actor = ''; + } + + $activity = $this->activityManager->generateEvent(); + $activity->setApp(Extension::APP_NAME) + ->setType(Extension::APP_NAME) + ->setAuthor($actor) + ->setObject($event->getObjectType(), $event->getObjectId()); + + foreach ($users as $user => $path) { + $activity->setAffectedUser($user); + + foreach ($tags as $tag) { + if ($event->getEvent() === MapperEvent::EVENT_ASSIGN) { + $activity->setSubject(Extension::ASSIGN_TAG, [ + $actor, + $path, + $this->prepareTagAsParameter($tag), + ]); + } else if ($event->getEvent() === MapperEvent::EVENT_UNASSIGN) { + $activity->setSubject(Extension::UNASSIGN_TAG, [ + $actor, + $path, + $this->prepareTagAsParameter($tag), + ]); + } + + $this->activityManager->publish($activity); + } + } + } + /** * @param ISystemTag $tag * @return string diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php index c6fa21fa9b..d948f4857f 100644 --- a/apps/systemtags/appinfo/app.php +++ b/apps/systemtags/appinfo/app.php @@ -22,6 +22,7 @@ use OCA\SystemTags\Activity\Extension; use OCA\SystemTags\Activity\Listener; use OCP\SystemTag\ManagerEvent; +use OCP\SystemTag\MapperEvent; $eventDispatcher = \OC::$server->getEventDispatcher(); $eventDispatcher->addListener( @@ -46,21 +47,25 @@ $eventDispatcher->addListener( $activityManager = \OC::$server->getActivityManager(); $activityManager->registerExtension(function() { - return new Extension( - \OC::$server->getL10NFactory(), - \OC::$server->getActivityManager() - ); + $application = new \OCP\AppFramework\App('systemtags'); + return $application->getContainer()->query('OCA\SystemTags\Activity\Extension'); }); $managerListener = function(ManagerEvent $event) use ($activityManager) { - $listener = new Listener( - \OC::$server->getGroupManager(), - $activityManager, - \OC::$server->getUserSession() - ); + $application = new \OCP\AppFramework\App('systemtags'); + $listener = $application->getContainer()->query('OCA\SystemTags\Activity\Listener'); $listener->event($event); }; $eventDispatcher->addListener(ManagerEvent::EVENT_CREATE, $managerListener); $eventDispatcher->addListener(ManagerEvent::EVENT_DELETE, $managerListener); $eventDispatcher->addListener(ManagerEvent::EVENT_UPDATE, $managerListener); + +$mapperListener = function(MapperEvent $event) use ($activityManager) { + $application = new \OCP\AppFramework\App('systemtags'); + $listener = $application->getContainer()->query('OCA\SystemTags\Activity\Listener'); + $listener->mapperEvent($event); +}; + +$eventDispatcher->addListener(MapperEvent::EVENT_ASSIGN, $mapperListener); +$eventDispatcher->addListener(MapperEvent::EVENT_UNASSIGN, $mapperListener); diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php index ff9da88cd8..5fc45fdd2e 100644 --- a/lib/private/appframework/dependencyinjection/dicontainer.php +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -152,6 +152,10 @@ class DIContainer extends SimpleContainer implements IAppContainer { return $this->getServer()->getL10N($c->query('AppName')); }); + $this->registerService('OCP\\L10N\\IFactory', function($c) { + return $this->getServer()->getL10NFactory(); + }); + $this->registerService('OCP\\ILogger', function($c) { return $this->getServer()->getLogger(); }); From b5e40bc8f615caeba0a0b5d5d777037fff87e2f9 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 2 Feb 2016 10:11:19 +0100 Subject: [PATCH 6/7] Fix IManager::isFormattingFilteredObject() --- lib/private/activitymanager.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/private/activitymanager.php b/lib/private/activitymanager.php index e5235cb9bf..68bb8c1340 100644 --- a/lib/private/activitymanager.php +++ b/lib/private/activitymanager.php @@ -310,20 +310,19 @@ class ActivityManager implements IManager { /** * @param string $type - * @param int $id + * @param string $id */ public function setFormattingObject($type, $id) { $this->formattingObjectType = $type; - $this->formattingObjectId = $id; + $this->formattingObjectId = (string) $id; } /** * @return bool */ public function isFormattingFilteredObject() { - return 'filter' === $this->request->getParam('filter') - && $this->formattingObjectType === $this->request->getParam('objecttype') - && $this->formattingObjectId === $this->request->getParam('objectid'); + return $this->formattingObjectType === $this->request->getParam('object_type') + && $this->formattingObjectId === $this->request->getParam('object_id'); } /** From 7f563dc3e9a03284d9eff69214b6686cf9eb1916 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 2 Feb 2016 11:12:16 +0100 Subject: [PATCH 7/7] Fix path match --- apps/systemtags/activity/listener.php | 2 +- apps/systemtags/appinfo/app.php | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/systemtags/activity/listener.php b/apps/systemtags/activity/listener.php index 3afbde7a66..9b6597119c 100644 --- a/apps/systemtags/activity/listener.php +++ b/apps/systemtags/activity/listener.php @@ -163,7 +163,7 @@ class Listener { /** @var Node $node */ $node = array_shift($nodes); $path = $node->getPath(); - if (strpos($path, '/' . $owner . '/files') === 0) { + if (strpos($path, '/' . $owner . '/files/') === 0) { $path = substr($path, strlen('/' . $owner . '/files')); } // Get all users that have access to the mount point diff --git a/apps/systemtags/appinfo/app.php b/apps/systemtags/appinfo/app.php index d948f4857f..0bb57e1227 100644 --- a/apps/systemtags/appinfo/app.php +++ b/apps/systemtags/appinfo/app.php @@ -48,11 +48,14 @@ $eventDispatcher->addListener( $activityManager = \OC::$server->getActivityManager(); $activityManager->registerExtension(function() { $application = new \OCP\AppFramework\App('systemtags'); - return $application->getContainer()->query('OCA\SystemTags\Activity\Extension'); + /** @var \OCA\SystemTags\Activity\Extension $extension */ + $extension = $application->getContainer()->query('OCA\SystemTags\Activity\Extension'); + return $extension; }); $managerListener = function(ManagerEvent $event) use ($activityManager) { $application = new \OCP\AppFramework\App('systemtags'); + /** @var \OCA\SystemTags\Activity\Listener $listener */ $listener = $application->getContainer()->query('OCA\SystemTags\Activity\Listener'); $listener->event($event); }; @@ -63,6 +66,7 @@ $eventDispatcher->addListener(ManagerEvent::EVENT_UPDATE, $managerListener); $mapperListener = function(MapperEvent $event) use ($activityManager) { $application = new \OCP\AppFramework\App('systemtags'); + /** @var \OCA\SystemTags\Activity\Listener $listener */ $listener = $application->getContainer()->query('OCA\SystemTags\Activity\Listener'); $listener->mapperEvent($event); };