Browse Source

Settings to vuejs

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
John Molakvoæ (skjnldsv) 6 years ago
parent
commit
c8f670dd8f
42 changed files with 2156 additions and 6610 deletions
  1. 180 3
      core/css/inputs.scss
  2. 6 0
      settings/.babelrc
  3. 9 0
      settings/.editorconfig
  4. 12 0
      settings/.gitignore
  5. 0 1018
      settings/Controller/UsersController.php
  6. 18 0
      settings/README.md
  7. 171 95
      settings/css/settings.scss
  8. 11 0
      settings/index.html
  9. 70 0
      settings/js/main.js
  10. 0 0
      settings/js/main.js.map
  11. 0 213
      settings/js/users/deleteHandler.js
  12. 0 78
      settings/js/users/filter.js
  13. 0 385
      settings/js/users/groups.js
  14. 0 1189
      settings/js/users/users.js
  15. 24 0
      settings/main.php
  16. 42 0
      settings/package.json
  17. 1 1
      settings/routes.php
  18. 3 0
      settings/src/.jshintrc
  19. 16 0
      settings/src/App.vue
  20. 32 0
      settings/src/components/appNavigation.vue
  21. 108 0
      settings/src/components/appNavigation/navigationItem.vue
  22. 18 0
      settings/src/components/popoverMenu.vue
  23. 23 0
      settings/src/components/popoverMenu/popoverItem.vue
  24. 205 0
      settings/src/components/userList.vue
  25. 370 0
      settings/src/components/userList/userRow.vue
  26. 20 0
      settings/src/main.js
  27. 23 0
      settings/src/router.js
  28. 50 0
      settings/src/store/api.js
  29. 24 0
      settings/src/store/index.js
  30. 18 0
      settings/src/store/settings.js
  31. 380 0
      settings/src/store/users.js
  32. 152 0
      settings/src/views/Users.vue
  33. 26 8
      settings/templates/settings.php
  34. 0 80
      settings/templates/users/main.php
  35. 0 3
      settings/templates/users/part.createuser.php
  36. 0 69
      settings/templates/users/part.grouplist.php
  37. 0 35
      settings/templates/users/part.setquota.php
  38. 0 149
      settings/templates/users/part.userlist.php
  39. 0 220
      settings/tests/js/users/deleteHandlerSpec.js
  40. 36 42
      settings/users.php
  41. 108 0
      settings/webpack.config.js
  42. 0 3022
      tests/Settings/Controller/UsersControllerTest.php

+ 180 - 3
core/css/inputs.scss

@@ -67,6 +67,13 @@ div[contenteditable=true],
 		cursor: default;
 		opacity: 0.5;
 	}
+    &:required {
+        box-shadow: none;
+    }
+    &:invalid {
+        box-shadow: none !important;
+        border-color: $color-error;
+    }
 	/* Primary action button, use sparingly */
 	&.primary {
 		background-color: $color-primary-element;
@@ -216,7 +223,8 @@ input {
 			margin-left: -8px !important;
 			border-left-color: transparent !important;
 			border-radius: 0 $border-radius $border-radius 0 !important;
-			background-clip: padding-box; /* Avoid background under border */
+			background-clip: padding-box;
+			/* Avoid background under border */
 			background-color: $color-main-background !important;
 			opacity: 1;
 			width: 34px;
@@ -227,6 +235,7 @@ input {
 				background-image: url('../img/actions/confirm-fade.svg?v=2') !important;
 			}
 		}
+
 		/* only show confirm borders if input is not focused */
 		&:not(:active):not(:hover):not(:focus){
 			+ .icon-confirm {
@@ -244,14 +253,19 @@ input {
 		&:active,
 		&:hover,
 		&:focus {
+            &:invalid {
+                + .icon-confirm {
+                    border-color: $color-error;
+                }
+            }
 			+ .icon-confirm {
 				border-color: $color-primary-element !important;
 				border-left-color: transparent !important;
-				z-index: 2; /* above previous input */
+                /* above previous input */
+				z-index: 2;
 			}
 		}
 	}
-
 }
 
 
@@ -606,6 +620,169 @@ input {
 	}
 }
 
+
+/* Vue multiselect */
+.multiselect.multiselect-vue {
+    margin: 1px 2px;
+    padding: 0 !important;
+    display: inline-block;
+    min-width: 160px;
+    width: 160px;
+    position: relative;
+    background-color: $color-main-background;
+    &.multiselect--active {
+        /* Opened: force display the input */
+        input.multiselect__input {
+            opacity: 1 !important;
+        }
+    }
+    &.multiselect--disabled {
+        background-color: nc-darken($color-main-background, 8%);
+    }
+    .multiselect__tags {
+        display: flex;
+        flex-wrap: nowrap;
+        overflow: hidden;
+        border: 1px solid nc-darken($color-main-background, 14%);
+        cursor: pointer;
+        position: relative;
+        border-radius: 3px;
+        height: 38px;
+        /* tag wrapper */
+        .multiselect__tags-wrap {
+            align-items: center;
+            display: inline-flex;
+            overflow: hidden;
+            max-width: 100%;
+            position: relative;
+            padding: 3px 5px;
+            /* no tags or simple select? Show input directly
+			   input is used to display single value */
+            &:empty ~ input.multiselect__input {
+                opacity: 1 !important;
+                /* hide default empty text, show input instead */
+                + span:not(.multiselect__single) {
+                    display: none;
+                }
+            }
+            /* selected tag */
+            .multiselect__tag {
+                flex: 0 0 auto;
+                line-height: 20px;
+                padding: 1px 5px;
+                background-image: none;
+                color: nc-lighten($color-main-text, 33%);
+                border: 1px solid nc-darken($color-main-background, 14%);
+                display: inline-flex;
+                align-items: center;
+                border-radius: 3px;
+                margin-right: 5px;
+            }
+        }
+        /* Single select default value */
+        .multiselect__single {
+            padding: 8px 10px;
+            flex: 0 0 100%;
+            z-index: 5;
+            background-color: $color-main-background;
+            cursor: pointer;
+        }
+        /* displayed text if tag limit reached */
+        .multiselect__strong {
+            flex: 0 0 auto;
+            line-height: 20px;
+            color: nc-lighten($color-main-text, 33%);
+            display: inline-flex;
+            align-items: center;
+	        opacity: .7;
+        }
+        /* default multiselect input for search and placeholder */
+        input.multiselect__input {
+            width: 100% !important;
+            position: absolute !important;
+            margin: 0;
+            opacity: 0;
+            /* let's leave it on top of tags but hide it */
+            height: 100%;
+            border: none;
+            /* override hide to force show the placeholder */
+            display: block !important;
+        }
+    }
+    /* results wrapper */
+    .multiselect__content-wrapper {
+        position: absolute;
+        width: 100%;
+        margin-top: -1px;
+        border: 1px solid nc-darken($color-main-background, 14%);
+        background: $color-main-background;
+        z-index: 50;
+        .multiselect__content {
+            width: 100%;
+            padding: 5px 0;
+        }
+        li {
+            padding: 5px;
+            position: relative;
+            display: flex;
+            align-items: center;
+            background-color: transparent;
+            &,
+            span {
+                cursor: pointer;
+            }
+            > span {
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                height: 20px;
+                margin: 0;
+                min-height: 1em;
+                -webkit-touch-callout: none;
+                -webkit-user-select: none;
+                -moz-user-select: none;
+                -ms-user-select: none;
+                user-select: none;
+                display: inline-flex;
+                align-items: center;
+                background-color: transparent !important;
+                color: nc-lighten($color-main-text, 33%);
+                width: 100%;
+                /* selected checkmark icon */
+                &::before {
+                    content: ' ';
+                    background-image: url('../img/actions/checkmark.svg?v=1');
+                    background-repeat: no-repeat;
+                    background-position: center;
+                    min-width: 16px;
+                    min-height: 16px;
+                    display: block;
+                    opacity: 0.5;
+                    margin-right: 5px;
+                    visibility: hidden;
+                }
+                /* add the prop tag-placeholder="create" to add the +
+				 * icon on top of an unknown-and-ready-to-be-created entry
+				 */
+                &[data-select='create'] {
+                    &::before {
+                        background-image: url('../img/actions/add.svg?v=1');
+                        visibility: visible;
+                    }
+                }
+                &.multiselect__option--highlight {
+                    color: $color-main-text;
+                }
+                &.multiselect__option--selected {
+                    &::before {
+                        visibility: visible;
+                    }
+                }
+            }
+        }
+    }
+}
+
 /* Progressbar */
 progress {
 	display: block;

+ 6 - 0
settings/.babelrc

@@ -0,0 +1,6 @@
+{
+  "presets": [
+    ["env", { "modules": false }],
+    "stage-3"
+  ]
+}

+ 9 - 0
settings/.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 12 - 0
settings/.gitignore

@@ -0,0 +1,12 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log
+yarn-error.log
+
+# Editor directories and files
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 0 - 1018
settings/Controller/UsersController.php

@@ -1,1018 +0,0 @@
-<?php
-// FIXME: disabled for now to be able to inject IGroupManager and also use
-// getSubAdmin()
-//declare(strict_types=1);
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@owncloud.com>
- * @author Clark Tomlinson <fallen013@gmail.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Thomas Pulzer <t.pulzer@kniel.de>
- * @author Tobia De Koninck <tobia@ledfan.be>
- * @author Tobias Kaminsky <tobias@kaminsky.me>
- * @author Vincent Petry <pvince81@owncloud.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 OC\Settings\Controller;
-
-use OC\Accounts\AccountManager;
-use OC\AppFramework\Http;
-use OC\ForbiddenException;
-use OC\HintException;
-use OC\Settings\Mailer\NewUserMailHelper;
-use OC\Security\IdentityProof\Manager;
-use OCP\App\IAppManager;
-use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http\DataResponse;
-use OCP\BackgroundJob\IJobList;
-use OCP\Files\Config\IUserMountCache;
-use OCP\Encryption\IEncryptionModule;
-use OCP\Encryption\IManager;
-use OCP\IConfig;
-use OCP\IGroupManager;
-use OCP\IL10N;
-use OCP\ILogger;
-use OCP\IRequest;
-use OCP\IURLGenerator;
-use OCP\IUser;
-use OCP\IUserManager;
-use OCP\IUserSession;
-use OCP\Mail\IMailer;
-use OCP\IAvatarManager;
-use OCP\Security\ISecureRandom;
-use OCP\Util;
-use OC\Settings\BackgroundJobs\VerifyUserData;
-
-/**
- * @package OC\Settings\Controller
- */
-class UsersController extends Controller {
-	/** @var IL10N */
-	private $l10n;
-	/** @var IUserSession */
-	private $userSession;
-	/** @var bool */
-	private $isAdmin;
-	/** @var IUserManager */
-	private $userManager;
-	/** @var IGroupManager */
-	private $groupManager;
-	/** @var IConfig */
-	private $config;
-	/** @var ILogger */
-	private $log;
-	/** @var IMailer */
-	private $mailer;
-	/** @var bool contains the state of the encryption app */
-	private $isEncryptionAppEnabled;
-	/** @var bool contains the state of the admin recovery setting */
-	private $isRestoreEnabled = false;
-	/** @var IAppManager */
-	private $appManager;
-	/** @var IAvatarManager */
-	private $avatarManager;
-	/** @var AccountManager */
-	private $accountManager;
-	/** @var ISecureRandom */
-	private $secureRandom;
-	/** @var NewUserMailHelper */
-	private $newUserMailHelper;
-	/** @var Manager */
-	private $keyManager;
-	/** @var IJobList */
-	private $jobList;
-
-	/** @var IUserMountCache */
-	private $userMountCache;
-
-	/** @var IManager */
-	private $encryptionManager;
-
-	public function __construct(string $appName,
-								IRequest $request,
-								IUserManager $userManager,
-								IGroupManager $groupManager,
-								IUserSession $userSession,
-								IConfig $config,
-								bool $isAdmin,
-								IL10N $l10n,
-								ILogger $log,
-								IMailer $mailer,
-								IURLGenerator $urlGenerator,
-								IAppManager $appManager,
-								IAvatarManager $avatarManager,
-								AccountManager $accountManager,
-								ISecureRandom $secureRandom,
-								NewUserMailHelper $newUserMailHelper,
-								Manager $keyManager,
-								IJobList $jobList,
-								IUserMountCache $userMountCache,
-								IManager $encryptionManager) {
-		parent::__construct($appName, $request);
-		$this->userManager = $userManager;
-		$this->groupManager = $groupManager;
-		$this->userSession = $userSession;
-		$this->config = $config;
-		$this->isAdmin = $isAdmin;
-		$this->l10n = $l10n;
-		$this->log = $log;
-		$this->mailer = $mailer;
-		$this->appManager = $appManager;
-		$this->avatarManager = $avatarManager;
-		$this->accountManager = $accountManager;
-		$this->secureRandom = $secureRandom;
-		$this->newUserMailHelper = $newUserMailHelper;
-		$this->keyManager = $keyManager;
-		$this->jobList = $jobList;
-		$this->userMountCache = $userMountCache;
-		$this->encryptionManager = $encryptionManager;
-
-		// check for encryption state - TODO see formatUserForIndex
-		$this->isEncryptionAppEnabled = $appManager->isEnabledForUser('encryption');
-		if ($this->isEncryptionAppEnabled) {
-			// putting this directly in empty is possible in PHP 5.5+
-			$result = $config->getAppValue('encryption', 'recoveryAdminEnabled', '0');
-			$this->isRestoreEnabled = !empty($result);
-		}
-	}
-
-	/**
-	 * @param IUser $user
-	 * @param array|null $userGroups
-	 * @return array
-	 */
-	private function formatUserForIndex(IUser $user, array $userGroups = null): array {
-
-		// TODO: eliminate this encryption specific code below and somehow
-		// hook in additional user info from other apps
-
-		// recovery isn't possible if admin or user has it disabled and encryption
-		// is enabled - so we eliminate the else paths in the conditional tree
-		// below
-		$restorePossible = false;
-
-		if ($this->isEncryptionAppEnabled) {
-			if ($this->isRestoreEnabled) {
-				// check for the users recovery setting
-				$recoveryMode = $this->config->getUserValue($user->getUID(), 'encryption', 'recoveryEnabled', '0');
-				// method call inside empty is possible with PHP 5.5+
-				$recoveryModeEnabled = !empty($recoveryMode);
-				if ($recoveryModeEnabled) {
-					// user also has recovery mode enabled
-					$restorePossible = true;
-				}
-			} else {
-				$modules = $this->encryptionManager->getEncryptionModules();
-				$restorePossible = true;
-				foreach ($modules as $id => $module) {
-					/* @var IEncryptionModule $instance */
-					$instance = call_user_func($module['callback']);
-					if ($instance->needDetailedAccessList()) {
-						$restorePossible = false;
-						break;
-					}
-				}
-			}
-		} else {
-			// recovery is possible if encryption is disabled (plain files are
-			// available)
-			$restorePossible = true;
-		}
-
-		$subAdminGroups = $this->groupManager->getSubAdmin()->getSubAdminsGroupsName($user);
-
-		$displayName = $user->getEMailAddress();
-		if (is_null($displayName)) {
-			$displayName = '';
-		}
-
-		$avatarAvailable = false;
-		try {
-			$avatarAvailable = $this->avatarManager->getAvatar($user->getUID())->exists();
-		} catch (\Exception $e) {
-			//No avatar yet
-		}
-
-		return [
-			'name' => $user->getUID(),
-			'displayname' => $user->getDisplayName(),
-			'groups' => empty($userGroups) ? $this->groupManager->getUserGroupNames($user) : $userGroups,
-			'subadmin' => $subAdminGroups,
-			'quota' => $user->getQuota(),
-			'quota_bytes' => Util::computerFileSize($user->getQuota()),
-			'storageLocation' => $user->getHome(),
-			'lastLogin' => $user->getLastLogin() * 1000,
-			'backend' => $user->getBackendClassName(),
-			'email' => $displayName,
-			'isRestoreDisabled' => !$restorePossible,
-			'isAvatarAvailable' => $avatarAvailable,
-			'isEnabled' => $user->isEnabled(),
-		];
-	}
-
-	/**
-	 * @param array $userIDs Array with schema [$uid => $displayName]
-	 * @return IUser[]
-	 */
-	private function getUsersForUID(array $userIDs): array {
-		$users = [];
-		foreach ($userIDs as $uid => $displayName) {
-			$users[$uid] = $this->userManager->get($uid);
-		}
-		return $users;
-	}
-
-	/**
-	 * @NoAdminRequired
-	 *
-	 * @param int $offset
-	 * @param int $limit
-	 * @param string $gid GID to filter for
-	 * @param string $pattern Pattern to search for in the username
-	 * @param string $backend Backend to filter for (class-name)
-	 * @return DataResponse
-	 *
-	 * TODO: Tidy up and write unit tests - code is mainly static method calls
-	 */
-	public function index(int $offset = 0, int $limit = 10, string $gid = '', string $pattern = '', string $backend = ''): DataResponse {
-		// Remove backends
-		if (!empty($backend)) {
-			$activeBackends = $this->userManager->getBackends();
-			$this->userManager->clearBackends();
-			foreach ($activeBackends as $singleActiveBackend) {
-				if ($backend === get_class($singleActiveBackend)) {
-					$this->userManager->registerBackend($singleActiveBackend);
-					break;
-				}
-			}
-		}
-
-		$userObjects = [];
-		$users = [];
-		if ($this->isAdmin) {
-			if ($gid !== '' && $gid !== '_disabledUsers' && $gid !== '_everyone') {
-				$batch = $this->getUsersForUID($this->groupManager->displayNamesInGroup($gid, $pattern, $limit, $offset));
-			} else {
-				$batch = $this->userManager->search($pattern, $limit, $offset);
-			}
-
-			foreach ($batch as $user) {
-				if (($gid !== '_disabledUsers' && $user->isEnabled()) ||
-					($gid === '_disabledUsers' && !$user->isEnabled())
-				) {
-					$userObjects[] = $user;
-					$users[] = $this->formatUserForIndex($user);
-				}
-			}
-
-		} else {
-			$subAdminOfGroups = $this->groupManager->getSubAdmin()->getSubAdminsGroups($this->userSession->getUser());
-			// New class returns IGroup[] so convert back
-			$gids = [];
-			foreach ($subAdminOfGroups as $group) {
-				$gids[] = $group->getGID();
-			}
-			$subAdminOfGroups = $gids;
-
-			// Set the $gid parameter to an empty value if the subadmin has no rights to access a specific group
-			if ($gid !== '' && $gid !== '_disabledUsers' && !in_array($gid, $subAdminOfGroups)) {
-				$gid = '';
-			}
-
-			// Batch all groups the user is subadmin of when a group is specified
-			$batch = [];
-			if ($gid !== '' && $gid !== '_disabledUsers' && $gid !== '_everyone') {
-				$batch = $this->groupManager->displayNamesInGroup($gid, $pattern, $limit, $offset);
-			} else {
-				foreach ($subAdminOfGroups as $group) {
-					$groupUsers = $this->groupManager->displayNamesInGroup($group, $pattern, $limit, $offset);
-
-					foreach ($groupUsers as $uid => $displayName) {
-						$batch[$uid] = $displayName;
-					}
-				}
-			}
-			$batch = $this->getUsersForUID($batch);
-
-			foreach ($batch as $user) {
-				// Only add the groups, this user is a subadmin of
-				$userGroups = array_values(array_intersect(
-					$this->groupManager->getUserGroupIds($user),
-					$subAdminOfGroups
-				));
-				if (($gid !== '_disabledUsers' && $user->isEnabled()) ||
-					($gid === '_disabledUsers' && !$user->isEnabled())
-				) {
-					$userObjects[] = $user;
-					$users[] = $this->formatUserForIndex($user, $userGroups);
-				}
-			}
-		}
-
-		$usedSpace = $this->userMountCache->getUsedSpaceForUsers($userObjects);
-
-		foreach ($users as &$userData) {
-			$userData['size'] = isset($usedSpace[$userData['name']]) ? $usedSpace[$userData['name']] : 0;
-		}
-
-		return new DataResponse($users);
-	}
-
-	/**
-	 * @NoAdminRequired
-	 * @PasswordConfirmationRequired
-	 *
-	 * @param string $username
-	 * @param string $password
-	 * @param array $groups
-	 * @param string $email
-	 * @return DataResponse
-	 */
-	public function create(string $username, string $password, array $groups = [], $email = ''): DataResponse {
-		if ($email !== '' && !$this->mailer->validateMailAddress($email)) {
-			return new DataResponse(
-				[
-					'message' => $this->l10n->t('Invalid mail address')
-				],
-				Http::STATUS_UNPROCESSABLE_ENTITY
-			);
-		}
-
-		$currentUser = $this->userSession->getUser();
-
-		if (!$this->isAdmin) {
-			if (!empty($groups)) {
-				foreach ($groups as $key => $group) {
-					$groupObject = $this->groupManager->get($group);
-					if ($groupObject === null) {
-						unset($groups[$key]);
-						continue;
-					}
-
-					if (!$this->groupManager->getSubAdmin()->isSubAdminOfGroup($currentUser, $groupObject)) {
-						unset($groups[$key]);
-					}
-				}
-			}
-
-			if (empty($groups)) {
-				return new DataResponse(
-					[
-						'message' => $this->l10n->t('No valid group selected'),
-					],
-					Http::STATUS_FORBIDDEN
-				);
-			}
-		}
-
-		if ($this->userManager->userExists($username)) {
-			return new DataResponse(
-				[
-					'message' => $this->l10n->t('A user with that name already exists.')
-				],
-				Http::STATUS_CONFLICT
-			);
-		}
-
-		$generatePasswordResetToken = false;
-		if ($password === '') {
-			if ($email === '') {
-				return new DataResponse(
-					[
-						'message' => $this->l10n->t('To send a password link to the user an email address is required.')
-					],
-					Http::STATUS_UNPROCESSABLE_ENTITY
-				);
-			}
-
-			$password = $this->secureRandom->generate(30);
-			// Make sure we pass the password_policy
-			$password .= $this->secureRandom->generate(2, '$!.,;:-~+*[]{}()');
-			$generatePasswordResetToken = true;
-		}
-
-		try {
-			$user = $this->userManager->createUser($username, $password);
-		} catch (\Exception $exception) {
-			$message = $exception->getMessage();
-			if ($exception instanceof HintException && $exception->getHint()) {
-				$message = $exception->getHint();
-			}
-			if (!$message) {
-				$message = $this->l10n->t('Unable to create user.');
-			}
-			return new DataResponse(
-				[
-					'message' => (string)$message,
-				],
-				Http::STATUS_FORBIDDEN
-			);
-		}
-
-		if ($user instanceof IUser) {
-			if ($groups !== null) {
-				foreach ($groups as $groupName) {
-					$group = $this->groupManager->get($groupName);
-
-					if (empty($group)) {
-						$group = $this->groupManager->createGroup($groupName);
-					}
-					$group->addUser($user);
-				}
-			}
-			/**
-			 * Send new user mail only if a mail is set
-			 */
-			if ($email !== '') {
-				$user->setEMailAddress($email);
-				try {
-					$emailTemplate = $this->newUserMailHelper->generateTemplate($user, $generatePasswordResetToken);
-					$this->newUserMailHelper->sendMail($user, $emailTemplate);
-				} catch (\Exception $e) {
-					$this->log->logException($e, [
-						'message' => "Can't send new user mail to $email",
-						'level' => ILogger::ERROR,
-						'app' => 'settings',
-					]);
-				}
-			}
-			// fetch users groups
-			$userGroups = $this->groupManager->getUserGroupNames($user);
-
-			return new DataResponse(
-				$this->formatUserForIndex($user, $userGroups),
-				Http::STATUS_CREATED
-			);
-		}
-
-		return new DataResponse(
-			[
-				'message' => $this->l10n->t('Unable to create user.')
-			],
-			Http::STATUS_FORBIDDEN
-		);
-
-	}
-
-	/**
-	 * @NoAdminRequired
-	 * @PasswordConfirmationRequired
-	 *
-	 * @param string $id
-	 * @return DataResponse
-	 */
-	public function destroy(string $id): DataResponse {
-		$userId = $this->userSession->getUser()->getUID();
-		$user = $this->userManager->get($id);
-
-		if ($userId === $id) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $this->l10n->t('Unable to delete user.')
-					]
-				],
-				Http::STATUS_FORBIDDEN
-			);
-		}
-
-		if (!$this->isAdmin && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $this->l10n->t('Authentication error')
-					]
-				],
-				Http::STATUS_FORBIDDEN
-			);
-		}
-
-		if ($user && $user->delete()) {
-			return new DataResponse(
-				[
-					'status' => 'success',
-					'data' => [
-						'username' => $id
-					]
-				],
-				Http::STATUS_NO_CONTENT
-			);
-		}
-
-		return new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => $this->l10n->t('Unable to delete user.')
-				]
-			],
-			Http::STATUS_FORBIDDEN
-		);
-	}
-
-	/**
-	 * @NoAdminRequired
-	 *
-	 * @param string $id
-	 * @param int $enabled
-	 * @return DataResponse
-	 */
-	public function setEnabled(string $id, int $enabled): DataResponse {
-		$enabled = (bool)$enabled;
-		if ($enabled) {
-			$errorMsgGeneral = $this->l10n->t('Error while enabling user.');
-		} else {
-			$errorMsgGeneral = $this->l10n->t('Error while disabling user.');
-		}
-
-		$userId = $this->userSession->getUser()->getUID();
-		$user = $this->userManager->get($id);
-
-		if ($userId === $id) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $errorMsgGeneral
-					]
-				], Http::STATUS_FORBIDDEN
-			);
-		}
-
-		if ($user) {
-			if (!$this->isAdmin && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) {
-				return new DataResponse(
-					[
-						'status' => 'error',
-						'data' => [
-							'message' => $this->l10n->t('Authentication error')
-						]
-					],
-					Http::STATUS_FORBIDDEN
-				);
-			}
-
-			$user->setEnabled($enabled);
-			return new DataResponse(
-				[
-					'status' => 'success',
-					'data' => [
-						'username' => $id,
-						'enabled' => $enabled
-					]
-				]
-			);
-		} else {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $errorMsgGeneral
-					]
-				],
-				Http::STATUS_FORBIDDEN
-			);
-		}
-
-	}
-
-	/**
-	 * Set the mail address of a user
-	 *
-	 * @NoAdminRequired
-	 * @NoSubadminRequired
-	 * @PasswordConfirmationRequired
-	 *
-	 * @param string $account
-	 * @param bool $onlyVerificationCode only return verification code without updating the data
-	 * @return DataResponse
-	 */
-	public function getVerificationCode(string $account, bool $onlyVerificationCode): DataResponse {
-
-		$user = $this->userSession->getUser();
-
-		if ($user === null) {
-			return new DataResponse([], Http::STATUS_BAD_REQUEST);
-		}
-
-		$accountData = $this->accountManager->getUser($user);
-		$cloudId = $user->getCloudId();
-		$message = 'Use my Federated Cloud ID to share with me: ' . $cloudId;
-		$signature = $this->signMessage($user, $message);
-
-		$code = $message . ' ' . $signature;
-		$codeMd5 = $message . ' ' . md5($signature);
-
-		switch ($account) {
-			case 'verify-twitter':
-				$accountData[AccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
-				$msg = $this->l10n->t('In order to verify your Twitter account, post the following tweet on Twitter (please make sure to post it without any line breaks):');
-				$code = $codeMd5;
-				$type = AccountManager::PROPERTY_TWITTER;
-				$data = $accountData[AccountManager::PROPERTY_TWITTER]['value'];
-				$accountData[AccountManager::PROPERTY_TWITTER]['signature'] = $signature;
-				break;
-			case 'verify-website':
-				$accountData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
-				$msg = $this->l10n->t('In order to verify your Website, store the following content in your web-root at \'.well-known/CloudIdVerificationCode.txt\' (please make sure that the complete text is in one line):');
-				$type = AccountManager::PROPERTY_WEBSITE;
-				$data = $accountData[AccountManager::PROPERTY_WEBSITE]['value'];
-				$accountData[AccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
-				break;
-			default:
-				return new DataResponse([], Http::STATUS_BAD_REQUEST);
-		}
-
-		if ($onlyVerificationCode === false) {
-			$this->accountManager->updateUser($user, $accountData);
-
-			$this->jobList->add(VerifyUserData::class,
-				[
-					'verificationCode' => $code,
-					'data' => $data,
-					'type' => $type,
-					'uid' => $user->getUID(),
-					'try' => 0,
-					'lastRun' => $this->getCurrentTime()
-				]
-			);
-		}
-
-		return new DataResponse(['msg' => $msg, 'code' => $code]);
-	}
-
-	/**
-	 * get current timestamp
-	 *
-	 * @return int
-	 */
-	protected function getCurrentTime(): int {
-		return time();
-	}
-
-	/**
-	 * sign message with users private key
-	 *
-	 * @param IUser $user
-	 * @param string $message
-	 *
-	 * @return string base64 encoded signature
-	 */
-	protected function signMessage(IUser $user, string $message): string {
-		$privateKey = $this->keyManager->getKey($user)->getPrivate();
-		openssl_sign(json_encode($message), $signature, $privateKey, OPENSSL_ALGO_SHA512);
-		return base64_encode($signature);
-	}
-
-	/**
-	 * @NoAdminRequired
-	 * @NoSubadminRequired
-	 * @PasswordConfirmationRequired
-	 *
-	 * @param string $avatarScope
-	 * @param string $displayname
-	 * @param string $displaynameScope
-	 * @param string $phone
-	 * @param string $phoneScope
-	 * @param string $email
-	 * @param string $emailScope
-	 * @param string $website
-	 * @param string $websiteScope
-	 * @param string $address
-	 * @param string $addressScope
-	 * @param string $twitter
-	 * @param string $twitterScope
-	 * @return DataResponse
-	 */
-	public function setUserSettings($avatarScope,
-									$displayname,
-									$displaynameScope,
-									$phone,
-									$phoneScope,
-									$email,
-									$emailScope,
-									$website,
-									$websiteScope,
-									$address,
-									$addressScope,
-									$twitter,
-									$twitterScope
-	) {
-
-		if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $this->l10n->t('Invalid mail address')
-					]
-				],
-				Http::STATUS_UNPROCESSABLE_ENTITY
-			);
-		}
-
-		$user = $this->userSession->getUser();
-
-		$data = $this->accountManager->getUser($user);
-
-		$data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
-		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
-			$data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
-			$data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
-		}
-
-		if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
-			$federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application();
-			$shareProvider = $federatedFileSharing->getFederatedShareProvider();
-			if ($shareProvider->isLookupServerUploadEnabled()) {
-				$data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
-				$data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
-				$data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
-				$data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
-			}
-		}
-
-		try {
-			$this->saveUserSettings($user, $data);
-			return new DataResponse(
-				[
-					'status' => 'success',
-					'data' => [
-						'userId' => $user->getUID(),
-						'avatarScope' => $data[AccountManager::PROPERTY_AVATAR]['scope'],
-						'displayname' => $data[AccountManager::PROPERTY_DISPLAYNAME]['value'],
-						'displaynameScope' => $data[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
-						'email' => $data[AccountManager::PROPERTY_EMAIL]['value'],
-						'emailScope' => $data[AccountManager::PROPERTY_EMAIL]['scope'],
-						'website' => $data[AccountManager::PROPERTY_WEBSITE]['value'],
-						'websiteScope' => $data[AccountManager::PROPERTY_WEBSITE]['scope'],
-						'address' => $data[AccountManager::PROPERTY_ADDRESS]['value'],
-						'addressScope' => $data[AccountManager::PROPERTY_ADDRESS]['scope'],
-						'message' => $this->l10n->t('Settings saved')
-					]
-				],
-				Http::STATUS_OK
-			);
-		} catch (ForbiddenException $e) {
-			return new DataResponse([
-				'status' => 'error',
-				'data' => [
-					'message' => $e->getMessage()
-				],
-			]);
-		}
-
-	}
-
-
-	/**
-	 * update account manager with new user data
-	 *
-	 * @param IUser $user
-	 * @param array $data
-	 * @throws ForbiddenException
-	 */
-	protected function saveUserSettings(IUser $user, array $data) {
-
-		// keep the user back-end up-to-date with the latest display name and email
-		// address
-		$oldDisplayName = $user->getDisplayName();
-		$oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
-		if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
-			&& $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']
-		) {
-			$result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']);
-			if ($result === false) {
-				throw new ForbiddenException($this->l10n->t('Unable to change full name'));
-			}
-		}
-
-		$oldEmailAddress = $user->getEMailAddress();
-		$oldEmailAddress = is_null($oldEmailAddress) ? '' : $oldEmailAddress;
-		if (isset($data[AccountManager::PROPERTY_EMAIL]['value'])
-			&& $oldEmailAddress !== $data[AccountManager::PROPERTY_EMAIL]['value']
-		) {
-			// this is the only permission a backend provides and is also used
-			// for the permission of setting a email address
-			if (!$user->canChangeDisplayName()) {
-				throw new ForbiddenException($this->l10n->t('Unable to change email address'));
-			}
-			$user->setEMailAddress($data[AccountManager::PROPERTY_EMAIL]['value']);
-		}
-
-		$this->accountManager->updateUser($user, $data);
-	}
-
-	/**
-	 * Count all unique users visible for the current admin/subadmin.
-	 *
-	 * @NoAdminRequired
-	 *
-	 * @return DataResponse
-	 */
-	public function stats(): DataResponse {
-		$userCount = 0;
-		if ($this->isAdmin) {
-			$countByBackend = $this->userManager->countUsers();
-
-			if (!empty($countByBackend)) {
-				foreach ($countByBackend as $count) {
-					$userCount += $count;
-				}
-			}
-		} else {
-			$groups = $this->groupManager->getSubAdmin()->getSubAdminsGroups($this->userSession->getUser());
-
-			$uniqueUsers = [];
-			foreach ($groups as $group) {
-				foreach ($group->getUsers() as $uid => $displayName) {
-					$uniqueUsers[$uid] = true;
-				}
-			}
-
-			$userCount = count($uniqueUsers);
-		}
-
-		return new DataResponse(
-			[
-				'totalUsers' => $userCount
-			]
-		);
-	}
-
-
-	/**
-	 * Set the displayName of a user
-	 *
-	 * @NoAdminRequired
-	 * @NoSubadminRequired
-	 * @PasswordConfirmationRequired
-	 * @todo merge into saveUserSettings
-	 *
-	 * @param string $username
-	 * @param string $displayName
-	 * @return DataResponse
-	 */
-	public function setDisplayName(string $username, string $displayName) {
-		$currentUser = $this->userSession->getUser();
-		$user = $this->userManager->get($username);
-
-		if ($user === null ||
-			!$user->canChangeDisplayName() ||
-			(
-				!$this->groupManager->isAdmin($currentUser->getUID()) &&
-				!$this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $user) &&
-				$currentUser->getUID() !== $username
-
-			)
-		) {
-			return new DataResponse([
-				'status' => 'error',
-				'data' => [
-					'message' => $this->l10n->t('Authentication error'),
-				],
-			]);
-		}
-
-		$userData = $this->accountManager->getUser($user);
-		$userData[AccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayName;
-
-
-		try {
-			$this->saveUserSettings($user, $userData);
-			return new DataResponse([
-				'status' => 'success',
-				'data' => [
-					'message' => $this->l10n->t('Your full name has been changed.'),
-					'username' => $username,
-					'displayName' => $displayName,
-				],
-			]);
-		} catch (ForbiddenException $e) {
-			return new DataResponse([
-				'status' => 'error',
-				'data' => [
-					'message' => $e->getMessage(),
-					'displayName' => $user->getDisplayName(),
-				],
-			]);
-		}
-	}
-
-	/**
-	 * Set the mail address of a user
-	 *
-	 * @NoAdminRequired
-	 * @NoSubadminRequired
-	 * @PasswordConfirmationRequired
-	 *
-	 * @param string $id
-	 * @param string $mailAddress
-	 * @return DataResponse
-	 */
-	public function setEMailAddress(string $id, string $mailAddress) {
-		$user = $this->userManager->get($id);
-		if (!$this->isAdmin
-			&& !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)
-		) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $this->l10n->t('Forbidden')
-					]
-				],
-				Http::STATUS_FORBIDDEN
-			);
-		}
-
-		if ($mailAddress !== '' && !$this->mailer->validateMailAddress($mailAddress)) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $this->l10n->t('Invalid mail address')
-					]
-				],
-				Http::STATUS_UNPROCESSABLE_ENTITY
-			);
-		}
-
-		if (!$user) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $this->l10n->t('Invalid user')
-					]
-				],
-				Http::STATUS_UNPROCESSABLE_ENTITY
-			);
-		}
-		// this is the only permission a backend provides and is also used
-		// for the permission of setting a email address
-		if (!$user->canChangeDisplayName()) {
-			return new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => $this->l10n->t('Unable to change mail address')
-					]
-				],
-				Http::STATUS_FORBIDDEN
-			);
-		}
-
-		$userData = $this->accountManager->getUser($user);
-		$userData[AccountManager::PROPERTY_EMAIL]['value'] = $mailAddress;
-
-		try {
-			$this->saveUserSettings($user, $userData);
-			return new DataResponse(
-				[
-					'status' => 'success',
-					'data' => [
-						'username' => $id,
-						'mailAddress' => $mailAddress,
-						'message' => $this->l10n->t('Email saved')
-					]
-				],
-				Http::STATUS_OK
-			);
-		} catch (ForbiddenException $e) {
-			return new DataResponse([
-				'status' => 'error',
-				'data' => [
-					'message' => $e->getMessage()
-				],
-			]);
-		}
-	}
-
-}

