diff --git a/apps/dav/lib/CalDAV/Activity/Backend.php b/apps/dav/lib/CalDAV/Activity/Backend.php index 6cf09f6de4..6a557fef7b 100644 --- a/apps/dav/lib/CalDAV/Activity/Backend.php +++ b/apps/dav/lib/CalDAV/Activity/Backend.php @@ -112,7 +112,7 @@ class Backend { $event = $this->activityManager->generateEvent(); $event->setApp('dav') - ->setObject(Extension::CALENDAR, $calendarData['id']) + ->setObject(Extension::CALENDAR, (int) $calendarData['id']) ->setType(Extension::CALENDAR) ->setAuthor($currentUser); @@ -162,7 +162,7 @@ class Backend { $event = $this->activityManager->generateEvent(); $event->setApp('dav') - ->setObject(Extension::CALENDAR, $calendarData['id']) + ->setObject(Extension::CALENDAR, (int) $calendarData['id']) ->setType(Extension::CALENDAR) ->setAuthor($currentUser); @@ -387,7 +387,7 @@ class Backend { $event = $this->activityManager->generateEvent(); $event->setApp('dav') - ->setObject(Extension::CALENDAR, $calendarData['id']) + ->setObject(Extension::CALENDAR, (int) $calendarData['id']) ->setType($object['type'] === 'event' ? Extension::CALENDAR_EVENT : Extension::CALENDAR_TODO) ->setAuthor($currentUser); diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index afb327e24b..a194bb5e79 100644 --- a/apps/files/appinfo/app.php +++ b/apps/files/appinfo/app.php @@ -67,16 +67,3 @@ $templateManager->registerTemplate('application/vnd.oasis.opendocument.spreadshe 'name' => $l->t('Recent'), ]; }); - -\OC::$server->getActivityManager()->registerExtension(function() { - return new \OCA\Files\Activity( - \OC::$server->query('L10NFactory'), - \OC::$server->getURLGenerator(), - \OC::$server->getActivityManager(), - new \OCA\Files\ActivityHelper( - \OC::$server->getTagManager() - ), - \OC::$server->getDatabaseConnection(), - \OC::$server->getConfig() - ); -}); diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml index 0bf4388b39..1992b94a03 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -17,6 +17,25 @@ user-files + + + OCA\Files\Activity\Settings\FileChanged + OCA\Files\Activity\Settings\FileCreated + OCA\Files\Activity\Settings\FileDeleted + OCA\Files\Activity\Settings\FileFavorite + OCA\Files\Activity\Settings\FileRestored + + + + OCA\Files\Activity\Filter\FileChanges + OCA\Files\Activity\Filter\Favorites + + + + OCA\Files\Activity\Provider + + + OCA\Files\BackgroundJob\ScanFiles OCA\Files\BackgroundJob\DeleteOrphanedItems diff --git a/apps/files/img/add-color.svg b/apps/files/img/add-color.svg new file mode 100644 index 0000000000..acf5543c43 --- /dev/null +++ b/apps/files/img/add-color.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/files/img/change.svg b/apps/files/img/change.svg new file mode 100644 index 0000000000..cbc5d982b3 --- /dev/null +++ b/apps/files/img/change.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/files/img/delete-color.svg b/apps/files/img/delete-color.svg new file mode 100644 index 0000000000..810c63e811 --- /dev/null +++ b/apps/files/img/delete-color.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/files/lib/Activity.php b/apps/files/lib/Activity.php deleted file mode 100644 index 25146456b4..0000000000 --- a/apps/files/lib/Activity.php +++ /dev/null @@ -1,452 +0,0 @@ - - * @author Morris Jobke - * - * @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\Files; - -use OCP\IDBConnection; -use OCP\L10N\IFactory; -use OCP\Activity\IExtension; -use OCP\Activity\IManager; -use OCP\IConfig; -use OCP\IL10N; -use OCP\IURLGenerator; - -class Activity implements IExtension { - const APP_FILES = 'files'; - const FILTER_FILES = 'files'; - const FILTER_FAVORITES = 'files_favorites'; - - const TYPE_SHARE_CREATED = 'file_created'; - const TYPE_SHARE_CHANGED = 'file_changed'; - const TYPE_SHARE_DELETED = 'file_deleted'; - const TYPE_SHARE_RESTORED = 'file_restored'; - const TYPE_FAVORITES = 'files_favorites'; - - /** @var IL10N */ - protected $l; - - /** @var IFactory */ - protected $languageFactory; - - /** @var IURLGenerator */ - protected $URLGenerator; - - /** @var \OCP\Activity\IManager */ - protected $activityManager; - - /** @var \OCP\IDBConnection */ - protected $connection; - - /** @var \OCP\IConfig */ - protected $config; - - /** @var \OCA\Files\ActivityHelper */ - protected $helper; - - /** - * @param IFactory $languageFactory - * @param IURLGenerator $URLGenerator - * @param IManager $activityManager - * @param ActivityHelper $helper - * @param IDBConnection $connection - * @param IConfig $config - */ - public function __construct(IFactory $languageFactory, IURLGenerator $URLGenerator, IManager $activityManager, ActivityHelper $helper, IDBConnection $connection, IConfig $config) { - $this->languageFactory = $languageFactory; - $this->URLGenerator = $URLGenerator; - $this->l = $this->getL10N(); - $this->activityManager = $activityManager; - $this->helper = $helper; - $this->connection = $connection; - $this->config = $config; - } - - /** - * @param string|null $languageCode - * @return IL10N - */ - protected function getL10N($languageCode = null) { - return $this->languageFactory->get(self::APP_FILES, $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 Array "stringID of the type" => "translated string description for the setting" - * or Array "stringID of the type" => [ - * 'desc' => "translated string description for the setting" - * 'methods' => [self::METHOD_*], - * ] - */ - public function getNotificationTypes($languageCode) { - $l = $this->getL10N($languageCode); - return [ - self::TYPE_SHARE_CREATED => (string) $l->t('A new file or folder has been created'), - self::TYPE_SHARE_CHANGED => (string) $l->t('A file or folder has been changed or renamed'), - self::TYPE_FAVORITES => [ - 'desc' => (string) $l->t('Limit notifications about creation and changes to your favorite files (Stream only)'), - 'methods' => [self::METHOD_STREAM], - ], - self::TYPE_SHARE_DELETED => (string) $l->t('A file or folder has been deleted'), - self::TYPE_SHARE_RESTORED => (string) $l->t('A file or folder has been restored'), - ]; - } - - /** - * 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) { - if ($method === self::METHOD_STREAM) { - $settings = array(); - $settings[] = self::TYPE_SHARE_CREATED; - $settings[] = self::TYPE_SHARE_CHANGED; - $settings[] = self::TYPE_SHARE_DELETED; - $settings[] = self::TYPE_SHARE_RESTORED; - return $settings; - } - - 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_FILES) { - 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 string|false - */ - protected function translateLong($text, IL10N $l, array $params) { - switch ($text) { - case 'created_self': - return (string) $l->t('You created %1$s', $params); - case 'created_by': - return (string) $l->t('%2$s created %1$s', $params); - case 'created_public': - return (string) $l->t('%1$s was created in a public folder', $params); - case 'changed_self': - return (string) $l->t('You changed %1$s', $params); - case 'changed_by': - return (string) $l->t('%2$s changed %1$s', $params); - case 'deleted_self': - return (string) $l->t('You deleted %1$s', $params); - case 'deleted_by': - return (string) $l->t('%2$s deleted %1$s', $params); - case 'restored_self': - return (string) $l->t('You restored %1$s', $params); - case 'restored_by': - return (string) $l->t('%2$s restored %1$s', $params); - case 'renamed_self': - return (string) $l->t('You renamed %2$s to %1$s', $params); - case 'renamed_by': - return (string) $l->t('%2$s renamed %3$s to %1$s', $params); - case 'moved_self': - return (string) $l->t('You moved %2$s to %1$s', $params); - case 'moved_by': - return (string) $l->t('%2$s moved %3$s to %1$s', $params); - - default: - return false; - } - } - - /** - * @param string $text - * @param IL10N $l - * @param array $params - * @return string|false - */ - protected function translateShort($text, IL10N $l, array $params) { - switch ($text) { - case 'changed_by': - return (string) $l->t('Changed by %2$s', $params); - case 'deleted_by': - return (string) $l->t('Deleted by %2$s', $params); - case 'restored_by': - return (string) $l->t('Restored by %2$s', $params); - case 'renamed_by': - return (string) $l->t('Renamed by %2$s', $params); - case 'moved_by': - return (string) $l->t('Moved by %2$s', $params); - - default: - 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 - */ - function getSpecialParameterList($app, $text) { - if ($app === self::APP_FILES) { - switch ($text) { - case 'created_self': - case 'created_by': - case 'created_public': - case 'changed_self': - case 'changed_by': - case 'deleted_self': - case 'deleted_by': - case 'restored_self': - case 'restored_by': - return [ - 0 => 'file', - 1 => 'username', - ]; - case 'renamed_self': - case 'moved_self': - return [ - 0 => 'file', - 1 => 'file', - ]; - case 'renamed_by': - case 'moved_by': - return [ - 0 => 'file', - 1 => 'username', - 2 => 'file', - ]; - } - } - - return false; - } - - /** - * 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::TYPE_SHARE_CHANGED: - return 'icon-change'; - case self::TYPE_SHARE_CREATED: - return 'icon-add-color'; - case self::TYPE_SHARE_DELETED: - return 'icon-delete-color'; - - default: - 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_FILES) { - switch ($activity['subject']) { - case 'created_self': - case 'created_by': - case 'changed_self': - case 'changed_by': - case 'deleted_self': - case 'deleted_by': - case 'restored_self': - case 'restored_by': - return 0; - } - } - - 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 [ - 'top' => [ - self::FILTER_FAVORITES => [ - 'id' => self::FILTER_FAVORITES, - 'icon' => 'icon-favorite', - 'name' => (string) $this->l->t('Favorites'), - 'url' => $this->URLGenerator->linkToRoute('activity.Activities.showList', ['filter' => self::FILTER_FAVORITES]), - ], - ], - 'apps' => [ - self::FILTER_FILES => [ - 'id' => self::FILTER_FILES, - 'icon' => 'icon-files-dark', - 'name' => (string) $this->l->t('File changes'), - 'url' => $this->URLGenerator->linkToRoute('activity.Activities.showList', ['filter' => self::FILTER_FILES]), - ], - ], - ]; - } - - /** - * The extension can check if a customer filter (given by a query string like filter=abc) is valid or not. - * - * @param string $filterValue - * @return boolean - */ - public function isFilterValid($filterValue) { - return $filterValue === self::FILTER_FILES || $filterValue === self::FILTER_FAVORITES; - } - - /** - * 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) { - if ($filter === self::FILTER_FILES || $filter === self::FILTER_FAVORITES) { - return array_intersect([ - self::TYPE_SHARE_CREATED, - self::TYPE_SHARE_CHANGED, - self::TYPE_SHARE_DELETED, - self::TYPE_SHARE_RESTORED, - ], $types); - } - 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) { - $user = $this->activityManager->getCurrentUserId(); - // Display actions from all files - if ($filter === self::FILTER_FILES) { - return ['`app` = ?', [self::APP_FILES]]; - } - - if (!$user) { - // Remaining filters only work with a user/token - return false; - } - - // Display actions from favorites only - if ($filter === self::FILTER_FAVORITES || in_array($filter, ['all', 'by', 'self']) && $this->userSettingFavoritesOnly($user)) { - try { - $favorites = $this->helper->getFavoriteFilePaths($user); - } catch (\RuntimeException $e) { - // Too many favorites, can not put them into one query anymore... - return ['`app` = ?', [self::APP_FILES]]; - } - - /* - * Display activities only, when they are not `type` create/change - * or `file` is a favorite or in a favorite folder - */ - $parameters = $fileQueryList = []; - $parameters[] = self::APP_FILES; - $parameters[] = self::APP_FILES; - - $fileQueryList[] = '(`type` <> ? AND `type` <> ?)'; - $parameters[] = self::TYPE_SHARE_CREATED; - $parameters[] = self::TYPE_SHARE_CHANGED; - - foreach ($favorites['items'] as $favorite) { - $fileQueryList[] = '`file` = ?'; - $parameters[] = $favorite; - } - foreach ($favorites['folders'] as $favorite) { - $fileQueryList[] = '`file` LIKE ?'; - $parameters[] = $this->connection->escapeLikeParameter($favorite) . '/%'; - } - - return [ - ' CASE ' - . 'WHEN `app` <> ? THEN 1 ' - . 'WHEN `app` = ? AND (' . implode(' OR ', $fileQueryList) . ') THEN 1 ' - . 'ELSE 0 ' - . 'END = 1 ', - $parameters, - ]; - } - return false; - } - - /** - * Is the file actions favorite limitation enabled? - * - * @param string $user - * @return bool - */ - protected function userSettingFavoritesOnly($user) { - return (bool) $this->config->getUserValue($user, 'activity', 'notify_' . self::METHOD_STREAM . '_' . self::TYPE_FAVORITES, false); - } -} diff --git a/apps/files/lib/Activity/Filter/Favorites.php b/apps/files/lib/Activity/Filter/Favorites.php new file mode 100644 index 0000000000..2639ae847f --- /dev/null +++ b/apps/files/lib/Activity/Filter/Favorites.php @@ -0,0 +1,160 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files\Activity\Filter; + + +use OCA\Files\Activity\Helper; +use OCP\Activity\IFilter; +use OCP\Activity\IManager; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IL10N; +use OCP\IURLGenerator; + +class Favorites implements IFilter { + + /** @var IL10N */ + protected $l; + + /** @var IURLGenerator */ + protected $url; + + /** @var IManager */ + protected $activityManager; + + /** @var Helper */ + protected $helper; + + /** @var IDBConnection */ + protected $db; + + /** + * @param IL10N $l + * @param IURLGenerator $url + * @param IManager $activityManager + * @param Helper $helper + * @param IDBConnection $db + */ + public function __construct(IL10N $l, IURLGenerator $url, IManager $activityManager, Helper $helper, IDBConnection $db) { + $this->l = $l; + $this->url = $url; + $this->activityManager = $activityManager; + $this->helper = $helper; + $this->db = $db; + } + + /** + * @return string Lowercase a-z only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'files_favorites'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('Favorites'); + } + + /** + * @return int + * @since 11.0.0 + */ + public function getPriority() { + return 10; + } + + /** + * @return string Full URL to an icon, empty string when none is given + * @since 11.0.0 + */ + public function getIcon() { + return $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/star-dark.svg')); + } + + /** + * @param string[] $types + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function filterTypes(array $types) { + return array_intersect([ + 'file_created', + 'file_changed', + 'file_deleted', + 'file_restored', + ], $types); + } + + /** + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function allowedApps() { + return ['files']; + } + + /** + * @param IQueryBuilder $query + */ + public function filterFavorites(IQueryBuilder $query) { + try { + $user = $this->activityManager->getCurrentUserId(); + } catch (\UnexpectedValueException $e) { + return; + } + + try { + $favorites = $this->helper->getFavoriteFilePaths($user); + } catch (\RuntimeException $e) { + return; + } + + $limitations = []; + if (!empty($favorites['items'])) { + $limitations[] = $query->expr()->in('file', $query->createNamedParameter($favorites['items'], IQueryBuilder::PARAM_STR_ARRAY)); + } + foreach ($favorites['folders'] as $favorite) { + $limitations[] = $query->expr()->like('file', $query->createNamedParameter( + $this->db->escapeLikeParameter($favorite . '/') . '%' + )); + } + + if (empty($limitations)) { + return; + } + + $function = $query->createFunction(' + CASE + WHEN ' . $query->getColumnName('app') . ' <> ' . $query->createNamedParameter('files') . ' THEN 1 + WHEN ' . $query->getColumnName('app') . ' = ' . $query->createNamedParameter('files') . ' + AND (' . implode(' OR ', $limitations) . ') + THEN 1 + END = 1' + ); + + $query->andWhere($function); + } +} diff --git a/apps/files/lib/Activity/Filter/FileChanges.php b/apps/files/lib/Activity/Filter/FileChanges.php new file mode 100644 index 0000000000..dc7daf96ba --- /dev/null +++ b/apps/files/lib/Activity/Filter/FileChanges.php @@ -0,0 +1,95 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files\Activity\Filter; + + +use OCP\Activity\IFilter; +use OCP\IL10N; +use OCP\IURLGenerator; + +class FileChanges implements IFilter { + + /** @var IL10N */ + protected $l; + + /** @var IURLGenerator */ + protected $url; + + public function __construct(IL10N $l, IURLGenerator $url) { + $this->l = $l; + $this->url = $url; + } + + /** + * @return string Lowercase a-z only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'files'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('File changes'); + } + + /** + * @return int + * @since 11.0.0 + */ + public function getPriority() { + return 30; + } + + /** + * @return string Full URL to an icon, empty string when none is given + * @since 11.0.0 + */ + public function getIcon() { + return $this->url->getAbsoluteURL($this->url->imagePath('core', 'places/files-dark.svg')); + } + + /** + * @param string[] $types + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function filterTypes(array $types) { + return array_intersect([ + 'file_created', + 'file_changed', + 'file_deleted', + 'file_restored', + ], $types); + } + + /** + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function allowedApps() { + return ['files']; + } +} diff --git a/apps/files/lib/ActivityHelper.php b/apps/files/lib/Activity/Helper.php similarity index 96% rename from apps/files/lib/ActivityHelper.php rename to apps/files/lib/Activity/Helper.php index f5660de4b3..d03d6e8e97 100644 --- a/apps/files/lib/ActivityHelper.php +++ b/apps/files/lib/Activity/Helper.php @@ -20,16 +20,16 @@ * */ -namespace OCA\Files; +namespace OCA\Files\Activity; use OCP\Files\Folder; use OCP\ITagManager; -class ActivityHelper { +class Helper { /** If a user has a lot of favorites the query might get too slow and long */ const FAVORITE_LIMIT = 50; - /** @var \OCP\ITagManager */ + /** @var ITagManager */ protected $tagManager; /** diff --git a/apps/files/lib/Activity/Provider.php b/apps/files/lib/Activity/Provider.php new file mode 100644 index 0000000000..e95522a7d0 --- /dev/null +++ b/apps/files/lib/Activity/Provider.php @@ -0,0 +1,276 @@ + + * + * @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\Files\Activity; + +use OCP\Activity\IEvent; +use OCP\Activity\IManager; +use OCP\Activity\IProvider; +use OCP\IL10N; +use OCP\IURLGenerator; + +class Provider implements IProvider { + + /** @var IL10N */ + protected $l; + + /** @var IURLGenerator */ + protected $url; + + /** @var IManager */ + protected $activityManager; + + /** + * @param IL10N $l + * @param IURLGenerator $url + * @param IManager $activityManager + */ + public function __construct(IL10N $l, IURLGenerator $url, IManager $activityManager) { + $this->l = $l; + $this->url = $url; + $this->activityManager = $activityManager; + } + + /** + * @param IEvent $event + * @param IEvent|null $previousEvent + * @return IEvent + * @throws \InvalidArgumentException + * @since 11.0.0 + */ + public function parse(IEvent $event, IEvent $previousEvent = null) { + if ($event->getApp() !== 'files') { + throw new \InvalidArgumentException(); + } + + if ($previousEvent instanceof IEvent && $event->getSubject() !== $previousEvent->getSubject()) { + // Different subject means not the same string, so no grouping + $previousEvent = null; + } + + if ($this->activityManager->isFormattingFilteredObject()) { + try { + return $this->parseShortVersion($event); + } catch (\InvalidArgumentException $e) { + // Ignore and simply use the long version... + } + } + + return $this->parseLongVersion($event); + } + + /** + * @param IEvent $event + * @return IEvent + * @throws \InvalidArgumentException + * @since 11.0.0 + */ + public function parseShortVersion(IEvent $event) { + $parsedParameters = $this->getParsedParameters($event->getSubject(), $event->getSubjectParameters()); + $richParameters = $this->getRichParameters($event->getSubject(), $event->getSubjectParameters()); + + if ($event->getSubject() === 'created_by') { + $event->setParsedSubject($this->l->t('Created by %s', [$parsedParameters[1]])) + ->setRichSubject($this->l->t('Created by {user1}'), ['user1' => $richParameters['user1']]) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'add-color.svg'))); + } else if ($event->getSubject() === 'changed_by') { + $event->setParsedSubject($this->l->t('Changed by %2$s', [$parsedParameters[1]])) + ->setRichSubject($this->l->t('Changed by {user1}'), ['user1' => $richParameters['user1']]) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else if ($event->getSubject() === 'deleted_by') { + $event->setParsedSubject($this->l->t('Deleted by %2$s', [$parsedParameters[1]])) + ->setRichSubject($this->l->t('Deleted by {user1}'), ['user1' => $richParameters['user1']]) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'delete-color.svg'))); + } else if ($event->getSubject() === 'restored_by') { + $event->setParsedSubject($this->l->t('Restored by %2$s', [$parsedParameters[1]])) + ->setRichSubject($this->l->t('Restored by {user1}'), ['user1' => $richParameters['user1']]); + } else if ($event->getSubject() === 'renamed_by') { + $event->setParsedSubject($this->l->t('Renamed by %2$s', [$parsedParameters[1]])) + ->setRichSubject($this->l->t('Renamed by {user1}'), ['user1' => $richParameters['user1']]) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else if ($event->getSubject() === 'moved_by') { + $event->setParsedSubject($this->l->t('Moved by %2$s', [$parsedParameters[1]])) + ->setRichSubject($this->l->t('Moved by {user1}'), ['user1' => $richParameters['user1']]) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else { + throw new \InvalidArgumentException(); + } + + return $event; + } + + /** + * @param IEvent $event + * @return IEvent + * @throws \InvalidArgumentException + * @since 11.0.0 + */ + public function parseLongVersion(IEvent $event) { + $parsedParameters = $this->getParsedParameters($event->getSubject(), $event->getSubjectParameters()); + $richParameters = $this->getRichParameters($event->getSubject(), $event->getSubjectParameters()); + + if ($event->getSubject() === 'created_self') { + $event->setParsedSubject($this->l->t('You created %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('You created {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'add-color.svg'))); + } else if ($event->getSubject() === 'created_by') { + $event->setParsedSubject($this->l->t('%2$s created %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('{user1} created {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'add-color.svg'))); + } else if ($event->getSubject() === 'created_public') { + $event->setParsedSubject($this->l->t('%1$s was created in a public folder', $parsedParameters)) + ->setRichSubject($this->l->t('{file1} was created in a public folder'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'add-color.svg'))); + } else if ($event->getSubject() === 'changed_self') { + $event->setParsedSubject($this->l->t('You changed %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('You changed {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else if ($event->getSubject() === 'changed_by') { + $event->setParsedSubject($this->l->t('%2$s changed %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('{user1} changed {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else if ($event->getSubject() === 'deleted_self') { + $event->setParsedSubject($this->l->t('You deleted %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('You deleted {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'delete-color.svg'))); + } else if ($event->getSubject() === 'deleted_by') { + $event->setParsedSubject($this->l->t('%2$s deleted %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('{user1} deleted {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'delete-color.svg'))); + } else if ($event->getSubject() === 'restored_self') { + $event->setParsedSubject($this->l->t('You restored %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('You restored {file1}'), $richParameters); + } else if ($event->getSubject() === 'restored_by') { + $event->setParsedSubject($this->l->t('%2$s restored %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('{user1} restored {file1}'), $richParameters); + } else if ($event->getSubject() === 'renamed_self') { + $event->setParsedSubject($this->l->t('You renamed %2$s to %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('You renamed {file2} to {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else if ($event->getSubject() === 'renamed_by') { + $event->setParsedSubject($this->l->t('%2$s renamed %3$s to %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('{user1} renamed {file2} to {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else if ($event->getSubject() === 'moved_self') { + $event->setParsedSubject($this->l->t('You moved %2$s to %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('You moved {file2} to {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else if ($event->getSubject() === 'moved_by') { + $event->setParsedSubject($this->l->t('%2$s moved %3$s to %1$s', $parsedParameters)) + ->setRichSubject($this->l->t('{user1} moved {file2} to {file1}'), $richParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('files', 'change.svg'))); + } else { + throw new \InvalidArgumentException(); + } + + return $event; + } + + protected function getParsedParameters($subject, array $parameters) { + switch ($subject) { + case 'created_self': + case 'created_public': + case 'changed_self': + case 'deleted_self': + case 'restored_self': + return [ + array_shift($parameters[0]), + ]; + case 'created_by': + case 'changed_by': + case 'deleted_by': + case 'restored_by': + return [ + array_shift($parameters[0]), + $parameters[1], + ]; + case 'renamed_self': + case 'moved_self': + return [ + array_shift($parameters[0]), + array_shift($parameters[1]), + ]; + case 'renamed_by': + case 'moved_by': + return [ + array_shift($parameters[0]), + $parameters[1], + array_shift($parameters[2]), + ]; + } + return []; + } + + protected function getRichParameters($subject, array $parameters) { + switch ($subject) { + case 'created_self': + case 'created_public': + case 'changed_self': + case 'deleted_self': + case 'restored_self': + return [ + 'file1' => $this->getRichFileParameter($parameters[0]), + ]; + case 'created_by': + case 'changed_by': + case 'deleted_by': + case 'restored_by': + return [ + 'file1' => $this->getRichFileParameter($parameters[0]), + 'user1' => $this->getRichUserParameter($parameters[1]), + ]; + case 'renamed_self': + case 'moved_self': + return [ + 'file1' => $this->getRichFileParameter($parameters[0]), + 'file2' => $this->getRichFileParameter($parameters[1]), + ]; + case 'renamed_by': + case 'moved_by': + return [ + 'file1' => $this->getRichFileParameter($parameters[0]), + 'user1' => $this->getRichUserParameter($parameters[1]), + 'file2' => $this->getRichFileParameter($parameters[2]), + ]; + } + return []; + } + + protected function getRichFileParameter($parameter) { + $path = reset($parameter); + $id = key($parameter); + return [ + 'type' => 'file', + 'id' => $id, + 'name' => basename($path), + 'path' => $path, + ]; + } + + protected function getRichUserParameter($parameter) { + return [ + 'type' => 'user', + 'id' => $parameter, + 'name' => $parameter,// FIXME Use display name + ]; + } +} diff --git a/apps/files/lib/Activity/Settings/FileChanged.php b/apps/files/lib/Activity/Settings/FileChanged.php new file mode 100644 index 0000000000..1c20fb6f01 --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileChanged.php @@ -0,0 +1,98 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files\Activity\Settings; + + +use OCP\Activity\ISetting; +use OCP\IL10N; + +class FileChanged implements ISetting { + + /** @var IL10N */ + protected $l; + + /** + * @param IL10N $l + */ + public function __construct(IL10N $l) { + $this->l = $l; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'file_changed'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('A file or folder has been changed or renamed'); + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 1; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail() { + return false; + } +} + diff --git a/apps/files/lib/Activity/Settings/FileCreated.php b/apps/files/lib/Activity/Settings/FileCreated.php new file mode 100644 index 0000000000..dfde00ae7e --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileCreated.php @@ -0,0 +1,98 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files\Activity\Settings; + + +use OCP\Activity\ISetting; +use OCP\IL10N; + +class FileCreated implements ISetting { + + /** @var IL10N */ + protected $l; + + /** + * @param IL10N $l + */ + public function __construct(IL10N $l) { + $this->l = $l; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'file_created'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('A new file or folder has been created'); + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 0; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail() { + return false; + } +} + diff --git a/apps/files/lib/Activity/Settings/FileDeleted.php b/apps/files/lib/Activity/Settings/FileDeleted.php new file mode 100644 index 0000000000..c4948ded2f --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileDeleted.php @@ -0,0 +1,98 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files\Activity\Settings; + + +use OCP\Activity\ISetting; +use OCP\IL10N; + +class FileDeleted implements ISetting { + + /** @var IL10N */ + protected $l; + + /** + * @param IL10N $l + */ + public function __construct(IL10N $l) { + $this->l = $l; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'file_deleted'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('A new file or folder has been deleted'); + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 3; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail() { + return false; + } +} + diff --git a/apps/files/lib/Activity/Settings/FileFavorite.php b/apps/files/lib/Activity/Settings/FileFavorite.php new file mode 100644 index 0000000000..b2f20688df --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileFavorite.php @@ -0,0 +1,98 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files\Activity\Settings; + + +use OCP\Activity\ISetting; +use OCP\IL10N; + +class FileFavorite implements ISetting { + + /** @var IL10N */ + protected $l; + + /** + * @param IL10N $l + */ + public function __construct(IL10N $l) { + $this->l = $l; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'file_favorite'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('Limit notifications about creation and changes to your favorite files (Stream only)'); + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 2; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream() { + return false; + } + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail() { + return false; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail() { + return false; + } +} + diff --git a/apps/files/lib/Activity/Settings/FileRestored.php b/apps/files/lib/Activity/Settings/FileRestored.php new file mode 100644 index 0000000000..cedfef441e --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileRestored.php @@ -0,0 +1,98 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files\Activity\Settings; + + +use OCP\Activity\ISetting; +use OCP\IL10N; + +class FileRestored implements ISetting { + + /** @var IL10N */ + protected $l; + + /** + * @param IL10N $l + */ + public function __construct(IL10N $l) { + $this->l = $l; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'file_restored'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('A new file or folder has been restored'); + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 4; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail() { + return false; + } +} + diff --git a/apps/files/tests/ActivityTest.php b/apps/files/tests/ActivityTest.php deleted file mode 100644 index 65e914c1a5..0000000000 --- a/apps/files/tests/ActivityTest.php +++ /dev/null @@ -1,374 +0,0 @@ - - * @author Morris Jobke - * - * @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\Files\Tests; - -use OCA\Files\Activity; -use OCP\IL10N; -use OCP\L10N\IFactory; -use Test\TestCase; - -/** - * Class ActivityTest - * - * @group DB - * @package OCA\Files\Tests - */ -class ActivityTest extends TestCase { - - /** @var \OCP\Activity\IManager */ - private $activityManager; - - /** @var \OCP\IRequest|\PHPUnit_Framework_MockObject_MockObject */ - protected $request; - - /** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ - protected $session; - - /** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */ - protected $config; - - /** @var \OCA\Files\ActivityHelper|\PHPUnit_Framework_MockObject_MockObject */ - protected $activityHelper; - - /** @var \OCP\L10N\IFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $l10nFactory; - - /** @var \OCA\Files\Activity */ - protected $activityExtension; - - protected function setUp() { - parent::setUp(); - - $this->request = $this->getMockBuilder('OCP\IRequest') - ->disableOriginalConstructor() - ->getMock(); - $this->session = $this->getMockBuilder('OCP\IUserSession') - ->disableOriginalConstructor() - ->getMock(); - $this->config = $this->getMockBuilder('OCP\IConfig') - ->disableOriginalConstructor() - ->getMock(); - $this->activityHelper = $this->getMockBuilder('OCA\Files\ActivityHelper') - ->disableOriginalConstructor() - ->getMock(); - - $this->activityManager = new \OC\Activity\Manager( - $this->request, - $this->session, - $this->config - ); - - $this->l10nFactory = $this->createMock(IFactory::class); - $deL10n = $this->createMock(IL10N::class); - $deL10n->expects($this->any()) - ->method('t') - ->willReturnCallback(function ($argument) { - return 'translate(' . $argument . ')'; - }); - - $this->l10nFactory->expects($this->any()) - ->method('get') - ->willReturnMap([ - ['files', null, \OC::$server->getL10N('files', 'en')], - ['files', 'en', \OC::$server->getL10N('files', 'en')], - ['files', 'de', $deL10n], - ]); - - $this->activityExtension = $activityExtension = new Activity( - $this->l10nFactory, - $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), - $this->activityManager, - $this->activityHelper, - \OC::$server->getDatabaseConnection(), - $this->config - ); - - $this->activityManager->registerExtension(function() use ($activityExtension) { - return $activityExtension; - }); - } - - public function testNotificationTypes() { - $result = $this->activityExtension->getNotificationTypes('en'); - $this->assertTrue(is_array($result), 'Asserting getNotificationTypes() returns an array'); - $this->assertCount(5, $result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_CREATED, $result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_CHANGED, $result); - $this->assertArrayHasKey(Activity::TYPE_FAVORITES, $result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_DELETED, $result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_RESTORED, $result); - } - - public function testDefaultTypes() { - $result = $this->activityExtension->getDefaultTypes('stream'); - $this->assertTrue(is_array($result), 'Asserting getDefaultTypes(stream) returns an array'); - $this->assertCount(4, $result); - $result = array_flip($result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_CREATED, $result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_CHANGED, $result); - $this->assertArrayNotHasKey(Activity::TYPE_FAVORITES, $result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_DELETED, $result); - $this->assertArrayHasKey(Activity::TYPE_SHARE_RESTORED, $result); - - $result = $this->activityExtension->getDefaultTypes('email'); - $this->assertFalse($result, 'Asserting getDefaultTypes(email) returns false'); - } - - public function testTranslate() { - $this->assertFalse( - $this->activityExtension->translate('files_sharing', '', [], false, false, 'en'), - 'Asserting that no translations are set for files_sharing' - ); - - // Test english - $this->assertNotFalse( - $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'en'), - 'Asserting that translations are set for files.deleted_self' - ); - $this->assertStringStartsWith( - 'You deleted ', - $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'en') - ); - - // Test translation - $this->assertNotFalse( - $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'de'), - 'Asserting that translations are set for files.deleted_self' - ); - $this->assertStringStartsWith( - 'translate(You deleted ', - $this->activityExtension->translate('files', 'deleted_self', ['file'], false, false, 'de') - ); - } - - public function testGetSpecialParameterList() { - $this->assertFalse( - $this->activityExtension->getSpecialParameterList('files_sharing', ''), - 'Asserting that no special parameters are set for files_sharing' - ); - } - - public function typeIconData() { - return [ - [Activity::TYPE_SHARE_CHANGED, 'icon-change'], - [Activity::TYPE_SHARE_CREATED, 'icon-add-color'], - [Activity::TYPE_SHARE_DELETED, 'icon-delete-color'], - [Activity::TYPE_SHARE_RESTORED, false], - [Activity::TYPE_FAVORITES, false], - ['unknown type', false], - ]; - } - - /** - * @dataProvider typeIconData - * - * @param string $type - * @param mixed $expected - */ - public function testTypeIcon($type, $expected) { - $this->assertSame($expected, $this->activityExtension->getTypeIcon($type)); - } - - public function testGroupParameter() { - $this->assertFalse( - $this->activityExtension->getGroupParameter(['app' => 'files_sharing']), - 'Asserting that no group parameters are set for files_sharing' - ); - } - - public function testNavigation() { - $result = $this->activityExtension->getNavigation(); - $this->assertCount(1, $result['top']); - $this->assertArrayHasKey(Activity::FILTER_FAVORITES, $result['top']); - - $this->assertCount(1, $result['apps']); - $this->assertArrayHasKey(Activity::FILTER_FILES, $result['apps']); - } - - public function testIsFilterValid() { - $this->assertTrue($this->activityExtension->isFilterValid(Activity::FILTER_FAVORITES)); - $this->assertTrue($this->activityExtension->isFilterValid(Activity::FILTER_FILES)); - $this->assertFalse($this->activityExtension->isFilterValid('unknown filter')); - } - - public function filterNotificationTypesData() { - return [ - [ - Activity::FILTER_FILES, - [ - 'NT0', - Activity::TYPE_SHARE_CREATED, - Activity::TYPE_SHARE_CHANGED, - Activity::TYPE_SHARE_DELETED, - Activity::TYPE_SHARE_RESTORED, - Activity::TYPE_FAVORITES, - ], [ - Activity::TYPE_SHARE_CREATED, - Activity::TYPE_SHARE_CHANGED, - Activity::TYPE_SHARE_DELETED, - Activity::TYPE_SHARE_RESTORED, - ], - ], - [ - Activity::FILTER_FILES, - [ - 'NT0', - Activity::TYPE_SHARE_CREATED, - Activity::TYPE_FAVORITES, - ], - [ - Activity::TYPE_SHARE_CREATED, - ], - ], - [ - Activity::FILTER_FAVORITES, - [ - 'NT0', - Activity::TYPE_SHARE_CREATED, - Activity::TYPE_SHARE_CHANGED, - Activity::TYPE_SHARE_DELETED, - Activity::TYPE_SHARE_RESTORED, - Activity::TYPE_FAVORITES, - ], [ - Activity::TYPE_SHARE_CREATED, - Activity::TYPE_SHARE_CHANGED, - Activity::TYPE_SHARE_DELETED, - Activity::TYPE_SHARE_RESTORED, - ], - ], - [ - 'unknown filter', - [ - 'NT0', - Activity::TYPE_SHARE_CREATED, - Activity::TYPE_SHARE_CHANGED, - Activity::TYPE_SHARE_DELETED, - Activity::TYPE_SHARE_RESTORED, - Activity::TYPE_FAVORITES, - ], - false, - ], - ]; - } - - /** - * @dataProvider filterNotificationTypesData - * - * @param string $filter - * @param array $types - * @param mixed $expected - */ - public function testFilterNotificationTypes($filter, $types, $expected) { - $result = $this->activityExtension->filterNotificationTypes($types, $filter); - $this->assertEquals($expected, $result); - } - - public function queryForFilterData() { - return [ - [ - new \RuntimeException(), - '`app` = ?', - ['files'] - ], - [ - [ - 'items' => [], - 'folders' => [], - ], - ' CASE WHEN `app` <> ? THEN 1 WHEN `app` = ? AND ((`type` <> ? AND `type` <> ?)) THEN 1 ELSE 0 END = 1 ', - ['files', 'files', Activity::TYPE_SHARE_CREATED, Activity::TYPE_SHARE_CHANGED] - ], - [ - [ - 'items' => ['file.txt', 'folder'], - 'folders' => ['folder'], - ], - ' CASE WHEN `app` <> ? THEN 1 WHEN `app` = ? AND ((`type` <> ? AND `type` <> ?) OR `file` = ? OR `file` = ? OR `file` LIKE ?) THEN 1 ELSE 0 END = 1 ', - ['files', 'files', Activity::TYPE_SHARE_CREATED, Activity::TYPE_SHARE_CHANGED, 'file.txt', 'folder', 'folder/%'] - ], - ]; - } - - /** - * @dataProvider queryForFilterData - * - * @param mixed $will - * @param string $query - * @param array $parameters - */ - public function testQueryForFilter($will, $query, $parameters) { - $this->mockUserSession('test'); - - $this->config->expects($this->any()) - ->method('getUserValue') - ->willReturnMap([ - ['test', 'activity', 'notify_stream_' . Activity::TYPE_FAVORITES, false, true], - ]); - if (is_array($will)) { - $this->activityHelper->expects($this->any()) - ->method('getFavoriteFilePaths') - ->with('test') - ->willReturn($will); - } else { - $this->activityHelper->expects($this->any()) - ->method('getFavoriteFilePaths') - ->with('test') - ->willThrowException($will); - } - - $result = $this->activityExtension->getQueryForFilter('all'); - $this->assertEquals([$query, $parameters], $result); - - $this->executeQueryForFilter($result); - } - - public function executeQueryForFilter(array $result) { - list($resultQuery, $resultParameters) = $result; - $resultQuery = str_replace('`file`', '`user`', $resultQuery); - $resultQuery = str_replace('`type`', '`key`', $resultQuery); - - $connection = \OC::$server->getDatabaseConnection(); - // Test the query on the privatedata table, because the activity table - // does not exist in core - $result = $connection->executeQuery('SELECT * FROM `*PREFIX*privatedata` WHERE ' . $resultQuery, $resultParameters); - $rows = $result->fetchAll(); - $result->closeCursor(); - } - - protected function mockUserSession($user) { - $mockUser = $this->getMockBuilder('\OCP\IUser') - ->disableOriginalConstructor() - ->getMock(); - $mockUser->expects($this->any()) - ->method('getUID') - ->willReturn($user); - - $this->session->expects($this->any()) - ->method('isLoggedIn') - ->willReturn(true); - $this->session->expects($this->any()) - ->method('getUser') - ->willReturn($mockUser); - } -} diff --git a/lib/private/Activity/Event.php b/lib/private/Activity/Event.php index af0605d82c..df6756940a 100644 --- a/lib/private/Activity/Event.php +++ b/lib/private/Activity/Event.php @@ -24,113 +24,361 @@ namespace OC\Activity; use OCP\Activity\IEvent; +use OCP\RichObjectStrings\InvalidObjectExeption; +use OCP\RichObjectStrings\IValidator; class Event implements IEvent { + + /** @var string */ + protected $app = ''; + /** @var string */ + protected $type = ''; + /** @var string */ + protected $affectedUser = ''; + /** @var string */ + protected $author = ''; + /** @var int */ + protected $timestamp = 0; + /** @var string */ + protected $subject = ''; /** @var array */ - protected $data = [ - 'app' => null, - 'type' => null, - 'affected_user' => null, - 'author' => null, - 'timestamp' => null, - 'subject' => null, - 'subject_parameters' => null, - 'message' => '', - 'message_parameters' => [], - 'object_type' => '', - 'object_id' => 0, - 'object_name' => '', - 'link' => '', - ]; + protected $subjectParameters = []; + /** @var string */ + protected $subjectParsed; + /** @var string */ + protected $subjectRich; + /** @var array */ + protected $subjectRichParameters; + /** @var string */ + protected $message = ''; + /** @var array */ + protected $messageParameters = []; + /** @var string */ + protected $messageParsed; + /** @var string */ + protected $messageRich; + /** @var array */ + protected $messageRichParameters; + /** @var string */ + protected $objectType = ''; + /** @var int */ + protected $objectId = 0; + /** @var string */ + protected $objectName = ''; + /** @var string */ + protected $link = ''; + /** @var string */ + protected $icon = ''; + + /** @var IEvent */ + protected $child = null; + /** @var IValidator */ + protected $richValidator; + + /** + * @param IValidator $richValidator + */ + public function __construct(IValidator $richValidator) { + $this->richValidator = $richValidator; + } /** * Set the app of the activity * * @param string $app * @return IEvent + * @throws \InvalidArgumentException if the app id is invalid * @since 8.2.0 */ public function setApp($app) { - $this->data['app'] = (string) $app; + if (!is_string($app) || $app === '' || isset($app[32])) { + throw new \InvalidArgumentException('The given app is invalid'); + } + $this->app = (string) $app; return $this; } + /** + * @return string + */ + public function getApp() { + return $this->app; + } + /** * Set the type of the activity * * @param string $type * @return IEvent + * @throws \InvalidArgumentException if the type is invalid * @since 8.2.0 */ public function setType($type) { - $this->data['type'] = (string) $type; + if (!is_string($type) || $type === '' || isset($type[255])) { + throw new \InvalidArgumentException('The given type is invalid'); + } + $this->type = (string) $type; return $this; } + /** + * @return string + */ + public function getType() { + return $this->type; + } + /** * Set the affected user of the activity * * @param string $affectedUser * @return IEvent + * @throws \InvalidArgumentException if the affected user is invalid * @since 8.2.0 */ public function setAffectedUser($affectedUser) { - $this->data['affected_user'] = (string) $affectedUser; + if (!is_string($affectedUser) || $affectedUser === '' || isset($affectedUser[64])) { + throw new \InvalidArgumentException('The given affected user is invalid'); + } + $this->affectedUser = (string) $affectedUser; return $this; } + /** + * @return string + */ + public function getAffectedUser() { + return $this->affectedUser; + } + /** * Set the author of the activity * * @param string $author * @return IEvent + * @throws \InvalidArgumentException if the author is invalid * @since 8.2.0 */ public function setAuthor($author) { - $this->data['author'] = (string) $author; + if (!is_string($author) || isset($author[64])) { + throw new \InvalidArgumentException('The given author user is invalid'. serialize($author)); + } + $this->author = (string) $author; return $this; } + /** + * @return string + */ + public function getAuthor() { + return $this->author; + } + /** * Set the timestamp of the activity * * @param int $timestamp * @return IEvent + * @throws \InvalidArgumentException if the timestamp is invalid * @since 8.2.0 */ public function setTimestamp($timestamp) { - $this->data['timestamp'] = (int) $timestamp; + if (!is_int($timestamp)) { + throw new \InvalidArgumentException('The given timestamp is invalid'); + } + $this->timestamp = (int) $timestamp; return $this; } + /** + * @return int + */ + public function getTimestamp() { + return $this->timestamp; + } + /** * Set the subject of the activity * * @param string $subject * @param array $parameters * @return IEvent + * @throws \InvalidArgumentException if the subject or parameters are invalid * @since 8.2.0 */ public function setSubject($subject, array $parameters = []) { - $this->data['subject'] = (string) $subject; - $this->data['subject_parameters'] = $parameters; + if (!is_string($subject) || isset($subject[255])) { + throw new \InvalidArgumentException('The given subject is invalid'); + } + $this->subject = (string) $subject; + $this->subjectParameters = $parameters; return $this; } + /** + * @return string + */ + public function getSubject() { + return $this->subject; + } + + /** + * @return array + */ + public function getSubjectParameters() { + return $this->subjectParameters; + } + + /** + * @param string $subject + * @return $this + * @throws \InvalidArgumentException if the subject is invalid + * @since 11.0.0 + */ + public function setParsedSubject($subject) { + if (!is_string($subject) || $subject === '') { + throw new \InvalidArgumentException('The given parsed subject is invalid'); + } + $this->subjectParsed = $subject; + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getParsedSubject() { + return $this->subjectParsed; + } + + /** + * @param string $subject + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 11.0.0 + */ + public function setRichSubject($subject, array $parameters = []) { + if (!is_string($subject) || $subject === '') { + throw new \InvalidArgumentException('The given parsed subject is invalid'); + } + $this->subjectRich = $subject; + + if (!is_array($parameters)) { + throw new \InvalidArgumentException('The given subject parameters are invalid'); + } + $this->subjectRichParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getRichSubject() { + return $this->subjectRich; + } + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichSubjectParameters() { + return $this->subjectRichParameters; + } + /** * Set the message of the activity * * @param string $message * @param array $parameters * @return IEvent + * @throws \InvalidArgumentException if the message or parameters are invalid * @since 8.2.0 */ public function setMessage($message, array $parameters = []) { - $this->data['message'] = (string) $message; - $this->data['message_parameters'] = $parameters; + if (!is_string($message) || isset($message[255])) { + throw new \InvalidArgumentException('The given message is invalid'); + } + $this->message = (string) $message; + $this->messageParameters = $parameters; return $this; } + /** + * @return string + */ + public function getMessage() { + return $this->message; + } + + /** + * @return array + */ + public function getMessageParameters() { + return $this->messageParameters; + } + + /** + * @param string $message + * @return $this + * @throws \InvalidArgumentException if the message is invalid + * @since 11.0.0 + */ + public function setParsedMessage($message) { + if (!is_string($message)) { + throw new \InvalidArgumentException('The given parsed message is invalid'); + } + $this->messageParsed = $message; + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getParsedMessage() { + return $this->messageParsed; + } + + /** + * @param string $message + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 11.0.0 + */ + public function setRichMessage($message, array $parameters = []) { + if (!is_string($message)) { + throw new \InvalidArgumentException('The given parsed message is invalid'); + } + $this->messageRich = $message; + + if (!is_array($parameters)) { + throw new \InvalidArgumentException('The given message parameters are invalid'); + } + $this->messageRichParameters = $parameters; + + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getRichMessage() { + return $this->messageRich; + } + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichMessageParameters() { + return $this->messageRichParameters; + } + /** * Set the object of the activity * @@ -138,115 +386,166 @@ class Event implements IEvent { * @param int $objectId * @param string $objectName * @return IEvent + * @throws \InvalidArgumentException if the object is invalid * @since 8.2.0 */ public function setObject($objectType, $objectId, $objectName = '') { - $this->data['object_type'] = (string) $objectType; - $this->data['object_id'] = (int) $objectId; - $this->data['object_name'] = (string) $objectName; + if (!is_string($objectType) || isset($objectType[255])) { + throw new \InvalidArgumentException('The given object type is invalid'); + } + if (!is_int($objectId)) { + throw new \InvalidArgumentException('The given object id is invalid'); + } + if (!is_string($objectName) || isset($objectName[4000])) { + throw new \InvalidArgumentException('The given object name is invalid'); + } + $this->objectType = (string) $objectType; + $this->objectId = (int) $objectId; + $this->objectName = (string) $objectName; return $this; } + /** + * @return string + */ + public function getObjectType() { + return $this->objectType; + } + + /** + * @return string + */ + public function getObjectId() { + return $this->objectId; + } + + /** + * @return string + */ + public function getObjectName() { + return $this->objectName; + } + /** * Set the link of the activity * * @param string $link * @return IEvent + * @throws \InvalidArgumentException if the link is invalid * @since 8.2.0 */ public function setLink($link) { - $this->data['link'] = (string) $link; + if (!is_string($link) || isset($link[4000])) { + throw new \InvalidArgumentException('The given link is invalid'); + } + $this->link = (string) $link; return $this; } - /** - * @return string - */ - public function getApp() { - return $this->data['app']; - } - - /** - * @return string - */ - public function getType() { - return $this->data['type']; - } - - /** - * @return string - */ - public function getAffectedUser() { - return $this->data['affected_user']; - } - - /** - * @return string - */ - public function getAuthor() { - return $this->data['author']; - } - - /** - * @return int - */ - public function getTimestamp() { - return $this->data['timestamp']; - } - - /** - * @return string - */ - public function getSubject() { - return $this->data['subject']; - } - - /** - * @return array - */ - public function getSubjectParameters() { - return $this->data['subject_parameters']; - } - - /** - * @return string - */ - public function getMessage() { - return $this->data['message']; - } - - /** - * @return array - */ - public function getMessageParameters() { - return $this->data['message_parameters']; - } - - /** - * @return string - */ - public function getObjectType() { - return $this->data['object_type']; - } - - /** - * @return string - */ - public function getObjectId() { - return $this->data['object_id']; - } - - /** - * @return string - */ - public function getObjectName() { - return $this->data['object_name']; - } - /** * @return string */ public function getLink() { - return $this->data['link']; + return $this->link; + } + + /** + * @param string $icon + * @return $this + * @throws \InvalidArgumentException if the icon is invalid + * @since 11.0.0 + */ + public function setIcon($icon) { + if (!is_string($icon) || isset($icon[4000])) { + throw new \InvalidArgumentException('The given icon is invalid'); + } + $this->icon = $icon; + return $this; + } + + /** + * @return string + * @since 11.0.0 + */ + public function getIcon() { + return $this->icon; + } + + /** + * @param IEvent $child + * @since 11.0.0 + */ + public function setChildEvent(IEvent $child) { + $this->child = $child; + } + + /** + * @return IEvent|null + * @since 11.0.0 + */ + public function getChildEvent() { + return $this->child; + } + + /** + * @return bool + * @since 8.2.0 + */ + public function isValid() { + return + $this->isValidCommon() + && + $this->getSubject() !== '' + ; + } + + /** + * @return bool + * @since 8.2.0 + */ + public function isValidParsed() { + if ($this->getRichSubject() !== '' || !empty($this->getRichSubjectParameters())) { + try { + $this->richValidator->validate($this->getRichSubject(), $this->getRichSubjectParameters()); + } catch (InvalidObjectExeption $e) { + return false; + } + } + + if ($this->getRichMessage() !== '' || !empty($this->getRichMessageParameters())) { + try { + $this->richValidator->validate($this->getRichMessage(), $this->getRichMessageParameters()); + } catch (InvalidObjectExeption $e) { + return false; + } + } + + return + $this->isValidCommon() + && + $this->getParsedSubject() !== '' + ; + } + + /** + * @return bool + */ + protected function isValidCommon() { + return + $this->getApp() !== '' + && + $this->getType() !== '' + && + $this->getAffectedUser() !== '' + && + $this->getTimestamp() !== 0 + /** + * Disabled for BC with old activities + && + $this->getObjectType() !== '' + && + $this->getObjectId() !== 0 + */ + ; } } diff --git a/lib/private/Activity/LegacyFilter.php b/lib/private/Activity/LegacyFilter.php new file mode 100644 index 0000000000..eadb5b1558 --- /dev/null +++ b/lib/private/Activity/LegacyFilter.php @@ -0,0 +1,108 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Activity; + +use OCP\Activity\IFilter; +use OCP\Activity\IManager; + +class LegacyFilter implements IFilter { + + /** @var IManager */ + protected $manager; + /** @var string */ + protected $identifier; + /** @var string */ + protected $name; + /** @var bool */ + protected $isTopFilter; + + /** + * LegacySetting constructor. + * + * @param IManager $manager + * @param string $identifier + * @param string $name + * @param bool $isTopFilter + */ + public function __construct(IManager $manager, + $identifier, + $name, + $isTopFilter) { + $this->manager = $manager; + $this->identifier = $identifier; + $this->name = $name; + $this->isTopFilter = $isTopFilter; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return $this->identifier; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->name; + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return $this->isTopFilter ? 40 : 50; + } + + /** + * @return string Full URL to an icon, empty string when none is given + * @since 11.0.0 + */ + public function getIcon() { + // Old API was CSS class, so we can not use this... + return ''; + } + + /** + * @param string[] $types + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function filterTypes(array $types) { + return $this->manager->filterNotificationTypes($types, $this->getIdentifier()); + } + + /** + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function allowedApps() { + return []; + } +} + diff --git a/lib/private/Activity/LegacySetting.php b/lib/private/Activity/LegacySetting.php new file mode 100644 index 0000000000..27495afddb --- /dev/null +++ b/lib/private/Activity/LegacySetting.php @@ -0,0 +1,123 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OC\Activity; + +use OCP\Activity\ISetting; + +class LegacySetting implements ISetting { + + /** @var string */ + protected $identifier; + /** @var string */ + protected $name; + /** @var bool */ + protected $canChangeStream; + /** @var bool */ + protected $isDefaultEnabledStream; + /** @var bool */ + protected $canChangeMail; + /** @var bool */ + protected $isDefaultEnabledMail; + + /** + * LegacySetting constructor. + * + * @param string $identifier + * @param string $name + * @param bool $canChangeStream + * @param bool $isDefaultEnabledStream + * @param bool $canChangeMail + * @param bool $isDefaultEnabledMail + */ + public function __construct($identifier, + $name, + $canChangeStream, + $isDefaultEnabledStream, + $canChangeMail, + $isDefaultEnabledMail) { + $this->identifier = $identifier; + $this->name = $name; + $this->canChangeStream = $canChangeStream; + $this->isDefaultEnabledStream = $isDefaultEnabledStream; + $this->canChangeMail = $canChangeMail; + $this->isDefaultEnabledMail = $isDefaultEnabledMail; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return $this->identifier; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->name; + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 70; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return $this->canChangeStream; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream() { + return $this->isDefaultEnabledStream; + } + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail() { + return $this->canChangeMail; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail() { + return $this->isDefaultEnabledMail; + } +} + diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php index 455bb3b8ee..805124dc60 100644 --- a/lib/private/Activity/Manager.php +++ b/lib/private/Activity/Manager.php @@ -27,11 +27,15 @@ namespace OC\Activity; use OCP\Activity\IConsumer; use OCP\Activity\IEvent; use OCP\Activity\IExtension; +use OCP\Activity\IFilter; use OCP\Activity\IManager; +use OCP\Activity\IProvider; +use OCP\Activity\ISetting; use OCP\IConfig; use OCP\IRequest; use OCP\IUser; use OCP\IUserSession; +use OCP\RichObjectStrings\IValidator; class Manager implements IManager { /** @var IRequest */ @@ -43,6 +47,9 @@ class Manager implements IManager { /** @var IConfig */ protected $config; + /** @var IValidator */ + protected $validator; + /** @var string */ protected $formattingObjectType; @@ -58,13 +65,16 @@ class Manager implements IManager { * @param IRequest $request * @param IUserSession $session * @param IConfig $config + * @param IValidator $validator */ public function __construct(IRequest $request, IUserSession $session, - IConfig $config) { + IConfig $config, + IValidator $validator) { $this->request = $request; $this->session = $session; $this->config = $config; + $this->validator = $validator; } /** @var \Closure[] */ @@ -147,7 +157,7 @@ class Manager implements IManager { * @return IEvent */ public function generateEvent() { - return new Event(); + return new Event($this->validator); } /** @@ -160,24 +170,10 @@ class Manager implements IManager { * - setSubject() * * @param IEvent $event - * @return null * @throws \BadMethodCallException if required values have not been set */ public function publish(IEvent $event) { - if (!$event->getApp()) { - throw new \BadMethodCallException('App not set', 10); - } - if (!$event->getType()) { - throw new \BadMethodCallException('Type not set', 11); - } - if ($event->getAffectedUser() === null) { - throw new \BadMethodCallException('Affected user not set', 12); - } - if ($event->getSubject() === null || $event->getSubjectParameters() === null) { - throw new \BadMethodCallException('Subject not set', 13); - } - - if ($event->getAuthor() === null) { + if ($event->getAuthor() === '') { if ($this->session->getUser() instanceof IUser) { $event->setAuthor($this->session->getUser()->getUID()); } @@ -187,6 +183,10 @@ class Manager implements IManager { $event->setTimestamp(time()); } + if (!$event->isValid()) { + throw new \BadMethodCallException('The given event is invalid'); + } + foreach ($this->getConsumers() as $c) { $c->receive($event); } @@ -203,7 +203,6 @@ class Manager implements IManager { * @param string $affectedUser Recipient of the activity * @param string $type Type of the notification * @param int $priority Priority of the notification - * @return null */ public function publishActivity($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority) { $event = $this->generateEvent(); @@ -235,59 +234,195 @@ class Manager implements IManager { * In order to improve lazy loading a closure can be registered which will be called in case * activity consumers are actually requested * - * $callable has to return an instance of OCA\Activity\IConsumer + * $callable has to return an instance of OCA\Activity\IExtension * * @param \Closure $callable - * @return void */ public function registerExtension(\Closure $callable) { array_push($this->extensionsClosures, $callable); $this->extensions = []; } + /** @var string[] */ + protected $filterClasses = []; + + /** @var IFilter[] */ + protected $filters = []; + + /** @var bool */ + protected $loadedLegacyFilters = false; + /** - * Will return additional notification types as specified by other apps - * - * @param string $languageCode - * @return array + * @param string $filter Class must implement OCA\Activity\IFilter + * @return void */ - public function getNotificationTypes($languageCode) { - $filesNotificationTypes = []; - $sharingNotificationTypes = []; - - $notificationTypes = array(); - foreach ($this->getExtensions() as $c) { - $result = $c->getNotificationTypes($languageCode); - if (is_array($result)) { - if (class_exists('\OCA\Files\Activity', false) && $c instanceof \OCA\Files\Activity) { - $filesNotificationTypes = $result; - continue; - } - if (class_exists('\OCA\Files_Sharing\Activity', false) && $c instanceof \OCA\Files_Sharing\Activity) { - $sharingNotificationTypes = $result; - continue; - } - - $notificationTypes = array_merge($notificationTypes, $result); - } - } - - return array_merge($filesNotificationTypes, $sharingNotificationTypes, $notificationTypes); + public function registerFilter($filter) { + $this->filterClasses[$filter] = false; } /** - * @param string $method - * @return array + * @return IFilter[] + * @throws \InvalidArgumentException */ - public function getDefaultTypes($method) { - $defaultTypes = array(); - foreach ($this->getExtensions() as $c) { - $types = $c->getDefaultTypes($method); - if (is_array($types)) { - $defaultTypes = array_merge($types, $defaultTypes); + public function getFilters() { + if (!$this->loadedLegacyFilters) { + $legacyFilters = $this->getNavigation(); + + foreach ($legacyFilters['top'] as $filter => $data) { + $this->filters[$filter] = new LegacyFilter( + $this, $filter, $data['name'], true + ); } + + foreach ($legacyFilters['apps'] as $filter => $data) { + $this->filters[$filter] = new LegacyFilter( + $this, $filter, $data['name'], false + ); + } + $this->loadedLegacyFilters = true; } - return $defaultTypes; + + foreach ($this->filterClasses as $class => $false) { + /** @var IFilter $filter */ + $filter = \OC::$server->query($class); + + if (!$filter instanceof IFilter) { + throw new \InvalidArgumentException('Invalid activity filter registered'); + } + + $this->filters[$filter->getIdentifier()] = $filter; + + unset($this->filterClasses[$class]); + } + return $this->filters; + } + + /** + * @param string $id + * @return IFilter + * @throws \InvalidArgumentException when the filter was not found + * @since 11.0.0 + */ + public function getFilterById($id) { + $filters = $this->getFilters(); + + if (isset($filters[$id])) { + return $filters[$id]; + } + + throw new \InvalidArgumentException('Requested filter does not exist'); + } + + /** @var string[] */ + protected $providerClasses = []; + + /** @var IProvider[] */ + protected $providers = []; + + /** + * @param string $provider Class must implement OCA\Activity\IProvider + * @return void + */ + public function registerProvider($provider) { + $this->providerClasses[$provider] = false; + } + + /** + * @return IProvider[] + * @throws \InvalidArgumentException + */ + public function getProviders() { + foreach ($this->providerClasses as $class => $false) { + /** @var IProvider $provider */ + $provider = \OC::$server->query($class); + + if (!$provider instanceof IProvider) { + throw new \InvalidArgumentException('Invalid activity provider registered'); + } + + $this->providers[] = $provider; + + unset($this->providerClasses[$class]); + } + return $this->providers; + } + + /** @var string[] */ + protected $settingsClasses = []; + + /** @var ISetting[] */ + protected $settings = []; + + /** @var bool */ + protected $loadedLegacyTypes = false; + + /** + * @param string $setting Class must implement OCA\Activity\ISetting + * @return void + */ + public function registerSetting($setting) { + $this->settingsClasses[$setting] = false; + } + + /** + * @return ISetting[] + * @throws \InvalidArgumentException + */ + public function getSettings() { + if (!$this->loadedLegacyTypes) { + $l = \OC::$server->getL10N('core'); + $legacyTypes = $this->getNotificationTypes($l->getLanguageCode()); + $streamTypes = $this->getDefaultTypes(IExtension::METHOD_STREAM); + $mailTypes = $this->getDefaultTypes(IExtension::METHOD_MAIL); + foreach ($legacyTypes as $type => $data) { + if (is_string($data)) { + $desc = $data; + $canChangeStream = true; + $canChangeMail = true; + } else { + $desc = $data['desc']; + $canChangeStream = in_array(IExtension::METHOD_STREAM, $data['methods']); + $canChangeMail = in_array(IExtension::METHOD_MAIL, $data['methods']); + } + + $this->settings[$type] = new LegacySetting( + $type, $desc, + $canChangeStream, in_array($type, $streamTypes), + $canChangeMail, in_array($type, $mailTypes) + ); + } + $this->loadedLegacyTypes = true; + } + + foreach ($this->settingsClasses as $class => $false) { + /** @var ISetting $setting */ + $setting = \OC::$server->query($class); + + if (!$setting instanceof ISetting) { + throw new \InvalidArgumentException('Invalid activity filter registered'); + } + + $this->settings[$setting->getIdentifier()] = $setting; + + unset($this->settingsClasses[$class]); + } + return $this->settings; + } + + /** + * @param string $id + * @return ISetting + * @throws \InvalidArgumentException when the setting was not found + * @since 11.0.0 + */ + public function getSettingById($id) { + $settings = $this->getSettings(); + + if (isset($settings[$id])) { + return $settings[$id]; + } + + throw new \InvalidArgumentException('Requested setting does not exist'); } /** @@ -390,94 +525,6 @@ class Manager implements IManager { return false; } - /** - * @return array - */ - public function getNavigation() { - $entries = array( - 'apps' => array(), - 'top' => array(), - ); - foreach ($this->getExtensions() as $c) { - $additionalEntries = $c->getNavigation(); - if (is_array($additionalEntries)) { - $entries['apps'] = array_merge($entries['apps'], $additionalEntries['apps']); - $entries['top'] = array_merge($entries['top'], $additionalEntries['top']); - } - } - - return $entries; - } - - /** - * @param string $filterValue - * @return boolean - */ - public function isFilterValid($filterValue) { - if (isset($this->validFilters[$filterValue])) { - return $this->validFilters[$filterValue]; - } - - foreach ($this->getExtensions() as $c) { - if ($c->isFilterValid($filterValue) === true) { - $this->validFilters[$filterValue] = true; - return true; - } - } - - $this->validFilters[$filterValue] = false; - return false; - } - - /** - * @param array $types - * @param string $filter - * @return array - */ - public function filterNotificationTypes($types, $filter) { - if (!$this->isFilterValid($filter)) { - return $types; - } - - foreach ($this->getExtensions() as $c) { - $result = $c->filterNotificationTypes($types, $filter); - if (is_array($result)) { - $types = $result; - } - } - return $types; - } - - /** - * @param string $filter - * @return array - */ - public function getQueryForFilter($filter) { - if (!$this->isFilterValid($filter)) { - return [null, null]; - } - - $conditions = array(); - $parameters = array(); - - foreach ($this->getExtensions() as $c) { - $result = $c->getQueryForFilter($filter); - if (is_array($result)) { - list($condition, $parameter) = $result; - if ($condition && is_array($parameter)) { - $conditions[] = $condition; - $parameters = array_merge($parameters, $parameter); - } - } - } - - if (empty($conditions)) { - return array(null, null); - } - - return array(' and ((' . implode(') or (', $conditions) . '))', $parameters); - } - /** * Set the user we need to use * @@ -531,4 +578,136 @@ class Manager implements IManager { // Token found login as that user return array_shift($users); } + + /** + * @return array + * @deprecated 11.0.0 - Use getFilters() instead + */ + public function getNavigation() { + $entries = array( + 'apps' => array(), + 'top' => array(), + ); + foreach ($this->getExtensions() as $c) { + $additionalEntries = $c->getNavigation(); + if (is_array($additionalEntries)) { + $entries['apps'] = array_merge($entries['apps'], $additionalEntries['apps']); + $entries['top'] = array_merge($entries['top'], $additionalEntries['top']); + } + } + + return $entries; + } + + /** + * @param string $filterValue + * @return boolean + * @deprecated 11.0.0 - Use getFilterById() instead + */ + public function isFilterValid($filterValue) { + if (isset($this->validFilters[$filterValue])) { + return $this->validFilters[$filterValue]; + } + + foreach ($this->getExtensions() as $c) { + if ($c->isFilterValid($filterValue) === true) { + $this->validFilters[$filterValue] = true; + return true; + } + } + + $this->validFilters[$filterValue] = false; + return false; + } + + /** + * @param array $types + * @param string $filter + * @return array + * @deprecated 11.0.0 - Use getFilterById()->filterTypes() instead + */ + public function filterNotificationTypes($types, $filter) { + if (!$this->isFilterValid($filter)) { + return $types; + } + + foreach ($this->getExtensions() as $c) { + $result = $c->filterNotificationTypes($types, $filter); + if (is_array($result)) { + $types = $result; + } + } + return $types; + } + + /** + * @param string $filter + * @return array + * @deprecated 11.0.0 - Use getFilterById() instead + */ + public function getQueryForFilter($filter) { + if (!$this->isFilterValid($filter)) { + return [null, null]; + } + + $conditions = array(); + $parameters = array(); + + foreach ($this->getExtensions() as $c) { + $result = $c->getQueryForFilter($filter); + if (is_array($result)) { + list($condition, $parameter) = $result; + if ($condition && is_array($parameter)) { + $conditions[] = $condition; + $parameters = array_merge($parameters, $parameter); + } + } + } + + if (empty($conditions)) { + return array(null, null); + } + + return array(' and ((' . implode(') or (', $conditions) . '))', $parameters); + } + + /** + * Will return additional notification types as specified by other apps + * + * @param string $languageCode + * @return array + * @deprecated 11.0.0 - Use getSettings() instead + */ + public function getNotificationTypes($languageCode) { + $notificationTypes = $sharingNotificationTypes = []; + foreach ($this->getExtensions() as $c) { + $result = $c->getNotificationTypes($languageCode); + if (is_array($result)) { + if (class_exists('\OCA\Files_Sharing\Activity', false) && $c instanceof \OCA\Files_Sharing\Activity) { + $sharingNotificationTypes = $result; + continue; + } + + $notificationTypes = array_merge($notificationTypes, $result); + } + } + + return array_merge($sharingNotificationTypes, $notificationTypes); + } + + /** + * @param string $method + * @return array + * @deprecated 11.0.0 - Use getSettings()->isDefaulEnabled() instead + */ + public function getDefaultTypes($method) { + $defaultTypes = array(); + foreach ($this->getExtensions() as $c) { + $types = $c->getDefaultTypes($method); + if (is_array($types)) { + $defaultTypes = array_merge($types, $defaultTypes); + } + } + return $defaultTypes; + } } diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php index 44f495534c..47ce28e6e9 100644 --- a/lib/private/App/InfoParser.php +++ b/lib/private/App/InfoParser.php @@ -110,6 +110,18 @@ class InfoParser { if (!array_key_exists('commands', $array)) { $array['commands'] = []; } + if (!array_key_exists('activity', $array)) { + $array['activity'] = []; + } + if (!array_key_exists('filters', $array['activity'])) { + $array['activity']['filters'] = []; + } + if (!array_key_exists('settings', $array['activity'])) { + $array['activity']['settings'] = []; + } + if (!array_key_exists('providers', $array['activity'])) { + $array['activity']['providers'] = []; + } if (array_key_exists('types', $array)) { if (is_array($array['types'])) { @@ -144,6 +156,15 @@ class InfoParser { if (isset($array['commands']['command']) && is_array($array['commands']['command'])) { $array['commands'] = $array['commands']['command']; } + if (isset($array['activity']['filters']['filter']) && is_array($array['activity']['filters']['filter'])) { + $array['activity']['filters'] = $array['activity']['filters']['filter']; + } + if (isset($array['activity']['settings']['setting']) && is_array($array['activity']['settings']['setting'])) { + $array['activity']['settings'] = $array['activity']['settings']['setting']; + } + if (isset($array['activity']['providers']['provider']) && is_array($array['activity']['providers']['provider'])) { + $array['activity']['providers'] = $array['activity']['providers']['provider']; + } if(!is_null($this->cache)) { $this->cache->set($fileCacheKey, json_encode($array)); diff --git a/lib/private/Server.php b/lib/private/Server.php index 8e41ba212a..abedf8230e 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -90,6 +90,7 @@ use OC\Tagging\TagMapper; use OCA\Theming\ThemingDefaults; use OCP\IL10N; use OCP\IServerContainer; +use OCP\RichObjectStrings\IValidator; use OCP\Security\IContentSecurityPolicyManager; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -394,9 +395,11 @@ class Server extends ServerContainer implements IServerContainer { return new \OC\Activity\Manager( $c->getRequest(), $c->getUserSession(), - $c->getConfig() + $c->getConfig(), + $c->query(IValidator::class) ); }); + $this->registerAlias(IValidator::class, Validator::class); $this->registerService('AvatarManager', function (Server $c) { return new AvatarManager( $c->getUserManager(), @@ -662,7 +665,7 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerService('NotificationManager', function (Server $c) { return new Manager( - $c->query(Validator::class) + $c->query(IValidator::class) ); }); $this->registerService('CapabilitiesManager', function (Server $c) { diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index a89a4650c5..33b18c0c6a 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -162,6 +162,23 @@ class OC_App { } \OC::$server->getEventLogger()->end('load_app_' . $app); } + + $info = self::getAppInfo($app); + if (!empty($info['activity']['filters'])) { + foreach ($info['activity']['filters'] as $filter) { + \OC::$server->getActivityManager()->registerFilter($filter); + } + } + if (!empty($info['activity']['settings'])) { + foreach ($info['activity']['settings'] as $setting) { + \OC::$server->getActivityManager()->registerSetting($setting); + } + } + if (!empty($info['activity']['providers'])) { + foreach ($info['activity']['providers'] as $provider) { + \OC::$server->getActivityManager()->registerProvider($provider); + } + } } /** diff --git a/lib/public/Activity/IEvent.php b/lib/public/Activity/IEvent.php index 0d4966e617..a12ba8642a 100644 --- a/lib/public/Activity/IEvent.php +++ b/lib/public/Activity/IEvent.php @@ -41,6 +41,7 @@ interface IEvent { * * @param string $app * @return IEvent + * @throws \InvalidArgumentException if the app id is invalid * @since 8.2.0 */ public function setApp($app); @@ -50,6 +51,7 @@ interface IEvent { * * @param string $type * @return IEvent + * @throws \InvalidArgumentException if the type is invalid * @since 8.2.0 */ public function setType($type); @@ -59,6 +61,7 @@ interface IEvent { * * @param string $user * @return IEvent + * @throws \InvalidArgumentException if the affected user is invalid * @since 8.2.0 */ public function setAffectedUser($user); @@ -68,6 +71,7 @@ interface IEvent { * * @param string $author * @return IEvent + * @throws \InvalidArgumentException if the author is invalid * @since 8.2.0 */ public function setAuthor($author); @@ -77,6 +81,7 @@ interface IEvent { * * @param int $timestamp * @return IEvent + * @throws \InvalidArgumentException if the timestamp is invalid * @since 8.2.0 */ public function setTimestamp($timestamp); @@ -87,20 +92,92 @@ interface IEvent { * @param string $subject * @param array $parameters * @return IEvent + * @throws \InvalidArgumentException if the subject or parameters are invalid * @since 8.2.0 */ public function setSubject($subject, array $parameters = []); + /** + * @param string $subject + * @return $this + * @throws \InvalidArgumentException if the subject is invalid + * @since 11.0.0 + */ + public function setParsedSubject($subject); + + /** + * @return string + * @since 11.0.0 + */ + public function getParsedSubject(); + + /** + * @param string $subject + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the subject or parameters are invalid + * @since 11.0.0 + */ + public function setRichSubject($subject, array $parameters = []); + + /** + * @return string + * @since 11.0.0 + */ + public function getRichSubject(); + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichSubjectParameters(); + /** * Set the message of the activity * * @param string $message * @param array $parameters * @return IEvent + * @throws \InvalidArgumentException if the message or parameters are invalid * @since 8.2.0 */ public function setMessage($message, array $parameters = []); + /** + * @param string $message + * @return $this + * @throws \InvalidArgumentException if the message is invalid + * @since 11.0.0 + */ + public function setParsedMessage($message); + + /** + * @return string + * @since 11.0.0 + */ + public function getParsedMessage(); + + /** + * @param string $message + * @param array $parameters + * @return $this + * @throws \InvalidArgumentException if the message or parameters are invalid + * @since 11.0.0 + */ + public function setRichMessage($message, array $parameters = []); + + /** + * @return string + * @since 11.0.0 + */ + public function getRichMessage(); + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichMessageParameters(); + /** * Set the object of the activity * @@ -108,6 +185,7 @@ interface IEvent { * @param int $objectId * @param string $objectName * @return IEvent + * @throws \InvalidArgumentException if the object is invalid * @since 8.2.0 */ public function setObject($objectType, $objectId, $objectName = ''); @@ -117,6 +195,7 @@ interface IEvent { * * @param string $link * @return IEvent + * @throws \InvalidArgumentException if the link is invalid * @since 8.2.0 */ public function setLink($link); @@ -198,4 +277,42 @@ interface IEvent { * @since 8.2.0 */ public function getLink(); + + /** + * @param string $icon + * @return $this + * @throws \InvalidArgumentException if the icon is invalid + * @since 11.0.0 + */ + public function setIcon($icon); + + /** + * @return string + * @since 11.0.0 + */ + public function getIcon(); + + /** + * @param IEvent $child + * @since 11.0.0 + */ + public function setChildEvent(IEvent $child); + + /** + * @return IEvent|null + * @since 11.0.0 + */ + public function getChildEvent(); + + /** + * @return bool + * @since 11.0.0 + */ + public function isValid(); + + /** + * @return bool + * @since 11.0.0 + */ + public function isValidParsed(); } diff --git a/lib/public/Activity/IExtension.php b/lib/public/Activity/IExtension.php index aaa4c86956..3f605a47e4 100644 --- a/lib/public/Activity/IExtension.php +++ b/lib/public/Activity/IExtension.php @@ -129,6 +129,7 @@ interface IExtension { * * @return array|false * @since 8.0.0 + * @deprecated 11.0.0 - Register an IFilter instead */ public function getNavigation(); @@ -138,6 +139,7 @@ interface IExtension { * @param string $filterValue * @return boolean * @since 8.0.0 + * @deprecated 11.0.0 - Register an IFilter instead */ public function isFilterValid($filterValue); @@ -149,6 +151,7 @@ interface IExtension { * @param string $filter * @return array|false * @since 8.0.0 + * @deprecated 11.0.0 - Register an IFilter instead */ public function filterNotificationTypes($types, $filter); @@ -161,6 +164,7 @@ interface IExtension { * @param string $filter * @return array|false * @since 8.0.0 + * @deprecated 11.0.0 - Register an IFilter instead */ public function getQueryForFilter($filter); } diff --git a/lib/public/Activity/IFilter.php b/lib/public/Activity/IFilter.php new file mode 100644 index 0000000000..e5b65a7d91 --- /dev/null +++ b/lib/public/Activity/IFilter.php @@ -0,0 +1,74 @@ + + * @author Morris Jobke + * @author Thomas Müller + * + * @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\Activity; + +/** + * Interface IFilter + * + * @package OCP\Activity + * @since 11.0.0 + */ +interface IFilter { + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier(); + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName(); + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority(); + + /** + * @return string Full URL to an icon, empty string when none is given + * @since 11.0.0 + */ + public function getIcon(); + + /** + * @param string[] $types + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function filterTypes(array $types); + + /** + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function allowedApps(); +} + diff --git a/lib/public/Activity/IManager.php b/lib/public/Activity/IManager.php index c1476e1a2a..2fe38ddb8d 100644 --- a/lib/public/Activity/IManager.php +++ b/lib/public/Activity/IManager.php @@ -64,7 +64,6 @@ interface IManager { * - setSubject() * * @param IEvent $event - * @return null * @since 8.2.0 */ public function publish(IEvent $event); @@ -80,7 +79,6 @@ interface IManager { * @param string $affectedUser Recipient of the activity * @param string $type Type of the notification * @param int $priority Priority of the notification - * @return null * @since 6.0.0 * @deprecated 8.2.0 Grab an IEvent from generateEvent() instead and use the publish() method */ @@ -110,6 +108,61 @@ interface IManager { */ public function registerExtension(\Closure $callable); + /** + * @param string $filter Class must implement OCA\Activity\IFilter + * @return void + * @since 11.0.0 + */ + public function registerFilter($filter); + + /** + * @return IFilter[] + * @since 11.0.0 + */ + public function getFilters(); + + /** + * @param string $id + * @return IFilter + * @throws \InvalidArgumentException when the filter was not found + * @since 11.0.0 + */ + public function getFilterById($id); + + /** + * @param string $setting Class must implement OCA\Activity\ISetting + * @return void + * @since 11.0.0 + */ + public function registerSetting($setting); + + /** + * @return ISetting[] + * @since 11.0.0 + */ + public function getSettings(); + + /** + * @param string $provider Class must implement OCA\Activity\IProvider + * @return void + * @since 11.0.0 + */ + public function registerProvider($provider); + + /** + * @return IProvider[] + * @since 11.0.0 + */ + public function getProviders(); + + /** + * @param string $id + * @return ISetting + * @throws \InvalidArgumentException when the setting was not found + * @since 11.0.0 + */ + public function getSettingById($id); + /** * Will return additional notification types as specified by other apps * @@ -120,6 +173,7 @@ interface IManager { * 'methods' => [\OCP\Activity\IExtension::METHOD_*], * ] * @since 8.0.0 - 8.2.0: Added support to allow limiting notifications to certain methods + * @deprecated 11.0.0 - Use getSettings() instead */ public function getNotificationTypes($languageCode); @@ -127,6 +181,7 @@ interface IManager { * @param string $method * @return array * @since 8.0.0 + * @deprecated 11.0.0 - Use getSettings()->isDefaulEnabled() instead */ public function getDefaultTypes($method); @@ -177,34 +232,6 @@ interface IManager { */ public function getGroupParameter($activity); - /** - * @return array - * @since 8.0.0 - */ - public function getNavigation(); - - /** - * @param string $filterValue - * @return boolean - * @since 8.0.0 - */ - public function isFilterValid($filterValue); - - /** - * @param array $types - * @param string $filter - * @return array - * @since 8.0.0 - */ - public function filterNotificationTypes($types, $filter); - - /** - * @param string $filter - * @return array - * @since 8.0.0 - */ - public function getQueryForFilter($filter); - /** * Set the user we need to use @@ -225,4 +252,36 @@ interface IManager { * @since 8.1.0 */ public function getCurrentUserId(); + + /** + * @return array + * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilters() instead + */ + public function getNavigation(); + + /** + * @param string $filterValue + * @return boolean + * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilterById() instead + */ + public function isFilterValid($filterValue); + + /** + * @param array $types + * @param string $filter + * @return array + * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilterById()->filterTypes() instead + */ + public function filterNotificationTypes($types, $filter); + + /** + * @param string $filter + * @return array + * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilterById() instead + */ + public function getQueryForFilter($filter); } diff --git a/lib/public/Activity/IProvider.php b/lib/public/Activity/IProvider.php new file mode 100644 index 0000000000..5b78e26f4b --- /dev/null +++ b/lib/public/Activity/IProvider.php @@ -0,0 +1,39 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCP\Activity; + +/** + * Interface IProvider + * + * @package OCP\Activity + * @since 11.0.0 + */ +interface IProvider { + /** + * @param IEvent $event + * @param IEvent|null $previousEvent + * @return IEvent + * @throws \InvalidArgumentException + * @since 11.0.0 + */ + public function parse(IEvent $event, IEvent $previousEvent = null); +} diff --git a/lib/public/Activity/ISetting.php b/lib/public/Activity/ISetting.php new file mode 100644 index 0000000000..786581bcae --- /dev/null +++ b/lib/public/Activity/ISetting.php @@ -0,0 +1,76 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCP\Activity; + +/** + * Interface ISetting + * + * @package OCP\Activity + * @since 11.0.0 + */ +interface ISetting { + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier(); + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName(); + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority(); + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream(); + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream(); + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail(); + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail(); +} + diff --git a/tests/data/app/expected-info.json b/tests/data/app/expected-info.json index fb596f296d..646f22bea8 100644 --- a/tests/data/app/expected-info.json +++ b/tests/data/app/expected-info.json @@ -77,5 +77,10 @@ }, "background-jobs": [], "two-factor-providers": [], - "commands": [] + "commands": [], + "activity": { + "filters": [], + "settings": [], + "providers": [] + } } diff --git a/tests/lib/Activity/ManagerTest.php b/tests/lib/Activity/ManagerTest.php index cf855dd281..13932f389f 100644 --- a/tests/lib/Activity/ManagerTest.php +++ b/tests/lib/Activity/ManagerTest.php @@ -10,6 +10,10 @@ namespace Test\Activity; +use OCP\IConfig; +use OCP\IRequest; +use OCP\IUserSession; +use OCP\RichObjectStrings\IValidator; use Test\TestCase; class ManagerTest extends TestCase { @@ -17,32 +21,28 @@ class ManagerTest extends TestCase { /** @var \OC\Activity\Manager */ private $activityManager; - /** @var \OCP\IRequest|\PHPUnit_Framework_MockObject_MockObject */ + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ protected $request; - - /** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ protected $session; - - /** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */ + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ protected $config; + /** @var IValidator|\PHPUnit_Framework_MockObject_MockObject */ + protected $validator; protected function setUp() { parent::setUp(); - $this->request = $this->getMockBuilder('OCP\IRequest') - ->disableOriginalConstructor() - ->getMock(); - $this->session = $this->getMockBuilder('OCP\IUserSession') - ->disableOriginalConstructor() - ->getMock(); - $this->config = $this->getMockBuilder('OCP\IConfig') - ->disableOriginalConstructor() - ->getMock(); + $this->request = $this->createMock(IRequest::class); + $this->session = $this->createMock(IUserSession::class); + $this->config = $this->createMock(IConfig::class); + $this->validator = $this->createMock(IValidator::class); $this->activityManager = new \OC\Activity\Manager( $this->request, $this->session, - $this->config + $this->config, + $this->validator ); $this->assertSame([], $this->invokePrivate($this->activityManager, 'getConsumers')); @@ -264,32 +264,26 @@ class ManagerTest extends TestCase { /** * @expectedException \BadMethodCallException - * @expectedExceptionMessage App not set - * @expectedExceptionCode 10 */ public function testPublishExceptionNoApp() { - $event = new \OC\Activity\Event(); + $event = $this->activityManager->generateEvent(); $this->activityManager->publish($event); } /** * @expectedException \BadMethodCallException - * @expectedExceptionMessage Type not set - * @expectedExceptionCode 11 */ public function testPublishExceptionNoType() { - $event = new \OC\Activity\Event(); + $event = $this->activityManager->generateEvent(); $event->setApp('test'); $this->activityManager->publish($event); } /** * @expectedException \BadMethodCallException - * @expectedExceptionMessage Affected user not set - * @expectedExceptionCode 12 */ public function testPublishExceptionNoAffectedUser() { - $event = new \OC\Activity\Event(); + $event = $this->activityManager->generateEvent(); $event->setApp('test') ->setType('test_type'); $this->activityManager->publish($event); @@ -297,11 +291,9 @@ class ManagerTest extends TestCase { /** * @expectedException \BadMethodCallException - * @expectedExceptionMessage Subject not set - * @expectedExceptionCode 13 */ public function testPublishExceptionNoSubject() { - $event = new \OC\Activity\Event(); + $event = $this->activityManager->generateEvent(); $event->setApp('test') ->setType('test_type') ->setAffectedUser('test_affected'); @@ -310,16 +302,17 @@ class ManagerTest extends TestCase { public function dataPublish() { return [ - [null], - ['test_author'], + [null, ''], + ['test_author', 'test_author'], ]; } /** * @dataProvider dataPublish - * @param string $author + * @param string|null $author + * @param string $expected */ - public function testPublish($author) { + public function testPublish($author, $expected) { if ($author !== null) { $authorObject = $this->getMockBuilder('OCP\IUser') ->disableOriginalConstructor() @@ -332,11 +325,12 @@ class ManagerTest extends TestCase { ->willReturn($authorObject); } - $event = new \OC\Activity\Event(); + $event = $this->activityManager->generateEvent(); $event->setApp('test') ->setType('test_type') ->setSubject('test_subject', []) - ->setAffectedUser('test_affected'); + ->setAffectedUser('test_affected') + ->setObject('file', 123); $consumer = $this->getMockBuilder('OCP\Activity\IConsumer') ->disableOriginalConstructor() @@ -344,10 +338,10 @@ class ManagerTest extends TestCase { $consumer->expects($this->once()) ->method('receive') ->with($event) - ->willReturnCallback(function(\OCP\Activity\IEvent $event) use ($author) { + ->willReturnCallback(function(\OCP\Activity\IEvent $event) use ($expected) { $this->assertLessThanOrEqual(time() + 2, $event->getTimestamp(), 'Timestamp not set correctly'); $this->assertGreaterThanOrEqual(time() - 2, $event->getTimestamp(), 'Timestamp not set correctly'); - $this->assertSame($author, $event->getAuthor(), 'Author name not set correctly'); + $this->assertSame($expected, $event->getAuthor(), 'Author name not set correctly'); }); $this->activityManager->registerConsumer(function () use ($consumer) { return $consumer; @@ -357,7 +351,7 @@ class ManagerTest extends TestCase { } public function testPublishAllManually() { - $event = new \OC\Activity\Event(); + $event = $this->activityManager->generateEvent(); $event->setApp('test_app') ->setType('test_type') ->setAffectedUser('test_affected') @@ -397,7 +391,7 @@ class ManagerTest extends TestCase { } public function testDeprecatedPublishActivity() { - $event = new \OC\Activity\Event(); + $event = $this->activityManager->generateEvent(); $event->setApp('test_app') ->setType('test_type') ->setAffectedUser('test_affected') @@ -428,7 +422,7 @@ class ManagerTest extends TestCase { // The following values can not be used via publishActivity() $this->assertLessThanOrEqual(time() + 2, $event->getTimestamp(), 'Timestamp not set correctly'); $this->assertGreaterThanOrEqual(time() - 2, $event->getTimestamp(), 'Timestamp not set correctly'); - $this->assertSame(null, $event->getAuthor(), 'Author not set correctly'); + $this->assertSame('', $event->getAuthor(), 'Author not set correctly'); $this->assertSame('', $event->getObjectType(), 'Object type should not be set'); $this->assertSame(0, $event->getObjectId(), 'Object ID should not be set'); });