diff options
144 files changed, 4274 insertions, 1372 deletions
diff --git a/apps/admin_audit/appinfo/info.xml b/apps/admin_audit/appinfo/info.xml index 0da8b115dc8..14bce64aabc 100644 --- a/apps/admin_audit/appinfo/info.xml +++ b/apps/admin_audit/appinfo/info.xml @@ -9,7 +9,7 @@ <author>Nextcloud</author> <version>1.1.0</version> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <types> <logging/> diff --git a/apps/comments/appinfo/info.xml b/apps/comments/appinfo/info.xml index 39584694e23..1c0a38e5668 100644 --- a/apps/comments/appinfo/info.xml +++ b/apps/comments/appinfo/info.xml @@ -8,7 +8,7 @@ <default_enable/> <version>1.1.0</version> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <types> <logging/> diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 4d3b1757284..314391a1448 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -15,7 +15,7 @@ <webdav>appinfo/v1/publicwebdav.php</webdav> </public> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <background-jobs> <job>OCA\DAV\CardDAV\SyncJob</job> diff --git a/apps/dav/lib/CalDAV/Activity/Backend.php b/apps/dav/lib/CalDAV/Activity/Backend.php index 6cf09f6de46..6a557fef7bd 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/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index a35eed88073..95222dafec9 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -159,6 +159,7 @@ class Auth extends AbstractBasic { } catch (Exception $e) { $class = get_class($e); $msg = $e->getMessage(); + \OC::$server->getLogger()->logException($e); throw new ServiceUnavailable("$class: $msg"); } } diff --git a/apps/encryption/appinfo/info.xml b/apps/encryption/appinfo/info.xml index f6632b2ae92..0bb3efebf5f 100644 --- a/apps/encryption/appinfo/info.xml +++ b/apps/encryption/appinfo/info.xml @@ -25,7 +25,7 @@ </types> <dependencies> <lib>openssl</lib> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <settings> <admin>OCA\Encryption\Settings\Admin</admin> diff --git a/apps/federatedfilesharing/appinfo/info.xml b/apps/federatedfilesharing/appinfo/info.xml index 984235f0851..6a414496f37 100644 --- a/apps/federatedfilesharing/appinfo/info.xml +++ b/apps/federatedfilesharing/appinfo/info.xml @@ -9,7 +9,7 @@ <namespace>FederatedFileSharing</namespace> <category>other</category> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <settings> <admin>OCA\FederatedFileSharing\Settings\Admin</admin> diff --git a/apps/federatedfilesharing/js/settings-personal.js b/apps/federatedfilesharing/js/settings-personal.js index f89022dc8a0..04096cb0416 100644 --- a/apps/federatedfilesharing/js/settings-personal.js +++ b/apps/federatedfilesharing/js/settings-personal.js @@ -20,14 +20,21 @@ $(document).ready(function() { } }); + $('#fileSharingSettings .clipboardButton').tooltip({placement: 'bottom', title: t('core', 'Copy'), trigger: 'hover'}); + // Clipboard! var clipboard = new Clipboard('.clipboardButton'); clipboard.on('success', function(e) { $input = $(e.trigger); - $input.tooltip({placement: 'bottom', trigger: 'manual', title: t('core', 'Copied!')}); - $input.tooltip('show'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copied!')) + .tooltip('fixTitle') + .tooltip({placement: 'bottom', trigger: 'manual'}) + .tooltip('show'); _.delay(function() { - $input.tooltip('hide'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copy')) + .tooltip('fixTitle'); }, 3000); }); clipboard.on('error', function (e) { @@ -41,14 +48,18 @@ $(document).ready(function() { actionMsg = t('core', 'Press Ctrl-C to copy.'); } - $input.tooltip({ - placement: 'bottom', - trigger: 'manual', - title: actionMsg - }); - $input.tooltip('show'); + $input.tooltip('hide') + .attr('data-original-title', actionMsg) + .tooltip('fixTitle') + .tooltip({placement: 'bottom', trigger: 'manual'}) + .tooltip('show'); _.delay(function () { - $input.tooltip('hide'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copy')) + .tooltip('fixTitle'); }, 3000); }); + + + $('#fileSharingSettings .hasTooltip').tooltip({placement: 'right'}); }); diff --git a/apps/federation/appinfo/info.xml b/apps/federation/appinfo/info.xml index cec3f8341a9..a5eeb133d9e 100644 --- a/apps/federation/appinfo/info.xml +++ b/apps/federation/appinfo/info.xml @@ -9,7 +9,7 @@ <namespace>Federation</namespace> <category>other</category> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <default_enable/> <types> diff --git a/apps/files/appinfo/app.php b/apps/files/appinfo/app.php index afb327e24ba..a194bb5e795 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 37a85f33afc..1992b94a03c 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -11,12 +11,31 @@ <filesystem/> </types> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <documentation> <user>user-files</user> </documentation> + <activity> + <settings> + <setting>OCA\Files\Activity\Settings\FileChanged</setting> + <setting>OCA\Files\Activity\Settings\FileCreated</setting> + <setting>OCA\Files\Activity\Settings\FileDeleted</setting> + <setting>OCA\Files\Activity\Settings\FileFavorite</setting> + <setting>OCA\Files\Activity\Settings\FileRestored</setting> + </settings> + + <filters> + <filter>OCA\Files\Activity\Filter\FileChanges</filter> + <filter>OCA\Files\Activity\Filter\Favorites</filter> + </filters> + + <providers> + <provider>OCA\Files\Activity\Provider</provider> + </providers> + </activity> + <background-jobs> <job>OCA\Files\BackgroundJob\ScanFiles</job> <job>OCA\Files\BackgroundJob\DeleteOrphanedItems</job> diff --git a/apps/files/img/add-color.svg b/apps/files/img/add-color.svg new file mode 100644 index 00000000000..acf5543c43f --- /dev/null +++ b/apps/files/img/add-color.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1"> + <g transform="matrix(-.70711 -.70711 .70711 -.70711 -724.85 753.16)" fill="#00d400"> + <path d="m3.7547 1041.6 1.4142-1.4142 3.5355 3.5355 3.5355-3.5355 1.4142 1.4142-3.5355 3.5355 3.5355 3.5356-1.4142 1.4142-3.5355-3.5356-3.5164 3.5547-1.4333-1.4333 3.5355-3.5356z" fill="#00d400"/> + </g> +</svg> diff --git a/apps/files/img/change.svg b/apps/files/img/change.svg new file mode 100644 index 00000000000..cbc5d982b30 --- /dev/null +++ b/apps/files/img/change.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1"> + <path d="m7.9375 0c-3.1175 0.023214-6.0756 1.876-7.3438 4.9375l2.7812 1.1563c1.0568-2.5513 3.98-3.7756 6.5312-2.7188 0.8628 0.3573 1.5738 0.9274 2.0938 1.625l-2 2h6v-6l-1.875 1.875c-0.802-0.9616-1.825-1.7688-3.063-2.2812-1.02-0.4227-2.0853-0.60149-3.1245-0.59375z"/> + <path d="m0 9.5v6l2.0938-2.094c0.7676 0.843 1.7205 1.535 2.8437 2 4.082 1.691 8.7775-0.262 10.468-4.344l-2.781-1.1558c-1.057 2.5508-3.98 3.7758-6.5312 2.7188-0.7435-0.308-1.3509-0.805-1.8438-1.375l1.75-1.75h-6z"/> +</svg> diff --git a/apps/files/img/delete-color.svg b/apps/files/img/delete-color.svg new file mode 100644 index 00000000000..810c63e811b --- /dev/null +++ b/apps/files/img/delete-color.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1"> + <path d="m12.95 11.536-1.414 1.414-3.536-3.5358-3.5355 3.5358-1.4142-1.414 3.5355-3.536-3.5355-3.5356 1.4142-1.4142 3.5355 3.5356 3.516-3.5547 1.434 1.4333-3.5357 3.5356z" fill="#d40000"/> +</svg> diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index c53fa4f3d66..d32c3ba7c9e 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -923,7 +923,8 @@ tr, fileData, newTrs = [], - isAllSelected = this.isAllSelected(); + isAllSelected = this.isAllSelected(), + showHidden = this._filesConfig.get('showhidden'); if (index >= this.files.length) { return false; @@ -947,7 +948,10 @@ } newTrs.push(tr); index++; - count--; + // only count visible rows + if (showHidden || !tr.hasClass('hidden-file')) { + count--; + } } // trigger event for newly added rows diff --git a/apps/files/lib/Activity.php b/apps/files/lib/Activity.php deleted file mode 100644 index 25146456b4c..00000000000 --- a/apps/files/lib/Activity.php +++ /dev/null @@ -1,452 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OCA\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 <strong>created</strong>'), - self::TYPE_SHARE_CHANGED => (string) $l->t('A file or folder has been <strong>changed</strong> or <strong>renamed</strong>'), - self::TYPE_FAVORITES => [ - 'desc' => (string) $l->t('Limit notifications about creation and changes to your <strong>favorite files</strong> <em>(Stream only)</em>'), - 'methods' => [self::METHOD_STREAM], - ], - self::TYPE_SHARE_DELETED => (string) $l->t('A file or folder has been <strong>deleted</strong>'), - self::TYPE_SHARE_RESTORED => (string) $l->t('A file or folder has been <strong>restored</strong>'), - ]; - } - - /** - * 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 00000000000..2639ae847fc --- /dev/null +++ b/apps/files/lib/Activity/Filter/Favorites.php @@ -0,0 +1,160 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 00000000000..dc7daf96bac --- /dev/null +++ b/apps/files/lib/Activity/Filter/FileChanges.php @@ -0,0 +1,95 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 index f5660de4b37..d03d6e8e974 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 00000000000..e95522a7d08 --- /dev/null +++ b/apps/files/lib/Activity/Provider.php @@ -0,0 +1,276 @@ +<?php +/** + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\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 00000000000..1c20fb6f01a --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileChanged.php @@ -0,0 +1,98 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 <strong>changed</strong> or <strong>renamed</strong>'); + } + + /** + * @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 00000000000..dfde00ae7ec --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileCreated.php @@ -0,0 +1,98 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 <strong>created</strong>'); + } + + /** + * @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 00000000000..c4948ded2fa --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileDeleted.php @@ -0,0 +1,98 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 <strong>deleted</strong>'); + } + + /** + * @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 00000000000..b2f20688df9 --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileFavorite.php @@ -0,0 +1,98 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 <strong>favorite files</strong> <em>(Stream only)</em>'); + } + + /** + * @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 00000000000..cedfef441ed --- /dev/null +++ b/apps/files/lib/Activity/Settings/FileRestored.php @@ -0,0 +1,98 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 <strong>restored</strong>'); + } + + /** + * @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 65e914c1a54..00000000000 --- a/apps/files/tests/ActivityTest.php +++ /dev/null @@ -1,374 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OCA\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/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 15dab3b9882..d8fc3907d78 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -1120,6 +1120,34 @@ describe('OCA.Files.FileList tests', function() { expect(fileList.files.length).toEqual(65); expect($('#fileList tr').length).toEqual(20); }); + it('renders the full first page despite hidden rows', function() { + filesConfig.set('showhidden', false); + var files = _.map(generateFiles(0, 23), function(data) { + return _.extend(data, { + name: '.' + data.name + }); + }); + // only hidden files + one visible + files.push(testFiles[0]); + fileList.setFiles(files); + expect(fileList.files.length).toEqual(25); + // render 24 hidden elements + the visible one + expect($('#fileList tr').length).toEqual(25); + }); + it('renders the full first page despite hidden rows', function() { + filesConfig.set('showhidden', true); + var files = _.map(generateFiles(0, 23), function(data) { + return _.extend(data, { + name: '.' + data.name + }); + }); + // only hidden files + one visible + files.push(testFiles[0]); + fileList.setFiles(files); + expect(fileList.files.length).toEqual(25); + // render 20 first hidden elements as visible + expect($('#fileList tr').length).toEqual(20); + }); it('renders the second page when scrolling down (trigger nextPage)', function() { // TODO: can't simulate scrolling here, so calling nextPage directly fileList._nextPage(true); diff --git a/apps/files_external/appinfo/info.xml b/apps/files_external/appinfo/info.xml index 375d660bf62..c221ba4145f 100644 --- a/apps/files_external/appinfo/info.xml +++ b/apps/files_external/appinfo/info.xml @@ -22,7 +22,7 @@ <namespace>Files_External</namespace> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <settings> diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index cc97f65a5e6..65b8d4af5d1 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -15,7 +15,7 @@ Turning the feature off removes shared files and folders on the server for all s <filesystem/> </types> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <public> <files>public.php</files> diff --git a/apps/files_trashbin/appinfo/info.xml b/apps/files_trashbin/appinfo/info.xml index 2fd88ff7765..04b8a7293cb 100644 --- a/apps/files_trashbin/appinfo/info.xml +++ b/apps/files_trashbin/appinfo/info.xml @@ -16,7 +16,7 @@ To prevent a user from running out of disk space, the Deleted files app will not </types> <namespace>Files_Trashbin</namespace> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <documentation> <user>user-trashbin</user> diff --git a/apps/files_versions/appinfo/info.xml b/apps/files_versions/appinfo/info.xml index 026ed406d7c..5eb0df91d8d 100644 --- a/apps/files_versions/appinfo/info.xml +++ b/apps/files_versions/appinfo/info.xml @@ -14,7 +14,7 @@ In addition to the expiry of versions, the versions app makes certain never to u </types> <namespace>Files_Versions</namespace> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <documentation> <user>user-versions</user> diff --git a/apps/provisioning_api/appinfo/info.xml b/apps/provisioning_api/appinfo/info.xml index 009bdca0c1e..0ebcee9a7f7 100644 --- a/apps/provisioning_api/appinfo/info.xml +++ b/apps/provisioning_api/appinfo/info.xml @@ -23,6 +23,6 @@ <prevent_group_restriction/> </types> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> </info> diff --git a/apps/sharebymail/appinfo/info.xml b/apps/sharebymail/appinfo/info.xml index f1771fc9551..6c4882d349f 100644 --- a/apps/sharebymail/appinfo/info.xml +++ b/apps/sharebymail/appinfo/info.xml @@ -9,7 +9,7 @@ <namespace>ShareByMail</namespace> <category>other</category> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <default_enable/> </info> diff --git a/apps/systemtags/appinfo/info.xml b/apps/systemtags/appinfo/info.xml index 0a98d7ae680..3ed01d3d8c0 100644 --- a/apps/systemtags/appinfo/info.xml +++ b/apps/systemtags/appinfo/info.xml @@ -9,7 +9,7 @@ <default_enable/> <version>1.1.3</version> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <namespace>SystemTags</namespace> <types> diff --git a/apps/testing/appinfo/info.xml b/apps/testing/appinfo/info.xml index 0acccf357fb..41b07447d55 100644 --- a/apps/testing/appinfo/info.xml +++ b/apps/testing/appinfo/info.xml @@ -7,6 +7,6 @@ <author>Joas Schilling</author> <version>1.1.0</version> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> </info> diff --git a/apps/theming/appinfo/info.xml b/apps/theming/appinfo/info.xml index 423d11d2aef..3a8867dc6f4 100644 --- a/apps/theming/appinfo/info.xml +++ b/apps/theming/appinfo/info.xml @@ -10,7 +10,7 @@ <category>other</category> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <types> diff --git a/apps/theming/css/settings-admin.css b/apps/theming/css/settings-admin.css index 5d2b08f5e43..60b9c080ad6 100644 --- a/apps/theming/css/settings-admin.css +++ b/apps/theming/css/settings-admin.css @@ -6,12 +6,30 @@ display: none; } +#theming div > label { + position: relative; +} + #theming .theme-undo { + position: absolute; + top: -7px; + right: 7px; cursor: pointer; - opacity: .5; - padding: 11px 5px; + opacity: .3; + padding: 7px; vertical-align: top; display: inline-block; + visibility: hidden; +} +#theming form .theme-undo { + position: relative; + top: 4px; + left: 158px; + visibility: visible; +} +#theming input[type='text']:focus + .theme-undo, +#theming input[type='text']:active + .theme-undo { + visibility: visible; } #theming .icon-loading-small:after { @@ -48,4 +66,4 @@ div#theming_settings_msg { max-width: 20%; max-height: 20%; margin-top: 20px; -}
\ No newline at end of file +} diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index f274d245887..09b4a14f2b0 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -319,7 +319,7 @@ class ThemingController extends Controller { $responseCss .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . 'background-color: %s; background-position: center center; background-size:contain;' . - 'width:12px; height:12px; padding:0; margin:2px 6px 6px 9px; border-radius:1px;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . "}\n", \OC::$WEBROOT, $elementColor diff --git a/apps/theming/templates/settings-admin.php b/apps/theming/templates/settings-admin.php index b6c97040230..013b3bcf34f 100644 --- a/apps/theming/templates/settings-admin.php +++ b/apps/theming/templates/settings-admin.php @@ -34,50 +34,55 @@ style('theming', 'settings-admin'); <?php p($_['errorMessage']) ?> </p> <?php } else { ?> - <p> - <label><span><?php p($l->t('Name')) ?></span> + <div> + <label> + <span><?php p($l->t('Name')) ?></span> <input id="theming-name" type="text" placeholder="<?php p($l->t('Name')); ?>" value="<?php p($_['name']) ?>" maxlength="250" /> + <div data-setting="name" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></div> </label> - <span data-setting="name" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span> - </p> - <p> - <label><span><?php p($l->t('Web address')) ?></span> + </div> + <div> + <label> + <span><?php p($l->t('Web address')) ?></span> <input id="theming-url" type="text" placeholder="<?php p($l->t('Web address https://…')); ?>" value="<?php p($_['url']) ?>" maxlength="500" /> + <div data-setting="url" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></div> </label> - <span data-setting="url" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span> - </p> - <p> - <label><span><?php p($l->t('Slogan')) ?></span> + </div> + <div> + <label> + <span><?php p($l->t('Slogan')) ?></span> <input id="theming-slogan" type="text" placeholder="<?php p($l->t('Slogan')); ?>" value="<?php p($_['slogan']) ?>" maxlength="500" /> + <div data-setting="slogan" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></div> </label> - <span data-setting="slogan" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span> - </p> - <p> - <label><span><?php p($l->t('Color')) ?></span> + </div> + <div> + <label> + <span><?php p($l->t('Color')) ?></span> <input id="theming-color" type="text" class="jscolor" maxlength="6" value="<?php p($_['color']) ?>" /> + <div data-setting="color" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></div> </label> - <span data-setting="color" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span> - </p> - <p> - <form class="uploadButton" method="post" action="<?php p($_['uploadLogoRoute']) ?>"> + </div> + <div> + <form class="uploadButton inlineblock" method="post" action="<?php p($_['uploadLogoRoute']) ?>"> <input type="hidden" id="current-logoMime" name="current-logoMime" value="<?php p($_['logoMime']); ?>" /> <label for="uploadlogo"><span><?php p($l->t('Logo')) ?></span></label> <input id="uploadlogo" class="upload-logo-field" name="uploadlogo" type="file" /> <label for="uploadlogo" class="button icon-upload svg" id="uploadlogo" title="<?php p($l->t('Upload new logo')) ?>"></label> - <span data-setting="logoMime" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span> + <div data-setting="logoMime" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></div> </form> - </p> - <p> - <form class="uploadButton" method="post" action="<?php p($_['uploadLogoRoute']) ?>"> + </div> + <div> + <form class="uploadButton inlineblock" method="post" action="<?php p($_['uploadLogoRoute']) ?>"> <input type="hidden" id="current-backgroundMime" name="current-backgroundMime" value="<?php p($_['backgroundMime']); ?>" /> <label for="upload-login-background"><span><?php p($l->t('Log in image')) ?></span></label> <input id="upload-login-background" class="upload-logo-field" name="upload-login-background" type="file"> <label for="upload-login-background" class="button icon-upload svg" id="upload-login-background" title="<?php p($l->t("Upload new login background")) ?>"></label> - <span data-setting="backgroundMime" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></span> + <div data-setting="backgroundMime" data-toggle="tooltip" data-original-title="<?php p($l->t('reset to default')); ?>" class="theme-undo icon icon-history"></div> </form> - </p> - <div id="theming-preview" style="background-color:<?php p($_['color']);?>; background-image:url(<?php p($_['background']); ?>);"> - <img src="<?php p($_['logo']); ?>" id="theming-preview-logo" /> - </div> + </div> + + <div id="theming-preview" style="background-color:<?php p($_['color']);?>; background-image:url(<?php p($_['background']); ?>);"> + <img src="<?php p($_['logo']); ?>" id="theming-preview-logo" /> + </div> <?php } ?> </div> diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php index 4325e1988b2..d9d5005e25f 100644 --- a/apps/theming/tests/Controller/ThemingControllerTest.php +++ b/apps/theming/tests/Controller/ThemingControllerTest.php @@ -428,7 +428,7 @@ class ThemingControllerTest extends TestCase { $expectedData .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . 'background-color: %s; background-position: center center; background-size:contain;' . - 'width:12px; height:12px; padding:0; margin:2px 6px 6px 9px; border-radius:1px;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . "}\n", \OC::$WEBROOT, $color @@ -517,7 +517,7 @@ class ThemingControllerTest extends TestCase { $expectedData .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . 'background-color: #555555; background-position: center center; background-size:contain;' . - 'width:12px; height:12px; padding:0; margin:2px 6px 6px 9px; border-radius:1px;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . "}\n", \OC::$WEBROOT ); @@ -691,7 +691,7 @@ class ThemingControllerTest extends TestCase { $expectedData .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . 'background-color: %s; background-position: center center; background-size:contain;' . - 'width:12px; height:12px; padding:0; margin:2px 6px 6px 9px; border-radius:1px;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . "}\n", \OC::$WEBROOT, $color @@ -797,7 +797,7 @@ class ThemingControllerTest extends TestCase { $expectedData .= sprintf('input[type="checkbox"].checkbox:checked:enabled:not(.checkbox--white) + label:before {' . 'background-image:url(\'%s/core/img/actions/checkmark-white.svg\');' . 'background-color: #555555; background-position: center center; background-size:contain;' . - 'width:12px; height:12px; padding:0; margin:2px 6px 6px 9px; border-radius:1px;' . + 'width:12px; height:12px; padding:0; margin:2px 6px 6px 2px; border-radius:1px;' . "}\n", \OC::$WEBROOT ); diff --git a/apps/twofactor_backupcodes/appinfo/info.xml b/apps/twofactor_backupcodes/appinfo/info.xml index 7b9ef2d90e4..d5dd3f4db9e 100644 --- a/apps/twofactor_backupcodes/appinfo/info.xml +++ b/apps/twofactor_backupcodes/appinfo/info.xml @@ -14,6 +14,6 @@ </two-factor-providers> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> </info> diff --git a/apps/updatenotification/appinfo/info.xml b/apps/updatenotification/appinfo/info.xml index 2fe400a3587..4cd84ac827b 100644 --- a/apps/updatenotification/appinfo/info.xml +++ b/apps/updatenotification/appinfo/info.xml @@ -9,7 +9,7 @@ <namespace>UpdateNotification</namespace> <default_enable/> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <background-jobs> diff --git a/apps/updatenotification/js/admin.js b/apps/updatenotification/js/admin.js index 813ec48c87a..589765348af 100644 --- a/apps/updatenotification/js/admin.js +++ b/apps/updatenotification/js/admin.js @@ -72,4 +72,6 @@ $(document).ready(function(){ groups = JSON.stringify(groups); OCP.AppConfig.setValue('updatenotification', 'notify_groups', groups); }); + + $('#oca_updatenotification_section .icon-info').tooltip({placement: 'right'}); }); diff --git a/apps/user_ldap/appinfo/info.xml b/apps/user_ldap/appinfo/info.xml index b16824925c0..87eb61a0fba 100644 --- a/apps/user_ldap/appinfo/info.xml +++ b/apps/user_ldap/appinfo/info.xml @@ -18,7 +18,7 @@ A user logs into ownCloud with their LDAP or AD credentials, and is granted acce </documentation> <dependencies> <lib>ldap</lib> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <namespace>User_LDAP</namespace> diff --git a/apps/workflowengine/appinfo/info.xml b/apps/workflowengine/appinfo/info.xml index aae234cc717..83f4be9e398 100644 --- a/apps/workflowengine/appinfo/info.xml +++ b/apps/workflowengine/appinfo/info.xml @@ -18,7 +18,7 @@ </types> <dependencies> - <owncloud min-version="9.2" max-version="9.2" /> + <nextcloud min-version="11" max-version="11" /> </dependencies> <settings> diff --git a/core/Command/Encryption/DecryptAll.php b/core/Command/Encryption/DecryptAll.php index 7d77cc62e67..e02d7be5bb6 100644 --- a/core/Command/Encryption/DecryptAll.php +++ b/core/Command/Encryption/DecryptAll.php @@ -134,7 +134,7 @@ class DecryptAll extends Command { $uid = $input->getArgument('user'); if ($uid === '') { - $message = 'your ownCloud'; + $message = 'your Nextcloud'; } else { $message = "$uid's account"; } diff --git a/core/css/multiselect.css b/core/css/multiselect.css index cc1d6a3b468..8bcbd0e563d 100644 --- a/core/css/multiselect.css +++ b/core/css/multiselect.css @@ -31,9 +31,6 @@ ul.multiselectoptions.up { ul.multiselectoptions>li { overflow: hidden; white-space: nowrap; -} - -ul.multiselectoptions > li > input[type="checkbox"]+label:before { margin-left: 7px; } ul.multiselectoptions > li input[type='checkbox']+label { @@ -106,6 +103,7 @@ ul.multiselectoptions input.new { ul.multiselectoptions > li.creator { padding: 10px; + margin: 0; font-weight: bold; } ul.multiselectoptions > li.creator > input { diff --git a/core/js/files/client.js b/core/js/files/client.js index fdc51c4a197..87559b2084c 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -734,7 +734,7 @@ /** * Returns the dav.Client instance used internally * - * @since 9.2 + * @since 11.0.0 * @return {dav.Client} */ getClient: function() { @@ -744,7 +744,7 @@ /** * Returns the user name * - * @since 9.2 + * @since 11.0.0 * @return {String} userName */ getUserName: function() { @@ -754,7 +754,7 @@ /** * Returns the password * - * @since 9.2 + * @since 11.0.0 * @return {String} password */ getPassword: function() { @@ -764,7 +764,7 @@ /** * Returns the base URL * - * @since 9.2 + * @since 11.0.0 * @return {String} base URL */ getBaseUrl: function() { diff --git a/core/js/public/appconfig.js b/core/js/public/appconfig.js index cde2700c86c..de04f334ca8 100644 --- a/core/js/public/appconfig.js +++ b/core/js/public/appconfig.js @@ -20,7 +20,7 @@ /** * @namespace - * @since 9.2.0 + * @since 11.0.0 */ OCP.AppConfig = { /** @@ -46,7 +46,7 @@ OCP.AppConfig = { /** * @param {Object} [options] * @param {function} [options.success] - * @since 9.2.0 + * @since 11.0.0 */ getApps: function(options) { this._call('get', '', options); @@ -57,7 +57,7 @@ OCP.AppConfig = { * @param {Object} [options] * @param {function} [options.success] * @param {function} [options.error] - * @since 9.2.0 + * @since 11.0.0 */ getKeys: function(app, options) { this._call('get', '/' + app, options); @@ -70,7 +70,7 @@ OCP.AppConfig = { * @param {Object} [options] * @param {function} [options.success] * @param {function} [options.error] - * @since 9.2.0 + * @since 11.0.0 */ getValue: function(app, key, defaultValue, options) { options = options || {}; @@ -88,7 +88,7 @@ OCP.AppConfig = { * @param {Object} [options] * @param {function} [options.success] * @param {function} [options.error] - * @since 9.2.0 + * @since 11.0.0 */ setValue: function(app, key, value, options) { options = options || {}; @@ -105,7 +105,7 @@ OCP.AppConfig = { * @param {Object} [options] * @param {function} [options.success] * @param {function} [options.error] - * @since 9.2.0 + * @since 11.0.0 */ deleteKey: function(app, key, options) { this._call('delete', '/' + app + '/' + key, options); diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index bbc8f1b4547..85a20dce01a 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -134,10 +134,15 @@ var clipboard = new Clipboard('.clipboardButton'); clipboard.on('success', function(e) { var $input = $(e.trigger); - $input.tooltip({placement: 'bottom', trigger: 'manual', title: t('core', 'Copied!')}); - $input.tooltip('show'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copied!')) + .tooltip('fixTitle') + .tooltip({placement: 'bottom', trigger: 'manual'}) + .tooltip('show'); _.delay(function() { - $input.tooltip('hide'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copy')) + .tooltip('fixTitle'); }, 3000); }); clipboard.on('error', function (e) { @@ -151,14 +156,15 @@ actionMsg = t('core', 'Press Ctrl-C to copy.'); } - $input.tooltip({ - placement: 'bottom', - trigger: 'manual', - title: actionMsg - }); - $input.tooltip('show'); + $input.tooltip('hide') + .attr('data-original-title', actionMsg) + .tooltip('fixTitle') + .tooltip({placement: 'bottom', trigger: 'manual'}) + .tooltip('show'); _.delay(function () { - $input.tooltip('hide'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copy')) + .tooltip('fixTitle'); }, 3000); }); @@ -336,6 +342,8 @@ mailButtonText: t('core', 'Send') })); + this.$el.find('.clipboardButton').tooltip({placement: 'bottom', title: t('core', 'Copy'), trigger: 'hover'}); + this.delegateEvents(); return this; diff --git a/core/js/sharedialogview.js b/core/js/sharedialogview.js index b277a1226c7..0a29dec73ca 100644 --- a/core/js/sharedialogview.js +++ b/core/js/sharedialogview.js @@ -400,7 +400,7 @@ var infoTemplate = this._getRemoteShareInfoTemplate(); remoteShareInfo = infoTemplate({ docLink: this.configModel.getFederatedShareDocLink(), - tooltip: t('core', 'Share with people on other servers using the syntax username@example.com/nextcloud') + tooltip: t('core', 'Share with people on other servers using their Federated Cloud ID username@example.com/nextcloud') }); } diff --git a/core/shipped.json b/core/shipped.json index 9855a663847..f831d17f36a 100644 --- a/core/shipped.json +++ b/core/shipped.json @@ -21,6 +21,7 @@ "files_videoplayer", "firstrunwizard", "gallery", + "logreader", "notifications", "password_policy", "provisioning_api", diff --git a/db_structure.xml b/db_structure.xml index 09dbde710d3..c7e1e072a8e 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -1152,6 +1152,13 @@ <length>4</length> </field> + <field> + <name>scope</name> + <type>clob</type> + <default></default> + <notnull>false</notnull> + </field> + <index> <name>authtoken_token_index</name> <unique>true</unique> diff --git a/lib/autoloader.php b/lib/autoloader.php index 08188ef8e59..3fff025080e 100644 --- a/lib/autoloader.php +++ b/lib/autoloader.php @@ -117,6 +117,13 @@ class Autoloader { // This File is considered public API, so we make sure that the class // can still be loaded, although the PSR-4 paths have not been loaded. $paths[] = \OC::$SERVERROOT . '/tests/lib/TestCase.php'; + + } elseif ($class === 'Test\\TestCasePhpUnitCompatibility') { + $paths[] = \OC::$SERVERROOT . '/tests/lib/TestCasePhpUnitCompatibility.php'; + } elseif ($class === 'Test\\TestCasePhpUnit5') { + $paths[] = \OC::$SERVERROOT . '/tests/lib/TestCasePhpUnit5.php'; + } elseif ($class === 'Test\\TestCasePhpUnit4') { + $paths[] = \OC::$SERVERROOT . '/tests/lib/TestCasePhpUnit4.php'; } return $paths; } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 42cfb8c45e1..69e8428fdea 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -189,6 +189,7 @@ return array( 'OCP\\LDAP\\ILDAPProviderFactory' => $baseDir . '/lib/public/LDAP/ILDAPProviderFactory.php', 'OCP\\Lock\\ILockingProvider' => $baseDir . '/lib/public/Lock/ILockingProvider.php', 'OCP\\Lock\\LockedException' => $baseDir . '/lib/public/Lock/LockedException.php', + 'OCP\\Lockdown\\ILockdownManager' => $baseDir . '/lib/public/Lockdown/ILockdownManager.php', 'OCP\\Mail\\IMailer' => $baseDir . '/lib/public/Mail/IMailer.php', 'OCP\\Migration\\IOutput' => $baseDir . '/lib/public/Migration/IOutput.php', 'OCP\\Migration\\IRepairStep' => $baseDir . '/lib/public/Migration/IRepairStep.php', @@ -580,6 +581,9 @@ return array( 'OC\\Lock\\DBLockingProvider' => $baseDir . '/lib/private/Lock/DBLockingProvider.php', 'OC\\Lock\\MemcacheLockingProvider' => $baseDir . '/lib/private/Lock/MemcacheLockingProvider.php', 'OC\\Lock\\NoopLockingProvider' => $baseDir . '/lib/private/Lock/NoopLockingProvider.php', + 'OC\\Lockdown\\Filesystem\\NullCache' => $baseDir . '/lib/private/Lockdown/Filesystem/NullCache.php', + 'OC\\Lockdown\\Filesystem\\NullStorage' => $baseDir . '/lib/private/Lockdown/Filesystem/NullStorage.php', + 'OC\\Lockdown\\LockdownManager' => $baseDir . '/lib/private/Lockdown/LockdownManager.php', 'OC\\Log' => $baseDir . '/lib/private/Log.php', 'OC\\Log\\ErrorHandler' => $baseDir . '/lib/private/Log/ErrorHandler.php', 'OC\\Log\\Errorlog' => $baseDir . '/lib/private/Log/Errorlog.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d7e937577f2..c960a35d951 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -219,6 +219,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\LDAP\\ILDAPProviderFactory' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProviderFactory.php', 'OCP\\Lock\\ILockingProvider' => __DIR__ . '/../../..' . '/lib/public/Lock/ILockingProvider.php', 'OCP\\Lock\\LockedException' => __DIR__ . '/../../..' . '/lib/public/Lock/LockedException.php', + 'OCP\\Lockdown\\ILockdownManager' => __DIR__ . '/../../..' . '/lib/public/Lockdown/ILockdownManager.php', 'OCP\\Mail\\IMailer' => __DIR__ . '/../../..' . '/lib/public/Mail/IMailer.php', 'OCP\\Migration\\IOutput' => __DIR__ . '/../../..' . '/lib/public/Migration/IOutput.php', 'OCP\\Migration\\IRepairStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IRepairStep.php', @@ -610,6 +611,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Lock\\DBLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/DBLockingProvider.php', 'OC\\Lock\\MemcacheLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/MemcacheLockingProvider.php', 'OC\\Lock\\NoopLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/NoopLockingProvider.php', + 'OC\\Lockdown\\Filesystem\\NullCache' => __DIR__ . '/../../..' . '/lib/private/Lockdown/Filesystem/NullCache.php', + 'OC\\Lockdown\\Filesystem\\NullStorage' => __DIR__ . '/../../..' . '/lib/private/Lockdown/Filesystem/NullStorage.php', + 'OC\\Lockdown\\LockdownManager' => __DIR__ . '/../../..' . '/lib/private/Lockdown/LockdownManager.php', 'OC\\Log' => __DIR__ . '/../../..' . '/lib/private/Log.php', 'OC\\Log\\ErrorHandler' => __DIR__ . '/../../..' . '/lib/private/Log/ErrorHandler.php', 'OC\\Log\\Errorlog' => __DIR__ . '/../../..' . '/lib/private/Log/Errorlog.php', diff --git a/lib/private/Activity/Event.php b/lib/private/Activity/Event.php index af0605d82c8..df6756940a0 100644 --- a/lib/private/Activity/Event.php +++ b/lib/private/Activity/Event.php @@ -24,229 +24,528 @@ 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 $subjectParameters = []; + /** @var string */ + protected $subjectParsed; + /** @var string */ + protected $subjectRich; + /** @var array */ + protected $subjectRichParameters; + /** @var string */ + protected $message = ''; /** @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 $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; } /** - * Set the object of the activity - * - * @param string $objectType - * @param int $objectId - * @param string $objectName - * @return IEvent - * @since 8.2.0 + * @return string */ - public function setObject($objectType, $objectId, $objectName = '') { - $this->data['object_type'] = (string) $objectType; - $this->data['object_id'] = (int) $objectId; - $this->data['object_name'] = (string) $objectName; + 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; } /** - * Set the link of the activity - * - * @param string $link - * @return IEvent - * @since 8.2.0 + * @return string + * @since 11.0.0 */ - public function setLink($link) { - $this->data['link'] = (string) $link; + 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 getApp() { - return $this->data['app']; + public function getRichMessage() { + return $this->messageRich; + } + + /** + * @return array[] + * @since 11.0.0 + */ + public function getRichMessageParameters() { + return $this->messageRichParameters; + } + + /** + * Set the object of the activity + * + * @param string $objectType + * @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 = '') { + 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 getType() { - return $this->data['type']; + public function getObjectType() { + return $this->objectType; } /** * @return string */ - public function getAffectedUser() { - return $this->data['affected_user']; + public function getObjectId() { + return $this->objectId; } /** * @return string */ - public function getAuthor() { - return $this->data['author']; + public function getObjectName() { + return $this->objectName; } /** - * @return int + * Set the link of the activity + * + * @param string $link + * @return IEvent + * @throws \InvalidArgumentException if the link is invalid + * @since 8.2.0 */ - public function getTimestamp() { - return $this->data['timestamp']; + public function setLink($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 getSubject() { - return $this->data['subject']; + public function getLink() { + return $this->link; } /** - * @return array + * @param string $icon + * @return $this + * @throws \InvalidArgumentException if the icon is invalid + * @since 11.0.0 */ - public function getSubjectParameters() { - return $this->data['subject_parameters']; + 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 getMessage() { - return $this->data['message']; + public function getIcon() { + return $this->icon; } /** - * @return array + * @param IEvent $child + * @since 11.0.0 */ - public function getMessageParameters() { - return $this->data['message_parameters']; + public function setChildEvent(IEvent $child) { + $this->child = $child; } /** - * @return string + * @return IEvent|null + * @since 11.0.0 */ - public function getObjectType() { - return $this->data['object_type']; + public function getChildEvent() { + return $this->child; } /** - * @return string + * @return bool + * @since 8.2.0 */ - public function getObjectId() { - return $this->data['object_id']; + public function isValid() { + return + $this->isValidCommon() + && + $this->getSubject() !== '' + ; } /** - * @return string + * @return bool + * @since 8.2.0 */ - public function getObjectName() { - return $this->data['object_name']; + 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 string + * @return bool */ - public function getLink() { - return $this->data['link']; + 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 00000000000..eadb5b1558f --- /dev/null +++ b/lib/private/Activity/LegacyFilter.php @@ -0,0 +1,108 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 00000000000..27495afddb0 --- /dev/null +++ b/lib/private/Activity/LegacySetting.php @@ -0,0 +1,123 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 455bb3b8ee8..805124dc602 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 = []; + public function registerFilter($filter) { + $this->filterClasses[$filter] = false; + } - $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; - } + /** + * @return IFilter[] + * @throws \InvalidArgumentException + */ + 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 + ); + } - $notificationTypes = array_merge($notificationTypes, $result); + foreach ($legacyFilters['apps'] as $filter => $data) { + $this->filters[$filter] = new LegacyFilter( + $this, $filter, $data['name'], false + ); } + $this->loadedLegacyFilters = true; } - return array_merge($filesNotificationTypes, $sharingNotificationTypes, $notificationTypes); + 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 $method - * @return array + * @param string $id + * @return IFilter + * @throws \InvalidArgumentException when the filter was not found + * @since 11.0.0 */ - 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 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 $defaultTypes; + 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'); } /** @@ -391,7 +526,62 @@ class Manager implements IManager { } /** + * Set the user we need to use + * + * @param string|null $currentUserId + * @throws \UnexpectedValueException If the user is invalid + */ + public function setCurrentUserId($currentUserId) { + if (!is_string($currentUserId) && $currentUserId !== null) { + throw new \UnexpectedValueException('The given current user is invalid'); + } + $this->currentUserId = $currentUserId; + } + + /** + * Get the user we need to use + * + * Either the user is logged in, or we try to get it from the token + * + * @return string + * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique + */ + public function getCurrentUserId() { + if ($this->currentUserId !== null) { + return $this->currentUserId; + } else if (!$this->session->isLoggedIn()) { + return $this->getUserFromToken(); + } else { + return $this->session->getUser()->getUID(); + } + } + + /** + * Get the user for the token + * + * @return string + * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique + */ + protected function getUserFromToken() { + $token = (string) $this->request->getParam('token', ''); + if (strlen($token) !== 30) { + throw new \UnexpectedValueException('The token is invalid'); + } + + $users = $this->config->getUsersForUserValue('activity', 'rsstoken', $token); + + if (sizeof($users) !== 1) { + // No unique user found + throw new \UnexpectedValueException('The token is invalid'); + } + + // Token found login as that user + return array_shift($users); + } + + /** * @return array + * @deprecated 11.0.0 - Use getFilters() instead */ public function getNavigation() { $entries = array( @@ -412,6 +602,7 @@ class Manager implements IManager { /** * @param string $filterValue * @return boolean + * @deprecated 11.0.0 - Use getFilterById() instead */ public function isFilterValid($filterValue) { if (isset($this->validFilters[$filterValue])) { @@ -433,6 +624,7 @@ class Manager implements IManager { * @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)) { @@ -451,6 +643,7 @@ class Manager implements IManager { /** * @param string $filter * @return array + * @deprecated 11.0.0 - Use getFilterById() instead */ public function getQueryForFilter($filter) { if (!$this->isFilterValid($filter)) { @@ -479,56 +672,42 @@ class Manager implements IManager { } /** - * Set the user we need to use + * Will return additional notification types as specified by other apps * - * @param string|null $currentUserId - * @throws \UnexpectedValueException If the user is invalid + * @param string $languageCode + * @return array + * @deprecated 11.0.0 - Use getSettings() instead */ - public function setCurrentUserId($currentUserId) { - if (!is_string($currentUserId) && $currentUserId !== null) { - throw new \UnexpectedValueException('The given current user is invalid'); - } - $this->currentUserId = $currentUserId; - } + 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; + } - /** - * Get the user we need to use - * - * Either the user is logged in, or we try to get it from the token - * - * @return string - * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique - */ - public function getCurrentUserId() { - if ($this->currentUserId !== null) { - return $this->currentUserId; - } else if (!$this->session->isLoggedIn()) { - return $this->getUserFromToken(); - } else { - return $this->session->getUser()->getUID(); + $notificationTypes = array_merge($notificationTypes, $result); + } } + + return array_merge($sharingNotificationTypes, $notificationTypes); } /** - * Get the user for the token - * - * @return string - * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique + * @param string $method + * @return array + * @deprecated 11.0.0 - Use getSettings()->isDefaulEnabled<method>() instead */ - protected function getUserFromToken() { - $token = (string) $this->request->getParam('token', ''); - if (strlen($token) !== 30) { - throw new \UnexpectedValueException('The token is invalid'); - } - - $users = $this->config->getUsersForUserValue('activity', 'rsstoken', $token); - - if (sizeof($users) !== 1) { - // No unique user found - throw new \UnexpectedValueException('The token is invalid'); + public function getDefaultTypes($method) { + $defaultTypes = array(); + foreach ($this->getExtensions() as $c) { + $types = $c->getDefaultTypes($method); + if (is_array($types)) { + $defaultTypes = array_merge($types, $defaultTypes); + } } - - // Token found login as that user - return array_shift($users); + return $defaultTypes; } } diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index af26d30d8e9..4e13d70371b 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -215,11 +215,13 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); - if (isset($this->userCache[$userId][$appName][$key])) { - if ($this->userCache[$userId][$appName][$key] === (string)$value) { - return; - } else if ($preCondition !== null && $this->userCache[$userId][$appName][$key] !== (string)$preCondition) { + $prevValue = $this->getUserValue($userId, $appName, $key, null); + + if ($prevValue !== null) { + if ($prevValue === (string)$value) { return; + } else if ($preCondition !== null && $prevValue !== (string)$preCondition) { + throw new PreConditionNotMetException(); } else { $qb = $this->connection->getQueryBuilder(); $qb->update('preferences') diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php index c24b25ff14d..84d87efa3d3 100644 --- a/lib/private/App/DependencyAnalyzer.php +++ b/lib/private/App/DependencyAnalyzer.php @@ -336,13 +336,9 @@ class DependencyAnalyzer { switch ($version) { case '9.1': return '10'; - case '9.2': - return '11'; default: if (strpos($version, '9.1.') === 0) { $version = '10.0.' . substr($version, 4); - } else if (strpos($version, '9.2.') === 0) { - $version = '11.0.' . substr($version, 4); } return $version; } diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php index 44f495534c9..47ce28e6e98 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/AppFramework/Db/Db.php b/lib/private/AppFramework/Db/Db.php index 450549ffdbb..5aacdc517a2 100644 --- a/lib/private/AppFramework/Db/Db.php +++ b/lib/private/AppFramework/Db/Db.php @@ -306,7 +306,7 @@ class Db implements IDb { * Check whether or not the current database support 4byte wide unicode * * @return bool - * @since 9.2.0 + * @since 11.0.0 */ public function supports4ByteText() { return $this->connection->supports4ByteText(); diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index faef2f73b33..127430ea6cb 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -87,6 +87,17 @@ class DefaultToken extends Entity implements IToken { */ protected $lastCheck; + /** + * @var string + */ + protected $scope; + + public function __construct() { + $this->addType('type', 'int'); + $this->addType('lastActivity', 'int'); + $this->addType('lastCheck', 'int'); + } + public function getId() { return $this->id; } @@ -119,6 +130,7 @@ class DefaultToken extends Entity implements IToken { 'name' => $this->name, 'lastActivity' => $this->lastActivity, 'type' => $this->type, + 'scope' => $this->getScopeAsArray() ]; } @@ -140,4 +152,25 @@ class DefaultToken extends Entity implements IToken { return parent::setLastCheck($time); } + public function getScope() { + return parent::getScope(); + } + + public function getScopeAsArray() { + $scope = json_decode($this->getScope(), true); + if (!$scope) { + return [ + 'filesystem'=> true + ]; + } + return $scope; + } + + public function setScope($scope) { + if (is_array($scope)) { + parent::setScope(json_encode($scope)); + } else { + parent::setScope((string)$scope); + } + } } diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 752974ff240..8848cd3ec56 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -72,10 +72,9 @@ class DefaultTokenMapper extends Mapper { public function getToken($token) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check') + $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check', 'scope') ->from('authtoken') - ->where($qb->expr()->eq('token', $qb->createParameter('token'))) - ->setParameter('token', $token) + ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) ->execute(); $data = $result->fetch(); @@ -83,6 +82,30 @@ class DefaultTokenMapper extends Mapper { if ($data === false) { throw new DoesNotExistException('token does not exist'); } +; + return DefaultToken::fromRow($data); + } + + /** + * Get the token for $id + * + * @param string $id + * @throws DoesNotExistException + * @return DefaultToken + */ + public function getTokenById($id) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check', 'scope') + ->from('authtoken') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) + ->execute(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + throw new DoesNotExistException('token does not exist'); + }; return DefaultToken::fromRow($data); } @@ -98,7 +121,7 @@ class DefaultTokenMapper extends Mapper { public function getTokenByUser(IUser $user) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check') + $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check', 'scope') ->from('authtoken') ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) ->setMaxResults(1000); diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 87f434c684c..0fdbc4a51dd 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -145,7 +145,7 @@ class DefaultTokenProvider implements IProvider { } /** - * Get a token by token id + * Get a token by token * * @param string $tokenId * @throws InvalidTokenException @@ -160,6 +160,21 @@ class DefaultTokenProvider implements IProvider { } /** + * Get a token by token id + * + * @param string $tokenId + * @throws InvalidTokenException + * @return DefaultToken + */ + public function getTokenById($tokenId) { + try { + return $this->mapper->getTokenById($tokenId); + } catch (DoesNotExistException $ex) { + throw new InvalidTokenException(); + } + } + + /** * @param string $oldSessionId * @param string $sessionId * @throws InvalidTokenException diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index ce14a5880c5..9f280263d76 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -50,7 +50,16 @@ interface IProvider { * @throws InvalidTokenException * @return IToken */ - public function getToken($tokenId) ; + public function getToken($tokenId); + + /** + * Get a token by token id + * + * @param string $tokenId + * @throws InvalidTokenException + * @return DefaultToken + */ + public function getTokenById($tokenId); /** * Duplicate an existing session token diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index 14811dd3201..49745b266c4 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -67,9 +67,30 @@ interface IToken extends JsonSerializable { public function getLastCheck(); /** - * Get the timestamp of the last password check + * Set the timestamp of the last password check * * @param int $time */ public function setLastCheck($time); + + /** + * Get the authentication scope for this token + * + * @return string + */ + public function getScope(); + + /** + * Get the authentication scope for this token + * + * @return array + */ + public function getScopeAsArray(); + + /** + * Set the authentication scope for this token + * + * @param array $scope + */ + public function setScope($scope); } diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php index b5f063be323..b9a6103f67f 100644 --- a/lib/private/Comments/Comment.php +++ b/lib/private/Comments/Comment.php @@ -207,7 +207,7 @@ class Comment implements IComment { * returns an array containing mentions that are included in the comment * * @return array each mention provides a 'type' and an 'id', see example below - * @since 9.2.0 + * @since 11.0.0 * * The return array looks like: * [ diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 001f4f9441c..1467fef727b 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -768,7 +768,7 @@ class Manager implements ICommentsManager { * @param string $type * @param \Closure $closure * @throws \OutOfBoundsException - * @since 9.2.0 + * @since 11.0.0 * * Only one resolver shall be registered per type. Otherwise a * \OutOfBoundsException has to thrown. @@ -790,7 +790,7 @@ class Manager implements ICommentsManager { * @param string $id * @return string * @throws \OutOfBoundsException - * @since 9.2.0 + * @since 11.0.0 * * If a provided type was not registered, an \OutOfBoundsException shall * be thrown. It is upon the resolver discretion what to return of the diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 497ff0c8a26..a39dbe3783c 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -412,7 +412,7 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { * Check whether or not the current database support 4byte wide unicode * * @return bool - * @since 9.2.0 + * @since 11.0.0 */ public function supports4ByteText() { return ! ($this->getDatabasePlatform() instanceof MySqlPlatform && $this->getParams()['charset'] !== 'utf8mb4'); diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 55cf38bbdc9..ac0e66973d4 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -62,6 +62,7 @@ use OC\Cache\CappedMemoryCache; use OC\Files\Config\MountProviderCollection; use OC\Files\Mount\MountPoint; use OC\Files\Storage\StorageFactory; +use OC\Lockdown\Filesystem\NullStorage; use OCP\Files\Config\IMountProvider; use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; @@ -216,7 +217,7 @@ class Filesystem { * @internal */ public static function logWarningWhenAddingStorageWrapper($shouldLog) { - self::$logWarningWhenAddingStorageWrapper = (bool) $shouldLog; + self::$logWarningWhenAddingStorageWrapper = (bool)$shouldLog; } /** @@ -426,25 +427,36 @@ class Filesystem { self::$usersSetup[$user] = true; } - /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ - $mountConfigManager = \OC::$server->getMountProviderCollection(); + if (\OC::$server->getLockdownManager()->canAccessFilesystem()) { + /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ + $mountConfigManager = \OC::$server->getMountProviderCollection(); - // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers - $homeMount = $mountConfigManager->getHomeMountForUser($userObject); + // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers + $homeMount = $mountConfigManager->getHomeMountForUser($userObject); - self::getMountManager()->addMount($homeMount); + self::getMountManager()->addMount($homeMount); - \OC\Files\Filesystem::getStorage($user); + \OC\Files\Filesystem::getStorage($user); - // Chance to mount for other storages - if ($userObject) { - $mounts = $mountConfigManager->getMountsForUser($userObject); - array_walk($mounts, array(self::$mounts, 'addMount')); - $mounts[] = $homeMount; - $mountConfigManager->registerMounts($userObject, $mounts); - } + // Chance to mount for other storages + if ($userObject) { + $mounts = $mountConfigManager->getMountsForUser($userObject); + array_walk($mounts, array(self::$mounts, 'addMount')); + $mounts[] = $homeMount; + $mountConfigManager->registerMounts($userObject, $mounts); + } - self::listenForNewMountProviders($mountConfigManager, $userManager); + self::listenForNewMountProviders($mountConfigManager, $userManager); + } else { + self::$mounts->addMount(new MountPoint( + new NullStorage([]), + '/' . $user + )); + self::$mounts->addMount(new MountPoint( + new NullStorage([]), + '/' . $user . '/files' + )); + } \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', array('user' => $user)); } diff --git a/lib/private/Lockdown/Filesystem/NullCache.php b/lib/private/Lockdown/Filesystem/NullCache.php new file mode 100644 index 00000000000..8c6b5258aa8 --- /dev/null +++ b/lib/private/Lockdown/Filesystem/NullCache.php @@ -0,0 +1,122 @@ +<?php + +/** + * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl> + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Lockdown\Filesystem; + +use OC\Files\Cache\CacheEntry; +use OCP\Constants; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\FileInfo; + +class NullCache implements ICache { + public function getNumericStorageId() { + return -1; + } + + public function get($file) { + return $file !== '' ? null : + new CacheEntry([ + 'fileid' => -1, + 'parent' => -1, + 'name' => '', + 'path' => '', + 'size' => '0', + 'mtime' => time(), + 'storage_mtime' => time(), + 'etag' => '', + 'mimetype' => FileInfo::MIMETYPE_FOLDER, + 'mimepart' => 'httpd', + 'permissions' => Constants::PERMISSION_READ + ]); + } + + public function getFolderContents($folder) { + return []; + } + + public function getFolderContentsById($fileId) { + return []; + } + + public function put($file, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function insert($file, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function update($id, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getId($file) { + return -1; + } + + public function getParentId($file) { + return -1; + } + + public function inCache($file) { + return $file === ''; + } + + public function remove($file) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function move($source, $target) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getStatus($file) { + return ICache::COMPLETE; + } + + public function search($pattern) { + return []; + } + + public function searchByMime($mimetype) { + return []; + } + + public function searchByTag($tag, $userId) { + return []; + } + + public function getIncomplete() { + return []; + } + + public function getPathById($id) { + return ''; + } + + public function normalize($path) { + return $path; + } + +} diff --git a/lib/private/Lockdown/Filesystem/NullStorage.php b/lib/private/Lockdown/Filesystem/NullStorage.php new file mode 100644 index 00000000000..967b6d2c6e7 --- /dev/null +++ b/lib/private/Lockdown/Filesystem/NullStorage.php @@ -0,0 +1,177 @@ +<?php + +/** + * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl> + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Lockdown\Filesystem; + +use Icewind\Streams\IteratorDirectory; +use OC\Files\Storage\Common; + +class NullStorage extends Common { + public function __construct($parameters) { + parent::__construct($parameters); + } + + public function getId() { + return 'null'; + } + + public function mkdir($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function rmdir($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function opendir($path) { + return new IteratorDirectory([]); + } + + public function is_dir($path) { + return $path === ''; + } + + public function is_file($path) { + return false; + } + + public function stat($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function filetype($path) { + return ($path === '') ? 'dir' : false; + } + + public function filesize($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function isCreatable($path) { + return false; + } + + public function isReadable($path) { + return $path === ''; + } + + public function isUpdatable($path) { + return false; + } + + public function isDeletable($path) { + return false; + } + + public function isSharable($path) { + return false; + } + + public function getPermissions($path) { + return null; + } + + public function file_exists($path) { + return $path === ''; + } + + public function filemtime($path) { + return ($path === '') ? time() : false; + } + + public function file_get_contents($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function file_put_contents($path, $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function unlink($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function rename($path1, $path2) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function copy($path1, $path2) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function fopen($path, $mode) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getMimeType($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function hash($type, $path, $raw = false) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function free_space($path) { + return 0; + } + + public function touch($path, $mtime = null) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getLocalFile($path) { + return false; + } + + public function hasUpdated($path, $time) { + return false; + } + + public function getETag($path) { + return ''; + } + + public function isLocal() { + return false; + } + + public function getDirectDownload($path) { + return false; + } + + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function test() { + return true; + } + + public function getOwner($path) { + return null; + } + + public function getCache($path = '', $storage = null) { + return new NullCache(); + } +} diff --git a/lib/private/Lockdown/LockdownManager.php b/lib/private/Lockdown/LockdownManager.php new file mode 100644 index 00000000000..5ce52a03683 --- /dev/null +++ b/lib/private/Lockdown/LockdownManager.php @@ -0,0 +1,46 @@ +<?php + +/** + * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl> + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Lockdown; + +use OC\Authentication\Token\IToken; +use OCP\Lockdown\ILockdownManager; + +class LockdownManager implements ILockdownManager { + private $enabled = false; + + /** @var array|null */ + private $scope; + + public function enable() { + $this->enabled = true; + } + + public function setToken(IToken $token) { + $this->scope = $token->getScopeAsArray(); + $this->enable(); + } + + public function canAccessFilesystem() { + if (!$this->enabled) { + return true; + } + return !$this->scope || $this->scope['filesystem']; + } +} diff --git a/lib/private/Notification/Notification.php b/lib/private/Notification/Notification.php index ffcb36af6e5..e5a8976f54d 100644 --- a/lib/private/Notification/Notification.php +++ b/lib/private/Notification/Notification.php @@ -289,7 +289,7 @@ class Notification implements INotification { * @param array $parameters * @return $this * @throws \InvalidArgumentException if the subject or parameters are invalid - * @since 9.2.0 + * @since 11.0.0 */ public function setRichSubject($subject, array $parameters = []) { if (!is_string($subject) || $subject === '') { @@ -304,7 +304,7 @@ class Notification implements INotification { /** * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getRichSubject() { return $this->subjectRich; @@ -312,7 +312,7 @@ class Notification implements INotification { /** * @return array[] - * @since 9.2.0 + * @since 11.0.0 */ public function getRichSubjectParameters() { return $this->subjectRichParameters; @@ -379,7 +379,7 @@ class Notification implements INotification { * @param array $parameters * @return $this * @throws \InvalidArgumentException if the message or parameters are invalid - * @since 9.2.0 + * @since 11.0.0 */ public function setRichMessage($message, array $parameters = []) { if (!is_string($message) || $message === '') { @@ -394,7 +394,7 @@ class Notification implements INotification { /** * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getRichMessage() { return $this->messageRich; @@ -402,7 +402,7 @@ class Notification implements INotification { /** * @return array[] - * @since 9.2.0 + * @since 11.0.0 */ public function getRichMessageParameters() { return $this->messageRichParameters; @@ -434,7 +434,7 @@ class Notification implements INotification { * @param string $icon * @return $this * @throws \InvalidArgumentException if the icon is invalid - * @since 9.2.0 + * @since 11.0.0 */ public function setIcon($icon) { if (!is_string($icon) || $icon === '' || isset($icon[4000])) { @@ -446,7 +446,7 @@ class Notification implements INotification { /** * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getIcon() { return $this->icon; diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index a2ef9659b3b..600557da5a0 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -155,7 +155,7 @@ class PreviewManager implements IPreview { * @param string $mimeType * @return ISimpleFile * @throws NotFoundException - * @since 9.2.0 + * @since 11.0.0 */ public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) { if ($this->generator === null) { diff --git a/lib/private/RichObjectStrings/Validator.php b/lib/private/RichObjectStrings/Validator.php index 11d17fef645..d90a6780ea8 100644 --- a/lib/private/RichObjectStrings/Validator.php +++ b/lib/private/RichObjectStrings/Validator.php @@ -30,7 +30,7 @@ use OCP\RichObjectStrings\IValidator; * Class Validator * * @package OCP\RichObjectStrings - * @since 9.2.0 + * @since 11.0.0 */ class Validator implements IValidator { @@ -53,7 +53,7 @@ class Validator implements IValidator { * @param string $subject * @param array[] $parameters * @throws InvalidObjectExeption - * @since 9.2.0 + * @since 11.0.0 */ public function validate($subject, array $parameters) { $matches = []; diff --git a/lib/private/Server.php b/lib/private/Server.php index 8e41ba212ad..c6755357a1d 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -69,6 +69,7 @@ use OC\IntegrityCheck\Helpers\FileAccessHelper; use OC\Lock\DBLockingProvider; use OC\Lock\MemcacheLockingProvider; use OC\Lock\NoopLockingProvider; +use OC\Lockdown\LockdownManager; use OC\Mail\Mailer; use OC\Memcache\ArrayCache; use OC\Notification\Manager; @@ -90,6 +91,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 +396,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 +666,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) { @@ -792,6 +796,9 @@ class Server extends ServerContainer implements IServerContainer { $c->getSystemConfig() ); }); + $this->registerService('LockdownManager', function (Server $c) { + return new LockdownManager(); + }); } /** @@ -1531,4 +1538,11 @@ class Server extends ServerContainer implements IServerContainer { $factory = $this->query(\OC\Files\AppData\Factory::class); return $factory->get($app); } + + /** + * @return \OCP\Lockdown\ILockdownManager + */ + public function getLockdownManager() { + return $this->query('LockdownManager'); + } } diff --git a/lib/private/Settings/Admin/ServerDevNotice.php b/lib/private/Settings/Admin/ServerDevNotice.php new file mode 100644 index 00000000000..1d7f34f782d --- /dev/null +++ b/lib/private/Settings/Admin/ServerDevNotice.php @@ -0,0 +1,62 @@ +<?php +/** + * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Settings\Admin; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Platforms\SqlitePlatform; +use OC\Lock\DBLockingProvider; +use OC\Lock\NoopLockingProvider; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IL10N; +use OCP\Lock\ILockingProvider; +use OCP\Settings\ISettings; + +class ServerDevNotice implements ISettings { + /** + * @return TemplateResponse + */ + public function getForm() { + return new TemplateResponse('settings', 'admin/server.development.notice'); + } + + /** + * @return string the section ID, e.g. 'sharing' + */ + public function getSection() { + return 'server'; + } + + /** + * @return int whether the form should be rather on the top or bottom of + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * + * E.g.: 70 + */ + public function getPriority() { + return 1000; + } +} diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index e7be3d4e77d..a6f9aacccb2 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -369,6 +369,8 @@ class Manager implements IManager { /** @var ISettings $form */ $form = new Admin\Server($this->dbc, $this->config, $this->lockingProvider, $this->l); $forms[$form->getPriority()] = [$form]; + $form = new Admin\ServerDevNotice(); + $forms[$form->getPriority()] = [$form]; } if($section === 'encryption') { /** @var ISettings $form */ diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 43e09cdabcf..3d016700ee3 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -375,7 +375,7 @@ class Manager extends PublicEmitter implements IUserManager { * returns how many users have logged in once * * @return int - * @since 9.2.0 + * @since 11.0.0 */ public function countSeenUsers() { $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder(); @@ -395,7 +395,7 @@ class Manager extends PublicEmitter implements IUserManager { /** * @param \Closure $callback - * @since 9.2.0 + * @since 11.0.0 */ public function callForSeenUsers(\Closure $callback) { $limit = 1000; diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index ef408aa4077..6033f060504 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -525,6 +525,7 @@ class Session implements IUserSession, Emitter { //login $this->setUser($user); $this->setLoginName($dbToken->getLoginName()); + \OC::$server->getLockdownManager()->setToken($dbToken); $this->manager->emit('\OC\User', 'postLogin', array($user, $password)); if ($this->isLoggedIn()) { diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index a89a4650c5d..0d9adfa9d2b 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); + } + } } /** @@ -359,11 +376,15 @@ class OC_App { $config, $l ); + $appPath = self::getAppPath($appId); + self::registerAutoloading($appId, $appPath); $installer->installApp($appId); } else { // check for required dependencies $info = self::getAppInfo($appId); self::checkAppDependencies($config, $l, $info); + $appPath = self::getAppPath($appId); + self::registerAutoloading($appId, $appPath); $installer->installApp($appId); } diff --git a/lib/private/legacy/defaults.php b/lib/private/legacy/defaults.php index 38bbaa8daa1..23590cd9235 100644 --- a/lib/private/legacy/defaults.php +++ b/lib/private/legacy/defaults.php @@ -59,7 +59,7 @@ class OC_Defaults { $this->defaultiOSClientUrl = 'https://itunes.apple.com/us/app/nextcloud/id1125420102?mt=8'; $this->defaultiTunesAppId = '1125420102'; $this->defaultAndroidClientUrl = 'https://play.google.com/store/apps/details?id=com.nextcloud.client'; - $this->defaultDocBaseUrl = 'https://docs.nextcloud.org'; + $this->defaultDocBaseUrl = 'https://docs.nextcloud.com'; $this->defaultDocVersion = '10'; // used to generate doc links $this->defaultSlogan = $this->l->t('a safe home for all your data'); $this->defaultLogoClaim = ''; diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index ecc8f053704..3bd5b5586ab 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -66,6 +66,9 @@ class OC_Util { private static $rootMounted = false; private static $fsSetup = false; + /** @var array Local cache of version.php */ + private static $versionCache = null; + protected static function getAppManager() { return \OC::$server->getAppManager(); } @@ -397,7 +400,7 @@ class OC_Util { */ public static function getVersion() { OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_Version'); + return self::$versionCache['OC_Version']; } /** @@ -407,7 +410,7 @@ class OC_Util { */ public static function getVersionString() { OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_VersionString'); + return self::$versionCache['OC_VersionString']; } /** @@ -424,7 +427,13 @@ class OC_Util { */ public static function getChannel() { OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_Channel'); + + // Allow overriding update channel + if (\OC::$server->getSystemConfig()->getValue('installed', false)) { + self::$versionCache['OC_Channel'] = \OC::$server->getAppConfig()->getValue('core', 'OC_Channel'); + } + + return self::$versionCache['OC_Channel']; } /** @@ -433,42 +442,30 @@ class OC_Util { */ public static function getBuild() { OC_Util::loadVersion(); - return \OC::$server->getSession()->get('OC_Build'); + return self::$versionCache['OC_Build']; } /** * @description load the version.php into the session as cache */ private static function loadVersion() { - $timestamp = filemtime(OC::$SERVERROOT . '/version.php'); - if (!\OC::$server->getSession()->exists('OC_Version') or OC::$server->getSession()->get('OC_Version_Timestamp') != $timestamp) { - require OC::$SERVERROOT . '/version.php'; - $session = \OC::$server->getSession(); - /** @var $timestamp int */ - $session->set('OC_Version_Timestamp', $timestamp); - /** @var $OC_Version string */ - $session->set('OC_Version', $OC_Version); - /** @var $OC_VersionString string */ - $session->set('OC_VersionString', $OC_VersionString); - /** @var $OC_Build string */ - $session->set('OC_Build', $OC_Build); - - // Allow overriding update channel - - if (\OC::$server->getSystemConfig()->getValue('installed', false)) { - $channel = \OC::$server->getAppConfig()->getValue('core', 'OC_Channel'); - } else { - /** @var $OC_Channel string */ - $channel = $OC_Channel; - } - - if (!is_null($channel)) { - $session->set('OC_Channel', $channel); - } else { - /** @var $OC_Channel string */ - $session->set('OC_Channel', $OC_Channel); - } + if (self::$versionCache !== null) { + return; } + + $timestamp = filemtime(OC::$SERVERROOT . '/version.php'); + require OC::$SERVERROOT . '/version.php'; + /** @var $timestamp int */ + self::$versionCache['OC_Version_Timestamp'] = $timestamp; + /** @var $OC_Version string */ + self::$versionCache['OC_Version'] = $OC_Version; + /** @var $OC_VersionString string */ + self::$versionCache['OC_VersionString'] = $OC_VersionString; + /** @var $OC_Build string */ + self::$versionCache['OC_Build'] = $OC_Build; + + /** @var $OC_Channel string */ + self::$versionCache['OC_Channel'] = $OC_Channel; } /** diff --git a/lib/public/Activity/IEvent.php b/lib/public/Activity/IEvent.php index 0d4966e617e..a12ba8642a1 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,27 +92,100 @@ 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 * * @param string $objectType * @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 aaa4c869561..3f605a47e4b 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 00000000000..e5b65a7d91f --- /dev/null +++ b/lib/public/Activity/IFilter.php @@ -0,0 +1,74 @@ +<?php +/** + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Joas Schilling <coding@schilljs.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace 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 c1476e1a2ae..2fe38ddb8d8 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 */ @@ -111,6 +109,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 * * @param string $languageCode @@ -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<method>() instead */ public function getDefaultTypes($method); @@ -177,9 +232,31 @@ interface IManager { */ public function getGroupParameter($activity); + + /** + * Set the user we need to use + * + * @param string|null $currentUserId + * @throws \UnexpectedValueException If the user is invalid + * @since 9.0.1 + */ + public function setCurrentUserId($currentUserId); + + /** + * Get the user we need to use + * + * Either the user is logged in, or we try to get it from the token + * + * @return string + * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique + * @since 8.1.0 + */ + public function getCurrentUserId(); + /** * @return array * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilters() instead */ public function getNavigation(); @@ -187,6 +264,7 @@ interface IManager { * @param string $filterValue * @return boolean * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilterById() instead */ public function isFilterValid($filterValue); @@ -195,6 +273,7 @@ interface IManager { * @param string $filter * @return array * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilterById()->filterTypes() instead */ public function filterNotificationTypes($types, $filter); @@ -202,27 +281,7 @@ interface IManager { * @param string $filter * @return array * @since 8.0.0 + * @deprecated 11.0.0 - Use getFilterById() instead */ public function getQueryForFilter($filter); - - - /** - * Set the user we need to use - * - * @param string|null $currentUserId - * @throws \UnexpectedValueException If the user is invalid - * @since 9.0.1 - */ - public function setCurrentUserId($currentUserId); - - /** - * Get the user we need to use - * - * Either the user is logged in, or we try to get it from the token - * - * @return string - * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique - * @since 8.1.0 - */ - public function getCurrentUserId(); } diff --git a/lib/public/Activity/IProvider.php b/lib/public/Activity/IProvider.php new file mode 100644 index 00000000000..5b78e26f4bc --- /dev/null +++ b/lib/public/Activity/IProvider.php @@ -0,0 +1,39 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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 00000000000..786581bcae6 --- /dev/null +++ b/lib/public/Activity/ISetting.php @@ -0,0 +1,76 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +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/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php index ae4ceef1923..90ba47a2f3f 100644 --- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php @@ -88,7 +88,7 @@ class EmptyContentSecurityPolicy { * * @param string $nonce * @return $this - * @since 9.2.0 + * @since 11.0.0 */ public function useJsNonce($nonce) { $this->useJsNonce = $nonce; diff --git a/lib/public/AppFramework/Http/FileDisplayResponse.php b/lib/public/AppFramework/Http/FileDisplayResponse.php index 03a6fbec2dd..ab23701f893 100644 --- a/lib/public/AppFramework/Http/FileDisplayResponse.php +++ b/lib/public/AppFramework/Http/FileDisplayResponse.php @@ -28,7 +28,7 @@ use OCP\AppFramework\Http; * Class FileDisplayResponse * * @package OCP\AppFramework\Http - * @since 9.2.0 + * @since 11.0.0 */ class FileDisplayResponse extends Response implements ICallbackResponse { @@ -41,7 +41,7 @@ class FileDisplayResponse extends Response implements ICallbackResponse { * @param \OCP\Files\File|\OCP\Files\SimpleFS\ISimpleFile $file * @param int $statusCode * @param array $headers - * @since 9.2.0 + * @since 11.0.0 */ public function __construct($file, $statusCode=Http::STATUS_OK, $headers=[]) { @@ -58,7 +58,7 @@ class FileDisplayResponse extends Response implements ICallbackResponse { /** * @param IOutput $output - * @since 9.2.0 + * @since 11.0.0 */ public function callback(IOutput $output) { if ($output->getHttpResponseCode() !== Http::STATUS_NOT_MODIFIED) { diff --git a/lib/public/AppFramework/OCSController.php b/lib/public/AppFramework/OCSController.php index 9b3b12e2b94..700d9ea1a22 100644 --- a/lib/public/AppFramework/OCSController.php +++ b/lib/public/AppFramework/OCSController.php @@ -76,7 +76,7 @@ abstract class OCSController extends ApiController { /** * @param int $version - * @since 9.2.0 + * @since 11.0.0 * @internal */ public function setOCSVersion($version) { diff --git a/lib/public/Comments/IComment.php b/lib/public/Comments/IComment.php index 8210d4c8c7e..f93bed13ea0 100644 --- a/lib/public/Comments/IComment.php +++ b/lib/public/Comments/IComment.php @@ -136,7 +136,7 @@ interface IComment { * returns an array containing mentions that are included in the comment * * @return array each mention provides a 'type' and an 'id', see example below - * @since 9.2.0 + * @since 11.0.0 * * The return array looks like: * [ diff --git a/lib/public/Comments/ICommentsEventHandler.php b/lib/public/Comments/ICommentsEventHandler.php index 33524199012..79b0d0d883c 100644 --- a/lib/public/Comments/ICommentsEventHandler.php +++ b/lib/public/Comments/ICommentsEventHandler.php @@ -27,13 +27,13 @@ namespace OCP\Comments; * Interface ICommentsEventHandler * * @package OCP\Comments - * @since 9.2.0 + * @since 11.0.0 */ interface ICommentsEventHandler { /** * @param CommentsEvent $event - * @since 9.2.0 + * @since 11.0.0 */ public function handle(CommentsEvent $event); } diff --git a/lib/public/Comments/ICommentsManager.php b/lib/public/Comments/ICommentsManager.php index 6a32cfd803d..b43d5e8800b 100644 --- a/lib/public/Comments/ICommentsManager.php +++ b/lib/public/Comments/ICommentsManager.php @@ -242,7 +242,7 @@ interface ICommentsManager { * to consumers of the comments infrastructure * * @param \Closure $closure - * @since 9.2.0 + * @since 11.0.0 */ public function registerEventHandler(\Closure $closure); @@ -252,7 +252,7 @@ interface ICommentsManager { * @param string $type * @param \Closure $closure * @throws \OutOfBoundsException - * @since 9.2.0 + * @since 11.0.0 * * Only one resolver shall be registered per type. Otherwise a * \OutOfBoundsException has to thrown. @@ -266,7 +266,7 @@ interface ICommentsManager { * @param string $id * @return string * @throws \OutOfBoundsException - * @since 9.2.0 + * @since 11.0.0 * * If a provided type was not registered, an \OutOfBoundsException shall * be thrown. It is upon the resolver discretion what to return of the diff --git a/lib/public/Diagnostics/IQuery.php b/lib/public/Diagnostics/IQuery.php index 0bd1a6d9685..7f26bd7f1b8 100644 --- a/lib/public/Diagnostics/IQuery.php +++ b/lib/public/Diagnostics/IQuery.php @@ -50,13 +50,13 @@ interface IQuery { /** * @return float - * @since 9.2.0 + * @since 11.0.0 */ public function getStartTime(); /** * @return array - * @since 9.2.0 + * @since 11.0.0 */ public function getStacktrace(); } diff --git a/lib/public/Files/Config/ICachedMountInfo.php b/lib/public/Files/Config/ICachedMountInfo.php index f5c5bb87ead..2df79a7d642 100644 --- a/lib/public/Files/Config/ICachedMountInfo.php +++ b/lib/public/Files/Config/ICachedMountInfo.php @@ -73,7 +73,7 @@ interface ICachedMountInfo { * Get the internal path (within the storage) of the root of the mount * * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getRootInternalPath(); } diff --git a/lib/public/Files/IAppData.php b/lib/public/Files/IAppData.php index 92e54fee366..bf612996c53 100644 --- a/lib/public/Files/IAppData.php +++ b/lib/public/Files/IAppData.php @@ -28,7 +28,7 @@ use OCP\Files\SimpleFS\ISimpleRoot; * Interface IAppData * * @package OCP\Files - * @since 9.2.0 + * @since 11.0.0 * @internal This interface is experimental and might change for NC12 */ interface IAppData extends ISimpleRoot { diff --git a/lib/public/Files/SimpleFS/ISimpleFile.php b/lib/public/Files/SimpleFS/ISimpleFile.php index efd682e7855..660580ae464 100644 --- a/lib/public/Files/SimpleFS/ISimpleFile.php +++ b/lib/public/Files/SimpleFS/ISimpleFile.php @@ -28,7 +28,7 @@ use OCP\Files\NotPermittedException; * Interface ISimpleFile * * @package OCP\Files\SimpleFS - * @since 9.2.0 + * @since 11.0.0 * @internal This interface is experimental and might change for NC12 */ interface ISimpleFile { @@ -37,7 +37,7 @@ interface ISimpleFile { * Get the name * * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getName(); @@ -45,7 +45,7 @@ interface ISimpleFile { * Get the size in bytes * * @return int - * @since 9.2.0 + * @since 11.0.0 */ public function getSize(); @@ -53,7 +53,7 @@ interface ISimpleFile { * Get the ETag * * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getETag(); @@ -61,7 +61,7 @@ interface ISimpleFile { * Get the last modification time * * @return int - * @since 9.2.0 + * @since 11.0.0 */ public function getMTime(); @@ -69,7 +69,7 @@ interface ISimpleFile { * Get the content * * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getContent(); @@ -78,7 +78,7 @@ interface ISimpleFile { * * @param string $data * @throws NotPermittedException - * @since 9.2.0 + * @since 11.0.0 */ public function putContent($data); @@ -86,7 +86,7 @@ interface ISimpleFile { * Delete the file * * @throws NotPermittedException - * @since 9.2.0 + * @since 11.0.0 */ public function delete(); @@ -94,7 +94,7 @@ interface ISimpleFile { * Get the MimeType * * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getMimeType(); } diff --git a/lib/public/Files/SimpleFS/ISimpleFolder.php b/lib/public/Files/SimpleFS/ISimpleFolder.php index 406bb631159..66f80816216 100644 --- a/lib/public/Files/SimpleFS/ISimpleFolder.php +++ b/lib/public/Files/SimpleFS/ISimpleFolder.php @@ -29,7 +29,7 @@ use OCP\Files\NotPermittedException; * Interface ISimpleFolder * * @package OCP\Files\SimpleFS - * @since 9.2.0 + * @since 11.0.0 * @internal This interface is experimental and might change for NC12 */ interface ISimpleFolder { @@ -37,7 +37,7 @@ interface ISimpleFolder { * Get all the files in a folder * * @return ISimpleFile[] - * @since 9.2.0 + * @since 11.0.0 */ public function getDirectoryListing(); @@ -46,7 +46,7 @@ interface ISimpleFolder { * * @param string $name * @return bool - * @since 9.2.0 + * @since 11.0.0 */ public function fileExists($name); @@ -56,7 +56,7 @@ interface ISimpleFolder { * @param string $name * @return ISimpleFile * @throws NotFoundException - * @since 9.2.0 + * @since 11.0.0 */ public function getFile($name); @@ -66,7 +66,7 @@ interface ISimpleFolder { * @param string $name * @return ISimpleFile * @throws NotPermittedException - * @since 9.2.0 + * @since 11.0.0 */ public function newFile($name); @@ -74,7 +74,7 @@ interface ISimpleFolder { * Remove the folder and all the files in it * * @throws NotPermittedException - * @since 9.2.0 + * @since 11.0.0 */ public function delete(); @@ -82,7 +82,7 @@ interface ISimpleFolder { * Get the folder name * * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getName(); } diff --git a/lib/public/Files/SimpleFS/ISimpleRoot.php b/lib/public/Files/SimpleFS/ISimpleRoot.php index c2f9d4ff05d..3bfea656965 100644 --- a/lib/public/Files/SimpleFS/ISimpleRoot.php +++ b/lib/public/Files/SimpleFS/ISimpleRoot.php @@ -29,7 +29,7 @@ use OCP\Files\NotPermittedException; * Interface ISimpleRoot * * @package OCP\Files\SimpleFS - * @since 9.2.0 + * @since 11.0.0 * @internal This interface is experimental and might change for NC12 */ interface ISimpleRoot { @@ -40,7 +40,7 @@ interface ISimpleRoot { * @return ISimpleFolder * @throws NotFoundException * @throws \RuntimeException - * @since 9.2.0 + * @since 11.0.0 */ public function getFolder($name); @@ -50,7 +50,7 @@ interface ISimpleRoot { * @return ISimpleFolder[] * @throws NotFoundException * @throws \RuntimeException - * @since 9.2.0 + * @since 11.0.0 */ public function getDirectoryListing(); @@ -61,7 +61,7 @@ interface ISimpleRoot { * @return ISimpleFolder * @throws NotPermittedException * @throws \RuntimeException - * @since 9.2.0 + * @since 11.0.0 */ public function newFolder($name); } diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php index 31706342228..efd65d55f7e 100644 --- a/lib/public/IDBConnection.php +++ b/lib/public/IDBConnection.php @@ -256,7 +256,7 @@ interface IDBConnection { * Check whether or not the current database support 4byte wide unicode * * @return bool - * @since 9.2.0 + * @since 11.0.0 */ public function supports4ByteText(); } diff --git a/lib/public/IPreview.php b/lib/public/IPreview.php index c6417b4d182..cddf7fc6a66 100644 --- a/lib/public/IPreview.php +++ b/lib/public/IPreview.php @@ -99,7 +99,7 @@ interface IPreview { * @param string $mimeType To force a given mimetype for the file (files_versions needs this) * @return ISimpleFile * @throws NotFoundException - * @since 9.2.0 + * @since 11.0.0 */ public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null); diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php index 5220a0c65c7..1f8c23dbedf 100644 --- a/lib/public/IUserManager.php +++ b/lib/public/IUserManager.php @@ -148,13 +148,13 @@ interface IUserManager { * returns how many users have logged in once * * @return int - * @since 9.2.0 + * @since 11.0.0 */ public function countSeenUsers(); /** * @param \Closure $callback - * @since 9.2.0 + * @since 11.0.0 */ public function callForSeenUsers(\Closure $callback); diff --git a/lib/public/LDAP/IDeletionFlagSupport.php b/lib/public/LDAP/IDeletionFlagSupport.php index 5f7d3909195..26b90a3493d 100644 --- a/lib/public/LDAP/IDeletionFlagSupport.php +++ b/lib/public/LDAP/IDeletionFlagSupport.php @@ -26,20 +26,20 @@ namespace OCP\LDAP; * Interface IDeletionFlagSupport * * @package OCP\LDAP - * @since 9.2.0 + * @since 11.0.0 */ interface IDeletionFlagSupport { /** * Flag record for deletion. * @param string $uid user id - * @since 9.2.0 + * @since 11.0.0 */ public function flagRecord($uid); /** * Unflag record for deletion. * @param string $uid user id - * @since 9.2.0 + * @since 11.0.0 */ public function unflagRecord($uid); } diff --git a/lib/public/LDAP/ILDAPProvider.php b/lib/public/LDAP/ILDAPProvider.php index 473afb13885..3c07dfcbe8e 100644 --- a/lib/public/LDAP/ILDAPProvider.php +++ b/lib/public/LDAP/ILDAPProvider.php @@ -26,14 +26,14 @@ namespace OCP\LDAP; * Interface ILDAPProvider * * @package OCP\LDAP - * @since 9.2.0 + * @since 11.0.0 */ interface ILDAPProvider { /** * Translate a user id to LDAP DN. * @param string $uid user id * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getUserDN($uid); @@ -42,7 +42,7 @@ interface ILDAPProvider { * @param string $dn LDAP DN * @return string with the internal user name * @throws \Exception if translation was unsuccessful - * @since 9.2.0 + * @since 11.0.0 */ public function getUserName($dn); @@ -50,7 +50,7 @@ interface ILDAPProvider { * Convert a stored DN so it can be used as base parameter for LDAP queries. * @param string $dn the DN * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function DNasBaseParameter($dn); @@ -58,7 +58,7 @@ interface ILDAPProvider { * Sanitize a DN received from the LDAP server. * @param array $dn the DN in question * @return array the sanitized DN - * @since 9.2.0 + * @since 11.0.0 */ public function sanitizeDN($dn); @@ -66,7 +66,7 @@ interface ILDAPProvider { * Return a new LDAP connection resource for the specified user. * @param string $uid user id * @return resource of the LDAP connection - * @since 9.2.0 + * @since 11.0.0 */ public function getLDAPConnection($uid); @@ -75,7 +75,7 @@ interface ILDAPProvider { * @param string $uid user id * @return string the base for users * @throws \Exception if user id was not found in LDAP - * @since 9.2.0 + * @since 11.0.0 */ public function getLDAPBaseUsers($uid); @@ -84,7 +84,7 @@ interface ILDAPProvider { * @param string $uid user id * @return string the base for groups * @throws \Exception if user id was not found in LDAP - * @since 9.2.0 + * @since 11.0.0 */ public function getLDAPBaseGroups($uid); @@ -92,14 +92,14 @@ interface ILDAPProvider { * Check whether a LDAP DN exists * @param string $dn LDAP DN * @return bool whether the DN exists - * @since 9.2.0 + * @since 11.0.0 */ public function dnExists($dn); /** * Clear the cache if a cache is used, otherwise do nothing. * @param string $uid user id - * @since 9.2.0 + * @since 11.0.0 */ public function clearCache($uid); } diff --git a/lib/public/LDAP/ILDAPProviderFactory.php b/lib/public/LDAP/ILDAPProviderFactory.php index 99e7b8d27ea..74f84bff503 100644 --- a/lib/public/LDAP/ILDAPProviderFactory.php +++ b/lib/public/LDAP/ILDAPProviderFactory.php @@ -31,7 +31,7 @@ use OCP\IServerContainer; * instance. * * @package OCP\LDAP - * @since 9.2.0 + * @since 11.0.0 */ interface ILDAPProviderFactory { @@ -39,7 +39,7 @@ interface ILDAPProviderFactory { * Constructor for the LDAP provider factory * * @param IServerContainer $serverContainer server container - * @since 9.2.0 + * @since 11.0.0 */ public function __construct(IServerContainer $serverContainer); @@ -47,7 +47,7 @@ interface ILDAPProviderFactory { * creates and returns an instance of the ILDAPProvider * * @return ILDAPProvider - * @since 9.2.0 + * @since 11.0.0 */ public function getLDAPProvider(); } diff --git a/lib/public/Lockdown/ILockdownManager.php b/lib/public/Lockdown/ILockdownManager.php new file mode 100644 index 00000000000..d4d05b37ff8 --- /dev/null +++ b/lib/public/Lockdown/ILockdownManager.php @@ -0,0 +1,50 @@ +<?php + +/** + * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl> + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Lockdown; + +use OC\Authentication\Token\IToken; + +/** + * @since 9.2 + */ +interface ILockdownManager { + /** + * Enable the lockdown restrictions + * + * @since 9.2 + */ + public function enable(); + + /** + * Set the active token to get the restrictions from and enable the lockdown + * + * @param IToken $token + * @since 9.2 + */ + public function setToken(IToken $token); + + /** + * Check whether or not filesystem access is allowed + * + * @return bool + * @since 9.2 + */ + public function canAccessFilesystem(); +} diff --git a/lib/public/Notification/INotification.php b/lib/public/Notification/INotification.php index 55109a5ee2e..8d3f15c2380 100644 --- a/lib/public/Notification/INotification.php +++ b/lib/public/Notification/INotification.php @@ -132,19 +132,19 @@ interface INotification { * @param array $parameters * @return $this * @throws \InvalidArgumentException if the subject or parameters are invalid - * @since 9.2.0 + * @since 11.0.0 */ public function setRichSubject($subject, array $parameters = []); /** * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getRichSubject(); /** * @return array[] - * @since 9.2.0 + * @since 11.0.0 */ public function getRichSubjectParameters(); @@ -188,19 +188,19 @@ interface INotification { * @param array $parameters * @return $this * @throws \InvalidArgumentException if the message or parameters are invalid - * @since 9.2.0 + * @since 11.0.0 */ public function setRichMessage($message, array $parameters = []); /** * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getRichMessage(); /** * @return array[] - * @since 9.2.0 + * @since 11.0.0 */ public function getRichMessageParameters(); @@ -222,13 +222,13 @@ interface INotification { * @param string $icon * @return $this * @throws \InvalidArgumentException if the icon is invalid - * @since 9.2.0 + * @since 11.0.0 */ public function setIcon($icon); /** * @return string - * @since 9.2.0 + * @since 11.0.0 */ public function getIcon(); diff --git a/lib/public/RichObjectStrings/Definitions.php b/lib/public/RichObjectStrings/Definitions.php index d3e6b15cf99..2b35f9ceed1 100644 --- a/lib/public/RichObjectStrings/Definitions.php +++ b/lib/public/RichObjectStrings/Definitions.php @@ -26,12 +26,12 @@ namespace OCP\RichObjectStrings; * Class Definitions * * @package OCP\RichObjectStrings - * @since 9.2.0 + * @since 11.0.0 */ class Definitions { /** * @var array - * @since 9.2.0 + * @since 11.0.0 */ public $definitions = [ 'addressbook' => [ @@ -284,7 +284,7 @@ class Definitions { * @param string $type * @return array * @throws InvalidObjectExeption - * @since 9.2.0 + * @since 11.0.0 */ public function getDefinition($type) { if (isset($this->definitions[$type])) { diff --git a/lib/public/RichObjectStrings/IValidator.php b/lib/public/RichObjectStrings/IValidator.php index bba098bdfc1..8d9b86cfba3 100644 --- a/lib/public/RichObjectStrings/IValidator.php +++ b/lib/public/RichObjectStrings/IValidator.php @@ -25,7 +25,7 @@ namespace OCP\RichObjectStrings; * Class Validator * * @package OCP\RichObjectStrings - * @since 9.2.0 + * @since 11.0.0 */ interface IValidator { @@ -33,7 +33,7 @@ interface IValidator { * @param string $subject * @param array[] $parameters * @throws InvalidObjectExeption - * @since 9.2.0 + * @since 11.0.0 */ public function validate($subject, array $parameters); } diff --git a/lib/public/RichObjectStrings/InvalidObjectExeption.php b/lib/public/RichObjectStrings/InvalidObjectExeption.php index 8bf597ebe90..7d6ea6bc976 100644 --- a/lib/public/RichObjectStrings/InvalidObjectExeption.php +++ b/lib/public/RichObjectStrings/InvalidObjectExeption.php @@ -25,7 +25,7 @@ namespace OCP\RichObjectStrings; * Class InvalidObjectExeption * * @package OCP\RichObjectStrings - * @since 9.2.0 + * @since 11.0.0 */ class InvalidObjectExeption extends \InvalidArgumentException { } diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index e8b123cafb6..a15020bbd69 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -95,7 +95,7 @@ interface IManager { * @param Folder $node * @param bool $reshares * @return IShare[][] [$fileId => IShare[], ...] - * @since 9.2.0 + * @since 11.0.0 */ public function getSharesInFolder($userId, Folder $node, $reshares = false); @@ -290,7 +290,7 @@ interface IManager { * Check if a given share provider exists * @param int $shareType * @return bool - * @since 9.2.0 + * @since 11.0.0 */ public function shareProviderExists($shareType); diff --git a/lib/public/Share/IProviderFactory.php b/lib/public/Share/IProviderFactory.php index 928298a7860..7227fdea1f1 100644 --- a/lib/public/Share/IProviderFactory.php +++ b/lib/public/Share/IProviderFactory.php @@ -58,7 +58,7 @@ interface IProviderFactory { /** * @return IShareProvider[] - * @since 9.2.0 + * @since 11.0.0 */ public function getAllProviders(); } diff --git a/lib/public/Share/IShareProvider.php b/lib/public/Share/IShareProvider.php index 7d134583317..6257c97eb77 100644 --- a/lib/public/Share/IShareProvider.php +++ b/lib/public/Share/IShareProvider.php @@ -99,7 +99,7 @@ interface IShareProvider { * @param Folder $node * @param bool $reshares Also get the shares where $user is the owner instead of just the shares where $user is the initiator * @return \OCP\Share\IShare[] - * @since 9.2.0 + * @since 11.0.0 */ public function getSharesInFolder($userId, Folder $node, $reshares); diff --git a/lib/public/Util.php b/lib/public/Util.php index 08661fd88ca..8a7c8997613 100644 --- a/lib/public/Util.php +++ b/lib/public/Util.php @@ -83,7 +83,6 @@ class Util { */ public static function setChannel($channel) { //Flush timestamp to reload version.php - \OC::$server->getSession()->set('OC_Version_Timestamp', 0); \OC::$server->getAppConfig()->setValue('core', 'OC_Channel', $channel); \OC::$server->getConfig()->setSystemValue('updater.release.channel', $channel); } diff --git a/settings/Controller/AppSettingsController.php b/settings/Controller/AppSettingsController.php index 8164dd1fcfa..b668c5cfb38 100644 --- a/settings/Controller/AppSettingsController.php +++ b/settings/Controller/AppSettingsController.php @@ -169,10 +169,10 @@ class AppSettingsController extends Controller { $nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']); $nextCloudVersionDependencies = []; if($nextCloudVersion->getMinimumVersion() !== '') { - $nextCloudVersionDependencies['owncloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion(); + $nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion(); } if($nextCloudVersion->getMaximumVersion() !== '') { - $nextCloudVersionDependencies['owncloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion(); + $nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion(); } $phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']); $existsLocally = (\OC_App::getAppPath($app['id']) !== false) ? true : false; @@ -331,8 +331,8 @@ class AppSettingsController extends Controller { $app['canInstall'] = empty($missing); $app['missingDependencies'] = $missing; - $app['missingMinOwnCloudVersion'] = !isset($app['dependencies']['owncloud']['@attributes']['min-version']); - $app['missingMaxOwnCloudVersion'] = !isset($app['dependencies']['owncloud']['@attributes']['max-version']); + $app['missingMinOwnCloudVersion'] = !isset($app['dependencies']['nextcloud']['@attributes']['min-version']); + $app['missingMaxOwnCloudVersion'] = !isset($app['dependencies']['nextcloud']['@attributes']['max-version']); return $app; }, $apps); diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php index 58994f0d59c..4e3d05a14e8 100644 --- a/settings/Controller/AuthSettingsController.php +++ b/settings/Controller/AuthSettingsController.php @@ -135,11 +135,13 @@ class AuthSettingsController extends Controller { $token = $this->generateRandomDeviceToken(); $deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN); + $tokenData = $deviceToken->jsonSerialize(); + $tokenData['canDelete'] = true; return [ 'token' => $token, 'loginName' => $loginName, - 'deviceToken' => $deviceToken + 'deviceToken' => $tokenData ]; } @@ -180,4 +182,20 @@ class AuthSettingsController extends Controller { return []; } + /** + * @NoAdminRequired + * @NoSubadminRequired + * + * @param int $id + * @param array $scope + */ + public function update($id, array $scope) { + $token = $this->tokenProvider->getTokenById($id); + $token->setScope([ + 'filesystem' => $scope['filesystem'], + 'app' => array_values($scope['apps']) + ]); + $this->tokenProvider->updateToken($token); + return []; + } } diff --git a/settings/css/settings.css b/settings/css/settings.css index 7d139a632d0..9008ba5a985 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -149,6 +149,13 @@ table.nostyle td { padding: 0.2em 0; } padding: 10px 10px 10px 0; } +#sessions .token-list td.more, +#apppasswords .token-list td.more { + overflow: visible; + position: relative; + width: 16px; +} + #sessions .token-list td, #apppasswords .token-list td { border-top: 1px solid #DDD; @@ -156,18 +163,60 @@ table.nostyle td { padding: 0.2em 0; } max-width: 200px; white-space: nowrap; overflow: hidden; + vertical-align: top; + position: relative; } -#sessions tr *:nth-child(2), -#apppasswords tr *:nth-child(2) { +#sessions tr>*:nth-child(2), +#apppasswords tr>*:nth-child(2) { text-align: right; } -#sessions .token-list td a.icon-delete, -#apppasswords .token-list td a.icon-delete { +#sessions .token-list td > a.icon, +#apppasswords .token-list td > a.icon { + opacity: 0; + transition: opacity 0.5s; +} + +#sessions .token-list a.icon, +#apppasswords .token-list a.icon { + margin-top: 4px; display: block; +} + +#sessions .token-list tr:hover td > a.icon, +#apppasswords .token-list tr:hover td > a.icon, +#sessions .token-list tr.active td > a.icon, +#apppasswords .token-list tr.active td > a.icon{ opacity: 0.6; } +#sessions .token-list td div.configure, +#apppasswords .token-list td div.configure { + display: none; +} + +#sessions .token-list tr.active div.configure, +#apppasswords .token-list tr.active div.configure { + display: block; + position: absolute; + top: 45px; + right: -5px; + padding: 10px; +} + +#sessions .token-list tr.active div.configure > *, +#apppasswords .token-list tr.active div.configure > *{ + margin-top: 5px; + margin-bottom: 5px; + display: inline-block; +} + +#sessions .token-list tr.active a.icon-delete, +#apppasswords .token-list tr.active a.icon-delete { + background-position: left; + padding-left: 20px; +} + #new-app-login-name, #new-app-password { width: 186px; @@ -193,7 +242,7 @@ table.nostyle td { padding: 0.2em 0; } } .social-button { - padding-left: 0; + padding-left: 0 !important; margin-left: -10px } .social-button img { diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 6eb04b63f20..0939913cc1a 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -27,13 +27,22 @@ var TEMPLATE_TOKEN = '<tr data-id="{{id}}">' - + '<td class="has-tooltip" title="{{title}}"><span class="token-name">{{name}}</span></td>' + + '<td class="has-tooltip" title="{{title}}">' + + '<span class="token-name">{{name}}</span>' + + '</td>' + '<td><span class="last-activity has-tooltip" title="{{lastActivityTime}}">{{lastActivity}}</span></td>' + + '<td class="more">' + + '{{#if showMore}}<a class="icon icon-more"/>{{/if}}' + + '<div class="popovermenu bubble open menu configure">' + + '{{#if canScope}}' + + '<input class="filesystem checkbox" type="checkbox" id="{{id}}_filesystem" {{#if scope.filesystem}}checked{{/if}}/>' + + '<label for="{{id}}_filesystem">' + t('core', 'Allow filesystem access') + '</label><br/>' + + '{{/if}}' + '{{#if canDelete}}' - + '<td><a class="icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '"></a></td>' - + '{{else}}' - + '<td></td>' + + '<a class="icon icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '">' + t('core', 'Revoke') +'</a>' + '{{/if}}' + + '</div>' + + '</td>' + '<tr>'; var SubView = OC.Backbone.View.extend({ @@ -70,7 +79,7 @@ var list = this.$('.token-list'); var tokens = this.collection.filter(function (token) { - return parseInt(token.get('type'), 10) === _this.type; + return token.get('type') === _this.type; }); list.html(''); @@ -78,7 +87,7 @@ this._toggleHeader(tokens.length > 0); tokens.forEach(function (token) { - var viewData = this._formatViewData(token.toJSON()); + var viewData = this._formatViewData(token); var html = _this.template(viewData); var $html = $(html); $html.find('.has-tooltip').tooltip({container: 'body'}); @@ -94,10 +103,13 @@ this.$('.hidden-when-empty').toggleClass('hidden', !show); }, - _formatViewData: function (viewData) { + _formatViewData: function (token) { + var viewData = token.toJSON(); var ts = viewData.lastActivity * 1000; viewData.lastActivity = OC.Util.relativeModifiedDate(ts); viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL'); + viewData.canScope = token.get('type') === 1; + viewData.showMore = viewData.canScope || viewData.canDelete; // preserve title for cases where we format it further viewData.title = viewData.name; @@ -204,6 +216,8 @@ var $el = $(el); $el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this)); + $el.on('click', '.icon-more', _.bind(_this._onConfigureToken, _this)); + $el.on('change', 'input.filesystem', _.bind(_this._onSetTokenScope, _this)); }); this._form = $('#app-password-form'); @@ -219,14 +233,21 @@ this._hideAppPasswordBtn = $('#app-password-hide'); this._hideAppPasswordBtn.click(_.bind(this._hideToken, this)); + this._result.find('.clipboardButton').tooltip({placement: 'bottom', title: t('core', 'Copy'), trigger: 'hover'}); + // Clipboard! var clipboard = new Clipboard('.clipboardButton'); clipboard.on('success', function(e) { var $input = $(e.trigger); - $input.tooltip({placement: 'bottom', trigger: 'manual', title: t('core', 'Copied!')}); - $input.tooltip('show'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copied!')) + .tooltip('fixTitle') + .tooltip({placement: 'bottom', trigger: 'manual'}) + .tooltip('show'); _.delay(function() { - $input.tooltip('hide'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copy')) + .tooltip('fixTitle'); }, 3000); }); clipboard.on('error', function (e) { @@ -240,14 +261,15 @@ actionMsg = t('core', 'Press Ctrl-C to copy.'); } - $input.tooltip({ - placement: 'bottom', - trigger: 'manual', - title: actionMsg - }); - $input.tooltip('show'); + $input.tooltip('hide') + .attr('data-original-title', actionMsg) + .tooltip('fixTitle') + .tooltip({placement: 'bottom', trigger: 'manual'}) + .tooltip('show'); _.delay(function () { - $input.tooltip('hide'); + $input.tooltip('hide') + .attr('data-original-title', t('core', 'Copy')) + .tooltip('fixTitle'); }, 3000); }); }, @@ -325,6 +347,13 @@ this._addAppPasswordBtn.toggleClass('icon-loading-small', state); }, + _onConfigureToken: function (event) { + var $target = $(event.target); + var $row = $target.closest('tr'); + $row.toggleClass('active'); + var id = $row.data('id'); + }, + _onDeleteToken: function (event) { var $target = $(event.target); var $row = $target.closest('tr'); @@ -353,6 +382,24 @@ }); }, + _onSetTokenScope: function (event) { + var $target = $(event.target); + var $row = $target.closest('tr'); + var id = $row.data('id'); + + var token = this.collection.get(id); + if (_.isUndefined(token)) { + // Ignore event + return; + } + + var scope = token.get('scope'); + scope.filesystem = $target.is(":checked"); + + token.set('scope', scope); + token.save(); + }, + _toggleFormResult: function (showForm) { if (showForm) { this._result.slideUp(); diff --git a/settings/templates/admin/server.development.notice.php b/settings/templates/admin/server.development.notice.php new file mode 100644 index 00000000000..f58258fc0ae --- /dev/null +++ b/settings/templates/admin/server.development.notice.php @@ -0,0 +1,3 @@ +<div class="section"> + <p><?php include(__DIR__ . '/../settings.development.notice.php'); ?></p> +</div> diff --git a/settings/templates/admin/server.php b/settings/templates/admin/server.php index 430ca6ac8e2..d87fa81729f 100644 --- a/settings/templates/admin/server.php +++ b/settings/templates/admin/server.php @@ -224,5 +224,4 @@ <!-- should be the last part, so Updater can follow if enabled (it has no heading therefore). --> <h2><?php p($l->t('Version'));?></h2> <p><a href="<?php print_unescaped($theme->getBaseUrl()); ?>" rel="noreferrer" target="_blank"><?php p($theme->getTitle()); ?></a> <?php p(OC_Util::getHumanVersion()) ?></p> - <p><?php include(__DIR__ . '/../settings.development.notice.php'); ?></p> </div> diff --git a/settings/templates/personal.php b/settings/templates/personal.php index ea1c7ba6459..69d3660477d 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -182,12 +182,16 @@ if($_['passwordChangeSupported']) { </a> <p> - <?php print_unescaped($l->t('If you want to support the project - <a href="https://nextcloud.com/contribute" - target="_blank" rel="noreferrer">join development</a> - or - <a href="https://nextcloud.com/contribute" - target="_blank" rel="noreferrer">spread the word</a>!'));?> + <?php print_unescaped(str_replace( + [ + '{contributeopen}', + '{linkclose}', + ], + [ + '<a href="https://nextcloud.com/contribute" target="_blank" rel="noreferrer">', + '</a>', + ], + $l->t('If you want to support the project {contributeopen}join development{linkclose} or {contributeopen}spread the word{linkclose}!'))); ?> </p> <?php if(OC_APP::isEnabled('firstrunwizard')) {?> diff --git a/tests/Settings/Controller/AuthSettingsControllerTest.php b/tests/Settings/Controller/AuthSettingsControllerTest.php index 9cb49e4eb3f..782c9f644e0 100644 --- a/tests/Settings/Controller/AuthSettingsControllerTest.php +++ b/tests/Settings/Controller/AuthSettingsControllerTest.php @@ -42,6 +42,7 @@ class AuthSettingsControllerTest extends TestCase { /** @var AuthSettingsController */ private $controller; private $request; + /** @var IProvider|\PHPUnit_Framework_MockObject_MockObject */ private $tokenProvider; private $userManager; private $session; @@ -94,17 +95,19 @@ class AuthSettingsControllerTest extends TestCase { [ 'id' => 100, 'name' => null, - 'lastActivity' => null, - 'type' => null, + 'lastActivity' => 0, + 'type' => 0, 'canDelete' => false, 'current' => true, + 'scope' => ['filesystem' => true] ], [ 'id' => 200, 'name' => null, - 'lastActivity' => null, - 'type' => null, + 'lastActivity' => 0, + 'type' => 0, 'canDelete' => true, + 'scope' => ['filesystem' => true] ] ], $this->controller->index()); } @@ -141,9 +144,13 @@ class AuthSettingsControllerTest extends TestCase { ->with($newToken, $this->uid, 'User13', $password, $name, IToken::PERMANENT_TOKEN) ->will($this->returnValue($deviceToken)); + $deviceToken->expects($this->once()) + ->method('jsonSerialize') + ->will($this->returnValue(['dummy' => 'dummy', 'canDelete' => true])); + $expected = [ 'token' => $newToken, - 'deviceToken' => $deviceToken, + 'deviceToken' => ['dummy' => 'dummy', 'canDelete' => true], 'loginName' => 'User13', ]; $this->assertEquals($expected, $this->controller->create($name)); @@ -194,4 +201,26 @@ class AuthSettingsControllerTest extends TestCase { $this->assertEquals([], $this->controller->destroy($id)); } + public function testUpdateToken() { + $token = $this->createMock(DefaultToken::class); + + $this->tokenProvider->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willReturn($token); + + $token->expects($this->once()) + ->method('setScope') + ->with($this->equalTo([ + 'filesystem' => true, + 'app' => ['dav', 'myapp'] + ])); + + $this->tokenProvider->expects($this->once()) + ->method('updateToken') + ->with($this->equalTo($token)); + + $this->assertSame([], $this->controller->update(42, ['filesystem' => true, 'apps' => ['dav', 'myapp']])); + } + } diff --git a/tests/data/app/expected-info.json b/tests/data/app/expected-info.json index fb596f296da..646f22bea85 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 cf855dd2813..13932f389f8 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'); }); diff --git a/tests/lib/App/AppStore/Fetcher/AppFetcherTest.php b/tests/lib/App/AppStore/Fetcher/AppFetcherTest.php index 3b0418a7eba..fb2617cbfe8 100644 --- a/tests/lib/App/AppStore/Fetcher/AppFetcherTest.php +++ b/tests/lib/App/AppStore/Fetcher/AppFetcherTest.php @@ -27,7 +27,7 @@ class AppFetcherTest extends FetcherBase { public function setUp() { parent::setUp(); $this->fileName = 'apps.json'; - $this->endpoint = 'https://apps.nextcloud.com/api/v1/platform/9.2.0/apps.json'; + $this->endpoint = 'https://apps.nextcloud.com/api/v1/platform/11.0.0/apps.json'; $this->fetcher = new AppFetcher( $this->appData, diff --git a/tests/lib/App/DependencyAnalyzerTest.php b/tests/lib/App/DependencyAnalyzerTest.php index fd44954eaf4..65b45a002d4 100644 --- a/tests/lib/App/DependencyAnalyzerTest.php +++ b/tests/lib/App/DependencyAnalyzerTest.php @@ -295,7 +295,7 @@ class DependencyAnalyzerTest extends TestCase { ], [ [ - 'Server version 11 or higher is required.', + 'Server version 9.2 or higher is required.', ], [ 'nextcloud' => [ @@ -307,6 +307,18 @@ class DependencyAnalyzerTest extends TestCase { ], [ [ + 'Server version 11 or higher is required.', + ], + [ + 'nextcloud' => [ + '@attributes' => [ + 'min-version' => '11', + ], + ], + ], + ], + [ + [ 'Server version 8.0.1 or lower is required.', ], [ @@ -388,7 +400,7 @@ class DependencyAnalyzerTest extends TestCase { ], [ [ - 'Server version 11 or higher is required.', + 'Server version 9.2 or higher is required.', ], [ 'owncloud' => [ diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php index 418a4d14f62..8fe0762daad 100644 --- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php @@ -27,6 +27,7 @@ use OC\Authentication\Token\DefaultToken; use OC\Authentication\Token\DefaultTokenMapper; use OC\Authentication\Token\IToken; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; use OCP\IUser; use Test\TestCase; @@ -40,6 +41,8 @@ class DefaultTokenMapperTest extends TestCase { /** @var DefaultTokenMapper */ private $mapper; + + /** @var IDBConnection */ private $dbConnection; private $time; @@ -122,7 +125,6 @@ class DefaultTokenMapperTest extends TestCase { } public function testGetToken() { - $token = '1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'; $token = new DefaultToken(); $token->setUid('user2'); $token->setLoginName('User2'); @@ -151,6 +153,42 @@ class DefaultTokenMapperTest extends TestCase { $this->mapper->getToken($token); } + public function testGetTokenById() { + $token = new DefaultToken(); + $token->setUid('user2'); + $token->setLoginName('User2'); + $token->setPassword('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f'); + $token->setName('Firefox on Android'); + $token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'); + $token->setType(IToken::TEMPORARY_TOKEN); + $token->setRemember(IToken::DO_NOT_REMEMBER); + $token->setLastActivity($this->time - 60 * 60 * 24 * 3); + $token->setLastCheck($this->time - 10); + + $dbToken = $this->mapper->getToken($token->getToken()); + $token->setId($dbToken->getId()); // We don't know the ID + $token->resetUpdatedFields(); + + $dbToken = $this->mapper->getTokenById($token->getId()); + $this->assertEquals($token, $dbToken); + } + + /** + * @expectedException \OCP\AppFramework\Db\DoesNotExistException + */ + public function testGetTokenByIdNotFound() { + $this->mapper->getTokenById(-1); + } + + /** + * @expectedException \OCP\AppFramework\Db\DoesNotExistException + */ + public function testGetInvalidTokenById() { + $id = 42; + + $this->mapper->getToken($id); + } + public function testGetTokenByUser() { $user = $this->createMock(IUser::class); $user->expects($this->once()) diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php index 5e4d4f94366..8d92ee405a1 100644 --- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php @@ -22,9 +22,11 @@ namespace Test\Authentication\Token; +use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Token\DefaultToken; use OC\Authentication\Token\DefaultTokenProvider; use OC\Authentication\Token\IToken; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Mapper; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; @@ -376,4 +378,25 @@ class DefaultTokenProviderTest extends TestCase { $this->tokenProvider->renewSessionToken('oldId', 'newId'); } + public function testGetTokenById() { + $token = $this->createMock(DefaultToken::class); + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willReturn($token); + + $this->assertSame($token, $this->tokenProvider->getTokenById(42)); + } + + public function testGetInvalidTokenById() { + $this->expectException(InvalidTokenException::class); + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willThrowException(new DoesNotExistException('nope')); + + $this->tokenProvider->getTokenById(42); + } } diff --git a/tests/lib/Authentication/Token/DefaultTokenTest.php b/tests/lib/Authentication/Token/DefaultTokenTest.php new file mode 100644 index 00000000000..f00c32ccaf5 --- /dev/null +++ b/tests/lib/Authentication/Token/DefaultTokenTest.php @@ -0,0 +1,49 @@ +<?php +/** + * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Authentication\Token; + +use OC\Authentication\Token\DefaultToken; +use Test\TestCase; + +class DefaultTokenTest extends TestCase { + public function testSetScopeAsArray() { + $scope = ['filesystem' => false]; + $token = new DefaultToken(); + $token->setScope($scope); + $this->assertEquals(json_encode($scope), $token->getScope()); + $this->assertEquals($scope, $token->getScopeAsArray()); + } + + public function testSetScopeAsString() { + $scope = ['filesystem' => false]; + $token = new DefaultToken(); + $token->setScope(json_encode($scope)); + $this->assertEquals(json_encode($scope), $token->getScope()); + $this->assertEquals($scope, $token->getScopeAsArray()); + } + + public function testDefaultScope() { + $scope = ['filesystem' => true]; + $token = new DefaultToken(); + $this->assertEquals($scope, $token->getScopeAsArray()); + } +} diff --git a/tests/lib/Lockdown/Filesystem/NoFSTest.php b/tests/lib/Lockdown/Filesystem/NoFSTest.php new file mode 100644 index 00000000000..a0900ad769d --- /dev/null +++ b/tests/lib/Lockdown/Filesystem/NoFSTest.php @@ -0,0 +1,63 @@ +<?php +/** + * @copyright 2016, Robin Appelman <robin@icewind.nl> + * + * @author Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Lockdown\Filesystem; + +use OC\Authentication\Token\DefaultToken; +use OC\Files\Filesystem; +use OC\Lockdown\Filesystem\NullStorage; +use Test\Traits\UserTrait; + +/** + * @group DB + */ +class NoFSTest extends \Test\TestCase { + use UserTrait; + + public function tearDown() { + $token = new DefaultToken(); + $token->setScope([ + 'filesystem' => true + ]); + \OC::$server->getLockdownManager()->setToken($token); + return parent::tearDown(); + } + + public function setUp() { + parent::setUp(); + $token = new DefaultToken(); + $token->setScope([ + 'filesystem' => false + ]); + + \OC::$server->getLockdownManager()->setToken($token); + $this->createUser('foo', 'var'); + } + + public function testSetupFS() { + \OC_Util::tearDownFS(); + \OC_Util::setupFS('foo'); + + $this->assertInstanceOf(NullStorage::class, Filesystem::getStorage('/foo/files')); + } +} diff --git a/tests/lib/Lockdown/Filesystem/NullCacheTest.php b/tests/lib/Lockdown/Filesystem/NullCacheTest.php new file mode 100644 index 00000000000..3a4e3f3a402 --- /dev/null +++ b/tests/lib/Lockdown/Filesystem/NullCacheTest.php @@ -0,0 +1,157 @@ +<?php +/** + * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Lockdown\Filesystem; + +use OC\ForbiddenException; +use OC\Lockdown\Filesystem\NullCache; +use OCP\Constants; +use OCP\Files\Cache\ICache; +use OCP\Files\FileInfo; + +class NulLCacheTest extends \Test\TestCase { + + /** @var NullCache */ + private $cache; + + public function setUp() { + parent::setUp(); + + $this->cache = new NullCache(); + } + + public function testGetNumericStorageId() { + $this->assertSame(-1, $this->cache->getNumericStorageId()); + } + + public function testGetEmpty() { + $this->assertNull($this->cache->get('foo')); + } + + public function testGet() { + $data = $this->cache->get(''); + + $this->assertEquals(-1, $data['fileid']); + $this->assertEquals(-1, $data['parent']); + $this->assertEquals('', $data['name']); + $this->assertEquals('', $data['path']); + $this->assertEquals('0', $data['size']); + $this->assertEquals('', $data['etag']); + $this->assertEquals(FileInfo::MIMETYPE_FOLDER, $data['mimetype']); + $this->assertEquals('httpd', $data['mimepart']); + $this->assertEquals(Constants::PERMISSION_READ, $data['permissions']); + } + + public function testGetFolderContents() { + $this->assertSame([], $this->cache->getFolderContents('foo')); + } + + public function testGetFolderContentsById() { + $this->assertSame([], $this->cache->getFolderContentsById(42)); + } + + public function testPut() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->put('foo', ['size' => 100]); + } + + public function testInsert() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->insert('foo', ['size' => 100]); + } + + public function testUpdate() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->update('foo', ['size' => 100]); + } + + public function testGetId() { + $this->assertSame(-1, $this->cache->getId('foo')); + } + + public function testGetParentId() { + $this->assertSame(-1, $this->cache->getParentId('foo')); + } + + public function testInCache() { + $this->assertTrue($this->cache->inCache('')); + $this->assertFalse($this->cache->inCache('foo')); + } + + public function testRemove() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->remove('foo'); + } + + public function testMove() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->move('foo', 'bar'); + } + + public function testMoveFromCache() { + $sourceCache = $this->createMock(ICache::class); + + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->moveFromCache($sourceCache, 'foo', 'bar'); + } + + public function testGetStatus() { + $this->assertSame(ICache::COMPLETE, $this->cache->getStatus('foo')); + } + + public function testSearch() { + $this->assertSame([], $this->cache->search('foo')); + } + + public function testSearchByMime() { + $this->assertSame([], $this->cache->searchByMime('foo')); + } + + public function testSearchByTag() { + $this->assertSame([], $this->cache->searchByTag('foo', 'user')); + } + + public function testGetIncomplete() { + $this->assertSame([], $this->cache->getIncomplete()); + } + + public function testGetPathById() { + $this->assertSame('', $this->cache->getPathById(42)); + } + + public function testNormalize() { + $this->assertSame('foo/ bar /', $this->cache->normalize('foo/ bar /')); + } +} diff --git a/tests/lib/Lockdown/Filesystem/NullStorageTest.php b/tests/lib/Lockdown/Filesystem/NullStorageTest.php new file mode 100644 index 00000000000..dc99eb4c03a --- /dev/null +++ b/tests/lib/Lockdown/Filesystem/NullStorageTest.php @@ -0,0 +1,245 @@ +<?php +/** + * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Lockdown\Filesystem; + +use Icewind\Streams\IteratorDirectory; +use OC\ForbiddenException; +use OC\Lockdown\Filesystem\NullCache; +use OC\Lockdown\Filesystem\NullStorage; +use OCP\Files\Storage; +use Test\TestCase; + +class NullStorageTest extends TestCase { + + /** @var NullStorage */ + private $storage; + + public function setUp() { + parent::setUp(); + + $this->storage = new NullStorage([]); + } + + public function testGetId() { + $this->assertSame('null', $this->storage->getId()); + } + + public function testMkdir() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->mkdir('foo'); + } + + public function testRmdir() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->rmdir('foo'); + } + + public function testOpendir() { + $this->assertInstanceOf(IteratorDirectory::class, $this->storage->opendir('foo')); + } + + public function testIs_dir() { + $this->assertTrue($this->storage->is_dir('')); + $this->assertFalse($this->storage->is_dir('foo')); + } + + public function testIs_file() { + $this->assertFalse($this->storage->is_file('foo')); + } + + public function testStat() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->stat('foo'); + } + + public function testFiletype() { + $this->assertSame('dir', $this->storage->filetype('')); + $this->assertFalse($this->storage->filetype('foo')); + } + + public function testFilesize() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->filesize('foo'); + } + + public function testIsCreatable() { + $this->assertFalse($this->storage->isCreatable('foo')); + } + + public function testIsReadable() { + $this->assertTrue($this->storage->isReadable('')); + $this->assertFalse($this->storage->isReadable('foo')); + } + + public function testIsUpdatable() { + $this->assertFalse($this->storage->isUpdatable('foo')); + } + + public function testIsDeletable() { + $this->assertFalse($this->storage->isDeletable('foo')); + } + + public function testIsSharable() { + $this->assertFalse($this->storage->isSharable('foo')); + } + + public function testGetPermissions() { + $this->assertNull($this->storage->getPermissions('foo')); + } + + public function testFile_exists() { + $this->assertTrue($this->storage->file_exists('')); + $this->assertFalse($this->storage->file_exists('foo')); + } + + public function testFilemtime() { + $this->assertFalse($this->storage->filemtime('foo')); + } + + public function testFile_get_contents() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->file_get_contents('foo'); + } + + public function testFile_put_contents() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->file_put_contents('foo', 'bar'); + } + + public function testUnlink() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->unlink('foo'); + } + + public function testRename() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->rename('foo', 'bar'); + } + + public function testCopy() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->copy('foo', 'bar'); + } + + public function testFopen() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->fopen('foo', 'R'); + } + + public function testGetMimeType() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->getMimeType('foo'); + } + + public function testHash() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->hash('md5', 'foo', true); + } + + public function testFree_space() { + $this->assertSame(0, $this->storage->free_space('foo')); + } + + public function testTouch() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->touch('foo'); + } + + public function testGetLocalFile() { + $this->assertFalse($this->storage->getLocalFile('foo')); + } + + public function testHasUpdated() { + $this->assertFalse($this->storage->hasUpdated('foo', 42)); + } + + public function testGetETag() { + $this->assertSame('', $this->storage->getETag('foo')); + } + + public function testIsLocal() { + $this->assertFalse($this->storage->isLocal()); + } + + public function testGetDirectDownload() { + $this->assertFalse($this->storage->getDirectDownload('foo')); + } + + public function testCopyFromStorage() { + $sourceStorage = $this->createMock(Storage::class); + + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->copyFromStorage($sourceStorage, 'foo', 'bar'); + } + + public function testMoveFromStorage() { + $sourceStorage = $this->createMock(Storage::class); + + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->moveFromStorage($sourceStorage, 'foo', 'bar'); + } + + public function testTest() { + $this->assertTrue($this->storage->test()); + return true; + } + + public function testGetOwner() { + $this->assertNull($this->storage->getOwner('foo')); + } + + public function testGetCache() { + $this->assertInstanceOf(NullCache::class, $this->storage->getCache('foo')); + } +} diff --git a/tests/lib/Lockdown/LockdownManagerTest.php b/tests/lib/Lockdown/LockdownManagerTest.php new file mode 100644 index 00000000000..4cbd9d71a5c --- /dev/null +++ b/tests/lib/Lockdown/LockdownManagerTest.php @@ -0,0 +1,49 @@ +<?php +/** + * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Lockdown; + +use OC\Authentication\Token\DefaultToken; +use OC\Lockdown\LockdownManager; +use Test\TestCase; + +class LockdownManagerTest extends TestCase { + public function testCanAccessFilesystemDisabled() { + $manager = new LockdownManager(); + $this->assertTrue($manager->canAccessFilesystem()); + } + + public function testCanAccessFilesystemAllowed() { + $token = new DefaultToken(); + $token->setScope(['filesystem' => true]); + $manager = new LockdownManager(); + $manager->setToken($token); + $this->assertTrue($manager->canAccessFilesystem()); + } + + public function testCanAccessFilesystemNotAllowed() { + $token = new DefaultToken(); + $token->setScope(['filesystem' => false]); + $manager = new LockdownManager(); + $manager->setToken($token); + $this->assertFalse($manager->canAccessFilesystem()); + } +} diff --git a/tests/lib/TestCase.php b/tests/lib/TestCase.php index 42b2273e119..f115c11938a 100644 --- a/tests/lib/TestCase.php +++ b/tests/lib/TestCase.php @@ -24,7 +24,6 @@ namespace Test; use DOMDocument; use DOMNode; -use OC\Cache\CappedMemoryCache; use OC\Command\QueueBus; use OC\Files\Filesystem; use OC\Template\Base; @@ -34,7 +33,7 @@ use OCP\IDBConnection; use OCP\IL10N; use OCP\Security\ISecureRandom; -abstract class TestCase extends \PHPUnit_Framework_TestCase { +abstract class TestCase extends TestCasePhpUnitCompatibility { /** @var \OC\Command\QueueBus */ private $commandBus; @@ -153,7 +152,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase { } } - protected function onNotSuccessfulTest($e) { + protected function realOnNotSuccessfulTest() { $this->restoreAllServices(); // restore database connection @@ -162,8 +161,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase { return self::$realDatabase; }); } - - parent::onNotSuccessfulTest($e); } protected function tearDown() { diff --git a/tests/lib/TestCasePhpUnit4.php b/tests/lib/TestCasePhpUnit4.php new file mode 100644 index 00000000000..f49cf7d40f3 --- /dev/null +++ b/tests/lib/TestCasePhpUnit4.php @@ -0,0 +1,37 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test; + +/** + * FIXME Remove this once phpunit 5 is the lowest supported version, by reverting: + * https://github.com/nextcloud/server/pull/2137 + */ +abstract class TestCasePhpUnit4 extends \PHPUnit_Framework_TestCase { + + abstract protected function realOnNotSuccessfulTest(); + + protected function onNotSuccessfulTest(\Exception $e) { + $this->realOnNotSuccessfulTest(); + + parent::onNotSuccessfulTest($e); + } +} diff --git a/tests/lib/TestCasePhpUnit5.php b/tests/lib/TestCasePhpUnit5.php new file mode 100644 index 00000000000..5def70e57fa --- /dev/null +++ b/tests/lib/TestCasePhpUnit5.php @@ -0,0 +1,37 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test; + +/** + * FIXME Remove this once phpunit 5 is the lowest supported version, by reverting: + * https://github.com/nextcloud/server/pull/2137 + */ +abstract class TestCasePhpUnit5 extends \PHPUnit_Framework_TestCase { + + abstract protected function realOnNotSuccessfulTest(); + + protected function onNotSuccessfulTest($e) { + $this->realOnNotSuccessfulTest(); + + parent::onNotSuccessfulTest($e); + } +} diff --git a/tests/lib/TestCasePhpUnitCompatibility.php b/tests/lib/TestCasePhpUnitCompatibility.php new file mode 100644 index 00000000000..cb243d1ce6f --- /dev/null +++ b/tests/lib/TestCasePhpUnitCompatibility.php @@ -0,0 +1,32 @@ +<?php +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace Test; + +/** + * FIXME Remove this once phpunit 5 is the lowest supported version, by reverting: + * https://github.com/nextcloud/server/pull/2137 + */ +if (version_compare(\PHPUnit_Runner_Version::id(), '5.0.0', '>=')) { + abstract class TestCasePhpUnitCompatibility extends TestCasePhpUnit5 {} +} else { + abstract class TestCasePhpUnitCompatibility extends TestCasePhpUnit4 {} +} diff --git a/version.php b/version.php index e6de2e2bde0..d556386a848 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(9, 2, 0, 5); +$OC_Version = array(11, 0, 0, 1); // The human readable string $OC_VersionString = '11.0 alpha'; |