+ 18 - 0
settings/README.md

@@ -0,0 +1,18 @@
+# settings
+
+> A Vue.js project
+
+## Build Setup
+
+``` bash
+# install dependencies
+npm install
+
+# serve with hot reload at localhost:8080
+npm run dev
+
+# build for production with minification
+npm run build
+```
+
+For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader).

+ 171 - 95
settings/css/settings.scss

@@ -675,101 +675,6 @@ span.usersLastLoginTooltip {
 	}
 }
 
-tr:hover > td {
-	&.password > span, &.displayName > span, &.mailAddress > span {
-		margin: 0;
-		cursor: pointer;
-	}
-	&.password > img, &.displayName > img, &.mailAddress > img {
-		visibility: visible;
-		cursor: pointer;
-	}
-}
-
-td.userActions {
-	.toggleUserActions {
-		width: 44px;
-		height: 44px;
-		position: relative;
-		.action {
-			display: block;
-			padding: 14px;
-			opacity: 0.5;
-			.icon-more {
-				display: inline-block;
-			}
-			&:hover,
-			&:focus {
-				opacity: 1;
-			}
-		}
-	}
-}
-
-div.recoveryPassword {
-	left: 50em;
-	display: block;
-	position: absolute;
-	top: -1px;
-}
-
-input#recoveryPassword {
-	width: 15em;
-}
-
-#controls select.quota {
-	margin: 3px;
-	margin-right: 10px;
-	height: 37px;
-}
-
-#userlist td.quota {
-	position: relative;
-	width: 10em;
-	progress.quota-user-progress {
-		position: absolute;
-		width: calc(10em + 0px);
-		margin-top: -7px;
-		z-index: 0;
-		margin-left: 1px;
-		height: 3px;
-	}
-}
-
-select {
-	&.quota-user {
-		width: 10em;
-		height: 34px;
-		z-index: 50;
-		position: relative;
-	}
-	+ progress.quota-user-progress {
-		position: absolute;
-		width: calc(10em + 0px);
-		margin-top: -7px;
-		z-index: 0;
-		margin-left: 1px;
-		height: 3px;
-	}
-}
-
-input.userFilter {
-	width: 200px;
-}
-
-#newusergroups + input[type='submit'] {
-	position: relative;
-	top: -1px;
-}
-
-#headerGroups, #headerSubAdmins, #headerQuota {
-	padding-left: 18px;
-}
-
-#headerAvatar {
-	width: 32px;
-}
-
 /* used to highlight a user row in red */
 
 #userlist tr.row-warning {
@@ -1350,3 +1255,174 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 		margin-top: 22px;
 	}
 }
+
+
+/* USERS LIST -------------------------------------------------------------- */
+#body-settings {
+	#app-navigation {
+		/* Hack to override the javascript orderBy */
+		#usergrouplist > li {
+			order: 4;
+			&#_everyone {
+				order:1;
+			}
+			&#admin {
+				order:2;
+			}
+			&#_disabled {
+				order:3;
+			}
+		}
+	}
+	$grid-row-height: 46px;
+	#app-content.user-list-grid {
+		display: grid;
+		grid-auto-columns: 1fr;
+		grid-auto-rows: $grid-row-height;
+		grid-column-gap: 20px;
+		.row {
+			display: grid;
+			grid-row-start: span 1;
+			align-items: center;
+			/* let's define the column until storage path,
+			   what follows will be manually defined  */
+			grid-template-columns: 44px;
+			grid-auto-columns: min-content;
+			border-top: $color-border 1px solid;
+			.name,
+			.displayName,
+			.password {
+				width: 150px;
+			}
+			.mailAddress{
+				width: 200px;
+			}
+			.groups,
+			.subadmins,
+			.quota {
+				width: 170px;
+			}
+			.storageLocation {
+				width: 250px;
+			}
+			.userBackend,
+			.lastLogin,
+			.userActions {
+				width: 100px;
+			}
+			&#grid-header,
+			&#new-user {
+				position: sticky;
+				align-self: normal;
+				background-color: $color-main-background;
+				z-index: 55; /* above multiselect */
+				top: 0;
+				&.sticky {
+					box-shadow: 0 -2px 10px 1px $color-box-shadow;
+				}
+			}
+			&#grid-header {
+				color: nc-lighten($color-main-text, 60%);
+				z-index: 60; /* above new-user */
+			}
+			&#new-user {
+				top: $grid-row-height;
+			}
+			&:hover {
+				input:not([type='submit']):not(:focus):not(:active) {
+					border-color: nc-darken($color-main-background, 14%) !important;
+				}
+			}
+			> div,
+			> form {
+				grid-row: 1;
+				display: inline-flex;
+				align-items: center;
+				color: nc-lighten($color-main-text, 33%);
+				position: relative;
+				> input:not(:focus):not(:active) {
+					border-color: transparent;
+					cursor: pointer;
+				}
+				> input:focus, >input:active {
+					+ .icon-confirm {
+						display: block !important;
+					}
+				}
+				&:not(.userActions) > input:not([type='submit']) {
+					width: 100%;
+					min-width: 0;
+				}
+				&.quota {
+                    .multiselect--active + progress {
+                        display: none;
+                    }
+					progress {
+						position: absolute;
+						width: 160px;
+						left: 2px;
+						bottom: 2px;
+						height: 3px;
+					}
+				}
+				.icon-confirm {
+					width: 32px;
+					height: 32px;
+					flex: 0 0 32px;
+					cursor: pointer;
+					&:not(:active) {
+						display: none;
+					}
+				}
+				&.avatar {
+					height: 32px;
+					width: 32px;
+					margin: 6px;
+					img {
+						display: block;
+					}
+				}
+				.toggleUserActions {
+					position: relative;
+					.icon-more {
+						width: 44px;
+						height: 44px;
+						opacity: .5;
+						cursor: pointer;
+						:hover {
+							opacity: .7;
+						}
+					}
+				}
+				.v-select {
+					&.open .selected-tag-wrap {
+						display: none;
+					}
+					.dropdown-toggle .selected-tag {
+						padding-right: 5px;
+						.close {
+							/* no delete on tags*/
+							display: none;
+						}
+					}
+					.dropdown-menu li a .icon-add {
+						position: absolute;
+						width: 16px;
+						height: 16px;
+						opacity: .5;
+						left: 7px;
+					}
+				}
+			}
+		}
+		.infinite-loading-container {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+		.users-list-end {
+			opacity: .5;
+			user-select: none;
+		}
+	}
+}

+ 11 - 0
settings/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>settings</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script src="/dist/build.js"></script>
+  </body>
+</html>

File diff suppressed because it is too large
+ 70 - 0
settings/js/main.js


File diff suppressed because it is too large
+ 0 - 0
settings/js/main.js.map


+ 0 - 213
settings/js/users/deleteHandler.js

@@ -1,213 +0,0 @@
-/**
- * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
- */
-
-/**
- * takes care of deleting things represented by an ID
- *
- * @class
- * @param {string} endpoint the corresponding ajax PHP script. Currently limited
- * to settings - ajax path.
- * @param {string} paramID the by the script expected parameter name holding the
- * ID of the object to delete
- * @param {markCallback} markCallback function to be called after successfully
- * marking the object for deletion.
- * @param {removeCallback} removeCallback the function to be called after
- * successful delete.
- */
-
-/* globals escapeHTML */
-
-function DeleteHandler(endpoint, paramID, markCallback, removeCallback) {
-	this.oidToDelete = false;
-	this.canceled = false;
-
-	this.ajaxEndpoint = endpoint;
-	this.ajaxParamID = paramID;
-
-	this.markCallback = markCallback;
-	this.removeCallback = removeCallback;
-	this.undoCallback = false;
-
-	this.notifier = false;
-	this.notificationDataID = false;
-	this.notificationMessage = false;
-	this.notificationPlaceholder = '%oid';
-}
-
-/**
- * Number of milliseconds after which the operation is performed.
- */
-DeleteHandler.TIMEOUT_MS = 7000;
-
-/**
- * Timer after which the action will be performed anyway.
- */
-DeleteHandler.prototype._timeout = null;
-
-/**
- * The function to be called after successfully marking the object for deletion
- * @callback markCallback
- * @param {string} oid the ID of the specific user or group
- */
-
-/**
- * The function to be called after successful delete. The id of the object will
- * be passed as argument. Unsuccessful operations will display an error using
- * OC.dialogs, no callback is fired.
- * @callback removeCallback
- * @param {string} oid the ID of the specific user or group
- */
-
-/**
- * This callback is fired after "undo" was clicked so the consumer can update
- * the web interface
- * @callback undoCallback
- * @param {string} oid the ID of the specific user or group
- */
-
-/**
- * enabled the notification system. Required for undo UI.
- *
- * @param {object} notifier Usually OC.Notification
- * @param {string} dataID an identifier for the notifier, e.g. 'deleteuser'
- * @param {string} message the message that should be shown upon delete. %oid
- * will be replaced with the affected id of the item to be deleted
- * @param {undoCallback} undoCallback called after "undo" was clicked
- */
-DeleteHandler.prototype.setNotification = function(notifier, dataID, message, undoCallback) {
-	this.notifier = notifier;
-	this.notificationDataID = dataID;
-	this.notificationMessage = message;
-	this.undoCallback = undoCallback;
-
-	var dh = this;
-
-	$('#notification')
-		.off('click.deleteHandler_' + dataID)
-		.on('click.deleteHandler_' + dataID, '.undo', function () {
-		if ($('#notification').data(dh.notificationDataID)) {
-			var oid = dh.oidToDelete;
-			dh.cancel();
-			if(typeof dh.undoCallback !== 'undefined') {
-				dh.undoCallback(oid);
-			}
-		}
-		dh.notifier.hide();
-	});
-};
-
-/**
- * shows the Undo Notification (if configured)
- */
-DeleteHandler.prototype.showNotification = function() {
-	if(this.notifier !== false) {
-		if(!this.notifier.isHidden()) {
-			this.hideNotification();
-		}
-		$('#notification').data(this.notificationDataID, true);
-		var msg = this.notificationMessage.replace(
-			this.notificationPlaceholder, escapeHTML(this.oidToDelete));
-		this.notifier.showHtml(msg);
-	}
-};
-
-/**
- * hides the Undo Notification
- */
-DeleteHandler.prototype.hideNotification = function() {
-	if(this.notifier !== false) {
-		$('#notification').removeData(this.notificationDataID);
-		this.notifier.hide();
-	}
-};
-
-/**
- * initializes the delete operation for a given object id
- *
- * @param {string} oid the object id
- */
-DeleteHandler.prototype.mark = function(oid) {
-	if(this.oidToDelete !== false) {
-		// passing true to avoid hiding the notification
-		// twice and causing the second notification
-		// to disappear immediately
-		this.deleteEntry(true);
-	}
-	this.oidToDelete = oid;
-	this.canceled = false;
-	this.markCallback(oid);
-	this.showNotification();
-	if (this._timeout) {
-		clearTimeout(this._timeout);
-		this._timeout = null;
-	}
-	if (DeleteHandler.TIMEOUT_MS > 0) {
-		this._timeout = window.setTimeout(
-				_.bind(this.deleteEntry, this),
-			   	DeleteHandler.TIMEOUT_MS
-		);
-	}
-};
-
-/**
- * cancels a delete operation
- */
-DeleteHandler.prototype.cancel = function() {
-	if (this._timeout) {
-		clearTimeout(this._timeout);
-		this._timeout = null;
-	}
-
-	this.canceled = true;
-	this.oidToDelete = false;
-};
-
-/**
- * executes a delete operation. Requires that the operation has been
- * initialized by mark(). On error, it will show a message via
- * OC.dialogs.alert. On success, a callback is fired so that the client can
- * update the web interface accordingly.
- *
- * @param {boolean} [keepNotification] true to keep the notification, false to hide
- * it, defaults to false
- */
-DeleteHandler.prototype.deleteEntry = function(keepNotification) {
-	var deferred = $.Deferred();
-	if(this.canceled || this.oidToDelete === false) {
-		return deferred.resolve().promise();
-	}
-
-	var dh = this;
-	if(!keepNotification && $('#notification').data(this.notificationDataID) === true) {
-		dh.hideNotification();
-	}
-
-	if (this._timeout) {
-		clearTimeout(this._timeout);
-		this._timeout = null;
-	}
-
-	var payload = {};
-	payload[dh.ajaxParamID] = dh.oidToDelete;
-	return $.ajax({
-		type: 'DELETE',
-		url: OC.generateUrl(dh.ajaxEndpoint+'/{oid}',{oid: this.oidToDelete}),
-		// FIXME: do not use synchronous ajax calls as they block the browser !
-		async: false,
-		success: function (result) {
-			// Remove undo option, & remove user from table
-
-			//TODO: following line
-			dh.removeCallback(dh.oidToDelete);
-			dh.canceled = true;
-		},
-		error: function (jqXHR) {
-			OC.dialogs.alert(jqXHR.responseJSON.data.message, t('settings', 'Unable to delete {objName}', {objName: dh.oidToDelete}));
-			dh.undoCallback(dh.oidToDelete);
-
-		}
-	});
-};

+ 0 - 78
settings/js/users/filter.js

@@ -1,78 +0,0 @@
-/**
- * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
- */
-
-/**
- * @brief this object takes care of the filter functionality on the user
- * management page
- * @param {UserList} userList the UserList object
- * @param {GroupList} groupList the GroupList object
- */
-function UserManagementFilter (userList, groupList) {
-	this.userList = userList;
-	this.groupList = groupList;
-	this.oldFilter = '';
-
-	this.init();
-}
-
-/**
- * @brief sets up when the filter action shall be triggered
- */
-UserManagementFilter.prototype.init = function () {
-	OC.Plugins.register('OCA.Search', this);
-};
-
-/**
- * @brief the filter action needs to be done, here the accurate steps are being
- * taken care of
- */
-UserManagementFilter.prototype.run = _.debounce(function (filter) {
-		if (filter === this.oldFilter) {
-			return;
-		}
-		this.oldFilter = filter;
-		this.userList.filter = filter;
-		this.userList.empty();
-		this.userList.update(GroupList.getCurrentGID());
-		if (this.groupList.filterGroups) {
-			// user counts are being updated nevertheless
-			this.groupList.empty();
-		}
-		this.groupList.update();
-	},
-	300
-);
-
-/**
- * @brief returns the filter String
- * @returns string
- */
-UserManagementFilter.prototype.getPattern = function () {
-	var input = this.filterInput.val(),
-		html = $('html'),
-		isIE8or9 = html.hasClass('lte9');
-	// FIXME - TODO - once support for IE8 and IE9 is dropped
-	if (isIE8or9 && input == this.filterInput.attr('placeholder')) {
-		input = '';
-	}
-	return input;
-};
-
-/**
- * @brief adds reset functionality to an HTML element
- * @param jQuery the jQuery representation of that element
- */
-UserManagementFilter.prototype.addResetButton = function (button) {
-	var umf = this;
-	button.click(function () {
-		umf.filterInput.val('');
-		umf.run();
-	});
-};
-
-UserManagementFilter.prototype.attach = function (search) {
-	search.setFilter('settings', this.run.bind(this));
-};

+ 0 - 385
settings/js/users/groups.js

@@ -1,385 +0,0 @@
-/**
- * Copyright (c) 2014, Raghu Nayyar <beingminimal@gmail.com>
- * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
- */
-
-/* globals escapeHTML, UserList, DeleteHandler */
-
-var $userGroupList,
-	$sortGroupBy;
-
-var GroupList;
-GroupList = {
-	activeGID: '',
-	everyoneGID: '_everyone',
-	filter: '',
-	filterGroups: false,
-
-	addGroup: function (gid, displayName, usercount) {
-		if (_.isUndefined(displayName)) {
-			displayName = gid;
-		}
-		var $li = $userGroupList.find('.isgroup:last-child').clone();
-		$li
-			.data('gid', gid)
-			.find('.groupname').text(displayName);
-		GroupList.setUserCount($li, usercount);
-
-		$li.appendTo($userGroupList);
-
-		GroupList.sortGroups();
-
-		return $li;
-	},
-
-	setUserCount: function (groupLiElement, usercount) {
-		if ($sortGroupBy !== 1) {
-			// If we don't sort by group count we don't display them either
-			return;
-		}
-
-		var $groupLiElement = $(groupLiElement);
-		if (usercount === undefined || usercount === 0 || usercount < 0) {
-			usercount = '';
-			$groupLiElement.data('usercount', 0);
-		} else {
-			$groupLiElement.data('usercount', usercount);
-		}
-		$groupLiElement.find('.usercount').text(usercount);
-	},
-
-	getUserCount: function ($groupLiElement) {
-		var count = parseInt($groupLiElement.data('usercount'), 10);
-		return isNaN(count) ? 0 : count;
-	},
-
-	modGroupCount: function(gid, diff) {
-		var $li = GroupList.getGroupLI(gid);
-		var count = GroupList.getUserCount($li) + diff;
-		GroupList.setUserCount($li, count);
-	},
-
-	incEveryoneCount: function() {
-		GroupList.modGroupCount(GroupList.everyoneGID, 1);
-	},
-
-	decEveryoneCount: function() {
-		GroupList.modGroupCount(GroupList.everyoneGID, -1);
-	},
-
-	incGroupCount: function(gid) {
-		GroupList.modGroupCount(gid, 1);
-	},
-
-	decGroupCount: function(gid) {
-		GroupList.modGroupCount(gid, -1);
-	},
-
-	getCurrentGID: function () {
-		return GroupList.activeGID;
-	},
-
-	sortGroups: function () {
-		var lis = $userGroupList.find('.isgroup').get();
-
-		lis.sort(function (a, b) {
-			// "Everyone" always at the top
-			if ($(a).data('gid') === '_everyone') {
-				return -1;
-			} else if ($(b).data('gid') === '_everyone') {
-				return 1;
-			}
-
-			// "admin" always as second
-			if ($(a).data('gid') === 'admin') {
-				return -1;
-			} else if ($(b).data('gid') === 'admin') {
-				return 1;
-			}
-
-			if ($sortGroupBy === 1) {
-				// Sort by user count first
-				var $usersGroupA = $(a).data('usercount'),
-					$usersGroupB = $(b).data('usercount');
-				if ($usersGroupA > 0 && $usersGroupA > $usersGroupB) {
-					return -1;
-				}
-				if ($usersGroupB > 0 && $usersGroupB > $usersGroupA) {
-					return 1;
-				}
-			}
-
-			// Fallback or sort by group name
-			return UserList.alphanum(
-				$(a).find('a span').text(),
-				$(b).find('a span').text()
-			);
-		});
-
-		var items = [];
-		$.each(lis, function (index, li) {
-			items.push(li);
-			if (items.length === 100) {
-				$userGroupList.append(items);
-				items = [];
-			}
-		});
-		if (items.length > 0) {
-			$userGroupList.append(items);
-		}
-	},
-
-	createGroup: function (groupid) {
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.createGroup, this, groupid));
-			return;
-		}
-
-		$.post(
-			OC.generateUrl('/settings/users/groups'),
-			{
-				id: groupid
-			},
-			function (result) {
-				if (result.groupname) {
-					var addedGroup = result.groupname;
-					UserList.availableGroups[groupid] = {displayName: result.groupname};
-					GroupList.addGroup(groupid, result.groupname);
-				}
-				GroupList.toggleAddGroup();
-			}).fail(function(result) {
-				OC.Notification.showTemporary(t('settings', 'Error creating group: {message}', {message: result.responseJSON.message}));
-			});
-	},
-
-	update: function () {
-		if (GroupList.updating) {
-			return;
-		}
-		GroupList.updating = true;
-		$.get(
-			OC.generateUrl('/settings/users/groups'),
-			{
-				pattern: this.filter,
-				filterGroups: this.filterGroups ? 1 : 0,
-				sortGroups: $sortGroupBy
-			},
-			function (result) {
-
-				var lis = [];
-				if (result.status === 'success') {
-					$.each(result.data, function (i, subset) {
-						$.each(subset, function (index, group) {
-							if (GroupList.getGroupLI(group.name).length > 0) {
-								GroupList.setUserCount(GroupList.getGroupLI(group.name).first(), group.usercount);
-							}
-							else {
-								var $li = GroupList.addGroup(group.id, group.name, group.usercount);
-
-								$li.addClass('appear transparent');
-								lis.push($li);
-							}
-						});
-					});
-					if (result.data.length > 0) {
-						GroupList.doSort();
-					}
-					else {
-						GroupList.noMoreEntries = true;
-					}
-					_.defer(function () {
-						$(lis).each(function () {
-							this.removeClass('transparent');
-						});
-					});
-				}
-				GroupList.updating = false;
-
-			}
-		);
-	},
-
-	elementBelongsToAddGroup: function (el) {
-		return !(el !== $('#newgroup-form').get(0) &&
-		$('#newgroup-form').find($(el)).length === 0);
-	},
-
-	hasAddGroupNameText: function () {
-		var name = $('#newgroupname').val();
-		return $.trim(name) !== '';
-
-	},
-
-	showDisabledUsers: function () {
-		UserList.empty();
-		UserList.update('_disabledUsers');
-		$userGroupList.find('li').removeClass('active');
-		GroupList.getGroupLI('_disabledUsers').addClass('active');
-	},
-
-	showGroup: function (gid) {
-		GroupList.activeGID = gid;
-		UserList.empty();
-		UserList.update(gid === '_everyone' ? '' : gid);
-		$userGroupList.find('li').removeClass('active');
-		if (gid !== undefined) {
-			GroupList.getGroupLI(gid).addClass('active');
-		}
-	},
-
-	isAddGroupButtonVisible: function () {
-		return !$('#newgroup-entry').hasClass('editing');
-	},
-
-	toggleAddGroup: function (event) {
-		if (GroupList.isAddGroupButtonVisible()) {
-			if (event) {
-				event.stopPropagation();
-			}
-			$('#newgroup-entry').addClass('editing');
-			$('#newgroupname').select();
-			GroupList.handleAddGroupInput('');
-		}
-		else {
-			$('#newgroup-entry').removeClass('editing');
-			$('#newgroupname').val('');
-		}
-	},
-
-	handleAddGroupInput: function (input) {
-		if(input.length) {
-			$('#newgroup-form input[type="submit"]').attr('disabled', null);
-		} else {
-			$('#newgroup-form input[type="submit"]').attr('disabled', 'disabled');
-		}
-	},
-
-	isGroupNameValid: function (groupname) {
-		if ($.trim(groupname) === '') {
-			OC.Notification.showTemporary(t('settings', 'Error creating group: {message}', {
-				message: t('settings', 'A valid group name must be provided')
-			}));
-			return false;
-		}
-		return true;
-	},
-
-	hide: function (gid) {
-		GroupList.getGroupLI(gid).hide();
-	},
-	show: function (gid) {
-		GroupList.getGroupLI(gid).show();
-	},
-	remove: function (gid) {
-		GroupList.getGroupLI(gid).remove();
-	},
-	empty: function () {
-		$userGroupList.find('.isgroup').filter(function(index, item){
-			return $(item).data('gid') !== '';
-		}).remove();
-	},
-	initDeleteHandling: function () {
-		//set up handler
-		var GroupDeleteHandler = new DeleteHandler('/settings/users/groups', 'groupname',
-			GroupList.hide, GroupList.remove);
-
-		//configure undo
-		OC.Notification.hide();
-		var msg = escapeHTML(t('settings', 'deleted {groupName}', {groupName: '%oid'})) + '<span class="undo">' +
-			escapeHTML(t('settings', 'undo')) + '</span>';
-		GroupDeleteHandler.setNotification(OC.Notification, 'deletegroup', msg,
-			GroupList.show);
-
-		//when to mark user for delete
-		var deleteAction = function () {
-			if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-				OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(deleteAction, this));
-				return;
-			}
-
-			// Call function for handling delete/undo
-			GroupDeleteHandler.mark(GroupList.getElementGID($(this).parent()));
-		};
-		$userGroupList.on('click', '.delete', deleteAction);
-
-		//delete a marked user when leaving the page
-		$(window).on('beforeunload', function () {
-			GroupDeleteHandler.deleteEntry();
-		});
-	},
-
-	getGroupLI: function (gid) {
-		return $userGroupList.find('li.isgroup').filter(function () {
-			return GroupList.getElementGID(this) === gid;
-		});
-	},
-
-	getElementGID: function (element) {
-		return ($(element).closest('li').data('gid') || '').toString();
-	},
-	getEveryoneCount: function () {
-		$.ajax({
-			type: "GET",
-			dataType: "json",
-			url: OC.generateUrl('/settings/users/stats')
-		}).success(function (data) {
-			$('#everyonegroup').data('usercount', data.totalUsers);
-			$('#everyonecount').text(data.totalUsers);
-		});
-	}
-};
-
-$(document).ready( function () {
-	$userGroupList = $('#usergrouplist');
-	GroupList.initDeleteHandling();
-	$sortGroupBy = $userGroupList.data('sort-groups');
-	if ($sortGroupBy === 1) {
-		// Disabled due to performance issues, when we don't need it for sorting
-		GroupList.getEveryoneCount();
-	}
-
-	// Display or hide of Create Group List Element
-	$('#newgroup-init').on('click', function (e) {
-		GroupList.toggleAddGroup(e);
-	});
-
-	$(document).on('click keydown keyup', function(event) {
-		if(!GroupList.isAddGroupButtonVisible() &&
-			!GroupList.elementBelongsToAddGroup(event.target) &&
-			!GroupList.hasAddGroupNameText()) {
-			GroupList.toggleAddGroup();
-		}
-		// Escape
-		if(!GroupList.isAddGroupButtonVisible() && event.keyCode && event.keyCode === 27) {
-			GroupList.toggleAddGroup();
-		}
-	});
-
-
-	// Responsible for Creating Groups.
-	$('#newgroup-form form').submit(function (event) {
-		event.preventDefault();
-		if(GroupList.isGroupNameValid($('#newgroupname').val())) {
-			GroupList.createGroup($('#newgroupname').val());
-		}
-	});
-
-	// click on group name
-	$userGroupList.on('click', '.isgroup', function () {
-		GroupList.showGroup(GroupList.getElementGID(this));
-	});
-
-	// show disabled users
-	$userGroupList.on('click', '.disabledusers', function () {
-		GroupList.showDisabledUsers();
-	});
-
-	$('#newgroupname').on('input', function(){
-		GroupList.handleAddGroupInput(this.value);
-	});
-
-	// highlight `everyone` group at DOMReady by default
-	GroupList.showGroup('_everyone');
-});

+ 0 - 1189
settings/js/users/users.js

@@ -1,1189 +0,0 @@
-/**
- * Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
- * Copyright (c) 2014, Raghu Nayyar <beingminimal@gmail.com>
- * Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
- * Copyright (c) 2017, John Molakvoæ <skjnldsv@protonmail.com>
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
- */
-
-/* globals escapeHTML, GroupList, DeleteHandler, UserManagementFilter */
-
-var $userList;
-var $userListBody;
-var $emptyContainer;
-
-var UserDeleteHandler;
-var UserList = {
-	availableGroups: [],
-	offset: 0,
-	usersToLoad: 10, //So many users will be loaded when user scrolls down
-	initialUsersToLoad: 50, //initial number of users to load
-	currentGid: '',
-	filter: '',
-
-	/**
-	 * Initializes the user list
-	 * @param $el user list table element
-	 */
-	initialize: function ($el) {
-		this.$el = $el;
-
-		// initially the list might already contain user entries (not fully ajaxified yet)
-		// initialize these entries
-		this.$el.find('.quota-user').singleSelect().on('change', this.onQuotaSelect);
-		$('#new-user-button').on('click', function(event) {
-			event.preventDefault();
-			$('#newuserHeader').slideToggle(OC.menuSpeed);
-			$('#newusername').focus();
-		});
-		$('#newreset').on('click', function(event) {
-			$('#newuserHeader').slideToggle(OC.menuSpeed);
-		});
-		$('.has-tooltip').tooltip({
-			placement: 'bottom'
-		});
-	},
-
-	/**
-	 * Add a user row from user object
-	 *
-	 * @param user object containing following keys:
-	 *            {
-	 * 				'name': 			'username',
-	 * 				'displayname': 		'Users display name',
-	 * 				'groups': 			{group1: {displayName: 'Group 1'}, group2: {displayName: 'Group 2'}}
-	 * 				'subadmin': 		{group5: {displayName: 'Group 5'}, group6: {displayName: 'Group 6'}}
-	 *				'quota': 			'10 GB',
-	 *				'quota_bytes':		'10737418240',
-	 *				'storageLocation':	'/srv/www/owncloud/data/username',
-	 *				'lastLogin':		'1418632333'
-	 *				'backend':			'LDAP',
-	 *				'email':			'username@example.org'
-	 *				'isRestoreDisabled':false
-	 *				'isEnabled':		true,
-	 *				'size':				156789
-	 * 			}
-	 */
-	add: function (user) {
-		if (this.currentGid && this.currentGid !== '_everyone' && this.currentGid !== '_disabledUsers' && Object.keys(user.groups).indexOf(this.currentGid) < 0) {
-			return false;
-		}
-
-		var $tr = $userListBody.find('tr:first-child').clone();
-		// this removes just the `display:none` of the template row
-		$tr.removeAttr('style');
-
-		/**
-		 * Avatar or placeholder
-		 */
-		if ($tr.find('div.avatardiv').length) {
-			if (user.isAvatarAvailable === true) {
-				$('div.avatardiv', $tr).avatar(user.name, 32, undefined, undefined, undefined, user.displayname);
-			} else {
-				$('div.avatardiv', $tr).imageplaceholder(user.displayname, undefined, 32);
-			}
-		}
-
-		/**
-		 * add username and displayname to row (in data and visible markup)
-		 */
-		$tr.data('uid', user.name);
-		$tr.data('displayname', user.displayname);
-		$tr.data('mailAddress', user.email);
-		$tr.data('restoreDisabled', user.isRestoreDisabled);
-		$tr.data('userEnabled', user.isEnabled);
-		$tr.find('.name').text(user.name);
-		$tr.find('td.displayName > span').text(user.displayname);
-		$tr.find('td.mailAddress > span').text(user.email);
-		$tr.find('td.displayName > .action').tooltip({placement: 'top'});
-		$tr.find('td.mailAddress > .action').tooltip({placement: 'top'});
-		$tr.find('td.password > .action').tooltip({placement: 'top'});
-
-
-		/**
-		 * groups and subadmins
-		 */
-		var $tdGroups = $tr.find('td.groups');
-		this._updateGroupListLabel($tdGroups, user.groups);
-		$tdGroups.find('.action').tooltip({placement: 'top'});
-
-		var $tdSubadmins = $tr.find('td.subadmins');
-		this._updateGroupListLabel($tdSubadmins, user.subadmin);
-		$tdSubadmins.find('.action').tooltip({placement: 'top'});
-
-		/**
-		 * hide user actions menu for current user
-		 */
-		if (OC.currentUser === user.name) {
-			$tr.find('td.userActions').empty();
-		}
-
-		/**
-		 * quota
-		 */
-		UserList.updateQuotaProgressbar($tr, user.quota_bytes, user.size);
-		$tr.data('size', user.size);
-		var $quotaSelect = $tr.find('.quota-user');
-		var humanSize = humanFileSize(user.size, true);
-		$quotaSelect.tooltip({
-			title:  t('settings', '{size} used', {size: humanSize}, 0 , {escape: false}).replace('&lt;', '<'),
-			delay: {
-				show: 100,
-				hide: 0
-			}
-		});
-		if (user.quota === 'default') {
-			$quotaSelect
-				.data('previous', 'default')
-				.find('option').attr('selected', null)
-				.first().attr('selected', 'selected');
-		} else {
-			var $options = $quotaSelect.find('option');
-			var $foundOption = $options.filterAttr('value', user.quota);
-			if ($foundOption.length > 0) {
-				$foundOption.attr('selected', 'selected');
-			} else {
-				// append before "Other" entry
-				$options.last().before('<option value="' + escapeHTML(user.quota) + '" selected="selected">' + escapeHTML(user.quota) + '</option>');
-			}
-		}
-
-		/**
-		 * storage location
-		 */
-		$tr.find('td.storageLocation').text(user.storageLocation);
-
-		/**
-		 * user backend
-		 */
-		$tr.find('td.userBackend').text(user.backend);
-
-		/**
-		 * last login
-		 */
-		var lastLoginRel = t('settings', 'never');
-		var lastLoginAbs = lastLoginRel;
-		if (user.lastLogin !== 0) {
-			lastLoginRel = OC.Util.relativeModifiedDate(user.lastLogin);
-			lastLoginAbs = OC.Util.formatDate(user.lastLogin);
-		}
-		var $tdLastLogin = $tr.find('td.lastLogin');
-		$tdLastLogin.text(lastLoginRel);
-		$tdLastLogin.attr('title', lastLoginAbs);
-		// setup tooltip with #app-content as container to prevent the td to resize on hover
-		$tdLastLogin.tooltip({placement: 'top', container: '#app-content'});
-
-		/**
-		 * append generated row to user list
-		 */
-		$tr.appendTo($userList);
-
-		$quotaSelect.on('change', UserList.onQuotaSelect);
-
-		// defer init so the user first sees the list appear more quickly
-		window.setTimeout(function () {
-			$quotaSelect.singleSelect();
-		}, 0);
-	},
-	// From http://my.opera.com/GreyWyvern/blog/show.dml/1671288
-	alphanum: function (a, b) {
-		function chunkify (t) {
-			var tz = [], x = 0, y = -1, n = 0, i, j;
-
-			while (i = (j = t.charAt(x++)).charCodeAt(0)) {
-				var m = (i === 46 || (i >= 48 && i <= 57));
-				if (m !== n) {
-					tz[++y] = "";
-					n = m;
-				}
-				tz[y] += j;
-			}
-			return tz;
-		}
-
-		var aa = chunkify(a.toLowerCase());
-		var bb = chunkify(b.toLowerCase());
-
-		for (var x = 0; aa[x] && bb[x]; x++) {
-			if (aa[x] !== bb[x]) {
-				var c = Number(aa[x]), d = Number(bb[x]);
-				if (c === aa[x] && d === bb[x]) {
-					return c - d;
-				} else {
-					return (aa[x] > bb[x]) ? 1 : -1;
-				}
-			}
-		}
-		return aa.length - bb.length;
-	},
-	preSortSearchString: function (a, b) {
-		var pattern = this.filter;
-		if (typeof pattern === 'undefined') {
-			return undefined;
-		}
-		pattern = pattern.toLowerCase();
-		var aMatches = false;
-		var bMatches = false;
-		if (typeof a === 'string' && a.toLowerCase().indexOf(pattern) === 0) {
-			aMatches = true;
-		}
-		if (typeof b === 'string' && b.toLowerCase().indexOf(pattern) === 0) {
-			bMatches = true;
-		}
-
-		if ((aMatches && bMatches) || (!aMatches && !bMatches)) {
-			return undefined;
-		}
-
-		if (aMatches) {
-			return -1;
-		} else {
-			return 1;
-		}
-	},
-	doSort: function () {
-		// some browsers like Chrome lose the scrolling information
-		// when messing with the list elements
-		var lastScrollTop = this.scrollArea.scrollTop();
-		var lastScrollLeft = this.scrollArea.scrollLeft();
-		var rows = $userListBody.find('tr').get();
-
-		rows.sort(function (a, b) {
-			// FIXME: inefficient way of getting the names,
-			// better use a data attribute
-			a = $(a).find('.name').text();
-			b = $(b).find('.name').text();
-			var firstSort = UserList.preSortSearchString(a, b);
-			if (typeof firstSort !== 'undefined') {
-				return firstSort;
-			}
-			return OC.Util.naturalSortCompare(a, b);
-		});
-
-		var items = [];
-		$.each(rows, function (index, row) {
-			items.push(row);
-			if (items.length === 100) {
-				$userListBody.append(items);
-				items = [];
-			}
-		});
-		if (items.length > 0) {
-			$userListBody.append(items);
-		}
-		this.scrollArea.scrollTop(lastScrollTop);
-		this.scrollArea.scrollLeft(lastScrollLeft);
-	},
-	checkUsersToLoad: function () {
-		//30 shall be loaded initially, from then on always 10 upon scrolling
-		if (UserList.isEmpty === false) {
-			UserList.usersToLoad = 10;
-		} else {
-			UserList.usersToLoad = UserList.initialUsersToLoad;
-		}
-	},
-	empty: function () {
-		//one row needs to be kept, because it is cloned to add new rows
-		$userListBody.find('tr:not(:first)').remove();
-		var $tr = $userListBody.find('tr:first');
-		$tr.hide();
-		//on an update a user may be missing when the username matches with that
-		//of the hidden row. So change this to a random string.
-		$tr.data('uid', Math.random().toString(36).substring(2));
-		UserList.isEmpty = true;
-		UserList.offset = 0;
-		UserList.checkUsersToLoad();
-	},
-	hide: function (uid) {
-		UserList.getRow(uid).hide();
-	},
-	show: function (uid) {
-		UserList.getRow(uid).show();
-	},
-	markRemove: function (uid) {
-		var $tr = UserList.getRow(uid);
-		var groups = $tr.find('.groups').data('groups');
-		for (var i in groups) {
-			var gid = groups[i];
-			var $li = GroupList.getGroupLI(gid);
-			var userCount = GroupList.getUserCount($li);
-			GroupList.setUserCount($li, userCount - 1);
-		}
-		GroupList.decEveryoneCount();
-		UserList.hide(uid);
-	},
-	remove: function (uid) {
-		UserList.getRow(uid).remove();
-	},
-	undoRemove: function (uid) {
-		var $tr = UserList.getRow(uid);
-		var groups = $tr.find('.groups').data('groups');
-		for (var i in groups) {
-			var gid = groups[i];
-			var $li = GroupList.getGroupLI(gid);
-			var userCount = GroupList.getUserCount($li);
-			GroupList.setUserCount($li, userCount + 1);
-		}
-		GroupList.incEveryoneCount();
-		UserList.getRow(uid).show();
-	},
-	has: function (uid) {
-		return UserList.getRow(uid).length > 0;
-	},
-	getRow: function (uid) {
-		return $userListBody.find('tr').filter(function () {
-			return UserList.getUID(this) === uid;
-		});
-	},
-	getUID: function (element) {
-		return ($(element).closest('tr').data('uid') || '').toString();
-	},
-	getDisplayName: function (element) {
-		return ($(element).closest('tr').data('displayname') || '').toString();
-	},
-	getMailAddress: function (element) {
-		return ($(element).closest('tr').data('mailAddress') || '').toString();
-	},
-	getRestoreDisabled: function (element) {
-		return ($(element).closest('tr').data('restoreDisabled') || '');
-	},
-	getUserEnabled: function (element) {
-		return ($(element).closest('tr').data('userEnabled') || '');
-	},
-	initDeleteHandling: function () {
-		//set up handler
-		UserDeleteHandler = new DeleteHandler('/settings/users/users', 'username',
-			UserList.markRemove, UserList.remove);
-
-		//configure undo
-		OC.Notification.hide();
-		var msg = escapeHTML(t('settings', 'deleted {userName}', {userName: '%oid'})) + '<span class="undo">' +
-			escapeHTML(t('settings', 'undo')) + '</span>';
-		UserDeleteHandler.setNotification(OC.Notification, 'deleteuser', msg,
-			UserList.undoRemove);
-
-		//when to mark user for delete
-		$userListBody.on('click', '.action-remove', function () {
-			// Call function for handling delete/undo
-			var uid = UserList.getUID(this);
-
-			if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-				OC.PasswordConfirmation.requirePasswordConfirmation(function () {
-					UserDeleteHandler.mark(uid);
-				});
-				return;
-			}
-
-			UserDeleteHandler.mark(uid);
-		});
-
-		//delete a marked user when leaving the page
-		$(window).on('beforeunload', function () {
-			UserDeleteHandler.deleteEntry();
-		});
-	},
-	update: function (gid, limit) {
-		if (UserList.updating) {
-			return;
-		}
-		if (!limit) {
-			limit = UserList.usersToLoad;
-		}
-		$userList.siblings('.loading').css('visibility', 'visible');
-		UserList.updating = true;
-		if (gid === undefined) {
-			gid = '';
-		}
-		UserList.currentGid = gid;
-		var pattern = this.filter;
-		$.get(
-			OC.generateUrl('/settings/users/users'),
-			{offset: UserList.offset, limit: limit, gid: gid, pattern: pattern},
-			function (result) {
-				//The offset does not mirror the amount of users available,
-				//because it is backend-dependent. For correct retrieval,
-				//always the limit(requested amount of users) needs to be added.
-				$.each(result, function (index, user) {
-					if (UserList.has(user.name)) {
-						return true;
-					}
-					UserList.add(user);
-				});
-
-				if (result.length > 0) {
-					UserList.doSort();
-					$userList.siblings('.loading').css('visibility', 'hidden');
-					// reset state on load
-					UserList.noMoreEntries = false;
-					$userListHead.show();
-					$emptyContainer.hide();
-					$emptyContainer.find('h2').text('');
-				}
-				else {
-					UserList.noMoreEntries = true;
-					$userList.siblings('.loading').remove();
-
-					if (pattern !== "") {
-						$userListHead.hide();
-						$emptyContainer.show();
-						$emptyContainer.find('h2').html(t('settings', 'No user found for <strong>{pattern}</strong>', {pattern: pattern}));
-					}
-				}
-				UserList.offset += limit;
-			}).always(function () {
-			UserList.updating = false;
-		});
-	},
-
-	applyGroupSelect: function (element, user, checked) {
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applyGroupSelect, this, element, user, checked));
-			return;
-		}
-
-		var $element = $(element);
-
-		var addUserToGroup = null,
-			removeUserFromGroup = null;
-		if (user) { // Only if in a user row, and not the #newusergroups select
-			var handleUserGroupMembership = function (group, add) {
-				if (user === OC.getCurrentUser().uid && group === 'admin') {
-					return false;
-				}
-				if (!OC.isUserAdmin() && checked.length === 1 && checked[0] === group) {
-					return false;
-				}
-				if (add && OC.isUserAdmin() && _.isUndefined(UserList.availableGroups[group])) {
-					GroupList.createGroup(group);
-					if (_.isUndefined(UserList.availableGroups[group])) {
-						UserList.availableGroups[group] = {displayName: group};
-					}
-				}
-
-				$.ajax({
-					url: OC.linkToOCS('cloud/users/' + user, 2) + 'groups',
-					data: {
-						groupid: group
-					},
-					type: add ? 'POST' : 'DELETE',
-					beforeSend: function (request) {
-						request.setRequestHeader('Accept', 'application/json');
-					},
-					success: function () {
-						GroupList.update();
-						if (add && _.isUndefined(UserList.availableGroups[group])) {
-							UserList.availableGroups[group] = {displayName: group};
-						}
-
-						if (add) {
-							GroupList.incGroupCount(group);
-						} else {
-							GroupList.decGroupCount(group);
-						}
-					},
-					error: function () {
-						if (add) {
-							OC.Notification.show(t('settings', 'Unable to add user to group {group}', {
-								group: group
-							}));
-						} else {
-							OC.Notification.show(t('settings', 'Unable to remove user from group {group}', {
-								group: group
-							}));
-						}
-					}
-				});
-			};
-			addUserToGroup = function (group) {
-				return handleUserGroupMembership(group, true);
-			};
-			removeUserFromGroup = function (group) {
-				return handleUserGroupMembership(group, false);
-			};
-		}
-		var addGroup = function (select, group) {
-			GroupList.addGroup(escapeHTML(group));
-		};
-		var label;
-		if (OC.isUserAdmin()) {
-			label = t('settings', 'Add group');
-		}
-		else {
-			label = null;
-		}
-		$element.multiSelect({
-			createCallback: addGroup,
-			createText: label,
-			selectedFirst: true,
-			checked: checked,
-			oncheck: addUserToGroup,
-			onuncheck: removeUserFromGroup,
-			minWidth: 150
-		});
-	},
-
-	applySubadminSelect: function (element, user, checked) {
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.applySubadminSelect, this, element, user, checked));
-			return;
-		}
-
-		var $element = $(element);
-		var checkHandler = function (group) {
-			if (group === 'admin') {
-				return false;
-			}
-			$.post(
-				OC.filePath('settings', 'ajax', 'togglesubadmins.php'),
-				{
-					username: user,
-					group: group
-				},
-				function (response) {
-					if (response.data !== undefined && response.data.message) {
-						OC.Notification.show(response.data.message);
-					}
-				}
-			);
-		};
-
-		$element.multiSelect({
-			createText: null,
-			checked: checked,
-			oncheck: checkHandler,
-			onuncheck: checkHandler,
-			minWidth: 150
-		});
-	},
-
-	_onScroll: function () {
-		if (!!UserList.noMoreEntries) {
-			return;
-		}
-		if (UserList.scrollArea.scrollTop() + UserList.scrollArea.height() > UserList.scrollArea.get(0).scrollHeight - 500) {
-			UserList.update(UserList.currentGid);
-		}
-	},
-
-	updateQuotaProgressbar: function ($tr, quota, size) {
-		var usedQuota;
-		if (quota > 0) {
-			usedQuota = Math.min(100, Math.round(size / quota * 100));
-		} else {
-			var usedInGB = size / (10 * Math.pow(2, 30));
-			//asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
-			usedQuota = 95 * (1 - (1 / (usedInGB + 1)));
-		}
-		$tr.find('.quota-user-progress').val( isNaN(usedQuota) ? 0 : usedQuota );
-		if (usedQuota > 80) {
-			$tr.find('.quota-user-progress').addClass('warn');
-		} else {
-			$tr.find('.quota-user-progress').removeClass('warn');
-		}
-	},
-
-	/**
-	 * Event handler for when a quota has been changed through a single select.
-	 * This will save the value.
-	 */
-	onQuotaSelect: function (ev) {
-		var $select = $(ev.target);
-		var $tr = $select.closest('tr');
-		const size = $tr.data('size');
-		var uid = UserList.getUID($select);
-		var quota = $select.val();
-		if (quota === 'other') {
-			return;
-		}
-		if (quota !== 'default' && quota !== "none" && OC.Util.computerFileSize(quota) === null) {
-			// the select component has added the bogus value, delete it again
-			$select.find('option[selected]').remove();
-			OC.Notification.showTemporary(t('core', 'Invalid quota value "{val}"', {val: quota}));
-			return;
-		}
-
-		UserList._updateQuota(uid, quota, function (returnedQuota) {
-			if (quota !== returnedQuota) {
-				$select.find(':selected').text(returnedQuota);
-				UserList.updateQuotaProgressbar($tr, OC.Util.computerFileSize(returnedQuota), size);
-			}
-		});
-
-		UserList.updateQuotaProgressbar($tr, OC.Util.computerFileSize(quota), size);
-		// remove the background color that the "other" option placed on the select
-		$select.css('background-color', 'transparent');
-	},
-
-	/**
-	 * Saves the quota for the given user
-	 * @param {String} [uid] optional user id, sets default quota if empty
-	 * @param {String} quota quota value
-	 * @param {Function} ready callback after save
-	 */
-	_updateQuota: function (uid, quota, ready) {
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._updateQuota, this, uid, quota, ready));
-			return;
-		}
-
-		$.post(
-			OC.filePath('settings', 'ajax', 'setquota.php'),
-			{username: uid, quota: quota},
-			function (result) {
-				if (result.status === 'error') {
-					OC.Notification.showTemporary(result.data.message);
-				} else {
-					if (ready) {
-						ready(result.data.quota);
-					}
-				}
-			}
-		);
-	},
-
-	/**
-	 * Creates a temporary jquery.multiselect selector on the given group field
-	 */
-	_triggerGroupEdit: function ($td, isSubadminSelect) {
-		var self = this;
-		var $groupsListContainer = $td.find('.groupsListContainer');
-		var placeholder = $groupsListContainer.data('placeholder') || t('settings', 'no group');
-		var user = UserList.getUID($td);
-		var checked = $td.data('groups') || {};
-		var extraGroups = Object.assign({}, checked);
-
-		$td.find('.multiselectoptions').remove();
-
-		// jquery.multiselect can only work with select+options in DOM ? We'll give jquery.multiselect what it wants...
-		var $groupsSelect;
-		if (isSubadminSelect) {
-			$groupsSelect = $('<select multiple="multiple" class="groupsselect multiselect button" title="' + placeholder + '"></select>');
-		} else {
-			$groupsSelect = $('<select multiple="multiple" class="subadminsselect multiselect button" title="' + placeholder + '"></select>')
-		}
-
-		function createItem (gid, group) {
-			if (isSubadminSelect && group.displayName === 'admin') {
-				// can't become subadmin of "admin" group
-				return;
-			}
-			$groupsSelect.append($('<option value="' + escapeHTML(gid) + '">' + escapeHTML(group.displayName) + '</option>'));
-		}
-
-		$.each(this.availableGroups, function (gid, group) {
-			// some new groups might be selected but not in the available groups list yet
-			if (extraGroups[gid] !== undefined) {
-				// remove extra group as it was found
-				delete extraGroups[gid];
-			}
-			createItem(gid, group);
-		});
-		$.each(extraGroups, function (i, group) {
-			createItem(group);
-		});
-
-		$td.append($groupsSelect);
-
-		var checkedIds = Object.keys(checked).map(function(group, gid) {
-			return checked[group].displayName;
-		});
-		if (isSubadminSelect) {
-			UserList.applySubadminSelect($groupsSelect, user, checkedIds);
-		} else {
-			UserList.applyGroupSelect($groupsSelect, user, checkedIds);
-		}
-
-		$groupsListContainer.addClass('hidden');
-		$td.find('.multiselect:not(.groupsListContainer):first').click();
-		$groupsSelect.on('dropdownclosed', function (e) {
-			$groupsSelect.remove();
-			$td.find('.multiselect:not(.groupsListContainer)').parent().remove();
-			$td.find('.multiselectoptions').remove();
-			$groupsListContainer.removeClass('hidden');
-			// Pull all checked groups from this.availableGroups
-			var checked = Object.keys(self.availableGroups).reduce(function (previous, key) {
-				if(e.checked.indexOf(key) >= 0) {
-					return Object.assign(previous, {[key]:self.availableGroups[key]});
-				} else {
-					return previous;
-				}
-			}, {});
-			UserList._updateGroupListLabel($td, checked);
-		});
-	},
-
-	/**
-	 * Updates the groups list td with the given groups selection
-	 */
-	_updateGroupListLabel: function ($td, groups) {
-		var placeholder = $td.find('.groupsListContainer').data('placeholder');
-		var $groupsEl = $td.find('.groupsList');
-		var grouptext = Object.keys(groups).map(function(group, gid) {
-			return groups[group].displayName;
-		});
-		$groupsEl.text(grouptext.join(', ') || placeholder || t('settings', 'no group'));
-		$td.data('groups', groups);
-	}
-};
-
-$(document).ready(function () {
-	OC.Plugins.attach('OC.Settings.UserList', UserList);
-	$userList = $('#userlist');
-	$userListBody = $userList.find('tbody');
-	$userListHead = $userList.find('thead');
-	$emptyContainer = $userList.siblings('.emptycontent');
-
-	UserList.initDeleteHandling();
-
-	// Implements User Search
-	OCA.Search.users = new UserManagementFilter(UserList, GroupList);
-
-	UserList.scrollArea = $('#app-content');
-
-	UserList.doSort();
-	UserList.availableGroups = $userList.data('groups');
-
-	UserList.scrollArea.scroll(function (e) {
-		UserList._onScroll(e);
-	});
-
-	$userList.after($('<div class="loading" style="height: 200px; visibility: hidden;"></div>'));
-
-	// TODO: move other init calls inside of initialize
-	UserList.initialize($('#userlist'));
-
-	var _submitPasswordChange = function (uid, password, recoveryPasswordVal, blurFunction) {
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(function () {
-				_submitPasswordChange(uid, password, recoveryPasswordVal, blurFunction);
-			});
-			return;
-		}
-
-		$.post(
-			OC.generateUrl('/settings/users/changepassword'),
-			{
-				username: uid,
-				password: password,
-				recoveryPassword: recoveryPasswordVal
-			},
-			function (result) {
-				blurFunction();
-				if (result.status === 'success') {
-					OC.Notification.showTemporary(t('admin', 'Password successfully changed'));
-				} else {
-					OC.Notification.showTemporary(t('admin', result.data.message));
-				}
-			}
-		).fail(blurFunction);
-	};
-
-	$userListBody.on('click', '.password', function (event) {
-		event.stopPropagation();
-
-		var $td = $(this).closest('td');
-		var $tr = $(this).closest('tr');
-		var uid = UserList.getUID($td);
-		var $input = $('<input type="password">');
-		var isRestoreDisabled = UserList.getRestoreDisabled($td) === true;
-		var blurFunction = function () {
-			$(this).replaceWith($('<span>●●●●●●●</span>'));
-			$td.find('img').show();
-			// remove highlight class from users without recovery ability
-			$tr.removeClass('row-warning');
-		};
-		blurFunction = _.bind(blurFunction, $input);
-		if (isRestoreDisabled) {
-			$tr.addClass('row-warning');
-			// add tooltip if the password change could cause data loss - no recovery enabled
-			$input.attr('title', t('settings', 'Changing the password will result in data loss, because data recovery is not available for this user'));
-			$input.tooltip({placement: 'bottom'});
-		}
-		$td.find('img').hide();
-		$td.children('span').replaceWith($input);
-		$input
-			.focus()
-			.keypress(function (event) {
-				if (event.keyCode === 13) {
-					if ($(this).val().length > 0) {
-						var recoveryPasswordVal = $('input:password[id="recoveryPassword"]').val();
-						$input.off('blur');
-						_submitPasswordChange(uid, $(this).val(), recoveryPasswordVal, blurFunction);
-					} else {
-						$input.blur();
-					}
-				}
-			})
-			.blur(blurFunction);
-	});
-	$('input:password[id="recoveryPassword"]').keyup(function () {
-		OC.Notification.hide();
-	});
-
-	var _submitDisplayNameChange = function ($tr, uid, displayName, blurFunction) {
-		var $div = $tr.find('div.avatardiv');
-		if ($div.length) {
-			$div.imageplaceholder(uid, displayName);
-		}
-
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(function () {
-				_submitDisplayNameChange($tr, uid, displayName, blurFunction);
-			});
-			return;
-		}
-
-		$.ajax({
-			type: 'POST',
-			url: OC.generateUrl('/settings/users/{id}/displayName', {id: uid}),
-			data: {
-				username: uid,
-				displayName: displayName
-			}
-		}).success(function (result) {
-			if (result && result.status === 'success' && $div.length) {
-				$div.avatar(result.data.username, 32);
-			}
-			$tr.data('displayname', displayName);
-			blurFunction();
-		}).fail(function (result) {
-			OC.Notification.showTemporary(result.responseJSON.message);
-			$tr.find('.displayName input').blur(blurFunction);
-		});
-	};
-
-	$userListBody.on('click', '.displayName', function (event) {
-		event.stopPropagation();
-		var $td = $(this).closest('td');
-		var $tr = $td.closest('tr');
-		var uid = UserList.getUID($td);
-		var displayName = escapeHTML(UserList.getDisplayName($td));
-		var $input = $('<input type="text" value="' + displayName + '">');
-		var blurFunction = function () {
-			var displayName = $tr.data('displayname');
-			$input.replaceWith('<span>' + escapeHTML(displayName) + '</span>');
-			$td.find('img').show();
-		};
-		$td.find('img').hide();
-		$td.children('span').replaceWith($input);
-		$input
-			.focus()
-			.keypress(function (event) {
-				if (event.keyCode === 13) {
-					if ($(this).val().length > 0) {
-						$input.off('blur');
-						_submitDisplayNameChange($tr, uid, $(this).val(), blurFunction);
-					} else {
-						$input.blur();
-					}
-				}
-			})
-			.blur(blurFunction);
-	});
-
-	var _submitEmailChange = function ($tr, $td, $input, uid, mailAddress, blurFunction) {
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(function () {
-				_submitEmailChange($tr, $td, $input, uid, mailAddress, blurFunction);
-			});
-			return;
-		}
-
-		$.ajax({
-			type: 'PUT',
-			url: OC.generateUrl('/settings/users/{id}/mailAddress', {id: uid}),
-			data: {
-				mailAddress: mailAddress
-			}
-		}).success(function () {
-			// set data attribute to new value
-			// will in blur() be used to show the text instead of the input field
-			$tr.data('mailAddress', mailAddress);
-			$td.find('.loading-small').css('display', '');
-			$input.removeAttr('disabled')
-				.triggerHandler('blur'); // needed instead of $input.blur() for Firefox
-			blurFunction();
-		}).fail(function (result) {
-			if (!_.isUndefined(result.responseJSON.data)) {
-				OC.Notification.showTemporary(result.responseJSON.data.message);
-			} else if (!_.isUndefined(result.responseJSON.message)) {
-				OC.Notification.showTemporary(result.responseJSON.message);
-			} else {
-				OC.Notification.showTemporary(t('settings', 'Could not change the users email'));
-			}
-			$td.find('.loading-small').css('display', '');
-			$input.removeAttr('disabled')
-				.css('padding-right', '6px');
-			$input.blur(blurFunction);
-		});
-	};
-
-	$userListBody.on('click', '.mailAddress', function (event) {
-		event.stopPropagation();
-		var $td = $(this).closest('td');
-		var $tr = $td.closest('tr');
-		var uid = UserList.getUID($td);
-		var mailAddress = escapeHTML(UserList.getMailAddress($td));
-		var $input = $('<input type="text">').val(mailAddress);
-		var blurFunction = function () {
-			if ($td.find('.loading-small').css('display') === 'inline-block') {
-				// in Chrome the blur event is fired too early by the browser - even if the request is still running
-				return;
-			}
-			var $span = $('<span>').text($tr.data('mailAddress'));
-			$input.replaceWith($span);
-			$td.find('img').show();
-		};
-		$td.children('span').replaceWith($input);
-		$td.find('img').hide();
-		$input
-			.focus()
-			.keypress(function (event) {
-				if (event.keyCode === 13) {
-					// enter key
-
-					$td.find('.loading-small').css('display', 'inline-block');
-					$input.css('padding-right', '26px');
-					$input.attr('disabled', 'disabled');
-					$input.off('blur');
-					_submitEmailChange($tr, $td, $input, uid, $(this).val(), blurFunction);
-				}
-			})
-			.blur(blurFunction);
-	});
-
-	$('#newuser .groupsListContainer').on('click', function (event) {
-		event.stopPropagation();
-		var $div = $(this).closest('.groups');
-		UserList._triggerGroupEdit($div);
-	});
-	$userListBody.on('click', '.groups .groupsListContainer, .subadmins .groupsListContainer', function (event) {
-		event.stopPropagation();
-		var $td = $(this).closest('td');
-		var isSubadminSelect = $td.hasClass('subadmins');
-		UserList._triggerGroupEdit($td, isSubadminSelect);
-	});
-
-	$userListBody.on('click', '.toggleUserActions > .action', function (event) {
-		event.stopPropagation();
-		var $td = $(this).closest('td');
-		var $tr = $($td).closest('tr');
-		var menudiv = $tr.find('.popovermenu');
-
-		if ($tr.is('.active')) {
-			$tr.removeClass('active');
-			menudiv.removeClass('open');
-			return;
-		}
-		$('#userlist tr.active').removeClass('active');
-		$('#userlist .popovermenu').removeClass('open');
-		menudiv.addClass('open');
-		menudiv.find('.action-togglestate').empty();
-		if ($tr.data('userEnabled')) {
-			$('.action-togglestate', $td).html('<span class="icon icon-close"></span><span>' + t('settings', 'Disable') + '</span>');
-		} else {
-			$('.action-togglestate', $td).html('<span class="icon icon-add"></span><span>' + t('settings', 'Enable') + '</span>');
-		}
-		$tr.addClass('active');
-	});
-
-	$(document).on('mouseup', function (event) {
-		if (!$(event.target).closest('.toggleUserActions').length) {
-			$('#userlist tr.active').removeClass('active');
-			$('#userlist .popovermenu.open').removeClass('open');
-		}
-	});
-
-	$userListBody.on('click', '.action-togglestate', function (event) {
-		event.stopPropagation();
-		var $td = $(this).closest('td');
-		var $tr = $td.closest('tr');
-		var uid = UserList.getUID($td);
-		var setEnabled = UserList.getUserEnabled($td) ? 0 : 1;
-		$.post(
-			OC.generateUrl('/settings/users/{id}/setEnabled', {id: uid}),
-			{username: uid, enabled: setEnabled},
-			function (result) {
-				if (result && result.status === 'success') {
-					var count = GroupList.getUserCount(GroupList.getGroupLI('_disabledUsers'));
-					$tr.remove();
-					if (result.data.enabled == 1) {
-						$tr.data('userEnabled', true);
-						GroupList.setUserCount(GroupList.getGroupLI('_disabledUsers'), count - 1);
-					} else {
-						$tr.data('userEnabled', false);
-						GroupList.setUserCount(GroupList.getGroupLI('_disabledUsers'), count + 1);
-					}
-				} else {
-					OC.dialogs.alert(result.data.message, t('settings', 'Error while changing status of {user}', {user: uid}));
-				}
-			}
-		).fail(function (result) {
-			var message = 'Unknown error';
-			if (result.responseJSON &&
-				result.responseJSON.data &&
-				result.responseJSON.data.message) {
-				message = result.responseJSON.data.message;
-			}
-			OC.dialogs.alert(message, t('settings', 'Error while changing status of {user}', {user: uid}));
-		});
-	});
-
-	// init the quota field select box after it is shown the first time
-	$('#app-settings').one('show', function () {
-		$(this).find('#default_quota').singleSelect().on('change', UserList.onQuotaSelect);
-	});
-
-	$('#newuser input').click(function () {
-		// empty the container also here to avoid visual delay
-		$emptyContainer.hide();
-		OC.Search = new OCA.Search($('#searchbox'), $('#searchresults'));
-		OC.Search.clear();
-	});
-
-	UserList._updateGroupListLabel($('#newuser .groups'), {});
-	var _submitNewUserForm = function (event) {
-		event.preventDefault();
-		if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
-			OC.PasswordConfirmation.requirePasswordConfirmation(function () {
-				_submitNewUserForm(event);
-			});
-			return;
-		}
-
-		var username = $('#newusername').val();
-		var password = $('#newuserpassword').val();
-		var email = $('#newemail').val();
-		if ($.trim(username) === '') {
-			OC.Notification.showTemporary(t('settings', 'Error creating user: {message}', {
-				message: t('settings', 'A valid username must be provided')
-			}));
-			return false;
-		}
-
-		var promise;
-		if (UserDeleteHandler) {
-			promise = UserDeleteHandler.deleteEntry();
-		} else {
-			promise = $.Deferred().resolve().promise();
-		}
-
-		promise.then(function () {
-			var groups = $('#newuser .groups').data('groups') || {};
-			groups = Object.keys(groups);
-			$.post(
-				OC.generateUrl('/settings/users/users'),
-				{
-					username: username,
-					password: password,
-					groups: groups,
-					email: email
-				},
-				function (result) {
-					if (result.groups) {
-						for (var i in result.groups) {
-							var gid = result.groups[i];
-							if (_.isUndefined(UserList.availableGroups[gid])) {
-								UserList.availableGroups[gid] = {displayName: gid};
-							}
-							var $li = GroupList.getGroupLI(gid);
-							var userCount = GroupList.getUserCount($li);
-							GroupList.setUserCount($li, userCount + 1);
-						}
-					}
-					if (!UserList.has(username)) {
-						UserList.add(result);
-						UserList.doSort();
-					}
-					$('#newusername').focus();
-					GroupList.incEveryoneCount();
-				}).fail(function (result) {
-				OC.Notification.showTemporary(t('settings', 'Error creating user: {message}', {
-					message: result.responseJSON.message
-				}, undefined, {escape: false}));
-			}).success(function () {
-				$('#newuser').get(0).reset();
-			});
-		});
-	};
-	$('#newuser').submit(_submitNewUserForm);
-
-	if ($('#CheckboxStorageLocation').is(':checked')) {
-		$("#userlist .storageLocation").show();
-	}
-	// Option to display/hide the "Storage location" column
-	$('#CheckboxStorageLocation').click(function () {
-		if ($('#CheckboxStorageLocation').is(':checked')) {
-			$("#userlist .storageLocation").show();
-			if (OC.isUserAdmin()) {
-				OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'true');
-			}
-		} else {
-			$("#userlist .storageLocation").hide();
-			if (OC.isUserAdmin()) {
-				OCP.AppConfig.setValue('core', 'umgmt_show_storage_location', 'false');
-			}
-		}
-	});
-
-	if ($('#CheckboxLastLogin').is(':checked')) {
-		$("#userlist .lastLogin").show();
-	}
-	// Option to display/hide the "Last Login" column
-	$('#CheckboxLastLogin').click(function () {
-		if ($('#CheckboxLastLogin').is(':checked')) {
-			$("#userlist .lastLogin").show();
-			if (OC.isUserAdmin()) {
-				OCP.AppConfig.setValue('core', 'umgmt_show_last_login', 'true');
-			}
-		} else {
-			$("#userlist .lastLogin").hide();
-			if (OC.isUserAdmin()) {
-				OCP.AppConfig.setValue('core', 'umgmt_show_last_login', 'false');
-			}
-		}
-	});
-
-	if ($('#CheckboxUserBackend').is(':checked')) {
-		$("#userlist .userBackend").show();
-	}
-	// Option to display/hide the "User Backend" column
-	$('#CheckboxUserBackend').click(function () {
-		if ($('#CheckboxUserBackend').is(':checked')) {
-			$("#userlist .userBackend").show();
-			if (OC.isUserAdmin()) {
-				OCP.AppConfig.setValue('core', 'umgmt_show_backend', 'true');
-			}
-		} else {
-			$("#userlist .userBackend").hide();
-			if (OC.isUserAdmin()) {
-				OCP.AppConfig.setValue('core', 'umgmt_show_backend', 'false');
-			}
-		}
-	});
-
-	// calculate initial limit of users to load
-	var initialUserCountLimit = UserList.initialUsersToLoad,
-		containerHeight = $('#app-content').height();
-	if (containerHeight > 40) {
-		initialUserCountLimit = Math.floor(containerHeight / 40);
-		if (initialUserCountLimit < UserList.initialUsersToLoad) {
-			initialUserCountLimit = UserList.initialUsersToLoad;
-		}
-	}
-	//realign initialUserCountLimit with usersToLoad as a safeguard
-	while ((initialUserCountLimit % UserList.usersToLoad) !== 0) {
-		// must be a multiple of this, otherwise LDAP freaks out.
-		// FIXME: solve this in LDAP backend in  8.1
-		initialUserCountLimit = initialUserCountLimit + 1;
-	}
-
-	// trigger loading of users on startup
-	UserList.update(UserList.currentGid, initialUserCountLimit);
-
-	_.defer(function () {
-		$('#app-content').trigger($.Event('apprendered'));
-	});
-
-});

+ 24 - 0
settings/main.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
+ * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.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/>
+ *
+ */
+
+ $tmpl = new OC_Template("settings", "settings", "user");
+ $tmpl->printPage();

+ 42 - 0
settings/package.json

@@ -0,0 +1,42 @@
+{
+  "name": "settings",
+  "description": "Nextcloud settings",
+  "version": "1.0.0",
+  "author": "John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>",
+  "license": "AGPL3",
+  "private": true,
+  "scripts": {
+    "dev": "cross-env NODE_ENV=development webpack",
+    "watch": "cross-env NODE_ENV=development webpack --progress --watch",
+    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
+  },
+  "dependencies": {
+    "axios": "^0.18.0",
+    "vue": "^2.5.11",
+    "vue-click-outside": "^1.0.7",
+    "vue-infinite-loading": "^2.2.3",
+    "vue-localstorage": "^0.6.2",
+    "vue-multiselect": "^2.1",
+    "vue-router": "^3.0.1",
+    "vuex": "^3.0.1",
+    "vuex-router-sync": "^5.0.0"
+  },
+  "browserslist": [
+    "last 2 versions",
+    "ie >= 11"
+  ],
+  "devDependencies": {
+    "babel-core": "^6.26.0",
+    "babel-loader": "^7.1.2",
+    "babel-preset-env": "^1.6.0",
+    "babel-preset-stage-3": "^6.24.1",
+    "cross-env": "^5.0.5",
+    "css-loader": "^0.28.7",
+    "file-loader": "^1.1.4",
+    "node-sass": "^4.5.3",
+    "sass-loader": "^6.0.6",
+    "vue-loader": "^13.0.5",
+    "vue-template-compiler": "^2.4.4",
+    "webpack": "^3.6.0"
+  }
+}

+ 1 - 1
settings/routes.php

@@ -50,7 +50,7 @@ $application->registerRoutes($this, [
 		['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'],
 		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'],
 		['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
-		['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'],
+		['name' => 'Users#setDisplayName', 'url' => '/settings/users/{id}/displayName', 'verb' => 'PUT'],
 		['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
 		['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'],
 		['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'],

+ 3 - 0
settings/src/.jshintrc

@@ -0,0 +1,3 @@
+{
+    "esversion": 6
+}

+ 16 - 0
settings/src/App.vue

@@ -0,0 +1,16 @@
+<template>
+	<router-view></router-view>
+</template>
+
+<script>
+export default {
+	name: 'App',
+	beforeMount: function () {
+		// importing server data into the store
+		const serverDataElmt = document.getElementById('serverData');
+		if (serverDataElmt !== null) {
+			this.$store.commit('setServerData', JSON.parse(document.getElementById('serverData').dataset.server));
+		}
+	}
+}
+</script>

+ 32 - 0
settings/src/components/appNavigation.vue

@@ -0,0 +1,32 @@
+<template>
+	<div id="app-navigation">
+		<div class="app-navigation-new" v-if="menu.new">
+			<button type="button" :id="menu.new.id" :class="menu.new.icon" @click="menu.new.action">{{menu.new.text}}</button>
+		</div>
+		<ul :id="menu.id">
+			<navigation-item v-for="(item, key) in menu.items" :item="item" :key="key" />
+		</ul>
+		<div id="app-settings">
+			<div id="app-settings-header">
+				<button class="settings-button"
+						data-apps-slide-toggle="#app-settings-content"
+				>{{t('settings', 'Settings')}}</button>
+			</div>
+			<div id="app-settings-content">
+				<slot name="settings-content"></slot>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import navigationItem from './appNavigation/navigationItem';
+
+export default {
+	name: 'appNavigation',
+	props: ['menu'],
+	components: {
+		navigationItem
+	}
+}
+</script>

+ 108 - 0
settings/src/components/appNavigation/navigationItem.vue

@@ -0,0 +1,108 @@
+<template>
+	<li :id="item.id" :class="[{'icon-loading-small': item.loading, 'open': item.opened, 'collapsible': item.collapsible&&item.children&&item.children.length>0 }, item.classes]">
+
+		<!-- Bullet -->
+		<div v-if="item.bullet" class="app-navigation-entry-bullet" :style="{ backgroundColor: item.bullet }"></div>
+
+		<!-- Main link -->
+		<a :href="(item.href) ? item.href : '#' " @click="toggleCollapse" :class="item.icon" >{{item.text}}</a>
+
+		<!-- Popover, counter and button(s) -->
+		<div v-if="item.utils" class="app-navigation-entry-utils">
+			<ul>
+				<!-- counter -->
+				<li v-if="Number.isInteger(item.utils.counter)"
+					class="app-navigation-entry-utils-counter">{{item.utils.counter}}</li>
+
+				<!-- first action if only one action and counter -->
+				<li v-if="item.utils.actions && item.utils.actions.length === 1 && Number.isInteger(item.utils.counter)"
+					class="app-navigation-entry-utils-menu-button">
+					<button :class="item.utils.actions[0].icon"></button>
+				</li>
+
+				<!-- second action only two actions and no counter -->
+				<li v-else-if="item.utils.actions && item.utils.actions.length === 2 && !Number.isInteger(item.utils.counter)"
+					v-for="action in item.utils.actions" :key="action.action"
+					class="app-navigation-entry-utils-menu-button">
+					<button :class="action.icon"></button>
+				</li>
+
+				<!-- menu if only at least one action and counter OR two actions and no counter-->
+				<li v-else-if="item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)"
+					class="app-navigation-entry-utils-menu-button">
+					<button v-click-outside="hideMenu" @click="showMenu" ></button>
+				</li>
+			</ul>
+		</div>
+
+		<!-- if more than 2 actions or more than 1 actions with counter -->
+		<div v-if="item.utils && item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)"
+			 class="app-navigation-entry-menu" :class="{ 'open': openedMenu }">
+			 <popover-menu :menu="item.utils.actions"/>
+		</div>
+
+		<!-- undo entry -->
+		<div class="app-navigation-entry-deleted" v-if="item.undo">
+			<div class="app-navigation-entry-deleted-description">{{item.undo.text}}</div>
+			<button class="app-navigation-entry-deleted-button icon-history" :title="t('settings', 'Undo')"></button>
+		</div>
+
+		<!-- edit entry -->
+		<div class="app-navigation-entry-edit" v-if="item.edit">
+			<form>
+				<input type="text" v-model="item.text">
+				<input type="submit" value="" class="icon-confirm">
+				<input type="submit" value="" class="icon-close" @click.stop.prevent="cancelEdit">
+			</form>
+		</div>
+
+		<!-- if the item has children, inject the component with proper data -->
+		<ul v-if="item.children">
+			<navigation-item v-for="(item, key) in item.children" :item="item" :key="key" />
+		</ul>
+	</li>
+</template>
+
+<script>
+import popoverMenu from '../popoverMenu';
+import ClickOutside from 'vue-click-outside';
+import Vue from 'vue';
+
+export default {
+	name: 'navigationItem',
+	props: ['item'],
+	components: {
+		popoverMenu
+	},
+	directives: {
+		ClickOutside
+	},
+	data () {
+		return {
+			openedMenu: false
+		}
+	},
+	methods: {
+		showMenu () {
+			this.openedMenu = true;
+		},
+		hideMenu () {
+			this.openedMenu = false;
+		},
+		toggleCollapse () {
+			// if item.opened isn't set, Vue won't trigger view updates https://vuejs.org/v2/api/#Vue-set
+			// ternary is here to detect the undefined state of item.opened
+			Vue.set(this.item, 'opened', this.item.opened ? !this.item.opened : true);
+		},
+		cancelEdit () {
+			// remove the editing class
+			if (Array.isArray(this.item.classes))
+				this.item.classes = this.item.classes.filter(item => item !== 'editing');
+		}
+	},
+	mounted () {
+		// prevent click outside event with popupItem.
+		this.popupItem = this.$el;
+	},
+}
+</script>

+ 18 - 0
settings/src/components/popoverMenu.vue

@@ -0,0 +1,18 @@
+<template>
+	<ul>
+		<popover-item v-for="(item, key) in menu" :item="item" :key="key" />
+	</ul>
+</template>
+
+
+<script>
+import popoverItem from './popoverMenu/popoverItem';
+
+export default {
+	name: 'popoverMenu',
+	props: ['menu'],
+	components: {
+		popoverItem
+	}
+}
+</script>

+ 23 - 0
settings/src/components/popoverMenu/popoverItem.vue

@@ -0,0 +1,23 @@
+<template>
+	<li>
+		<a @click="dispatchToStore" v-if="item.href" :href="(item.href) ? item.href : '#' ">
+			<span :class="item.icon"></span>
+			<span>{{item.text}}</span>
+		</a>
+		<button @click="dispatchToStore(item.action)" v-else>
+			<span :class="item.icon"></span>
+			<span>{{item.text}}</span>
+		</button>
+	</li>
+</template>
+
+<script>
+export default {
+	props: ['item'],
+	methods: {
+		dispatchToStore () {
+			this.$store.dispatch(this.item.action, this.item.data);
+		}
+	}
+}
+</script>

+ 205 - 0
settings/src/components/userList.vue

@@ -0,0 +1,205 @@
+<template>
+	<div id="app-content" class="user-list-grid" v-on:scroll.passive="onScroll">
+		<div class="row" id="grid-header" :class="{'sticky': scrolled && !showConfig.showNewUserForm}">
+			<div id="headerAvatar" class="avatar"></div>
+			<div id="headerName" class="name">{{ t('settings', 'Username') }}</div>
+			<div id="headerDisplayName" class="displayName">{{ t('settings',  'Full name') }}</div>
+			<div id="headerPassword" class="password">{{ t('settings',  'Password') }}</div>
+			<div id="headerAddress" class="mailAddress">{{ t('settings',  'Email') }}</div>
+			<div id="headerGroups" class="groups">{{ t('settings',  'Groups') }}</div>
+			<div id="headerSubAdmins" class="subadmins"
+				 v-if="subAdminsGroups.length>0">{{ t('settings', 'Group admin for') }}</div>
+			<div id="headerQuota" class="quota">{{ t('settings', 'Quota') }}</div>
+			<div class="headerStorageLocation storageLocation"
+				 v-if="showConfig.showStoragePath">{{ t('settings', 'Storage location') }}</div>
+			<div class="headerUserBackend userBackend"
+				 v-if="showConfig.showUserBackend">{{ t('settings', 'User backend') }}</div>
+			<div class="headerLastLogin lastLogin" 
+				 v-if="showConfig.showLastLogin">{{ t('settings', 'Last login') }}</div>
+			<div class="userActions"></div>
+		</div>
+
+		<form class="row" id="new-user" v-show="showConfig.showNewUserForm"
+			  v-on:submit.prevent="createUser" :disabled="loading"
+			  :class="{'sticky': scrolled && showConfig.showNewUserForm}">
+			<div :class="loading?'icon-loading-small':'icon-add'"></div>
+			<div class="name">
+				<input id="newusername" type="text" required v-model="newUser.id"
+					   :placeholder="t('settings', 'User name')" name="username"
+					   autocomplete="off" autocapitalize="none" autocorrect="off"
+					   pattern="[a-zA-Z0-9 _\.@\-']+">
+			</div>
+			<div class="displayName">
+				<input id="newdisplayname" type="text" v-model="newUser.displayName"
+					   :placeholder="t('settings', 'Display name')" name="displayname"
+					   autocomplete="off" autocapitalize="none" autocorrect="off">
+			</div>
+			<div class="password">
+				<input id="newuserpassword" type="password" v-model="newUser.password"
+					   :required="newUser.mailAddress===''"
+					   :placeholder="t('settings', 'Password')" name="password"
+					   autocomplete="new-password" autocapitalize="none" autocorrect="off"
+					   :minlength="minPasswordLength">
+			</div>
+			<div class="mailAddress">
+				<input id="newemail" type="email" v-model="newUser.mailAddress"
+					   :required="newUser.password===''"
+					   :placeholder="t('settings', 'Mail address')" name="email"
+					   autocomplete="off" autocapitalize="none" autocorrect="off">
+			</div>
+			<div class="groups">
+				<multiselect :options="groups" v-model="newUser.groups"
+							 :placeholder="t('settings', 'Add user in group')"
+							 label="name" track-by="id" class="multiselect-vue"
+							 :multiple="true" :close-on-select="false">
+					<span slot="noResult">{{t('settings','No result')}}</span>
+				</multiselect>
+			</div>
+			<div class="subadmins" v-if="subAdminsGroups.length>0">
+				<multiselect :options="subAdminsGroups" v-model="newUser.subAdminsGroups"
+							 :placeholder="t('settings', 'Set user as admin for')"
+							 label="name" track-by="id" class="multiselect-vue"
+							 :multiple="true" :close-on-select="false">
+					<span slot="noResult">{{t('settings','No result')}}</span>
+			</multiselect>
+			</div>
+			<div class="quota">
+				<multiselect :options="quotaOptions" v-model="newUser.quota"
+							 :placeholder="t('settings', 'Select user quota')"
+							 label="label" track-by="id" class="multiselect-vue"
+							 :allowEmpty="false" :taggable="true"
+						 	 @tag="validateQuota" >
+				</multiselect>
+			</div>
+			<div class="storageLocation" v-if="showConfig.showStoragePath"></div>
+			<div class="userBackend" v-if="showConfig.showUserBackend"></div>
+			<div class="lastLogin" v-if="showConfig.showLastLogin"></div>
+			<div class="userActions">
+				<input type="submit" id="newsubmit" class="button primary icon-checkmark-white has-tooltip"
+					   value="" :title="t('settings', 'Add a new user')">
+				<input type="reset" id="newreset" class="button icon-close has-tooltip" @click="resetForm"
+					   value="" :title="t('settings', 'Cancel and reset the form')">
+			</div>
+		</form>
+
+		<user-row v-for="(user, key) in users" :user="user" :key="key" :settings="settings" :showConfig="showConfig"
+				  :groups="groups" :subAdminsGroups="subAdminsGroups" :quotaOptions="quotaOptions" />
+		<infinite-loading @infinite="infiniteHandler">
+			<span slot="spinner"><div class="users-icon-loading"></div></span>
+			<span slot="no-more"><div class="users-list-end">— {{t('settings', 'no more results')}} —</div></span>
+		</infinite-loading>
+	</div>
+</template>
+
+<script>
+import userRow from './userList/userRow';
+import Multiselect from 'vue-multiselect';
+import InfiniteLoading from 'vue-infinite-loading';
+
+export default {
+	name: 'userList',
+	props: ['users', 'showConfig'],
+	components: {
+		userRow,
+		Multiselect,
+		InfiniteLoading
+	},
+	data() {
+		let unlimitedQuota = {id:'none', label:t('settings', 'Unlimited')},
+			defaultQuota = {id:'default', label:t('settings', 'Default quota')};
+		return {
+			unlimitedQuota: unlimitedQuota,
+			defaultQuota: defaultQuota,
+			loading: false,
+			scrolled: false,
+			newUser: {
+				id:'',
+				displayName:'',
+				password:'',
+				mailAddress:'',
+				groups: [],
+				subAdminsGroups: [],
+				quota: defaultQuota
+			}
+		};
+	},
+	mounted() {
+		if (!this.settings.canChangePassword) {
+			OC.Notification.showTemporary(t('settings','Password change is disabled because the master key is disabled'));
+		}
+	},
+	computed: {
+		settings() {
+			return this.$store.getters.getServerData;
+		},
+		groups() {
+			// data provided php side + remove the disabled group
+			return this.$store.getters.getGroups.filter(group => group.id !== '_disabled');
+		},
+		subAdminsGroups() {
+			// data provided php side
+			return this.$store.getters.getServerData.subadmingroups;
+		},
+		quotaOptions() {
+			// convert the preset array into objects
+			let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({id:cur, label:cur}), []);
+			// add default presets
+			quotaPreset.unshift(this.unlimitedQuota);
+			quotaPreset.unshift(this.defaultQuota);
+			return quotaPreset;
+		},
+		minPasswordLength() {
+			return this.$store.getters.getPasswordPolicyMinLength;
+		},
+		usersOffset() {
+			return this.$store.getters.getUsersOffset;
+		},
+		usersLimit() {
+			return this.$store.getters.getUsersLimit;
+		}, 
+	},
+	methods: {
+		onScroll(event) {
+			this.scrolled = event.target.scrollTop>0;
+		},
+
+		/**
+		 * Validate quota string to make sure it's a valid human file size
+		 * 
+		 * @param {string} quota Quota in readable format '5 GB'
+		 * @returns {Object}
+		 */
+		validateQuota(quota) {
+			// only used for new presets sent through @Tag
+			let validQuota = OC.Util.computerFileSize(quota);
+			if (validQuota !== null && validQuota > 0) {
+				// unify format output
+				quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota));
+				return this.newUser.quota = {id: quota, label: quota};
+			}
+			// Default is unlimited
+			return this.newUser.quota = this.quotaOptions[0];
+		},
+
+		infiniteHandler($state) {
+			this.$store.dispatch('getUsers', {offset:this.usersOffset, limit:this.usersLimit})
+				.then((response) => {response?$state.loaded():$state.complete()});
+		},
+
+		resetForm () {
+			// revert form to original state
+			Object.assign(this.newUser, this.$options.data.call(this).newUser);
+			this.loading = false;
+        },
+		createUser() {
+			this.loading = true;
+			this.$store.dispatch('addUser', {
+				userid: this.newUser.id,
+				password: this.newUser.password,
+				email: this.newUser.mailAddress,
+				groups: this.newUser.groups.map(group => group.id)
+			}).then(() =>this.resetForm());
+		}
+	}
+}
+</script>

+ 370 - 0
settings/src/components/userList/userRow.vue

@@ -0,0 +1,370 @@
+<template>
+	<div class="row">
+		<div class="avatar"><img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)" :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"></div>
+		<div class="name">{{user.id}}</div>
+		<form class="displayName" :class="{'icon-loading-small': loading.displayName}" v-on:submit.prevent="updateDisplayName">
+			<input :id="'displayName'+user.id+rand" type="text"
+					:disabled="loading.displayName||loading.all"
+					:value="user.displayname" ref="displayName"
+					autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
+			<input type="submit" class="icon-confirm" value="" />
+		</form>
+		<form class="password" v-if="settings.canChangePassword" :class="{'icon-loading-small': loading.password}"
+			  v-on:submit.prevent="updatePassword">
+			<input :id="'password'+user.id+rand" type="password" required
+					:disabled="loading.password||loading.all" :minlength="minPasswordLength"
+					value="" :placeholder="t('settings', 'New password')" ref="password"
+					autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
+			<input type="submit" class="icon-confirm" value="" />
+		</form>
+		<div v-else></div>
+		<form class="mailAddress" :class="{'icon-loading-small': loading.mailAddress}" v-on:submit.prevent="updateEmail">
+			<input :id="'mailAddress'+user.id+rand" type="email"
+					:disabled="loading.mailAddress||loading.all"
+					:value="user.email" ref="mailAddress"
+					autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
+			<input type="submit" class="icon-confirm" value="" />
+		</form>
+		<div class="groups" :class="{'icon-loading-small': loading.groups}">
+			<multiselect :value="userGroups" :options="groups" :disabled="loading.groups||loading.all"
+						 tag-placeholder="create" :placeholder="t('settings', 'Add user in group')"
+						 label="name" track-by="id" class="multiselect-vue"
+						 :limit="2" :limitText="limitGroups"
+						 :multiple="true" :taggable="true" :closeOnSelect="false"
+						 @tag="createGroup" @select="addUserGroup" @remove="removeUserGroup">
+			</multiselect>
+		</div>
+		<div class="subadmins" v-if="subAdminsGroups.length>0" :class="{'icon-loading-small': loading.subadmins}">
+			<multiselect :value="userSubAdminsGroups" :options="subAdminsGroups" :disabled="loading.subadmins||loading.all"
+						 :placeholder="t('settings', 'Set user as admin for')"
+						 label="name" track-by="id" class="multiselect-vue"
+						 :limit="2" :limitText="limitGroups"
+						 :multiple="true" :closeOnSelect="false"
+						 @select="addUserSubAdmin" @remove="removeUserSubAdmin">
+				<span slot="noResult">{{t('settings','No result')}}</span>
+			</multiselect>
+		</div>
+		<div class="quota" :class="{'icon-loading-small': loading.quota}">
+			<multiselect :value="userQuota" :options="quotaOptions" :disabled="loading.quota||loading.all"
+						 tag-placeholder="create" :placeholder="t('settings', 'Select user quota')"
+						 label="label" track-by="id" class="multiselect-vue"
+						 :allowEmpty="false" :taggable="true"
+						 @tag="validateQuota" @input="setUserQuota">
+			</multiselect>
+			<progress class="quota-user-progress" :class="{'warn':usedQuota>80}" :value="usedQuota" max="100"></progress>
+		</div>
+		<div class="storageLocation" v-if="showConfig.showStoragePath">{{user.storageLocation}}</div>
+		<div class="userBackend" v-if="showConfig.showUserBackend">{{user.backend}}</div>
+		<div class="lastLogin" v-if="showConfig.showLastLogin" :title="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''">
+			{{user.lastLogin>0 ? OC.Util.relativeModifiedDate(user.lastLogin) : t('settings','Never')}}
+		</div>
+		<div class="userActions">
+			<div class="toggleUserActions" v-if="OC.currentUser !== user.id && user.id !== 'admin'">
+				<div class="icon-more" v-click-outside="hideMenu" @click="showMenu"></div>
+				<div class="popovermenu" :class="{ 'open': openedMenu }">
+					<popover-menu :menu="userActions" />
+				</div>
+			</div>
+		</div>
+		</div>
+</template>
+
+<script>
+import popoverMenu from '../popoverMenu';
+import ClickOutside from 'vue-click-outside';
+import Multiselect from 'vue-multiselect';
+//import Multiselect from '../../../node_modules/vue-multiselect/src/index';
+
+export default {
+	name: 'userRow',
+	props: ['user', 'settings', 'groups', 'subAdminsGroups', 'quotaOptions', 'showConfig'],
+	components: {
+		popoverMenu,
+		Multiselect
+	},
+	directives: {
+		ClickOutside
+	},
+	mounted() {
+		// prevent click outside event with popupItem.
+		this.popupItem = this.$el;
+	},
+	data() {
+		return {
+			rand: parseInt(Math.random() * 1000),
+			openedMenu: false,
+			loading: {
+				all: false,
+				displayName: false,
+				password: false,
+				mailAddress: false,
+				groups: false,
+				subadmins: false,
+				quota: false
+			}
+		}
+	},
+	computed: {
+		/* USER POPOVERMENU ACTIONS */
+		userActions() {
+			return [{
+				icon: 'icon-delete',
+				text: t('settings','Delete user'),
+				action: 'deleteUser',
+				data: this.user.id
+			},{
+				'icon': this.user.enabled ? 'icon-close' : 'icon-add',
+				'text': this.user.enabled ? t('settings','Disable user') : t('settings','Enable user'),
+				'action': 'enableDisableUser',
+				data: {userid: this.user.id, enabled: !this.user.enabled}
+			}]
+		},
+
+		/* GROUPS MANAGEMENT */
+		userGroups() {
+			let userGroups = this.groups.filter(group => this.user.groups.includes(group.id));
+			return userGroups;
+		},
+		userSubAdminsGroups() {
+			let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id));
+			return userSubAdminsGroups;
+		},
+
+		/* QUOTA MANAGEMENT */
+		usedQuota() {
+			let quota = this.user.quota.quota;
+			if (quota > 0) {
+				quota = Math.min(100, Math.round(this.user.quota.used / quota * 100));
+			} else {
+				var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30));
+				//asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
+				quota = 95 * (1 - (1 / (usedInGB + 1)));
+			}
+			return isNaN(quota) ? 0 : quota;
+		},
+		// Mapping saved values to objects
+		userQuota() {
+			if (this.user.quota.quota > 0) {
+				// if value is valid, let's map the quotaOptions or return custom quota
+				let humanQuota = OC.Util.humanFileSize(this.user.quota.quota);
+				let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota);
+				return userQuota ? userQuota : {id:humanQuota, label:humanQuota};
+			} else if (this.user.quota.quota === 0 || this.user.quota.quota === 'default') {
+				// default quota is replaced by the proper value on load
+				return this.quotaOptions[0];
+			}
+			return this.quotaOptions[1]; // unlimited
+		},
+
+		/* PASSWORD POLICY? */
+		minPasswordLength() {
+			return this.$store.getters.getPasswordPolicyMinLength;
+		}
+	},
+	methods: {
+		/* MENU HANDLING */
+		showMenu () {
+			this.openedMenu = true;
+		},
+		hideMenu () {
+			this.openedMenu = false;
+		},
+
+		/**
+		 * Generate avatar url
+		 * 
+		 * @param {string} user The user name
+		 * @param {int} size Size integer, default 32
+		 * @returns {string}
+		 */
+		generateAvatar(user, size=32) {
+			return OC.generateUrl(
+				'/avatar/{user}/{size}?v={version}',
+				{
+					user: user,
+					size: size,
+					version: oc_userconfig.avatar.version
+				}
+			);
+		},
+
+
+		/**
+		 * Format the limit text in the selected options
+		 * 
+		 * @param {int} count elements left
+		 * @returns {string}
+		 */
+		limitGroups(count) {
+			return '+'+count;
+		},
+
+		/**
+		 * Set user displayName
+		 * 
+		 * @param {string} displayName The display name
+		 * @returns {Promise}
+		 */
+		updateDisplayName() {
+			let displayName = this.$refs.displayName.value;
+			this.loading.displayName = true;
+			this.$store.dispatch('setUserData', {
+				userid: this.user.id, 
+				key: 'displayname',
+				value: displayName
+			}).then(() => {
+				this.loading.displayName = false;
+				this.$refs.displayName.value = displayName;
+			});
+		},
+
+		/**
+		 * Set user password
+		 * 
+		 * @param {string} password The email adress
+		 * @returns {Promise}
+		 */
+		updatePassword() {
+			let password = this.$refs.password.value;
+			this.loading.password = true;
+			this.$store.dispatch('setUserData', {
+				userid: this.user.id,
+				key: 'password',
+				value: password
+			}).then(() => {
+				this.loading.password = false;
+				this.$refs.password.value = ''; // empty & show placeholder 
+			});
+		},
+
+		/**
+		 * Set user mailAddress
+		 * 
+		 * @param {string} mailAddress The email adress
+		 * @returns {Promise}
+		 */
+		updateEmail() {
+			let mailAddress = this.$refs.mailAddress.value;
+			this.loading.mailAddress = true;
+			this.$store.dispatch('setUserData', {
+				userid: this.user.id,
+				key: 'email',
+				value: mailAddress
+			}).then(() => {
+				this.loading.mailAddress = false;
+				this.$refs.mailAddress.value = mailAddress;
+			});
+		},
+
+		/**
+		 * Create a new group
+		 * 
+		 * @param {string} groups Group id
+		 * @returns {Promise}
+		 */
+		createGroup(gid) {
+			this.loading = {groups:true, subadmins:true}
+			this.$store.dispatch('addGroup', gid).then(() => {
+				this.loading = {groups:false, subadmins:false};
+				let userid = this.user.id;
+				this.$store.dispatch('addUserGroup', {userid, gid});
+			});
+			return this.$store.getters.getGroups[this.groups.length];
+		},
+
+		/**
+		 * Add user to group
+		 * 
+		 * @param {object} group Group object
+		 * @returns {Promise}
+		 */
+		addUserGroup(group) {
+			this.loading.groups = true;
+			let userid = this.user.id;
+			let gid = group.id;
+			return this.$store.dispatch('addUserGroup', {userid, gid})
+					.then(() => this.loading.groups = false);
+		},
+
+		/**
+		 * Remove user from group
+		 * 
+		 * @param {object} group Group object
+		 * @returns {Promise}
+		 */
+		removeUserGroup(group) {
+			this.loading.groups = true;
+			let userid = this.user.id;
+			let gid = group.id;
+			return this.$store.dispatch('removeUserGroup', {userid, gid})
+					.then(() => this.loading.groups = false);
+		},
+
+		/**
+		 * Add user to group
+		 * 
+		 * @param {object} group Group object
+		 * @returns {Promise}
+		 */
+		addUserSubAdmin(group) {
+			this.loading.subadmins = true;
+			let userid = this.user.id;
+			let gid = group.id;
+			return this.$store.dispatch('addUserSubAdmin', {userid, gid})
+					.then(() => this.loading.subadmins = false);
+		},
+
+		/**
+		 * Remove user from group
+		 * 
+		 * @param {object} group Group object
+		 * @returns {Promise}
+		 */
+		removeUserSubAdmin(group) {
+			this.loading.subadmins = true;
+			let userid = this.user.id;
+			let gid = group.id;
+			return this.$store.dispatch('removeUserSubAdmin', {userid, gid})
+					.then(() => this.loading.subadmins = false);
+		},
+
+
+		/**
+		 * Validate quota string to make sure it's a valid human file size
+		 * 
+		 * @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
+		 * @returns {string}
+		 */
+		setUserQuota(quota = 'none') {
+			this.loading.quota = true;
+			// ensure we only send the preset id
+			quota = quota.id ? quota.id : quota;
+			this.$store.dispatch('setUserData', {
+				userid: this.user.id, 
+				key: 'quota',
+				value: quota
+			}).then(() => this.loading.quota = false);
+			return quota;
+		},
+
+		/**
+		 * Validate quota string to make sure it's a valid human file size
+		 * 
+		 * @param {string} quota Quota in readable format '5 GB'
+		 * @returns {Promise|boolean}
+		 */
+		validateQuota(quota) {
+			// only used for new presets sent through @Tag
+			let validQuota = OC.Util.computerFileSize(quota);
+			if (validQuota === 0) {
+				return this.setUserQuota('none');
+			} else if (validQuota !== null) {
+				// unify format output
+				return this.setUserQuota(OC.Util.humanFileSize(OC.Util.computerFileSize(quota)));
+			}
+			// if no valid doo not change
+			return false;
+		}
+	}
+}
+</script>

+ 20 - 0
settings/src/main.js

@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import { sync } from 'vuex-router-sync';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+
+sync(store, router);
+
+// bind to window
+Vue.prototype.t = t;
+Vue.prototype.OC = OC;
+Vue.prototype.oc_userconfig = oc_userconfig;
+
+const app = new Vue({
+    router,
+    store,
+    render: h => h(App)
+}).$mount('#content');
+
+export { app, router, store };

+ 23 - 0
settings/src/router.js

@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import Router from 'vue-router';
+import Users from './views/Users';
+
+Vue.use(Router);
+
+/*
+ * This is the list of routes where the vuejs app will
+ * take over php to provide data
+ * You need to forward the php routing (routes.php) to
+ * /settings/main.php, where the vue-router will ensure
+ * the proper route.
+ * ⚠️ Routes needs to match the php routes.
+ */
+
+export default new Router({
+    mode: 'history',
+    base: window.location.pathName,
+    routes: [{
+        path: '/settings/users',
+        component: Users
+    }]
+});

+ 50 - 0
settings/src/store/api.js

@@ -0,0 +1,50 @@
+import axios from 'axios';
+
+const requestToken = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken');
+const tokenHeaders = { headers: { requesttoken: requestToken } };
+
+const sanitize = function(url) {
+    return url.replace(/\/$/, ''); // Remove last slash of url
+}
+
+export default {
+    requireAdmin() {
+        return new Promise(function(resolve, reject) {
+            setTimeout(reject, 5000); // automatically reject 5s if not ok
+            function waitForpassword() {
+                if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
+                    setTimeout(waitForpassword, 500);
+                    return;
+                }
+                resolve();
+            }
+            waitForpassword();
+            OC.PasswordConfirmation.requirePasswordConfirmation();
+        }).catch((error) => console.log('Required password not entered'));
+    },
+    get(url) {
+        return axios.get(sanitize(url), tokenHeaders)
+            .then((response) => Promise.resolve(response))
+            .catch((error) => Promise.reject(error));
+    },
+    post(url, data) {
+        return axios.post(sanitize(url), data, tokenHeaders)
+            .then((response) => Promise.resolve(response))
+            .catch((error) => Promise.reject(error));
+    },
+    patch(url, data) {
+        return axios.patch(sanitize(url), { data: data, headers: tokenHeaders.headers })
+            .then((response) => Promise.resolve(response))
+            .catch((error) => Promise.reject(error));
+    },
+    put(url, data) {
+        return axios.put(sanitize(url), data, tokenHeaders)
+            .then((response) => Promise.resolve(response))
+            .catch((error) => Promise.reject(error));
+    },
+    delete(url, data) {
+        return axios.delete(sanitize(url), { data: data, headers: tokenHeaders.headers })
+            .then((response) => Promise.resolve(response))
+            .catch((error) => Promise.reject(error));
+    }
+};

+ 24 - 0
settings/src/store/index.js

@@ -0,0 +1,24 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import users from './users'
+import settings from './settings'
+
+Vue.use(Vuex)
+
+const debug = process.env.NODE_ENV !== 'production'
+
+const mutations = {
+	API_FAILURE(state, error) {
+		console.log(state, error);
+	}
+}
+
+export default new Vuex.Store({
+	modules: {
+		users,
+		settings
+	},
+	strict: debug,
+
+	mutations
+})

+ 18 - 0
settings/src/store/settings.js

@@ -0,0 +1,18 @@
+import api from './api';
+
+const state = {
+	serverData: {}
+};
+const mutations = {
+	setServerData(state, data) {
+		state.serverData = data;
+	}
+};
+const getters = {
+	getServerData(state) {
+		return state.serverData;
+	}
+}
+const actions = {}
+
+export default {state, mutations, getters, actions};

+ 380 - 0
settings/src/store/users.js

@@ -0,0 +1,380 @@
+import api from './api';
+
+const orderGroups = function(groups, orderBy) {
+    /* const SORT_USERCOUNT = 1;
+     * const SORT_GROUPNAME = 2;
+     * https://github.com/nextcloud/server/blob/208e38e84e1a07a49699aa90dc5b7272d24489f0/lib/private/Group/MetaData.php#L34
+     */
+    if (orderBy === 1) {
+        return groups.sort((a, b) => a.usercount < b.usercount);
+    } else {
+        return groups.sort((a, b) => a.name.localeCompare(b.name));
+    }
+}
+
+const state = {
+    users: [],
+    groups: [],
+    orderBy: 1,
+    minPasswordLength: 0,
+    usersOffset: 0,
+    usersLimit: 25,
+};
+
+const mutations = {
+    appendUsers(state, usersObj) {
+        // convert obj to array
+        let users = state.users.concat(Object.keys(usersObj).map(userid => usersObj[userid]));
+        state.usersOffset += state.usersLimit;
+        state.users = users;
+    },
+    setPasswordPolicyMinLength(state, length) {
+        state.minPasswordLength = length!=='' ? length : 0;
+    },
+    initGroups(state, {groups, orderBy}) {
+        state.groups = groups;
+        state.orderBy = orderBy;
+        state.groups = orderGroups(state.groups, state.orderBy);
+    },
+    addGroup(state, groupid) {
+        try {
+            state.groups.push({
+                id: groupid,
+                name: groupid,
+                usercount: 0 // user will be added after the creation
+            });
+            state.groups = orderGroups(state.groups, state.orderBy);
+        } catch (e) {
+            console.log('Can\'t create group', e);
+        }
+    },
+    addUserGroup(state, { userid, gid }) {
+        // this should not be needed as it would means the user contains a group
+        // the server database doesn't have.
+        let group = state.groups.find(groupSearch => groupSearch.id == gid);
+        if (group) {
+            group.usercount++; // increase count
+        }
+        let groups = state.users.find(user => user.id == userid).groups;
+        groups.push(gid);
+        state.groups = orderGroups(state.groups, state.orderBy);
+    },
+    removeUserGroup(state, { userid, gid }) {
+        // this should not be needed as it would means the user contains a group
+        // the server database doesn't have.
+        let group = state.groups.find(groupSearch => groupSearch.id == gid);
+        if (group) {
+            group.usercount--; // lower count
+        }
+        let groups = state.users.find(user => user.id == userid).groups;
+        groups.splice(groups.indexOf(gid),1);
+        state.groups = orderGroups(state.groups, state.orderBy);
+    },
+    addUserSubAdmin(state, { userid, gid }) {
+        let groups = state.users.find(user => user.id == userid).subadmin;
+        groups.push(gid);
+    },
+    removeUserSubAdmin(state, { userid, gid }) {
+        let groups = state.users.find(user => user.id == userid).subadmin;
+        groups.splice(groups.indexOf(gid),1);
+    },
+    deleteUser(state, userid) {
+        let userIndex = state.users.findIndex(user => user.id == userid);
+        state.users.splice(userIndex, 1);
+    },
+    addUserData(state, response) {
+        state.users.push(response.data.ocs.data);
+    },
+    enableDisableUser(state, { userid, enabled }) {
+        state.users.find(user => user.id == userid).enabled = enabled;
+        state.groups.find(group => group.id == '_disabled').usercount += enabled ? -1 : 1;
+    },
+    setUserData(state, { userid, key, value }) {
+        if (key === 'quota') {
+            let humanValue = OC.Util.computerFileSize(value);
+            state.users.find(user => user.id == userid)[key][key] = humanValue?humanValue:value;
+        } else {
+            state.users.find(user => user.id == userid)[key] = value;
+        }
+    },
+};
+
+const getters = {
+    getUsers(state) {
+        return state.users;
+    },
+    getGroups(state) {
+        return state.groups;
+    },
+    getPasswordPolicyMinLength(state) {
+        return state.minPasswordLength;
+    },
+    getUsersOffset(state) {
+        return state.usersOffset;
+    },
+    getUsersLimit(state) {
+        return state.usersLimit;
+    }
+};
+
+const actions = {
+    /**
+     * Get all users with full details
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {int} options.offset List offset to request
+     * @param {int} options.limit List number to return from offset
+     * @returns {Promise}
+     */
+    getUsers(context, { offset, limit, search }) {
+        search = typeof search === 'string' ? search : '';
+        return api.get(OC.linkToOCS(`cloud/users/details?offset=${offset}&limit=${limit}&search=${search}`, 2))
+            .then((response) => {
+                if (Object.keys(response.data.ocs.data.users).length > 0) {
+                    context.commit('appendUsers', response.data.ocs.data.users);
+                    return true;
+                }
+                return false;
+            })
+            .catch((error) => context.commit('API_FAILURE', error));
+    },
+
+    /**
+     * Get all users with full details
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {int} options.offset List offset to request
+     * @param {int} options.limit List number to return from offset
+     * @returns {Promise}
+     */
+    getUsersFromList(context, { offset, limit, search }) {
+        search = typeof search === 'string' ? search : '';
+        return api.get(OC.linkToOCS(`cloud/users/details?offset=${offset}&limit=${limit}&search=${search}`, 2))
+            .then((response) => {
+                if (Object.keys(response.data.ocs.data.users).length > 0) {
+                    context.commit('appendUsers', response.data.ocs.data.users);
+                    return true;
+                }
+                return false;
+            })
+            .catch((error) => context.commit('API_FAILURE', error));
+    },
+
+    /**
+     * Get all users with full details from a groupid
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {int} options.offset List offset to request
+     * @param {int} options.limit List number to return from offset
+     * @returns {Promise}
+     */
+    getUsersFromGroup(context, { groupid, offset, limit }) {
+        return api.get(OC.linkToOCS(`cloud/users/${groupid}/details?offset=${offset}&limit=${limit}`, 2))
+            .then((response) => context.commit('getUsersFromList', response.data.ocs.data.users))
+            .catch((error) => context.commit('API_FAILURE', error));
+    },
+    
+
+    getPasswordPolicyMinLength(context) {
+        return api.get(OC.linkToOCS('apps/provisioning_api/api/v1/config/apps/password_policy/minLength', 2))
+            .then((response) => context.commit('setPasswordPolicyMinLength', response.data.ocs.data.data))
+            .catch((error) => context.commit('API_FAILURE', error));
+    },
+
+    /**
+     * Add group
+     * 
+     * @param {Object} context
+     * @param {string} gid Group id
+     * @returns {Promise}
+     */
+    addGroup(context, gid) {
+        return api.requireAdmin().then((response) => {
+            return api.post(OC.linkToOCS(`cloud/groups`, 2), {groupid: gid})
+                .then((response) => context.commit('addGroup', gid))
+                .catch((error) => context.commit('API_FAILURE', error));
+        });
+    },
+
+    /**
+     * Add group
+     * 
+     * @param {Object} context
+     * @param {string} gid Group id
+     * @returns {Promise}
+     */
+    removeGroup(context, gid) {
+        return api.requireAdmin().then((response) => {
+            return api.post(OC.linkToOCS(`cloud/groups`, 2), {groupid: gid})
+                .then((response) => context.commit('removeGroup', gid))
+                .catch((error) => context.commit('API_FAILURE', error));
+        });
+    },
+
+    /**
+     * Add user to group
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {string} options.userid User id
+     * @param {string} options.gid Group id
+     * @returns {Promise}
+     */
+    addUserGroup(context, { userid, gid }) {
+        return api.requireAdmin().then((response) => {
+            return api.post(OC.linkToOCS(`cloud/users/${userid}/groups`, 2), { groupid: gid })
+                .then((response) => context.commit('addUserGroup', { userid, gid }))
+                .catch((error) => context.commit('API_FAILURE', error));
+        });
+    },
+
+    /**
+     * Remove user from group
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {string} options.userid User id
+     * @param {string} options.gid Group id
+     * @returns {Promise}
+     */
+    removeUserGroup(context, { userid, gid }) {
+        return api.requireAdmin().then((response) => {
+            return api.delete(OC.linkToOCS(`cloud/users/${userid}/groups`, 2), { groupid: gid })
+                .then((response) => context.commit('removeUserGroup', { userid, gid }))
+                .catch((error) => context.commit('API_FAILURE', { userid, error }));
+        });
+    },
+
+    /**
+     * Add user to group admin
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {string} options.userid User id
+     * @param {string} options.gid Group id
+     * @returns {Promise}
+     */
+    addUserSubAdmin(context, { userid, gid }) {
+        return api.requireAdmin().then((response) => {
+            return api.post(OC.linkToOCS(`cloud/users/${userid}/subadmins`, 2),  { groupid: gid })
+                .then((response) => context.commit('addUserSubAdmin', { userid, gid }))
+                .catch((error) => context.commit('API_FAILURE', error));
+        });
+    },
+
+    /**
+     * Remove user from group admin
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {string} options.userid User id
+     * @param {string} options.gid Group id
+     * @returns {Promise}
+     */
+    removeUserSubAdmin(context, { userid, gid }) {
+        return api.requireAdmin().then((response) => {
+            return api.delete(OC.linkToOCS(`cloud/users/${userid}/subadmins`, 2), { groupid: gid })
+                .then((response) => context.commit('removeUserSubAdmin', { userid, gid }))
+                .catch((error) => context.commit('API_FAILURE', { userid, error }));
+        });
+    },
+
+    /**
+     * Delete a user
+     * 
+     * @param {Object} context
+     * @param {string} userid User id 
+     * @returns {Promise}
+     */
+    deleteUser(context, userid) {
+        return api.requireAdmin().then((response) => {
+            return api.delete(OC.linkToOCS(`cloud/users/${userid}`, 2))
+                .then((response) => context.commit('deleteUser', userid))
+                .catch((error) => context.commit('API_FAILURE', { userid, error }));
+        });
+    },
+
+    /**
+     * Add a user
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {string} options.userid User id
+     * @param {string} options.password User password 
+     * @param {string} options.email User email
+     * @returns {Promise}
+     */
+    addUser({context, dispatch}, {userid, password, email, groups}) {
+        return api.requireAdmin().then((response) => {
+            return api.post(OC.linkToOCS(`cloud/users`, 2), {userid, password, email, groups})
+                .then((response) => dispatch('addUserData', userid))
+                .catch((error) => context.commit('API_FAILURE', { userid, error }));
+        });
+    },
+
+    /**
+     * Get user data and commit addition
+     * 
+     * @param {Object} context
+     * @param {string} userid User id 
+     * @returns {Promise}
+     */
+    addUserData(context, userid) {
+        return api.requireAdmin().then((response) => {
+            return api.get(OC.linkToOCS(`cloud/users/${userid}`, 2))
+                .then((response) => context.commit('addUserData', response))
+                .catch((error) => context.commit('API_FAILURE', { userid, error }));
+        });
+    },
+
+    /** Enable or disable user 
+     * 
+     * @param {Object} context
+     * @param {Object} options
+     * @param {string} options.userid User id
+     * @param {boolean} options.enabled User enablement status
+     * @returns {Promise}
+     */
+    enableDisableUser(context, { userid, enabled = true }) {
+        let userStatus = enabled ? 'enable' : 'disable';
+        return api.requireAdmin().then((response) => {
+            return api.put(OC.linkToOCS(`cloud/users/${userid}/${userStatus}`, 2))
+                .then((response) => context.commit('enableDisableUser', { userid, enabled }))
+                .catch((error) => context.commit('API_FAILURE', { userid, error }));
+        });
+    },
+
+    /**
+     * Edit user data
+     * 
+     * @param {Object} context 
+     * @param {Object} options
+     * @param {string} options.userid User id
+     * @param {string} options.key User field to edit
+     * @param {string} options.value Value of the change
+     * @returns {Promise}
+     */
+    setUserData(context, { userid, key, value }) {
+        if (['email', 'quota', 'displayname', 'password'].indexOf(key) !== -1) {
+            // We allow empty email or displayname
+            if (typeof value === 'string' &&
+                (
+                    (['quota', 'password'].indexOf(key) !== -1 && value.length > 0) ||
+                    ['email', 'displayname'].indexOf(key) !== -1
+                )
+            ) {
+                return api.requireAdmin().then((response) => {
+                    return api.put(OC.linkToOCS(`cloud/users/${userid}`, 2), { key: key, value: value })
+                        .then((response) => context.commit('setUserData', { userid, key, value }))
+                        .catch((error) => context.commit('API_FAILURE', { userid, error }));
+                });
+            }
+        }
+        return Promise.reject(new Error('Invalid request data'));
+    }
+};
+
+export default { state, mutations, getters, actions };

+ 152 - 0
settings/src/views/Users.vue

@@ -0,0 +1,152 @@
+<template>
+	<div id="app">
+		<app-navigation :menu="menu">
+			<template slot="settings-content">
+				<div>
+					<input type="checkbox" id="showLastLogin" class="checkbox"
+						   :checked="showLastLogin" v-model="showLastLogin">
+					<label for="showLastLogin">{{t('settings', 'Show last login')}}</label>
+				</div>
+				<div>
+					<input type="checkbox" id="showUserBackend" class="checkbox"
+						   :checked="showUserBackend" v-model="showUserBackend">
+					<label for="showUserBackend">{{t('settings', 'Show user backend')}}</label>
+				</div>
+				<div>
+					<input type="checkbox" id="showStoragePath" class="checkbox"
+						   :checked="showStoragePath" v-model="showStoragePath">
+					<label for="showStoragePath">{{t('settings', 'Show storage path')}}</label>
+				</div>
+			</template>
+		</app-navigation>
+		<user-list :users="users" :showConfig="showConfig" />
+	</div>
+</template>
+
+<script>
+import appNavigation from '../components/appNavigation';
+import userList from '../components/userList';
+import Vue from 'vue';
+import VueLocalStorage from 'vue-localstorage'
+Vue.use(VueLocalStorage)
+
+export default {
+	name: 'Users',
+	components: {
+		appNavigation,
+		userList
+	},
+	beforeMount() {
+		this.$store.commit('initGroups', {
+			groups: this.$store.getters.getServerData.groups,
+			orderBy: this.$store.getters.getServerData.sortGroups
+		});
+		this.$store.dispatch('getPasswordPolicyMinLength');
+	},
+	data() {
+		return {
+			showConfig: {
+				showStoragePath: false,
+				showUserBackend: false,
+				showLastLogin: false,
+				showNewUserForm: false
+			}
+		}
+	},
+	methods: {
+		getLocalstorage(key) {
+			// force initialization
+			this.showConfig[key] = this.$localStorage.get(key) === 'true';
+			return this.showConfig[key];
+		},
+		setLocalStorage(key, status) {
+			this.showConfig[key] = status;
+			this.$localStorage.set(key, status);
+			return status;
+		}
+	},
+	computed: {
+		users() {
+			return this.$store.getters.getUsers;
+		},
+		loading() {
+			return Object.keys(this.users).length === 0;
+		},
+		usersOffset() {
+			return this.$store.getters.getUsersOffset;
+		},
+		usersLimit() {
+			return this.$store.getters.getUsersLimit;
+		},
+		showLastLogin: {
+			get: function() {return this.getLocalstorage('showLastLogin')},
+			set: function(status) {
+				this.setLocalStorage('showLastLogin', status);
+			}
+		},
+		showUserBackend: {
+			get: function() {return this.getLocalstorage('showUserBackend')},
+			set: function(status) {
+				this.setLocalStorage('showUserBackend', status);
+			}
+		},
+		showStoragePath: {
+			get: function() {return this.getLocalstorage('showStoragePath')},
+			set: function(status) {
+				this.setLocalStorage('showStoragePath', status);
+			}
+		},
+		menu() {
+			let self = this;
+			// Data provided php side
+			let groups = this.$store.getters.getGroups;
+			groups = Array.isArray(groups) ? groups : [];
+
+			// Map groups
+			groups = groups.map(group => {
+				let item = {};
+				item.id = group.id.replace(' ', '_');
+				item.classes = [];
+				item.href = '#group'+group.id.replace(' ', '_');
+				item.text = group.name;
+				item.utils = {counter: group.usercount};
+				return item;
+			});
+
+			// Adjust data
+			if (groups[0].id === 'admin') {
+				groups[0].text = t('settings', 'Admins');}			// rename admin group
+			if (groups[1].id === '_disabled') {
+				groups[1].text = t('settings', 'Disabled users');	// rename disabled group
+				if (groups[1].utils.counter === 0) {
+					groups.splice(1, 1);							// remove disabled if empty
+				}
+			}
+
+			// Add everyone group
+			groups.unshift({
+				id: '_everyone',
+				classes: ['active'],
+				href:'#group_everyone',
+				text: t('settings', 'Everyone'),
+				utils: {counter: this.users.length}
+			});
+
+			// Return
+			return {
+				id: 'usergrouplist',
+				new: {
+					id:'new-user-button',
+					text: t('settings','New user'),
+					icon: 'icon-add',
+					action: function(){self.showConfig.showNewUserForm=!self.showConfig.showNewUserForm}
+				},
+				items: groups
+			}
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+</style>

+ 26 - 8
settings/templates/settings.php

@@ -1,9 +1,27 @@
-<?php /**
- * Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
- */?>
+<?php
+/**
+ * @copyright Copyright (c) 2018, John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
+ * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This is the default empty template to load Vue!
+ * Do your cbackend computations into a php files
+ * then serve this file as template and include your data into
+ * the $serverData template variable
+ *
+ * $tmpl = new OC_Template('settings', 'settings', 'user');
+ * $tmpl->assign('serverData', $serverData);
+ * $tmpl->printPage();
 
-<?php foreach($_['forms'] as $form) {
-	print_unescaped($form);
-}
+ */
+
+script('settings', 'main');
+style('settings', 'settings');
+
+// Did we have some data to inject ?
+if(is_array($_['serverData'])) {
+	$serverData = json_encode($_['serverData']);
+?>
+<span id="serverData" data-server="<?php p($serverData);?>"></span>
+<?php } ?>

+ 0 - 80
settings/templates/users/main.php

@@ -1,80 +0,0 @@
-<?php
-/**
- * Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
- * Copyright (c) 2017, John Molakvoæ <skjnldsv@protonmail.com>
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
- */
-
-script('settings', [
-	'users/deleteHandler',
-	'users/filter',
-	'users/users',
-	'users/groups'
-]);
-script('core', [
-	'multiselect',
-	'singleselect'
-]);
-style('settings', 'settings');
-
-$userlistParams = array();
-$allGroups=array();
-foreach($_["adminGroup"] as $group) {
-	$allGroups[$group['id']] = array('displayName' => $group['name']);
-}
-foreach($_["groups"] as $group) {
-	$allGroups[$group['id']] = array('displayName' => $group['name']);
-}
-$userlistParams['subadmingroups'] = $allGroups;
-$userlistParams['allGroups'] = json_encode($allGroups);
-$items = array_flip($userlistParams['subadmingroups']);
-unset($items['admin']);
-$userlistParams['subadmingroups'] = array_flip($items);
-
-translation('settings');
-?>
-
-<div id="app-navigation">
-	<?php print_unescaped($this->inc('users/part.createuser')); ?>
-	<?php print_unescaped($this->inc('users/part.grouplist')); ?>
-	<div id="app-settings">
-		<div id="app-settings-header">
-			<button class="settings-button" tabindex="0" data-apps-slide-toggle="#app-settings-content"><?php p($l->t('Settings'));?></button>
-		</div>
-		<div id="app-settings-content">
-			<?php print_unescaped($this->inc('users/part.setquota')); ?>
-
-			<div id="userlistoptions">
-				<p>
-					<input type="checkbox" name="StorageLocation" value="StorageLocation" id="CheckboxStorageLocation"
-						class="checkbox" <?php if ($_['show_storage_location'] === 'true') print_unescaped('checked="checked"'); ?> />
-					<label for="CheckboxStorageLocation">
-						<?php p($l->t('Show storage location')) ?>
-					</label>
-				</p>
-				<p>
-					<input type="checkbox" name="UserBackend" value="UserBackend" id="CheckboxUserBackend"
-						class="checkbox" <?php if ($_['show_backend'] === 'true') print_unescaped('checked="checked"'); ?> />
-					<label for="CheckboxUserBackend">
-						<?php p($l->t('Show user backend')) ?>
-					</label>
-				</p>
-				<p>
-					<input type="checkbox" name="LastLogin" value="LastLogin" id="CheckboxLastLogin"
-						class="checkbox" <?php if ($_['show_last_login'] === 'true') print_unescaped('checked="checked"'); ?> />
-					<label for="CheckboxLastLogin">
-						<?php p($l->t('Show last login')) ?>
-					</label>
-				</p>
-				<p class="info-text">
-					<?php p($l->t('When the password of a new user is left empty, an activation email with a link to set the password is sent.')) ?>
-				</p>
-			</div>
-		</div>
-	</div>
-</div>
-
-<div id="app-content">
-	<?php print_unescaped($this->inc('users/part.userlist', $userlistParams)); ?>
-</div>

+ 0 - 3
settings/templates/users/part.createuser.php

@@ -1,3 +0,0 @@
-<div class="app-navigation-new">
-	<button type="button" id="new-user-button" class="icon-add"><?php p($l->t('Add user'))?></button>
-</div>

+ 0 - 69
settings/templates/users/part.grouplist.php

@@ -1,69 +0,0 @@
-<ul id="usergrouplist" data-sort-groups="<?php p($_['sortGroups']); ?>">
-	<!-- Add new group -->
-	<?php if ($_['isAdmin']) { ?>
-	<li id="newgroup-entry">
-		<a href="#" class="icon-add" id="newgroup-init"><?php p($l->t('Add group'))?></a>
-		<div class="app-navigation-entry-edit" id="newgroup-form">
-			<form>
-				<input type="text" id="newgroupname" placeholder="<?php p($l->t('Add group'))?>">
-				<input type="submit" value="" class="icon-checkmark">
-			</form>
-		</div>
-	</li>
-	<?php } ?>
-	<!-- Everyone -->
-	<li id="everyonegroup" data-gid="_everyone" data-usercount="" class="isgroup">
-		<a href="#">
-			<span class="groupname">
-				<?php p($l->t('Everyone')); ?>
-			</span>
-		</a>
-		<div class="app-navigation-entry-utils">
-			<ul>
-				<li class="usercount app-navigation-entry-utils-counter" id="everyonecount"></li>
-			</ul>
-		</div>
-	</li>
-
-	<!-- The Admin Group -->
-	<?php foreach($_["adminGroup"] as $adminGroup): ?>
-		<li data-gid="admin" data-usercount="<?php if($adminGroup['usercount'] > 0) { p($adminGroup['usercount']); } ?>" class="isgroup">
-			<a href="#"><span class="groupname"><?php p($l->t('Admins')); ?></span></a>
-			<div class="app-navigation-entry-utils">
-				<ul>
-					<li class="app-navigation-entry-utils-counter"><?php if($adminGroup['usercount'] > 0) { p($adminGroup['usercount']); } ?></li>
-				</ul>
-			</div>
-		</li>
-	<?php endforeach; ?>
-
-	<!-- Disabled Users -->
-	<?php $disabledUsersGroup = $_["disabledUsersGroup"] ?>
-	<li data-gid="_disabledUsers" data-usercount="<?php if($disabledUsersGroup['usercount'] > 0) { p($disabledUsersGroup['usercount']); } ?>" class="isgroup">
-		<a href="#"><span class="groupname"><?php p($l->t('Disabled')); ?></span></a>
-		<div class="app-navigation-entry-utils">
-			<ul>
-				<li class="app-navigation-entry-utils-counter"><?php if($disabledUsersGroup['usercount'] > 0) { p($disabledUsersGroup['usercount']); } ?></li>
-			</ul>
-		</div>
-	</li>
-
-	<!--List of Groups-->
-	<?php foreach($_["groups"] as $group): ?>
-		<li data-gid="<?php p($group['id']) ?>" data-usercount="<?php p($group['usercount']) ?>" class="isgroup">
-			<a href="#" class="dorename">
-				<span class="groupname"><?php p($group['name']); ?></span>
-			</a>
-			<div class="app-navigation-entry-utils">
-				<ul>
-					<li class="app-navigation-entry-utils-counter"><?php if($group['usercount'] > 0) { p($group['usercount']); } ?></li>
-				 	<?php if($_['isAdmin']): ?>
-				 		<li class="app-navigation-entry-utils-menu-button delete">
-							<button class="icon-delete"></button>
-						</li>
-					<?php endif; ?>
-				</ul>
-			</div>
-		</li>
-	<?php endforeach; ?>
-</ul>

+ 0 - 35
settings/templates/users/part.setquota.php

@@ -1,35 +0,0 @@
-<div class="quota">
-	<!-- Default storage -->
-	<span><?php p($l->t('Default quota'));?></span>
-	<?php if((bool) $_['isAdmin']): ?>
-		<select id='default_quota' data-inputtitle="<?php p($l->t('Please enter storage quota (ex: "512 MB" or "12 GB")')) ?>" data-tipsy-gravity="s">
-			<option <?php if($_['default_quota'] === 'none') print_unescaped('selected="selected"');?> value='none'>
-				<?php p($l->t('Unlimited'));?>
-			</option>
-			<?php foreach($_['quota_preset'] as $preset):?>
-				<?php if($preset !== 'default'):?>
-					<option <?php if($_['default_quota']==$preset) print_unescaped('selected="selected"');?> value='<?php p($preset);?>'>
-						<?php p($preset);?>
-					</option>
-				<?php endif;?>
-			<?php endforeach;?>
-			<?php if($_['defaultQuotaIsUserDefined']):?>
-				<option selected="selected" value='<?php p($_['default_quota']);?>'>
-					<?php p($_['default_quota']);?>
-				</option>
-			<?php endif;?>
-			<option data-new value='other'>
-				<?php p($l->t('Other'));?>
-				...
-			</option>
-		</select>
-	<?php endif; ?>
-	<?php if((bool) !$_['isAdmin']): ?>
-		:
-		<?php if( $_['default_quota'] === 'none'): ?>
-			<?php p($l->t('Unlimited'));?>
-		<?php else: ?>
-			<?php p($_['default_quota']);?>
-		<?php endif; ?>
-	<?php endif; ?>
-</div>

+ 0 - 149
settings/templates/users/part.userlist.php

@@ -1,149 +0,0 @@
-<form class="newUserMenu" id="newuser" autocomplete="off">
-	<table id="userlist" class="grid" data-groups="<?php p($_['allGroups']);?>">
-		<thead>
-			<tr>
-				<th id="headerAvatar" scope="col"></th>
-				<th id="headerName" scope="col"><?php p($l->t('Username'))?></th>
-				<th id="headerDisplayName" scope="col"><?php p($l->t( 'Full name' )); ?></th>
-				<th id="headerPassword" scope="col"><?php p($l->t( 'Password' )); ?></th>
-				<th class="mailAddress" scope="col"><?php p($l->t( 'Email' )); ?></th>
-				<th id="headerGroups" scope="col"><?php p($l->t( 'Groups' )); ?></th>
-			<?php if(is_array($_['subadmins']) || $_['subadmins']): ?>
-				<th id="headerSubAdmins" scope="col"><?php p($l->t('Group admin for')); ?></th>
-			<?php endif;?>
-			<?php if((bool)$_['recoveryAdminEnabled']): ?>
-				<th id="recoveryPassword" scope="col"><?php p($l->t('Recovery password')); ?></th>
-			<?php endif; ?>
-				<th id="headerQuota" scope="col"><?php p($l->t('Quota')); ?></th>
-				<th class="storageLocation" scope="col"><?php p($l->t('Storage location')); ?></th>
-				<th class="userBackend" scope="col"><?php p($l->t('User backend')); ?></th>
-				<th class="lastLogin" scope="col"><?php p($l->t('Last login')); ?></th>
-				<th class="userActions"></th>
-			</tr>
-			<tr id="newuserHeader" style="display:none">
-				<td class="icon-add"></td>
-				<td class="name">
-					<input id="newusername" type="text" required
-						placeholder="<?php p($l->t('Username'))?>" name="username"
-						autocomplete="off" autocapitalize="none" autocorrect="off" />
-				</td>
-				<td class="displayName">
-					<input id="newdisplayname" type="text"
-						placeholder="<?php p($l->t('Full name'))?>" name="displayname"
-						autocomplete="off" autocapitalize="none" autocorrect="off" />
-				</td>
-				<td class="password">
-					<input id="newuserpassword" type="password"
-						   placeholder="<?php p($l->t('Password'))?>" name="password"
-						   autocomplete="new-password" autocapitalize="none" autocorrect="off" />
-				</td>
-				<td class="mailAddress">
-					<input id="newemail" type="email"
-						   placeholder="<?php p($l->t('E-Mail'))?>" name="email"
-						   autocomplete="off" autocapitalize="none" autocorrect="off" />
-				</td>
-				<td class="groups">
-					<div class="groupsListContainer multiselect button" data-placeholder="<?php p($l->t('Groups'))?>">
-						<span class="title groupsList"></span>
-						<span class="icon-triangle-s"></span>
-					</div>
-				</td>
-			<?php if(is_array($_['subadmins']) || $_['subadmins']): ?>
-				<td></td>
-			<?php endif;?>
-			<?php if((bool)$_['recoveryAdminEnabled']): ?>
-				<td class="recoveryPassword">
-					<input id="recoveryPassword"
-						   type="password"
-						   placeholder="<?php p($l->t('Admin Recovery Password'))?>"
-						   title="<?php p($l->t('Enter the recovery password in order to recover the users files during password change'))?>"
-						   alt="<?php p($l->t('Enter the recovery password in order to recover the users files during password change'))?>"/>
-				</td>
-			<?php endif; ?>
-				<td class="quota"></td>
-				<td class="storageLocation" scope="col"></td>
-				<td class="userBackend" scope="col"></td>
-				<td class="lastLogin" scope="col"></td>
-				<td class="userActions">
-					<input type="submit" id="newsubmit" class="button primary icon-checkmark-white has-tooltip" value="" title="<?php p($l->t('Add user'))?>" />
-					<input type="reset" id="newreset" class="button icon-close has-tooltip" value="" title="<?php p($l->t('Cancel'))?>" />
-				</td>
-			</tr>
-		</thead>
-		<tbody>
-			<!-- the following <tr> is used as a template for the JS part -->
-			<tr style="display:none">
-				<td class="avatar"><div class="avatardiv"></div></td>
-				<td class="name" scope="row"></td>
-				<td class="displayName"><span></span> <img class="action"
-					src="<?php p(image_path('core', 'actions/rename.svg'))?>"
-					alt="<?php p($l->t('change full name'))?>" title="<?php p($l->t('change full name'))?>"/>
-				</td>
-				<td class="password"><span>●●●●●●●</span> <img class="action"
-					src="<?php print_unescaped(image_path('core', 'actions/rename.svg'))?>"
-					alt="<?php p($l->t('set new password'))?>" title="<?php p($l->t('set new password'))?>"/>
-				</td>
-				<td class="mailAddress"><span></span><div class="loading-small hidden"></div> <img class="action"
-					src="<?php p(image_path('core', 'actions/rename.svg'))?>"
-					alt="<?php p($l->t('change email address'))?>" title="<?php p($l->t('change email address'))?>"/>
-				</td>
-				<td class="groups"><div class="groupsListContainer multiselect button"
-					><span class="title groupsList"></span><span class="icon-triangle-s"></span></div>
-				</td>
-			<?php if(is_array($_['subadmins']) || $_['subadmins']): ?>
-				<td class="subadmins"><div class="groupsListContainer multiselect button"
-					><span class="title groupsList"></span><span class="icon-triangle-s"></span></div>
-				</td>
-			<?php endif;?>
-			<?php if((bool)$_['recoveryAdminEnabled']): ?>
-				<td></td>
-			<?php endif; ?>
-				<td class="quota">
-					<select class="quota-user" data-inputtitle="<?php p($l->t('Please enter storage quota (ex: "512 MB" or "12 GB")')) ?>">
-						<option	value='default'>
-							<?php p($l->t('Default'));?>
-						</option>
-						<option value='none'>
-							<?php p($l->t('Unlimited'));?>
-						</option>
-						<?php foreach($_['quota_preset'] as $preset):?>
-							<option value='<?php p($preset);?>'>
-								<?php p($preset);?>
-							</option>
-						<?php endforeach;?>
-						<option value='other' data-new>
-							<?php p($l->t('Other'));?> ...
-						</option>
-					</select>
-					<progress class="quota-user-progress" value="" max="100"></progress>
-				</td>
-				<td class="storageLocation"></td>
-				<td class="userBackend"></td>
-				<td class="lastLogin"></td>
-				<td class="userActions">
-					<div class="toggleUserActions">
-						<a class="action"><span class="icon-more"></span></a>
-						<div class="popovermenu">
-							<ul class="userActionsMenu">
-								<li>
-									<a href="#" class="menuitem action-togglestate permanent" data-action="togglestate"></a>
-								</li>
-								<li>
-									<a href="#" class="menuitem action-remove permanent" data-action="remove">
-										<span class="icon icon-delete"></span>
-										<span><?php p($l->t('Delete')); ?></span>
-									</a>
-								</li>
-							</ul>
-						</div>
-					</div>
-				</td>
-			</tr>
-		</tbody>
-	</table>
-</form>
-
-<div class="emptycontent" style="display:none">
-	<div class="icon-search"></div>
-	<h2></h2>
-</div>

+ 0 - 220
settings/tests/js/users/deleteHandlerSpec.js

@@ -1,220 +0,0 @@
-/**
-* ownCloud
-*
-* @author Vincent Petry
-* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
-*
-* This library is free software; you can redistribute it and/or
-* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
-* License as published by the Free Software Foundation; either
-* version 3 of the License, or any later version.
-*
-* This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
-*
-*/
-
-describe('DeleteHandler tests', function() {
-	var showNotificationSpy;
-	var hideNotificationSpy;
-	var clock;
-	var removeCallback;
-	var markCallback;
-	var undoCallback;
-
-	function init(markCallback, removeCallback, undoCallback) {
-		var handler = new DeleteHandler('dummyendpoint.php', 'paramid', markCallback, removeCallback);
-		handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry', undoCallback);
-		return handler;
-	}
-
-	beforeEach(function() {
-		showNotificationSpy = sinon.spy(OC.Notification, 'showHtml');
-		hideNotificationSpy = sinon.spy(OC.Notification, 'hide');
-		clock = sinon.useFakeTimers();
-		removeCallback = sinon.stub();
-		markCallback = sinon.stub();
-		undoCallback = sinon.stub();
-
-		$('#testArea').append('<div id="notification"></div>');
-	});
-	afterEach(function() {
-		showNotificationSpy.restore();
-		hideNotificationSpy.restore();
-		clock.restore();
-	});
-	it('shows a notification when marking for delete', function() {
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.mark('some_uid');
-
-		expect(showNotificationSpy.calledOnce).toEqual(true);
-		expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_uid entry');
-
-		expect(markCallback.calledOnce).toEqual(true);
-		expect(markCallback.getCall(0).args[0]).toEqual('some_uid');
-		expect(removeCallback.notCalled).toEqual(true);
-		expect(undoCallback.notCalled).toEqual(true);
-
-		expect(fakeServer.requests.length).toEqual(0);
-	});
-	it('deletes first entry and reshows notification on second delete', function() {
-		fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
-			204,
-			{ 'Content-Type': 'application/json' },
-			JSON.stringify({status: 'success'})
-		]);
-		fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_other_uid/, [
-			204,
-			{ 'Content-Type': 'application/json' },
-			JSON.stringify({status: 'success'})
-		]);
-
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.mark('some_uid');
-
-		expect(showNotificationSpy.calledOnce).toEqual(true);
-		expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_uid entry');
-		showNotificationSpy.resetHistory();
-
-		handler.mark('some_other_uid');
-
-		expect(hideNotificationSpy.calledOnce).toEqual(true);
-		expect(showNotificationSpy.calledOnce).toEqual(true);
-		expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_other_uid entry');
-
-		expect(markCallback.calledTwice).toEqual(true);
-		expect(markCallback.getCall(0).args[0]).toEqual('some_uid');
-		expect(markCallback.getCall(1).args[0]).toEqual('some_other_uid');
-		// called only once, because it is called once the second user is deleted
-		expect(removeCallback.calledOnce).toEqual(true);
-		expect(undoCallback.notCalled).toEqual(true);
-
-		// previous one was delete
-		expect(fakeServer.requests.length).toEqual(1);
-		var	request = fakeServer.requests[0];
-		expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
-	});
-	it('automatically deletes after timeout', function() {
-		fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
-			204,
-			{ 'Content-Type': 'application/json' },
-			JSON.stringify({status: 'success'})
-		]);
-
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.mark('some_uid');
-
-		clock.tick(5000);
-		// nothing happens yet
-		expect(fakeServer.requests.length).toEqual(0);
-
-		clock.tick(3000);
-		expect(fakeServer.requests.length).toEqual(1);
-		var	request = fakeServer.requests[0];
-		expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
-	});
-	it('deletes when deleteEntry is called', function() {
-		fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
-			200,
-			{ 'Content-Type': 'application/json' },
-			JSON.stringify({status: 'success'})
-		]);
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.mark('some_uid');
-
-		handler.deleteEntry();
-		expect(fakeServer.requests.length).toEqual(1);
-		var	request = fakeServer.requests[0];
-		expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
-	});
-	it('deletes when deleteEntry is called and escapes', function() {
-		fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
-			200,
-			{ 'Content-Type': 'application/json' },
-			JSON.stringify({status: 'success'})
-		]);
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.mark('some_uid<>/"..\\');
-
-		handler.deleteEntry();
-		expect(fakeServer.requests.length).toEqual(1);
-		var	request = fakeServer.requests[0];
-		expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid%3C%3E%2F%22..%5C');
-	});
-	it('cancels deletion when undo is clicked', function() {
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry <span class="undo">Undo</span>', undoCallback);
-		handler.mark('some_uid');
-		$('#notification .undo').click();
-
-		expect(undoCallback.calledOnce).toEqual(true);
-
-		// timer was cancelled
-		clock.tick(10000);
-		expect(fakeServer.requests.length).toEqual(0);
-	});
-	it('cancels deletion when cancel method is called', function() {
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry <span class="undo">Undo</span>', undoCallback);
-		handler.mark('some_uid');
-		handler.cancel();
-
-		// not sure why, seems to be by design
-		expect(undoCallback.notCalled).toEqual(true);
-
-		// timer was cancelled
-		clock.tick(10000);
-		expect(fakeServer.requests.length).toEqual(0);
-	});
-	it('calls removeCallback after successful server side deletion', function() {
-		fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
-			200,
-			{ 'Content-Type': 'application/json' },
-			JSON.stringify({status: 'success'})
-		]);
-
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.mark('some_uid');
-		handler.deleteEntry();
-
-		expect(fakeServer.requests.length).toEqual(1);
-		var request = fakeServer.requests[0];
-		var query = OC.parseQueryString(request.requestBody);
-
-		expect(removeCallback.calledOnce).toEqual(true);
-		expect(undoCallback.notCalled).toEqual(true);
-		expect(removeCallback.getCall(0).args[0]).toEqual('some_uid');
-	});
-	it('calls undoCallback and shows alert after failed server side deletion', function() {
-		// stub t to avoid extra calls
-		var tStub = sinon.stub(window, 't').returns('text');
-		fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
-			403,
-			{ 'Content-Type': 'application/json' },
-			JSON.stringify({status: 'error', data: {message: 'test error'}})
-		]);
-
-		var alertDialogStub = sinon.stub(OC.dialogs, 'alert');
-		var handler = init(markCallback, removeCallback, undoCallback);
-		handler.mark('some_uid');
-		handler.deleteEntry();
-
-		expect(fakeServer.requests.length).toEqual(1);
-		var request = fakeServer.requests[0];
-		var query = OC.parseQueryString(request.requestBody);
-
-		expect(removeCallback.notCalled).toEqual(true);
-		expect(undoCallback.calledOnce).toEqual(true);
-		expect(undoCallback.getCall(0).args[0]).toEqual('some_uid');
-
-		expect(alertDialogStub.calledOnce);
-
-		alertDialogStub.restore();
-		tStub.restore();
-	});
-});

+ 36 - 42
settings/users.php

@@ -17,6 +17,7 @@
  * @author Stephan Peijnik <speijnik@anexia-it.com>
  * @author Thomas Müller <thomas.mueller@tmit.eu>
  * @author Thomas Pulzer <t.pulzer@kniel.de>
+ * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
  *
  * @license AGPL-3.0
  *
@@ -30,7 +31,7 @@
  * 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/>
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
  *
  */
 
@@ -41,12 +42,10 @@ OC_Util::checkSubAdminUser();
 $userManager = \OC::$server->getUserManager();
 $groupManager = \OC::$server->getGroupManager();
 $appManager = \OC::$server->getAppManager();
-
-// Set the sort option: SORT_USERCOUNT or SORT_GROUPNAME
-$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
-
 $config = \OC::$server->getConfig();
 
+/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
+$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
 if ($config->getSystemValue('sort_groups_by_name', false)) {
 	$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
 } else {
@@ -62,14 +61,16 @@ if ($config->getSystemValue('sort_groups_by_name', false)) {
 	}
 }
 
-$uid = \OC_User::getUser();
-$isAdmin = OC_User::isAdminUser($uid);
+/* ENCRYPTION CONFIG */
+$isEncryptionEnabled = \OC::$server->getEncryptionManager()->isEnabled();
+$useMasterKey = $config->getAppValue('encryption', 'useMasterKey', true);
+// If masterKey enabled, then you can change password. This is to avoid data loss!
+$canChangePassword = ($isEncryptionEnabled && $useMasterKey) || $useMasterKey;
 
-$isDisabled = true;
-$user = $userManager->get($uid);
-if ($user) {
-	$isDisabled = !$user->isEnabled();
-}
+
+/* GROUPS */
+$uid = \OC_User::getUser();
+$isAdmin = \OC_User::isAdminUser($uid);
 
 $groupsInfo = new \OC\Group\MetaData(
 	$uid,
@@ -81,10 +82,7 @@ $groupsInfo = new \OC\Group\MetaData(
 $groupsInfo->setSorting($sortGroupsBy);
 list($adminGroup, $groups) = $groupsInfo->get();
 
-$recoveryAdminEnabled = $appManager->isEnabledForUser('encryption') &&
-					    $config->getAppValue( 'encryption', 'recoveryAdminEnabled', '0');
-
-if($isAdmin) {
+if ($isAdmin) {
 	$subAdmins = \OC::$server->getGroupManager()->getSubAdmin()->getAllSubAdmins();
 	// New class returns IUser[] so convert back
 	$result = [];
@@ -95,7 +93,7 @@ if($isAdmin) {
 		];
 	}
 	$subAdmins = $result;
-}else{
+} else {
 	/* Retrieve group IDs from $groups array, so we can pass that information into OC_Group::displayNamesInGroups() */
 	$gids = array();
 	foreach($groups as $group) {
@@ -108,42 +106,38 @@ if($isAdmin) {
 
 $disabledUsers = $isLDAPUsed ? 0 : $userManager->countDisabledUsers();
 $disabledUsersGroup = [
-	'id' => '_disabledUsers',
-	'name' => '_disabledUsers',
+	'id' => '_disabled',
+	'name' => 'Disabled users',
 	'usercount' => $disabledUsers
 ];
+$allGroups = array_merge_recursive($adminGroup, $groups);
 
-// load preset quotas
+/* QUOTAS PRESETS */
 $quotaPreset=$config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
 $quotaPreset=explode(',', $quotaPreset);
 foreach($quotaPreset as &$preset) {
 	$preset=trim($preset);
 }
 $quotaPreset=array_diff($quotaPreset, array('default', 'none'));
-
 $defaultQuota=$config->getAppValue('files', 'default_quota', 'none');
-$defaultQuotaIsUserDefined=array_search($defaultQuota, $quotaPreset)===false
-	&& array_search($defaultQuota, array('none', 'default'))===false;
 
 \OC::$server->getEventDispatcher()->dispatch('OC\Settings\Users::loadAdditionalScripts');
 
-$tmpl = new OC_Template("settings", "users/main", "user");
-$tmpl->assign('groups', $groups);
-$tmpl->assign('sortGroups', $sortGroupsBy);
-$tmpl->assign('adminGroup', $adminGroup);
-$tmpl->assign('disabledUsersGroup', $disabledUsersGroup);
-$tmpl->assign('isAdmin', (int)$isAdmin);
-$tmpl->assign('subadmins', $subAdmins);
-$tmpl->assign('numofgroups', count($groups) + count($adminGroup));
-$tmpl->assign('quota_preset', $quotaPreset);
-$tmpl->assign('default_quota', $defaultQuota);
-$tmpl->assign('defaultQuotaIsUserDefined', $defaultQuotaIsUserDefined);
-$tmpl->assign('recoveryAdminEnabled', $recoveryAdminEnabled);
-
-$tmpl->assign('show_storage_location', $config->getAppValue('core', 'umgmt_show_storage_location', 'false'));
-$tmpl->assign('show_last_login', $config->getAppValue('core', 'umgmt_show_last_login', 'false'));
-$tmpl->assign('show_email', $config->getAppValue('core', 'umgmt_show_email', 'false'));
-$tmpl->assign('show_backend', $config->getAppValue('core', 'umgmt_show_backend', 'false'));
-$tmpl->assign('send_email', $config->getAppValue('core', 'umgmt_send_email', 'false'));
-
+/* FINAL DATA */
+$serverData = array();
+// groups
+$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
+$serverData['subadmingroups'] = $groups;
+// Various data
+$serverData['subadmins'] = $subAdmins;
+$serverData['sortGroups'] = $sortGroupsBy;
+$serverData['quotaPreset'] = $quotaPreset;
+$serverData['userCount'] = $userManager->countUsers();
+// Settings
+$serverData['defaultQuota'] = $defaultQuota;
+$serverData['canChangePassword'] = $canChangePassword;
+
+// print template + vue + serve data
+$tmpl = new OC_Template('settings', 'settings', 'user');
+$tmpl->assign('serverData', $serverData);
 $tmpl->printPage();

+ 108 - 0
settings/webpack.config.js

@@ -0,0 +1,108 @@
+var path = require('path')
+var webpack = require('webpack')
+
+module.exports = {
+  entry: './src/main.js',
+  output: {
+    path: path.resolve(__dirname, './js'),
+    publicPath: '/dist/',
+    filename: 'main.js'
+  },
+  module: {
+    rules: [
+      {
+        test: /\.css$/,
+        use: [
+          'vue-style-loader',
+          'css-loader'
+        ],
+      },
+      {
+        test: /\.scss$/,
+        use: [
+          'vue-style-loader',
+          'css-loader',
+          'sass-loader'
+        ],
+      },
+      {
+        test: /\.sass$/,
+        use: [
+          'vue-style-loader',
+          'css-loader',
+          'sass-loader?indentedSyntax'
+        ],
+      },
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: {
+          loaders: {
+            // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
+            // the "scss" and "sass" values for the lang attribute to the right configs here.
+            // other preprocessors should work out of the box, no loader config like this necessary.
+            'scss': [
+              'vue-style-loader',
+              'css-loader',
+              'sass-loader'
+            ],
+            'sass': [
+              'vue-style-loader',
+              'css-loader',
+              'sass-loader?indentedSyntax'
+            ]
+          }
+          // other vue-loader options go here
+        }
+      },
+      {
+        test: /\.js$/,
+        loader: 'babel-loader',
+        exclude: /node_modules/
+      },
+      {
+        test: /\.(png|jpg|gif|svg)$/,
+        loader: 'file-loader',
+        options: {
+          name: '[name].[ext]?[hash]'
+        }
+      }
+    ]
+  },
+  resolve: {
+    alias: {
+      'vue$': 'vue/dist/vue.esm.js'
+    },
+    extensions: ['*', '.js', '.vue', '.json']
+  },
+  devServer: {
+    historyApiFallback: true,
+    noInfo: true,
+    overlay: true
+  },
+  performance: {
+    hints: false
+  },
+  devtool: '#eval-source-map'
+}
+
+if (process.env.NODE_ENV === 'production') {
+  module.exports.devtool = '#source-map'
+  // http://vue-loader.vuejs.org/en/workflow/production.html
+  module.exports.plugins = (module.exports.plugins || []).concat([
+    new webpack.DefinePlugin({
+      'process.env': {
+        NODE_ENV: '"production"'
+      }
+    }),
+    new webpack.optimize.UglifyJsPlugin({
+      sourceMap: true,
+      compress: {
+        warnings: false
+      }
+    }),
+    new webpack.LoaderOptionsPlugin({
+      minimize: true
+    })
+  ])
+}

+ 0 - 3022
tests/Settings/Controller/UsersControllerTest.php

@@ -1,3022 +0,0 @@
-<?php
-/**
- * @author Lukas Reschke
- * @copyright 2014-2015 Lukas Reschke lukas@owncloud.com
- *
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
- */
-
-namespace Tests\Settings\Controller;
-
-use OC\Accounts\AccountManager;
-use OC\Group\Group;
-use OC\Group\Manager;
-use OC\Settings\Controller\UsersController;
-use OC\Settings\Mailer\NewUserMailHelper;
-use OC\SubAdmin;
-use OCP\App\IAppManager;
-use OCP\AppFramework\Http;
-use OCP\AppFramework\Http\DataResponse;
-use OCP\BackgroundJob\IJobList;
-use OCP\Files\Config\IUserMountCache;
-use OCP\Encryption\IEncryptionModule;
-use OCP\Encryption\IManager;
-use OCP\IAvatar;
-use OCP\IAvatarManager;
-use OCP\IConfig;
-use OCP\IGroup;
-use OCP\IGroupManager;
-use OCP\IL10N;
-use OCP\ILogger;
-use OCP\IRequest;
-use OCP\IURLGenerator;
-use OCP\IUser;
-use OCP\IUserManager;
-use OCP\IUserSession;
-use OCP\Mail\IEMailTemplate;
-use OCP\Mail\IMailer;
-use OCP\Security\ISecureRandom;
-use OC\User\User;
-use Test\Util\User\Dummy;
-
-/**
- * @group DB
- *
- * @package Tests\Settings\Controller
- */
-class UsersControllerTest extends \Test\TestCase {
-
-	/** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
-	private $groupManager;
-	/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
-	private $userManager;
-	/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
-	private $userSession;
-	/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
-	private $config;
-	/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
-	private $logger;
-	/** @var IMailer|\PHPUnit_Framework_MockObject_MockObject */
-	private $mailer;
-	/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
-	private $urlGenerator;
-	/** @var IAppManager|\PHPUnit_Framework_MockObject_MockObject */
-	private $appManager;
-	/** @var IAvatarManager|\PHPUnit_Framework_MockObject_MockObject */
-	private $avatarManager;
-	/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
-	private $l;
-	/** @var AccountManager | \PHPUnit_Framework_MockObject_MockObject */
-	private $accountManager;
-	/** @var ISecureRandom | \PHPUnit_Framework_MockObject_MockObject  */
-	private $secureRandom;
-	/** @var NewUserMailHelper|\PHPUnit_Framework_MockObject_MockObject */
-	private $newUserMailHelper;
-	/** @var  IJobList | \PHPUnit_Framework_MockObject_MockObject */
-	private $jobList;
-	/** @var \OC\Security\IdentityProof\Manager |\PHPUnit_Framework_MockObject_MockObject  */
-	private $securityManager;
-	/** @var IUserMountCache |\PHPUnit_Framework_MockObject_MockObject */
-	private $userMountCache;
-	/** @var  IManager | \PHPUnit_Framework_MockObject_MockObject */
-	private $encryptionManager;
-	/** @var  IEncryptionModule  | \PHPUnit_Framework_MockObject_MockObject */
-	private $encryptionModule;
-
-	protected function setUp() {
-		parent::setUp();
-
-		$this->groupManager = $this->createMock(Manager::class);
-		$this->userManager = $this->createMock(IUserManager::class);
-		$this->userSession = $this->createMock(IUserSession::class);
-		$this->config = $this->createMock(IConfig::class);
-		$this->logger = $this->createMock(ILogger::class);
-		$this->mailer = $this->createMock(IMailer::class);
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
-		$this->appManager = $this->createMock(IAppManager::class);
-		$this->avatarManager = $this->createMock(IAvatarManager::class);
-		$this->accountManager = $this->createMock(AccountManager::class);
-		$this->secureRandom = $this->createMock(ISecureRandom::class);
-		$this->newUserMailHelper = $this->createMock(NewUserMailHelper::class);
-		$this->securityManager = $this->getMockBuilder(\OC\Security\IdentityProof\Manager::class)->disableOriginalConstructor()->getMock();
-		$this->jobList = $this->createMock(IJobList::class);
-		$this->encryptionManager = $this->createMock(IManager::class);
-		$this->l = $this->createMock(IL10N::class);
-		$this->l->method('t')
-			->will($this->returnCallback(function ($text, $parameters = []) {
-				return vsprintf($text, $parameters);
-			}));
-		$this->userMountCache = $this->createMock(IUserMountCache::class);
-
-		$this->encryptionModule = $this->createMock(IEncryptionModule::class);
-		$this->encryptionManager->expects($this->any())->method('getEncryptionModules')
-			->willReturn(['encryptionModule' => ['callback' => function() { return $this->encryptionModule;}]]);
-
-		/*
-		 * Set default avatar behaviour for whole test suite
-		 */
-
-		$avatarExists = $this->createMock(IAvatar::class);
-		$avatarExists->method('exists')->willReturn(true);
-		$avatarNotExists = $this->createMock(IAvatar::class);
-		$avatarNotExists->method('exists')->willReturn(false);
-		$this->avatarManager->method('getAvatar')
-			->will($this->returnValueMap([
-				['foo', $avatarExists],
-				['bar', $avatarExists],
-				['admin', $avatarNotExists],
-			]));
-	}
-
-	/**
-	 * @param bool $isAdmin
-	 * @return UsersController | \PHPUnit_Framework_MockObject_MockObject
-	 */
-	protected function getController($isAdmin = false, $mockedMethods = []) {
-		if (empty($mockedMethods)) {
-			return new UsersController(
-				'settings',
-				$this->createMock(IRequest::class),
-				$this->userManager,
-				$this->groupManager,
-				$this->userSession,
-				$this->config,
-				$isAdmin,
-				$this->l,
-				$this->logger,
-				$this->mailer,
-				$this->urlGenerator,
-				$this->appManager,
-				$this->avatarManager,
-				$this->accountManager,
-				$this->secureRandom,
-				$this->newUserMailHelper,
-				$this->securityManager,
-				$this->jobList,
-				$this->userMountCache,
-				$this->encryptionManager
-			);
-		} else {
-			return $this->getMockBuilder(UsersController::class)
-				->setConstructorArgs(
-					[
-						'settings',
-						$this->createMock(IRequest::class),
-						$this->userManager,
-						$this->groupManager,
-						$this->userSession,
-						$this->config,
-						$isAdmin,
-						$this->l,
-						$this->logger,
-						$this->mailer,
-						$this->urlGenerator,
-						$this->appManager,
-						$this->avatarManager,
-						$this->accountManager,
-						$this->secureRandom,
-						$this->newUserMailHelper,
-						$this->securityManager,
-						$this->jobList,
-						$this->userMountCache,
-						$this->encryptionManager
-					]
-				)->setMethods($mockedMethods)->getMock();
-		}
-	}
-
-	public function testIndexAdmin() {
-		$controller = $this->getController(true);
-
-		$foo = $this->createMock(User::class);
-		$foo
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$foo
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('M. Foo'));
-		$foo
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('foo@bar.com'));
-		$foo
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue(1024));
-		$foo
-			->method('getLastLogin')
-			->will($this->returnValue(500));
-		$foo
-			->method('getHome')
-			->will($this->returnValue('/home/foo'));
-		$foo
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('OC_User_Database'));
-		$foo->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$admin = $this->createMock(User::class);
-		$admin
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('admin'));
-		$admin
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('S. Admin'));
-		$admin
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('admin@bar.com'));
-		$admin
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue(404));
-		$admin
-			->expects($this->once())
-			->method('getLastLogin')
-			->will($this->returnValue(12));
-		$admin
-			->expects($this->once())
-			->method('getHome')
-			->will($this->returnValue('/home/admin'));
-		$admin
-			->expects($this->once())
-			->method('getBackendClassName')
-			->willReturn(Dummy::class);
-		$admin->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$bar = $this->createMock(User::class);
-		$bar
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('bar'));
-		$bar
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('B. Ar'));
-		$bar
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('bar@dummy.com'));
-		$bar
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue(2323));
-		$bar
-			->method('getLastLogin')
-			->will($this->returnValue(3999));
-		$bar
-			->method('getHome')
-			->will($this->returnValue('/home/bar'));
-		$bar
-			->expects($this->once())
-			->method('getBackendClassName')
-			->willReturn(Dummy::class);
-		$bar->expects($this->at(0))
-			->method('isEnabled')
-			->willReturn(true);
-		$bar->expects($this->at(1))
-			->method('isEnabled')
-			->willReturn(true);
-		$bar->expects($this->at(2))
-			->method('isEnabled')
-			->willReturn(false);
-
-		$this->groupManager
-			->expects($this->once())
-			->method('displayNamesInGroup')
-			->with('gid', 'pattern')
-			->will($this->returnValue(array('foo' => 'M. Foo', 'admin' => 'S. Admin', 'bar' => 'B. Ar')));
-		$this->groupManager
-			->expects($this->exactly(3))
-			->method('getUserGroupNames')
-			->will($this->onConsecutiveCalls(
-				array(
-					'Users' => array('displayName' => 'Users'),
-					'Support' => array('displayName' => 'Support')
-				),
-				array(
-					'admins' => array('displayName' => 'admins'),
-					'Support' => array('displayName' => 'Support')
-				),
-				array(
-					'External Users' => array('displayName' => 'External Users')
-				)
-			));
-		$this->userManager
-			->expects($this->at(0))
-			->method('get')
-			->with('foo')
-			->will($this->returnValue($foo));
-		$this->userManager
-			->expects($this->at(1))
-			->method('get')
-			->with('admin')
-			->will($this->returnValue($admin));
-		$this->userManager
-			->expects($this->at(2))
-			->method('get')
-			->with('bar')
-			->will($this->returnValue($bar));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->expects($this->any())
-			->method('getSubAdminsGroupsName')
-			->with($foo)
-			->will($this->returnValue([]));
-		$subadmin
-			->expects($this->any())
-			->method('getSubAdminsGroupsName')
-			->with($admin)
-			->will($this->returnValue([]));
-		$subadmin
-			->expects($this->any())
-			->method('getSubAdminsGroupsName')
-			->with($bar)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$this->userMountCache
-			->expects($this->once())
-			->method('getUsedSpaceForUsers')
-			->will($this->returnValue(['admin' => 200, 'bar' => 2000, 'foo' => 512]));
-
-		$expectedResponse = new DataResponse(
-			array(
-				0 => array(
-					'name' => 'foo',
-					'displayname' => 'M. Foo',
-					'groups' => array(
-						'Users' => array('displayName' => 'Users'),
-						'Support' => array('displayName' => 'Support')
-					),
-					'subadmin' => array(),
-					'quota' => 1024,
-					'quota_bytes' => 1024.0,
-					'storageLocation' => '/home/foo',
-					'lastLogin' => 500000,
-					'backend' => 'OC_User_Database',
-					'email' => 'foo@bar.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => true,
-					'isEnabled' => true,
-					'size' => 512,
-				),
-				1 => array(
-					'name' => 'admin',
-					'displayname' => 'S. Admin',
-					'groups' => array(
-						'admins' => array('displayName' => 'admins'),
-						'Support' => array('displayName' => 'Support')
-					),
-					'subadmin' => array(),
-					'quota' => 404,
-					'quota_bytes' => 404.0,
-					'storageLocation' => '/home/admin',
-					'lastLogin' => 12000,
-					'backend' => Dummy::class,
-					'email' => 'admin@bar.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => false,
-					'isEnabled' => true,
-					'size' => 200,
-				),
-				2 => array(
-					'name' => 'bar',
-					'displayname' => 'B. Ar',
-					'groups' => array(
-						'External Users' => array('displayName' => 'External Users')
-					),
-					'subadmin' => array(),
-					'quota' => 2323,
-					'quota_bytes' => 2323.0,
-					'storageLocation' => '/home/bar',
-					'lastLogin' => 3999000,
-					'backend' => Dummy::class,
-					'email' => 'bar@dummy.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => true,
-					'isEnabled' => false,
-					'size' => 2000,
-				),
-			)
-		);
-		$response = $controller->index(0, 10, 'gid', 'pattern');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testIndexSubAdmin() {
-		$controller = $this->getController(false);
-
-		$user = $this->createMock(User::class);
-		$this->userSession
-			->expects($this->once())
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$foo = $this->createMock(User::class);
-		$foo
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$foo
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('M. Foo'));
-		$foo
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('foo@bar.com'));
-		$foo
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue('1024'));
-		$foo
-			->method('getLastLogin')
-			->will($this->returnValue(500));
-		$foo
-			->method('getHome')
-			->will($this->returnValue('/home/foo'));
-		$foo
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('OC_User_Database'));
-		$foo->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$admin = $this->createMock(User::class);
-		$admin
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('admin'));
-		$admin
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('S. Admin'));
-		$admin
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('admin@bar.com'));
-		$admin
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue('404'));
-		$admin
-			->expects($this->once())
-			->method('getLastLogin')
-			->will($this->returnValue(12));
-		$admin
-			->expects($this->once())
-			->method('getHome')
-			->will($this->returnValue('/home/admin'));
-		$admin
-			->expects($this->once())
-			->method('getBackendClassName')
-			->willReturn(Dummy::class);
-		$admin->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$bar = $this->createMock(User::class);
-		$bar
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('bar'));
-		$bar
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('B. Ar'));
-		$bar
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('bar@dummy.com'));
-		$bar
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue('2323'));
-		$bar
-			->method('getLastLogin')
-			->will($this->returnValue(3999));
-		$bar
-			->method('getHome')
-			->will($this->returnValue('/home/bar'));
-		$bar
-			->expects($this->once())
-			->method('getBackendClassName')
-			->willReturn(Dummy::class);
-		$bar->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-
-		$this->groupManager
-			->expects($this->at(2))
-			->method('displayNamesInGroup')
-			->with('SubGroup2', 'pattern')
-			->will($this->returnValue(['foo' => 'M. Foo', 'admin' => 'S. Admin']));
-		$this->groupManager
-			->expects($this->at(1))
-			->method('displayNamesInGroup')
-			->with('SubGroup1', 'pattern')
-			->will($this->returnValue(['bar' => 'B. Ar']));
-		$this->groupManager
-			->expects($this->exactly(3))
-			->method('getUserGroupIds')
-			->will($this->onConsecutiveCalls(
-				['admin', 'SubGroup1', 'testGroup'],
-				['SubGroup2', 'SubGroup1'],
-				['SubGroup2', 'Foo']
-			));
-		$this->userManager
-			->expects($this->at(0))
-			->method('get')
-			->with('bar')
-			->will($this->returnValue($bar));
-		$this->userManager
-			->expects($this->at(1))
-			->method('get')
-			->with('foo')
-			->will($this->returnValue($foo));
-		$this->userManager
-			->expects($this->at(2))
-			->method('get')
-			->with('admin')
-			->will($this->returnValue($admin));
-
-		$subgroup1 = $this->getMockBuilder(IGroup::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subgroup1->expects($this->any())
-			->method('getGID')
-			->will($this->returnValue('SubGroup1'));
-		$subgroup2 = $this->getMockBuilder(IGroup::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subgroup2->expects($this->any())
-			->method('getGID')
-			->will($this->returnValue('SubGroup2'));
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->expects($this->at(0))
-			->method('getSubAdminsGroups')
-			->will($this->returnValue([$subgroup1, $subgroup2]));
-		$subadmin
-			->expects($this->any())
-			->method('getSubAdminsGroupsName')
-			->will($this->returnValue([]));
-		$subadmin
-			->expects($this->any())
-			->method('getSubAdminsGroups')
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$this->userMountCache
-			->expects($this->once())
-			->method('getUsedSpaceForUsers')
-			->will($this->returnValue(['admin' => 200, 'bar' => 2000, 'foo' => 512]));
-
-		$expectedResponse = new DataResponse(
-			[
-				0 => [
-					'name' => 'bar',
-					'displayname' => 'B. Ar',
-					'groups' => ['SubGroup1'],
-					'subadmin' => [],
-					'quota' => '2323',
-					'quota_bytes' => 2323.0,
-					'storageLocation' => '/home/bar',
-					'lastLogin' => 3999000,
-					'backend' => Dummy::class,
-					'email' => 'bar@dummy.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => true,
-					'isEnabled' => true,
-					'size' => 2000,
-				],
-				1=> [
-					'name' => 'foo',
-					'displayname' => 'M. Foo',
-					'groups' => ['SubGroup2', 'SubGroup1'],
-					'subadmin' => [],
-					'quota' => '1024',
-					'quota_bytes' => 1024.0,
-					'storageLocation' => '/home/foo',
-					'lastLogin' => 500000,
-					'backend' => 'OC_User_Database',
-					'email' => 'foo@bar.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => true,
-					'isEnabled' => true,
-					'size' => 512,
-				],
-				2 => [
-					'name' => 'admin',
-					'displayname' => 'S. Admin',
-					'groups' => ['SubGroup2'],
-					'subadmin' => [],
-					'quota' => '404',
-					'quota_bytes' => 404.0,
-					'storageLocation' => '/home/admin',
-					'lastLogin' => 12000,
-					'backend' => Dummy::class,
-					'email' => 'admin@bar.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => false,
-					'isEnabled' => true,
-					'size' => 200,
-				],
-			]
-		);
-
-		$response = $controller->index(0, 10, '', 'pattern');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	/**
-	 * TODO: Since the function uses the static OC_Subadmin class it can't be mocked
-	 * to test for subadmins. Thus the test always assumes you have admin permissions...
-	 */
-	public function testIndexWithSearch() {
-		$controller = $this->getController(true);
-
-		$foo = $this->createMock(User::class);
-		$foo
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$foo
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('M. Foo'));
-		$foo
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('foo@bar.com'));
-		$foo
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue('1024'));
-		$foo
-			->method('getLastLogin')
-			->will($this->returnValue(500));
-		$foo
-			->method('getHome')
-			->will($this->returnValue('/home/foo'));
-		$foo
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('OC_User_Database'));
-		$foo->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$admin = $this->createMock(User::class);
-		$admin
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('admin'));
-		$admin
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('S. Admin'));
-		$admin
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('admin@bar.com'));
-		$admin
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue('404'));
-		$admin
-			->expects($this->once())
-			->method('getLastLogin')
-			->will($this->returnValue(12));
-		$admin
-			->expects($this->once())
-			->method('getHome')
-			->will($this->returnValue('/home/admin'));
-		$admin
-			->expects($this->once())
-			->method('getBackendClassName')
-			->willReturn(Dummy::class);
-		$admin->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$bar = $this->createMock(User::class);
-		$bar
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('bar'));
-		$bar
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('B. Ar'));
-		$bar
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue('bar@dummy.com'));
-		$bar
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue('2323'));
-		$bar
-			->method('getLastLogin')
-			->will($this->returnValue(3999));
-		$bar
-			->method('getHome')
-			->will($this->returnValue('/home/bar'));
-		$bar
-			->expects($this->once())
-			->method('getBackendClassName')
-			->willReturn(Dummy::class);
-		$bar->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-
-		$this->userManager
-			->expects($this->once())
-			->method('search')
-			->with('pattern', 10, 0)
-			->will($this->returnValue([$foo, $admin, $bar]));
-		$this->groupManager
-			->expects($this->exactly(3))
-			->method('getUserGroupNames')
-			->will($this->onConsecutiveCalls(
-				array(
-					'Users' => array('displayName' => 'Users'),
-					'Support' => array('displayName' => 'Support')
-				),
-				array(
-					'admins' => array('displayName' => 'admins'),
-					'Support' => array('displayName' => 'Support')
-				),
-				array(
-					'External Users' => array('displayName' => 'External Users')
-				)
-			));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->any())
-			->method('getSubAdminsGroupsName')
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$this->userMountCache
-			->expects($this->once())
-			->method('getUsedSpaceForUsers')
-			->will($this->returnValue(['admin' => 200, 'bar' => 2000, 'foo' => 512]));
-
-		$expectedResponse = new DataResponse(
-			array(
-				0 => array(
-					'name' => 'foo',
-					'displayname' => 'M. Foo',
-					'groups' => array(
-						'Users' => array('displayName' => 'Users'),
-						'Support' => array('displayName' => 'Support')
-					),
-					'subadmin' => array(),
-					'quota' => 1024,
-					'quota_bytes' => 1024,
-					'storageLocation' => '/home/foo',
-					'lastLogin' => 500000,
-					'backend' => 'OC_User_Database',
-					'email' => 'foo@bar.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => true,
-					'isEnabled' => true,
-					'size' => 512,
-				),
-				1 => array(
-					'name' => 'admin',
-					'displayname' => 'S. Admin',
-					'groups' => array(
-						'admins' => array('displayName' => 'admins'),
-						'Support' => array('displayName' => 'Support')
-					),
-					'subadmin' => array(),
-					'quota' => 404,
-					'quota_bytes' => 404,
-					'storageLocation' => '/home/admin',
-					'lastLogin' => 12000,
-					'backend' => Dummy::class,
-					'email' => 'admin@bar.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => false,
-					'isEnabled' => true,
-					'size' => 200,
-				),
-				2 => array(
-					'name' => 'bar',
-					'displayname' => 'B. Ar',
-					'groups' => array(
-						'External Users' => array('displayName' => 'External Users')
-					),
-					'subadmin' => array(),
-					'quota' => 2323,
-					'quota_bytes' => 2323,
-					'storageLocation' => '/home/bar',
-					'lastLogin' => 3999000,
-					'backend' => Dummy::class,
-					'email' => 'bar@dummy.com',
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => true,
-					'isEnabled' => true,
-					'size' => 2000,
-				),
-			)
-		);
-		$response = $controller->index(0, 10, '', 'pattern');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testIndexWithBackend() {
-		$controller = $this->getController(true);
-
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->exactly(2))
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$user
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue('M. Foo'));
-		$user
-			->expects($this->once())
-			->method('getEMailAddress')
-			->will($this->returnValue(null));
-		$user
-			->expects($this->exactly(2))
-			->method('getQuota')
-			->will($this->returnValue('none'));
-		$user
-			->method('getLastLogin')
-			->will($this->returnValue(500));
-		$user
-			->method('getHome')
-			->will($this->returnValue('/home/foo'));
-		$user
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('OC_User_Database'));
-		$user->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-
-		$this->userManager
-			->expects($this->once())
-			->method('getBackends')
-			->will($this->returnValue([new Dummy(), new \OC\User\Database()]));
-		$this->userManager
-			->expects($this->once())
-			->method('clearBackends');
-		$this->userManager
-			->expects($this->once())
-			->method('search')
-			->with('')
-			->will($this->returnValue([$user]));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$this->userMountCache
-			->expects($this->once())
-			->method('getUsedSpaceForUsers')
-			->will($this->returnValue(['foo' => 512]));
-
-		$expectedResponse = new DataResponse(
-			array(
-				0 => array(
-					'name' => 'foo',
-					'displayname' => 'M. Foo',
-					'groups' => null,
-					'subadmin' => array(),
-					'quota' => 'none',
-					'quota_bytes' => 0,
-					'storageLocation' => '/home/foo',
-					'lastLogin' => 500000,
-					'backend' => 'OC_User_Database',
-					'email' => null,
-					'isRestoreDisabled' => false,
-					'isAvatarAvailable' => true,
-					'isEnabled' => true,
-					'size' => 512,
-				)
-			)
-		);
-		$response = $controller->index(0, 10, '','', Dummy::class);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testIndexWithBackendNoUser() {
-		$controller = $this->getController(true);
-
-		$this->userManager
-			->expects($this->once())
-			->method('getBackends')
-			->will($this->returnValue([new Dummy(), new \OC\User\Database()]));
-		$this->userManager
-			->expects($this->once())
-			->method('search')
-			->with('')
-			->will($this->returnValue([]));
-
-		$this->userMountCache
-			->expects($this->once())
-			->method('getUsedSpaceForUsers')
-			->will($this->returnValue([]));
-
-		$expectedResponse = new DataResponse([]);
-		$response = $controller->index(0, 10, '','', Dummy::class);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testCreateSuccessfulWithoutGroupAdmin() {
-		$controller = $this->getController(true);
-
-		$user = $this->createMock(User::class);
-		$user
-			->method('getHome')
-			->will($this->returnValue('/home/user'));
-		$user
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$user
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('bar'));
-		$user->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-
-		$this->userManager
-			->expects($this->once())
-			->method('createUser')
-			->will($this->onConsecutiveCalls($user));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->expects($this->any())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$expectedResponse = new DataResponse(
-			array(
-				'name' => 'foo',
-				'groups' => null,
-				'storageLocation' => '/home/user',
-				'backend' => 'bar',
-				'lastLogin' => null,
-				'displayname' => null,
-				'quota' => null,
-				'subadmin' => array(),
-				'email' => null,
-				'isRestoreDisabled' => false,
-				'isAvatarAvailable' => true,
-				'isEnabled' => true,
-				'quota_bytes' => false,
-			),
-			Http::STATUS_CREATED
-		);
-		$response = $controller->create('foo', 'password', array());
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testCreateSuccessfulWithGroupAdmin() {
-		$controller = $this->getController(true);
-
-		$user = $this->createMock(User::class);
-		$user
-			->method('getHome')
-			->will($this->returnValue('/home/user'));
-		$user
-			->method('getHome')
-			->will($this->returnValue('/home/user'));
-		$user
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$user
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('bar'));
-		$user->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$existingGroup = $this->getMockBuilder(IGroup::class)
-			->disableOriginalConstructor()->getMock();
-		$existingGroup
-			->expects($this->once())
-			->method('addUser')
-			->with($user);
-		$newGroup = $this->getMockBuilder(IGroup::class)
-			->disableOriginalConstructor()->getMock();
-		$newGroup
-			->expects($this->once())
-			->method('addUser')
-			->with($user);
-
-		$this->userManager
-			->expects($this->once())
-			->method('createUser')
-			->will($this->onConsecutiveCalls($user));
-		$this->groupManager
-			->expects($this->exactly(2))
-			->method('get')
-			->will($this->onConsecutiveCalls(null, $existingGroup));
-		$this->groupManager
-			->expects($this->once())
-			->method('createGroup')
-			->with('NewGroup')
-			->will($this->onConsecutiveCalls($newGroup));
-		$this->groupManager
-			->expects($this->once())
-			->method('getUserGroupNames')
-			->with($user)
-			->will($this->onConsecutiveCalls(
-				array(
-					'NewGroup' => array('displayName' => 'NewGroup'),
-					'ExistingGroup' => array('displayName' => 'ExistingGroup')
-				)
-			));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$expectedResponse = new DataResponse(
-			array(
-				'name' => 'foo',
-				'groups' => array(
-					'NewGroup' => array('displayName' => 'NewGroup'),
-					'ExistingGroup' => array('displayName' => 'ExistingGroup')
-				),
-				'storageLocation' => '/home/user',
-				'backend' => 'bar',
-				'lastLogin' => null,
-				'displayname' => null,
-				'quota' => null,
-				'subadmin' => array(),
-				'email' => null,
-				'isRestoreDisabled' => false,
-				'isAvatarAvailable' => true,
-				'isEnabled' => true,
-				'quota_bytes' => false,
-			),
-			Http::STATUS_CREATED
-		);
-		$response = $controller->create('foo', 'password', array('NewGroup', 'ExistingGroup'));
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testCreateSuccessfulWithGroupSubAdmin() {
-		$controller = $this->getController(false);
-		$user = $this->createMock(IUser::class);
-		$this->userSession
-			->expects($this->once())
-			->method('getUser')
-			->will($this->returnValue($user));
-		$newUser = $this->createMock(IUser::class);
-		$newUser
-			->method('getHome')
-			->will($this->returnValue('/home/user'));
-		$newUser
-			->method('getHome')
-			->will($this->returnValue('/home/user'));
-		$newUser
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$newUser
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('bar'));
-		$subGroup1 = $this->createMock(IGroup::class);
-		$newUser->expects($this->any())
-			->method('isEnabled')
-			->willReturn(true);
-		$subGroup1
-			->expects($this->any())
-			->method('getGID')
-			->will($this->returnValue('SubGroup1'));
-		$subGroup1
-			->expects($this->once())
-			->method('addUser')
-			->with($user);
-		$this->userManager
-			->expects($this->once())
-			->method('createUser')
-			->will($this->returnValue($newUser));
-		$this->groupManager
-			->expects($this->once())
-			->method('getUserGroupNames')
-			->with($user)
-			->will($this->onConsecutiveCalls(array('SubGroup1' =>
-				array('displayName' => 'SubGroup1')
-			)));
-		$this->groupManager
-			->expects($this->once())
-			->method('getUserGroupNames')
-			->with($newUser)
-			->will($this->onConsecutiveCalls(['SubGroup1']));
-
-		$subadmin = $this->createMock(\OC\SubAdmin::class);
-		$subadmin->expects($this->atLeastOnce())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->willReturnMap([
-				[$user, [$subGroup1]],
-				[$newUser, []],
-			]);
-		$subadmin->expects($this->atLeastOnce())
-			->method('isSubAdminofGroup')
-			->willReturnMap([
-				[$user, $subGroup1, true],
-			]);
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-		$this->groupManager->expects($this->atLeastOnce())
-			->method('get')
-			->willReturnMap([
-				['SubGroup1', $subGroup1],
-			]);
-
-		$expectedResponse = new DataResponse(
-			array(
-				'name' => 'foo',
-				'groups' => array('SubGroup1' => array('displayName' => 'SubGroup1')),
-				'storageLocation' => '/home/user',
-				'backend' => 'bar',
-				'lastLogin' => 0,
-				'displayname' => null,
-				'quota' => null,
-				'subadmin' => [],
-				'email' => null,
-				'isRestoreDisabled' => false,
-				'isAvatarAvailable' => true,
-				'isEnabled' => true,
-				'quota_bytes' => false,
-			),
-			Http::STATUS_CREATED
-		);
-		$response = $controller->create('foo', 'password', ['SubGroup1', 'ExistingGroup']);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testCreateUnsuccessfulAdmin() {
-		$controller = $this->getController(true);
-
-		$this->userManager
-			->method('createUser')
-			->will($this->throwException(new \Exception()));
-
-		$expectedResponse = new DataResponse(
-			array(
-				'message' => 'Unable to create user.'
-			),
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->create('foo', 'password', array());
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testCreateUnsuccessfulSubAdminNoGroup() {
-		$controller = $this->getController(false);
-		$user = $this->createMock(IUser::class);
-		$user->expects($this->any())
-			->method('getUID')
-			->will($this->returnValue('username'));
-		$this->userSession->expects($this->once())
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$this->userManager->expects($this->never())
-			->method('createUser');
-
-		$expectedResponse = new DataResponse(
-			[
-				'message' => 'No valid group selected'
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->create('foo', 'password', []);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testCreateUnsuccessfulSubAdmin() {
-		$controller = $this->getController(false);
-		$user = $this->createMock(IUser::class);
-		$user->expects($this->any())
-			->method('getUID')
-			->will($this->returnValue('username'));
-		$this->userSession->expects($this->once())
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$this->userManager
-			->method('createUser')
-			->will($this->throwException(new \Exception()));
-
-		$subgroup1 = $this->createMock(IGroup::class);
-		$subgroup2 = $this->createMock(IGroup::class);
-		$subadmin = $this->createMock(\OC\SubAdmin::class);
-		$subadmin->expects($this->atLeastOnce())
-			->method('isSubAdminofGroup')
-			->willReturnMap([
-				[$user, $subgroup1, true],
-				[$user, $subgroup2, true],
-			]);
-		$this->groupManager->expects($this->any())
-			->method('getSubAdmin')
-			->willReturn($subadmin);
-		$this->groupManager->expects($this->atLeastOnce())
-			->method('get')
-			->willReturnMap([
-				['SubGroup1', $subgroup1],
-				['SubGroup2', $subgroup2],
-			]);
-
-		$expectedResponse = new DataResponse(
-			[
-				'message' => 'Unable to create user.'
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->create('foo', 'password', array('SubGroup1', 'SubGroup2'));
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDestroySelfAdmin() {
-		$controller = $this->getController(true);
-
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('myself'));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$expectedResponse = new DataResponse(
-			array(
-				'status' => 'error',
-				'data' => array(
-					'message' => 'Unable to delete user.'
-				)
-			),
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->destroy('myself');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDestroySelfSubadmin() {
-		$controller = $this->getController(false);
-
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('myself'));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$expectedResponse = new DataResponse(
-			array(
-				'status' => 'error',
-				'data' => array(
-					'message' => 'Unable to delete user.'
-				)
-			),
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->destroy('myself');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDestroyAdmin() {
-		$controller = $this->getController(true);
-
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('Admin'));
-		$toDeleteUser = $this->createMock(User::class);
-		$toDeleteUser
-			->expects($this->once())
-			->method('delete')
-			->will($this->returnValue(true));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->method('get')
-			->with('UserToDelete')
-			->will($this->returnValue($toDeleteUser));
-
-		$expectedResponse = new DataResponse(
-			array(
-				'status' => 'success',
-				'data' => array(
-					'username' => 'UserToDelete'
-				)
-			),
-			Http::STATUS_NO_CONTENT
-		);
-		$response = $controller->destroy('UserToDelete');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDestroySubAdmin() {
-		$controller = $this->getController(false);
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('myself'));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$user = $this->createMock(User::class);
-		$toDeleteUser = $this->createMock(User::class);
-		$toDeleteUser
-			->expects($this->once())
-			->method('delete')
-			->will($this->returnValue(true));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->method('get')
-			->with('UserToDelete')
-			->will($this->returnValue($toDeleteUser));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('isUserAccessible')
-			->with($user, $toDeleteUser)
-			->will($this->returnValue(true));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'success',
-				'data' => [
-					'username' => 'UserToDelete'
-				]
-			],
-			Http::STATUS_NO_CONTENT
-		);
-		$response = $controller->destroy('UserToDelete');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDestroyUnsuccessfulAdmin() {
-		$controller = $this->getController(true);
-
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('Admin'));
-		$toDeleteUser = $this->createMock(User::class);
-		$toDeleteUser
-			->expects($this->once())
-			->method('delete')
-			->will($this->returnValue(false));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->method('get')
-			->with('UserToDelete')
-			->will($this->returnValue($toDeleteUser));
-
-		$expectedResponse = new DataResponse(
-			array(
-				'status' => 'error',
-				'data' => array(
-					'message' => 'Unable to delete user.'
-				)
-			),
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->destroy('UserToDelete');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDestroyUnsuccessfulSubAdmin() {
-		$controller = $this->getController(false);
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('myself'));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$toDeleteUser = $this->createMock(User::class);
-		$toDeleteUser
-			->expects($this->once())
-			->method('delete')
-			->will($this->returnValue(false));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->method('get')
-			->with('UserToDelete')
-			->will($this->returnValue($toDeleteUser));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('isUserAccessible')
-			->with($user, $toDeleteUser)
-			->will($this->returnValue(true));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Unable to delete user.'
-				]
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->destroy('UserToDelete');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDestroyNotAccessibleToSubAdmin() {
-		$controller = $this->getController(false);
-
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('myself'));
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$toDeleteUser = $this->createMock(User::class);
-		$this->userSession
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->method('get')
-			->with('UserToDelete')
-			->will($this->returnValue($toDeleteUser));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('isUserAccessible')
-			->with($user, $toDeleteUser)
-			->will($this->returnValue(false));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Authentication error'
-				]
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $controller->destroy('UserToDelete');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	/**
-	 * test if an invalid mail result in a failure response
-	 */
-	public function testCreateUnsuccessfulWithInvalidEmailAdmin() {
-		$controller = $this->getController(true);
-
-		$expectedResponse = new DataResponse([
-				'message' => 'Invalid mail address',
-			],
-			Http::STATUS_UNPROCESSABLE_ENTITY
-		);
-		$response = $controller->create('foo', 'password', [], 'invalidMailAdress');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	/**
-	 * test if a valid mail result in a successful mail send
-	 */
-	public function testCreateSuccessfulWithValidEmailAdmin() {
-		$controller = $this->getController(true);
-		$this->mailer
-			->expects($this->at(0))
-			->method('validateMailAddress')
-			->with('validMail@Adre.ss')
-			->will($this->returnValue(true));
-
-		$user = $this->createMock(User::class);
-		$user
-			->method('getHome')
-			->will($this->returnValue('/home/user'));
-		$user
-			->method('getHome')
-			->will($this->returnValue('/home/user'));
-		$user
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$user
-			->method('getDisplayName')
-			->will($this->returnValue('foo'));
-		$user
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue('bar'));
-
-		$emailTemplate = $this->createMock(IEMailTemplate::class);
-		$this->newUserMailHelper
-			->expects($this->at(0))
-			->method('generateTemplate')
-			->with($user, false)
-			->willReturn($emailTemplate);
-		$this->newUserMailHelper
-			->expects($this->at(1))
-			->method('sendMail')
-			->with($user, $emailTemplate);
-
-		$this->userManager
-			->expects($this->once())
-			->method('createUser')
-			->will($this->onConsecutiveCalls($user));
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$response = $controller->create('foo', 'password', [], 'validMail@Adre.ss');
-		$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
-	}
-
-	private function mockUser($userId = 'foo', $displayName = 'M. Foo',
-							  $lastLogin = 500, $home = '/home/foo',
-							  $backend = 'OC_User_Database', $enabled = true) {
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->any())
-			->method('getUID')
-			->will($this->returnValue($userId));
-		$user
-			->expects($this->once())
-			->method('getDisplayName')
-			->will($this->returnValue($displayName));
-		$user
-			->method('getLastLogin')
-			->will($this->returnValue($lastLogin));
-		$user
-			->method('getHome')
-			->will($this->returnValue($home));
-		$user
-			->expects($this->once())
-			->method('getBackendClassName')
-			->will($this->returnValue($backend));
-		$user->expects($this->any())
-			->method('isEnabled')
-			->willReturn($enabled);
-
-		$result = [
-			'name' => $userId,
-			'displayname' => $displayName,
-			'groups' => null,
-			'subadmin' => array(),
-			'quota' => null,
-			'storageLocation' => $home,
-			'lastLogin' => $lastLogin * 1000,
-			'backend' => $backend,
-			'email' => null,
-			'isRestoreDisabled' => false,
-			'isAvatarAvailable' => true,
-			'isEnabled' => $enabled,
-			'quota_bytes' => false,
-		];
-
-		return [$user, $result];
-	}
-
-	public function testRestorePossibleWithoutEncryption() {
-		$controller = $this->getController(true);
-
-		list($user, $expectedResult) = $this->mockUser();
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$result = self::invokePrivate($controller, 'formatUserForIndex', [$user]);
-		$this->assertEquals($expectedResult, $result);
-	}
-
-	public function testRestorePossibleWithAdminAndUserRestore() {
-		list($user, $expectedResult) = $this->mockUser();
-
-		$this->appManager
-			->expects($this->once())
-			->method('isEnabledForUser')
-			->with(
-				$this->equalTo('encryption')
-			)
-			->will($this->returnValue(true));
-		$this->config
-			->expects($this->once())
-			->method('getAppValue')
-			->with(
-				$this->equalTo('encryption'),
-				$this->equalTo('recoveryAdminEnabled'),
-				$this->anything()
-			)
-			->will($this->returnValue('1'));
-
-		$this->config
-			->expects($this->at(1))
-			->method('getUserValue')
-			->with(
-				$this->anything(),
-				$this->equalTo('encryption'),
-				$this->equalTo('recoveryEnabled'),
-				$this->anything()
-			)
-			->will($this->returnValue('1'));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$controller = $this->getController(true);
-		$result = self::invokePrivate($controller, 'formatUserForIndex', [$user]);
-		$this->assertEquals($expectedResult, $result);
-	}
-
-	/**
-	 * @dataProvider dataTestRestoreNotPossibleWithoutAdminRestore
-	 *
-	 * @param bool $masterKeyEnabled
-	 */
-	public function testRestoreNotPossibleWithoutAdminRestore($masterKeyEnabled) {
-		list($user, $expectedResult) = $this->mockUser();
-
-		// without the master key enabled we use per-user keys
-		$this->encryptionModule->expects($this->once())->method('needDetailedAccessList')->willReturn(!$masterKeyEnabled);
-
-		$this->appManager
-			->method('isEnabledForUser')
-			->with(
-				$this->equalTo('encryption')
-			)
-			->will($this->returnValue(true));
-
-		// without the master key enabled we use per-user keys -> restore is disabled
-		$expectedResult['isRestoreDisabled'] = !$masterKeyEnabled;
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$controller = $this->getController(true);
-		$result = self::invokePrivate($controller, 'formatUserForIndex', [$user]);
-		$this->assertEquals($expectedResult, $result);
-	}
-
-	public function dataTestRestoreNotPossibleWithoutAdminRestore() {
-		return [
-			[true],
-			[false]
-		];
-	}
-
-	public function testRestoreNotPossibleWithoutUserRestore() {
-		list($user, $expectedResult) = $this->mockUser();
-
-		$this->appManager
-			->expects($this->once())
-			->method('isEnabledForUser')
-			->with(
-				$this->equalTo('encryption')
-			)
-			->will($this->returnValue(true));
-		$this->config
-			->expects($this->once())
-			->method('getAppValue')
-			->with(
-				$this->equalTo('encryption'),
-				$this->equalTo('recoveryAdminEnabled'),
-				$this->anything()
-			)
-			->will($this->returnValue('1'));
-
-		$this->config
-			->expects($this->at(1))
-			->method('getUserValue')
-			->with(
-				$this->anything(),
-				$this->equalTo('encryption'),
-				$this->equalTo('recoveryEnabled'),
-				$this->anything()
-			)
-			->will($this->returnValue('0'));
-
-		$expectedResult['isRestoreDisabled'] = true;
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$controller = $this->getController(true);
-		$result = self::invokePrivate($controller, 'formatUserForIndex', [$user]);
-		$this->assertEquals($expectedResult, $result);
-	}
-
-	public function testNoAvatar() {
-		$controller = $this->getController(true);
-
-		list($user, $expectedResult) = $this->mockUser();
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin->expects($this->once())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$this->avatarManager
-			->method('getAvatar')
-			->will($this->throwException(new \OCP\Files\NotFoundException()));
-		$expectedResult['isAvatarAvailable'] = false;
-
-		$result = self::invokePrivate($controller, 'formatUserForIndex', [$user]);
-		$this->assertEquals($expectedResult, $result);
-	}
-
-	public function testStatsAdmin() {
-		$controller = $this->getController(true);
-
-		$this->userManager
-			->expects($this->at(0))
-			->method('countUsers')
-			->will($this->returnValue([128, 44]));
-
-		$expectedResponse = new DataResponse(
-			[
-				'totalUsers' => 172
-			]
-		);
-		$response = $controller->stats();
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	/**
-	 * Tests that the subadmin stats return unique users, even
-	 * when a user appears in several groups.
-	 */
-	public function testStatsSubAdmin() {
-		$controller = $this->getController(false);
-
-		$user = $this->createMock(User::class);
-
-		$this->userSession
-			->expects($this->once())
-			->method('getUser')
-			->will($this->returnValue($user));
-
-		$group1 = $this->getMockBuilder(Group::class)
-			->disableOriginalConstructor()->getMock();
-		$group1
-			->expects($this->once())
-			->method('getUsers')
-			->will($this->returnValue(['foo' => 'M. Foo', 'admin' => 'S. Admin']));
-
-		$group2 = $this->getMockBuilder(Group::class)
-			->disableOriginalConstructor()->getMock();
-		$group2
-			->expects($this->once())
-			->method('getUsers')
-			->will($this->returnValue(['bar' => 'B. Ar']));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->expects($this->at(0))
-			->method('getSubAdminsGroupsName')
-			->will($this->returnValue([$group1, $group2]));
-		$subadmin
-			->expects($this->at(0))
-			->method('getSubAdminsGroups')
-			->will($this->returnValue([$group1, $group2]));
-
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$expectedResponse = new DataResponse(
-			[
-				'totalUsers' => 3
-			]
-		);
-
-		$response = $controller->stats();
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function dataSetDisplayName() {
-		$data = [];
-
-		$user1 = $this->createMock(IUser::class);
-		$user1->method('getUID')->willReturn('user1');
-		$user1->method('canChangeDisplayName')->willReturn(true);
-		$data[] = [$user1, $user1, false, false, true];
-
-		$user1 = $this->createMock(IUser::class);
-		$user1->method('getUID')->willReturn('user1');
-		$user1->method('canChangeDisplayName')->willReturn(false);
-		$data[] = [$user1, $user1, false, false, false];
-
-		$user1 = $this->createMock(IUser::class);
-		$user1->method('getUID')->willReturn('user1');
-		$user2 = $this->createMock(IUser::class);
-		$user2->method('getUID')->willReturn('user2');
-		$user2->method('canChangeDisplayName')->willReturn(true);
-		$data[] = [$user1, $user2, false, false, false];
-
-		$user1 = $this->createMock(IUser::class);
-		$user1->method('getUID')->willReturn('user1');
-		$user2 = $this->createMock(IUser::class);
-		$user2->method('getUID')->willReturn('user2');
-		$user2->method('canChangeDisplayName')->willReturn(true);
-		$data[] = [$user1, $user2, true, false, true];
-
-		$user1 = $this->createMock(IUser::class);
-		$user1->method('getUID')->willReturn('user1');
-		$user2 = $this->createMock(IUser::class);
-		$user2->method('getUID')->willReturn('user2');
-		$user2->method('canChangeDisplayName')->willReturn(true);
-		$data[] = [$user1, $user2, false, true, true];
-
-		return $data;
-	}
-
-	/**
-	 * @dataProvider dataSetDisplayName
-	 *
-	 * @param IUser|\PHPUnit_Framework_MockObject_MockObject $currentUser
-	 * @param IUser|\PHPUnit_Framework_MockObject_MockObject $editUser
-	 * @param bool $isAdmin
-	 * @param bool $isSubAdmin
-	 * @param bool $valid
-	 */
-	public function testSetDisplayName($currentUser, $editUser, $isAdmin, $isSubAdmin, $valid) {
-		$this->userSession
-			->expects($this->once())
-			->method('getUser')
-			->willReturn($currentUser);
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with($editUser->getUID())
-			->willReturn($editUser);
-		$this->accountManager->expects($this->any())->method('getUser')->willReturn([]);
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->method('isUserAccessible')
-			->with($currentUser, $editUser)
-			->willReturn($isSubAdmin);
-
-		$this->groupManager
-			->method('getSubAdmin')
-			->willReturn($subadmin);
-		$this->groupManager
-			->method('isAdmin')
-			->with($currentUser->getUID())
-			->willReturn($isAdmin);
-
-		if ($valid === true) {
-			$expectedResponse = new DataResponse(
-				[
-					'status' => 'success',
-					'data' => [
-						'message' => 'Your full name has been changed.',
-						'username' => $editUser->getUID(),
-						'displayName' => 'newDisplayName',
-					],
-				]
-			);
-		} else {
-			$expectedResponse = new DataResponse(
-				[
-					'status' => 'error',
-					'data' => [
-						'message' => 'Authentication error',
-					],
-				]
-				);
-			}
-
-		$controller = $this->getController(true);
-		$response = $controller->setDisplayName($editUser->getUID(), 'newDisplayName');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testSetDisplayNameFails() {
-		/** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
-		$user = $this->createMock(IUser::class);
-		$user->method('canChangeDisplayname')->willReturn(true);
-		$user->method('getUID')->willReturn('user');
-		$user->expects($this->once())
-			->method('setDisplayName')
-			->with('newDisplayName')
-			->willReturn(false);
-		$user->method('getDisplayName')->willReturn('oldDisplayName');
-
-		$this->userSession
-			->expects($this->once())
-			->method('getUser')
-			->willReturn($user);
-
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with($user->getUID())
-			->willReturn($user);
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->method('isUserAccessible')
-			->with($user, $user)
-			->willReturn(false);
-
-		$this->groupManager
-			->method('getSubAdmin')
-			->willReturn($subadmin);
-		$this->groupManager
-			->expects($this->once())
-			->method('isAdmin')
-			->with($user->getUID())
-			->willReturn(false);
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Unable to change full name',
-					'displayName' => 'oldDisplayName',
-				],
-			]
-		);
-		$controller = $this->getController(true);
-		$response = $controller->setDisplayName($user->getUID(), 'newDisplayName');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	/**
-	 * @dataProvider dataTestSetUserSettings
-	 *
-	 * @param string $email
-	 * @param bool $validEmail
-	 * @param $expectedStatus
-	 */
-	public function testSetUserSettings($email, $validEmail, $expectedStatus) {
-		$controller = $this->getController(false, ['saveUserSettings']);
-		$user = $this->createMock(IUser::class);
-
-		$this->userSession->method('getUser')->willReturn($user);
-
-		if (!empty($email) && $validEmail) {
-			$this->mailer->expects($this->once())->method('validateMailAddress')
-				->willReturn($validEmail);
-		}
-
-		$saveData = (!empty($email) && $validEmail) || empty($email);
-
-		if ($saveData) {
-			$this->accountManager->expects($this->once())
-				->method('getUser')
-				->with($user)
-				->willReturn([
-					AccountManager::PROPERTY_DISPLAYNAME =>
-						[
-							'value' => 'Display name',
-							'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
-							'verified' => AccountManager::NOT_VERIFIED,
-						],
-					AccountManager::PROPERTY_ADDRESS =>
-						[
-							'value' => '',
-							'scope' => AccountManager::VISIBILITY_PRIVATE,
-							'verified' => AccountManager::NOT_VERIFIED,
-						],
-					AccountManager::PROPERTY_WEBSITE =>
-						[
-							'value' => '',
-							'scope' => AccountManager::VISIBILITY_PRIVATE,
-							'verified' => AccountManager::NOT_VERIFIED,
-						],
-					AccountManager::PROPERTY_EMAIL =>
-						[
-							'value' => '',
-							'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
-							'verified' => AccountManager::NOT_VERIFIED,
-						],
-					AccountManager::PROPERTY_AVATAR =>
-						[
-							'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY
-						],
-					AccountManager::PROPERTY_PHONE =>
-						[
-							'value' => '',
-							'scope' => AccountManager::VISIBILITY_PRIVATE,
-							'verified' => AccountManager::NOT_VERIFIED,
-						],
-					AccountManager::PROPERTY_TWITTER =>
-						[
-							'value' => '',
-							'scope' => AccountManager::VISIBILITY_PRIVATE,
-							'verified' => AccountManager::NOT_VERIFIED,
-						],
-				]);
-
-			$controller->expects($this->once())->method('saveUserSettings');
-		} else {
-			$controller->expects($this->never())->method('saveUserSettings');
-		}
-
-		$result = $controller->setUserSettings(
-			AccountManager::VISIBILITY_CONTACTS_ONLY,
-			'displayName',
-			AccountManager::VISIBILITY_CONTACTS_ONLY,
-			'47658468',
-			AccountManager::VISIBILITY_CONTACTS_ONLY,
-			$email,
-			AccountManager::VISIBILITY_CONTACTS_ONLY,
-			'nextcloud.com',
-			AccountManager::VISIBILITY_CONTACTS_ONLY,
-			'street and city',
-			AccountManager::VISIBILITY_CONTACTS_ONLY,
-			'@nextclouders',
-			AccountManager::VISIBILITY_CONTACTS_ONLY
-		);
-
-		$this->assertSame($expectedStatus, $result->getStatus());
-	}
-
-	public function dataTestSetUserSettings() {
-		return [
-			['', true, Http::STATUS_OK],
-			['', false, Http::STATUS_OK],
-			['example.com', false, Http::STATUS_UNPROCESSABLE_ENTITY],
-			['john@example.com', true, Http::STATUS_OK],
-		];
-	}
-
-	/**
-	 * @dataProvider dataTestSaveUserSettings
-	 *
-	 * @param array $data
-	 * @param string $oldEmailAddress
-	 * @param string $oldDisplayName
-	 */
-	public function testSaveUserSettings($data,
-										 $oldEmailAddress,
-										 $oldDisplayName
-	) {
-		$controller = $this->getController();
-		$user = $this->createMock(IUser::class);
-
-		$user->method('getDisplayName')->willReturn($oldDisplayName);
-		$user->method('getEMailAddress')->willReturn($oldEmailAddress);
-		$user->method('canChangeDisplayName')->willReturn(true);
-
-		if ($data[AccountManager::PROPERTY_EMAIL]['value'] === $oldEmailAddress ||
-			($oldEmailAddress === null && $data[AccountManager::PROPERTY_EMAIL]['value'] === '')) {
-			$user->expects($this->never())->method('setEMailAddress');
-		} else {
-			$user->expects($this->once())->method('setEMailAddress')
-				->with($data[AccountManager::PROPERTY_EMAIL]['value'])
-				->willReturn(true);
-		}
-
-		if ($data[AccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName ||
-			($oldDisplayName === null && $data[AccountManager::PROPERTY_DISPLAYNAME]['value'] === '')) {
-			$user->expects($this->never())->method('setDisplayName');
-		} else {
-			$user->expects($this->once())->method('setDisplayName')
-				->with($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
-				->willReturn(true);
-		}
-
-		$this->accountManager->expects($this->once())->method('updateUser')
-			->with($user, $data);
-
-		$this->invokePrivate($controller, 'saveUserSettings', [$user, $data]);
-	}
-
-	public function dataTestSaveUserSettings() {
-		return [
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'john@example.com',
-				'john doe'
-			],
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'johnNew@example.com',
-				'john New doe'
-			],
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'johnNew@example.com',
-				'john doe'
-			],
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'john@example.com',
-				'john New doe'
-			],
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => ''],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				null,
-				'john New doe'
-			],
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'john@example.com',
-				null
-			],
-
-		];
-	}
-
-	/**
-	 * @dataProvider dataTestSaveUserSettingsException
-	 *
-	 * @param array $data
-	 * @param string $oldEmailAddress
-	 * @param string $oldDisplayName
-	 * @param bool $setDisplayNameResult
-	 * @param bool $canChangeEmail
-	 *
-	 * @expectedException \OC\ForbiddenException
-	 */
-	public function testSaveUserSettingsException($data,
-												  $oldEmailAddress,
-												  $oldDisplayName,
-												  $setDisplayNameResult,
-												  $canChangeEmail
-	) {
-		$controller = $this->getController();
-		$user = $this->createMock(IUser::class);
-
-		$user->method('getDisplayName')->willReturn($oldDisplayName);
-		$user->method('getEMailAddress')->willReturn($oldEmailAddress);
-
-		if ($data[AccountManager::PROPERTY_EMAIL]['value'] !== $oldEmailAddress) {
-			$user->method('canChangeDisplayName')
-				->willReturn($canChangeEmail);
-		}
-
-		if ($data[AccountManager::PROPERTY_DISPLAYNAME]['value'] !== $oldDisplayName) {
-			$user->method('setDisplayName')
-				->with($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
-				->willReturn($setDisplayNameResult);
-		}
-
-		$this->invokePrivate($controller, 'saveUserSettings', [$user, $data]);
-	}
-
-
-	public function dataTestSaveUserSettingsException() {
-		return [
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'johnNew@example.com',
-				'john New doe',
-				true,
-				false
-			],
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'johnNew@example.com',
-				'john New doe',
-				false,
-				true
-			],
-			[
-				[
-					AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
-					AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
-				],
-				'johnNew@example.com',
-				'john New doe',
-				false,
-				false
-			],
-
-		];
-	}
-
-	/**
-	 * @return array
-	 */
-	public function setEmailAddressData() {
-		return [
-			/* mailAddress,    isValid, expectsUpdate, canChangeDisplayName, responseCode */
-			[ '',              true,    true,          true,                 Http::STATUS_OK ],
-			[ 'foo@local',     true,    true,          true,                 Http::STATUS_OK],
-			[ 'foo@bar@local', false,   false,         true,                 Http::STATUS_UNPROCESSABLE_ENTITY],
-			[ 'foo@local',     true,    false,         false,                Http::STATUS_FORBIDDEN],
-		];
-	}
-	/**
-	 * @dataProvider setEmailAddressData
-	 *
-	 * @param string $mailAddress
-	 * @param bool $isValid
-	 * @param bool $expectsUpdate
-	 * @param bool $canChangeDisplayName
-	 * @param int $responseCode
-	 */
-	public function testSetEMailAddress($mailAddress, $isValid, $expectsUpdate, $canChangeDisplayName, $responseCode) {
-		$user = $this->createMock(User::class);
-		$user
-			->expects($this->any())
-			->method('getUID')
-			->will($this->returnValue('foo'));
-		$user
-			->expects($this->any())
-			->method('canChangeDisplayName')
-			->will($this->returnValue($canChangeDisplayName));
-		$user
-			->expects($expectsUpdate ? $this->once() : $this->never())
-			->method('setEMailAddress')
-			->with(
-				$this->equalTo($mailAddress)
-			);
-		$user->method('getEMailAddress')->willReturn('oldEmailAddress');
-		$this->mailer
-			->expects($this->any())
-			->method('validateMailAddress')
-			->with($mailAddress)
-			->willReturn($isValid);
-		if ($isValid) {
-			$user->expects($this->atLeastOnce())
-				->method('canChangeDisplayName')
-				->willReturn(true);
-			$this->userManager
-				->expects($this->atLeastOnce())
-				->method('get')
-				->with('foo')
-				->will($this->returnValue($user));
-		}
-		$controller = $this->getController(true);
-		$response = $controller->setEMailAddress($user->getUID(), $mailAddress);
-		$this->assertSame($responseCode, $response->getStatus());
-	}
-
-	public function testCreateUnsuccessfulWithoutPasswordAndEmail() {
-		$controller = $this->getController(true);
-
-		$expectedResponse = new DataResponse(
-			array(
-				'message' => 'To send a password link to the user an email address is required.'
-			),
-			Http::STATUS_UNPROCESSABLE_ENTITY
-		);
-		$response = $controller->create('foo', '', array(), '');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-
-
-	public function testCreateSuccessfulWithoutPasswordAndWithEmail() {
-		$user = $this->createMock(User::class);
-		$user
-			->method('getHome')
-			->willReturn('/home/user');
-		$user
-			->method('getUID')
-			->willReturn('foo');
-		$user
-			->method('getDisplayName')
-			->willReturn('John Doe');
-		$user
-			->method('getEmailAddress')
-			->willReturn('abc@example.org');
-		$user
-			->expects($this->once())
-			->method('getBackendClassName')
-			->willReturn('bar');
-		$user
-			->method('isEnabled')
-			->willReturn(true);
-
-		$this->userManager
-			->expects($this->once())
-			->method('createUser')
-			->will($this->onConsecutiveCalls($user));
-
-		$subadmin = $this->getMockBuilder(SubAdmin::class)
-			->disableOriginalConstructor()
-			->getMock();
-		$subadmin
-			->expects($this->any())
-			->method('getSubAdminsGroupsName')
-			->with($user)
-			->will($this->returnValue([]));
-		$this->groupManager
-			->expects($this->any())
-			->method('getSubAdmin')
-			->will($this->returnValue($subadmin));
-
-		$controller = $this->getController(true);
-		$this->mailer
-			->expects($this->at(0))
-			->method('validateMailAddress')
-			->with('abc@example.org')
-			->will($this->returnValue(true));
-		$emailTemplate = $this->createMock(IEMailTemplate::class);
-		$this->newUserMailHelper
-			->expects($this->at(0))
-			->method('generateTemplate')
-			->with($user, true)
-			->willReturn($emailTemplate);
-		$this->newUserMailHelper
-			->expects($this->at(1))
-			->method('sendMail')
-			->with($user, $emailTemplate);
-
-		$expectedResponse = new DataResponse(
-			[
-				'name' => 'foo',
-				'groups' => null,
-				'storageLocation' => '/home/user',
-				'backend' => 'bar',
-				'lastLogin' => 0,
-				'displayname' => 'John Doe',
-				'quota' => null,
-				'quota_bytes' => false,
-				'subadmin' => array(),
-				'email' => 'abc@example.org',
-				'isRestoreDisabled' => false,
-				'isAvatarAvailable' => true,
-				'isEnabled' => true,
-			],
-			Http::STATUS_CREATED
-		);
-		$response = $controller->create('foo', '', array(), 'abc@example.org');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	/**
-	 * @param string $account
-	 * @param string $type
-	 * @param array $dataBefore
-	 * @param array $expectedData
-	 *
-	 * @dataProvider dataTestGetVerificationCode
-	 */
-	public function testGetVerificationCode($account, $type, $dataBefore, $expectedData, $onlyVerificationCode) {
-
-		$message = 'Use my Federated Cloud ID to share with me: user@nextcloud.com';
-		$signature = 'theSignature';
-
-		$code = $message . ' ' . $signature;
-		if($type === AccountManager::PROPERTY_TWITTER) {
-			$code = $message . ' ' . md5($signature);
-		}
-
-		$controller = $this->getController(false, ['signMessage', 'getCurrentTime']);
-
-		$user = $this->createMock(IUser::class);
-		$this->userSession->expects($this->once())->method('getUser')->willReturn($user);
-		$this->accountManager->expects($this->once())->method('getUser')->with($user)->willReturn($dataBefore);
-		$user->expects($this->any())->method('getCloudId')->willReturn('user@nextcloud.com');
-		$user->expects($this->any())->method('getUID')->willReturn('uid');
-		$controller->expects($this->once())->method('signMessage')->with($user, $message)->willReturn($signature);
-		$controller->expects($this->any())->method('getCurrentTime')->willReturn(1234567);
-
-		if ($onlyVerificationCode === false) {
-			$this->accountManager->expects($this->once())->method('updateUser')->with($user, $expectedData);
-			$this->jobList->expects($this->once())->method('add')
-				->with('OC\Settings\BackgroundJobs\VerifyUserData',
-					[
-						'verificationCode' => $code,
-						'data' => $dataBefore[$type]['value'],
-						'type' => $type,
-						'uid' => 'uid',
-						'try' => 0,
-						'lastRun' => 1234567
-					]);
-		}
-
-		$result = $controller->getVerificationCode($account, $onlyVerificationCode);
-
-		$data = $result->getData();
-		$this->assertSame(Http::STATUS_OK, $result->getStatus());
-		$this->assertSame($code, $data['code']);
-	}
-
-	public function dataTestGetVerificationCode() {
-
-		$accountDataBefore = [
-			AccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::NOT_VERIFIED],
-			AccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
-		];
-
-		$accountDataAfterWebsite = [
-			AccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
-			AccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
-		];
-
-		$accountDataAfterTwitter = [
-			AccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::NOT_VERIFIED],
-			AccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
-		];
-
-		return [
-			['verify-twitter', AccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, false],
-			['verify-website', AccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, false],
-			['verify-twitter', AccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, true],
-			['verify-website', AccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, true],
-		];
-	}
-
-	/**
-	 * test get verification code in case no valid user was given
-	 */
-	public function testGetVerificationCodeInvalidUser() {
-
-		$controller = $this->getController();
-		$this->userSession->expects($this->once())->method('getUser')->willReturn(null);
-		$result = $controller->getVerificationCode('account', false);
-
-		$this->assertSame(Http::STATUS_BAD_REQUEST, $result->getStatus());
-	}
-
-	public function testDisableUserFailsDueSameUser() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('abc'));
-		$this->userSession
-			->expects($this->once())
-			->method('getUser')
-			->will($this->returnValue($user));
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Error while disabling user.',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(true)->setEnabled('abc', false);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDisableUserFailsDueNoAdminAndNoSubadmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(2))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$user2 = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user2->expects($this->never())
-			->method('setEnabled');
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn($user2);
-
-		$subadmin = $this->createMock(SubAdmin::class);
-		$subadmin->expects($this->once())
-			->method('isUserAccessible')
-			->will($this->returnValue(false));
-		$this->groupManager
-			->expects($this->once())
-			->method('getSubAdmin')
-			->willReturn($subadmin);
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Authentication error',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(false)->setEnabled('abc', false);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDisableUserFailsDueNoUser() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(1))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn(null);
-
-		$this->groupManager
-			->expects($this->never())
-			->method('getSubAdmin');
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Error while disabling user.',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(true)->setEnabled('abc', false);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDisableUserFailsDueNoUserForSubAdmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(1))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn(null);
-
-		$this->groupManager
-			->expects($this->never())
-			->method('getSubAdmin');
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Error while disabling user.',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(false)->setEnabled('abc', false);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDisableUserSuccessForAdmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(1))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$user2 = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user2->expects($this->once())
-			->method('setEnabled')
-			->with(false);
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn($user2);
-
-		$this->groupManager
-			->expects($this->never())
-			->method('getSubAdmin');
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'success',
-				'data' => [
-					'username' => 'abc',
-					'enabled' => 0,
-				],
-			]
-		);
-		$response = $this->getController(true)->setEnabled('abc', false);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testDisableUserSuccessForSubAdmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(2))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$user2 = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user2->expects($this->once())
-			->method('setEnabled');
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn($user2);
-
-		$subadmin = $this->createMock(SubAdmin::class);
-		$subadmin->expects($this->once())
-			->method('isUserAccessible')
-			->will($this->returnValue(true));
-		$this->groupManager
-			->expects($this->once())
-			->method('getSubAdmin')
-			->willReturn($subadmin);
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'success',
-				'data' => [
-					'username' => 'abc',
-					'enabled' => 0,
-				],
-			]
-		);
-		$response = $this->getController(false)->setEnabled('abc', false);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testEnableUserFailsDueSameUser() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('abc'));
-		$this->userSession
-			->expects($this->once())
-			->method('getUser')
-			->will($this->returnValue($user));
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Error while enabling user.',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(true)->setEnabled('abc', true);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testEnableUserFailsDueNoAdminAndNoSubadmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(2))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$user2 = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user2->expects($this->never())
-			->method('setEnabled');
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn($user2);
-
-		$subadmin = $this->createMock(SubAdmin::class);
-		$subadmin->expects($this->once())
-			->method('isUserAccessible')
-			->will($this->returnValue(false));
-		$this->groupManager
-			->expects($this->once())
-			->method('getSubAdmin')
-			->willReturn($subadmin);
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Authentication error',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(false)->setEnabled('abc', true);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testEnableUserFailsDueNoUser() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(1))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn(null);
-
-		$this->groupManager
-			->expects($this->never())
-			->method('getSubAdmin');
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Error while enabling user.',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(true)->setEnabled('abc', true);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testEnableUserFailsDueNoUserForSubAdmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(1))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn(null);
-
-		$this->groupManager
-			->expects($this->never())
-			->method('getSubAdmin');
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'error',
-				'data' => [
-					'message' => 'Error while enabling user.',
-				],
-			],
-			Http::STATUS_FORBIDDEN
-		);
-		$response = $this->getController(false)->setEnabled('abc', true);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testEnableUserSuccessForAdmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(1))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$user2 = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user2->expects($this->once())
-			->method('setEnabled');
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn($user2);
-
-		$this->groupManager
-			->expects($this->never())
-			->method('getSubAdmin');
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'success',
-				'data' => [
-					'username' => 'abc',
-					'enabled' => 1,
-				],
-			]
-		);
-		$response = $this->getController(true)->setEnabled('abc', true);
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testEnableUserSuccessForSubAdmin() {
-		$user = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user->expects($this->once())
-			->method('getUID')
-			->will($this->returnValue('def'));
-		$this->userSession
-			->expects($this->exactly(2))
-			->method('getUser')
-			->will($this->returnValue($user));
-		$user2 = $this->getMockBuilder(User::class)
-			->disableOriginalConstructor()->getMock();
-		$user2->expects($this->once())
-			->method('setEnabled')
-			->with(true);
-		$this->userManager
-			->expects($this->once())
-			->method('get')
-			->with('abc')
-			->willReturn($user2);
-
-		$subadmin = $this->createMock(SubAdmin::class);
-		$subadmin->expects($this->once())
-			->method('isUserAccessible')
-			->will($this->returnValue(true));
-		$this->groupManager
-			->expects($this->once())
-			->method('getSubAdmin')
-			->willReturn($subadmin);
-
-		$expectedResponse = new DataResponse(
-			[
-				'status' => 'success',
-				'data' => [
-					'username' => 'abc',
-					'enabled' => 1,
-				],
-			]
-		);
-		$response = $this->getController(false)->setEnabled('abc', true);
-		$this->assertEquals($expectedResponse, $response);
-	}
-}

Some files were not shown because too many files changed in this diff