diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 08aa20e37..3d5a29c06 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: [8.2] + php-versions: [8.3] nextcloud-versions: ['stable32', 'master'] include: - php: 8.2 diff --git a/composer.json b/composer.json index e479b701a..9b012e3a4 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require-dev": { "phpunit/phpunit": "^9.5", "nextcloud/coding-standard": "^1.1", - "nextcloud/ocp": "^30.0", + "nextcloud/ocp": "> 30.0 < 33.0", "symfony/console": "^6.3", "mockery/mockery": "^1.6", "nextcloud/openapi-extractor": "^1.0" diff --git a/composer.lock b/composer.lock index cea32ca93..cbedaca7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c582bfdee5f081d8f02d004beead9d0b", + "content-hash": "f65c97f4b302e12d0a7e707880b9d078", "packages": [], "packages-dev": [ { @@ -203,16 +203,16 @@ }, { "name": "kubawerlos/php-cs-fixer-custom-fixers", - "version": "v3.36.0", + "version": "v3.37.0", "source": { "type": "git", "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", - "reference": "e1f97f6463f0b2a22e0dd320948a04132ff9c501" + "reference": "c31fb2aa359dcb25fb48cc6f600810ad284343be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/e1f97f6463f0b2a22e0dd320948a04132ff9c501", - "reference": "e1f97f6463f0b2a22e0dd320948a04132ff9c501", + "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/c31fb2aa359dcb25fb48cc6f600810ad284343be", + "reference": "c31fb2aa359dcb25fb48cc6f600810ad284343be", "shasum": "" }, "require": { @@ -243,7 +243,7 @@ "description": "A set of custom fixers for PHP CS Fixer", "support": { "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", - "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.36.0" + "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.37.0" }, "funding": [ { @@ -251,7 +251,7 @@ "type": "github" } ], - "time": "2026-01-31T07:02:11+00:00" + "time": "2026-04-16T16:49:13+00:00" }, { "name": "mockery/mockery", @@ -443,29 +443,29 @@ }, { "name": "nextcloud/ocp", - "version": "v30.0.9", + "version": "v32.0.8", "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "d6000d61d8ae708199bd90e1da0c794712c81030" + "reference": "9eff94dcc966d95c1f1621cad35f0d83160b42eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/d6000d61d8ae708199bd90e1da0c794712c81030", - "reference": "d6000d61d8ae708199bd90e1da0c794712c81030", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/9eff94dcc966d95c1f1621cad35f0d83160b42eb", + "reference": "9eff94dcc966d95c1f1621cad35f0d83160b42eb", "shasum": "" }, "require": { - "php": "~8.0 || ~8.1 || ~8.2 || ~8.3", + "php": "~8.1 || ~8.2 || ~8.3 || ~8.4", "psr/clock": "^1.0", "psr/container": "^2.0.2", "psr/event-dispatcher": "^1.0", - "psr/log": "^2.0.0" + "psr/log": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-stable30": "30.0.0-dev" + "dev-stable32": "32.0.0-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -476,14 +476,18 @@ { "name": "Christoph Wurst", "email": "christoph@winzerhof-wurst.at" + }, + { + "name": "Joas Schilling", + "email": "coding@schilljs.com" } ], - "description": "Composer package containing Nextcloud's public API (classes, interfaces)", + "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", "support": { "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/v30.0.9" + "source": "https://github.com/nextcloud-deps/ocp/tree/v32.0.8" }, - "time": "2025-03-31T13:55:31+00:00" + "time": "2026-03-27T01:17:35+00:00" }, { "name": "nextcloud/openapi-extractor", @@ -710,16 +714,16 @@ }, { "name": "php-cs-fixer/shim", - "version": "v3.94.2", + "version": "v3.95.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "80fd29f44a736136a2f05bae5464816a444b91d1" + "reference": "f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/80fd29f44a736136a2f05bae5464816a444b91d1", - "reference": "80fd29f44a736136a2f05bae5464816a444b91d1", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a", + "reference": "f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a", "shasum": "" }, "require": { @@ -756,9 +760,9 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.94.2" + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.1" }, - "time": "2026-02-20T16:14:17+00:00" + "time": "2026-04-12T17:00:34+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -1390,16 +1394,16 @@ }, { "name": "psr/log", - "version": "2.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -1408,7 +1412,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { @@ -1434,9 +1438,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/2.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:41:46+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "sebastian/cli-parser", @@ -2451,16 +2455,16 @@ }, { "name": "symfony/console", - "version": "v6.4.34", + "version": "v6.4.36", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7b1f1c37eff5910ddda2831345467e593a5120ad" + "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7b1f1c37eff5910ddda2831345467e593a5120ad", - "reference": "7b1f1c37eff5910ddda2831345467e593a5120ad", + "url": "https://api.github.com/repos/symfony/console/zipball/9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", + "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", "shasum": "" }, "require": { @@ -2525,7 +2529,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.34" + "source": "https://github.com/symfony/console/tree/v6.4.36" }, "funding": [ { @@ -2545,7 +2549,7 @@ "type": "tidelift" } ], - "time": "2026-02-23T15:42:15+00:00" + "time": "2026-03-27T15:30:51+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2616,16 +2620,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -2675,7 +2679,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0" }, "funding": [ { @@ -2695,20 +2699,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df", "shasum": "" }, "require": { @@ -2757,7 +2761,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0" }, "funding": [ { @@ -2777,11 +2781,11 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -2842,7 +2846,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0" }, "funding": [ { @@ -2866,16 +2870,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", "shasum": "" }, "require": { @@ -2927,7 +2931,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0" }, "funding": [ { @@ -2947,7 +2951,7 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { "name": "symfony/service-contracts", @@ -3038,16 +3042,16 @@ }, { "name": "symfony/string", - "version": "v7.4.6", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "reference": "114ac57257d75df748eda23dd003878080b8e688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/string/zipball/114ac57257d75df748eda23dd003878080b8e688", + "reference": "114ac57257d75df748eda23dd003878080b8e688", "shasum": "" }, "require": { @@ -3105,7 +3109,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.6" + "source": "https://github.com/symfony/string/tree/v7.4.8" }, "funding": [ { @@ -3125,7 +3129,7 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "theseer/tokenizer", diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 89564152e..3f8bd6f64 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -39,6 +39,7 @@ use OCA\Workspace\Middleware\WorkspaceManagerAccessMiddleware; use OCA\Workspace\Service\SpaceService; use OCA\Workspace\Service\UserService; +use OCA\Workspace\User\Backend\UserBackend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -47,6 +48,7 @@ use OCP\AppFramework\Utility\IControllerMethodReflector; use OCP\IGroupManager; use OCP\IRequest; +use OCP\IUserManager; class Application extends App implements IBootstrap { public const APP_ID = 'workspace'; @@ -98,6 +100,10 @@ public function register(IRegistrationContext $context): void { $groupBackend = $container->get(GroupBackend::class); $groupManager->addBackend($groupBackend); } + + $userManager = $container->get(IUserManager::class); + $userBackend = $container->get(UserBackend::class); + $userManager->registerBackend($userBackend); } public function boot(IBootContext $context): void { diff --git a/lib/Group/GroupBackend.php b/lib/Group/GroupBackend.php index 9ee1e763a..2b92010f8 100644 --- a/lib/Group/GroupBackend.php +++ b/lib/Group/GroupBackend.php @@ -82,11 +82,21 @@ public function getUserGroups($uid) { } else { $groupIds = []; } + + $userGroups = []; + if (str_starts_with($uid, 'SPACE-UWS-')) { + $spaceId = (int)substr($uid, 10); + if ($spaceId !== 0) { + $userGroups[] = "SPACE-U-{$spaceId}"; + /// @TODO is it necessary ? + // $groupIds[] = "SPACE-GE-{$spaceId}"; + } + } + $this->avoidRecurse_groups = $avoid; if (empty($groupIds)) { - return []; + return $userGroups; } - $userGroups = []; foreach ($groupIds as $gid) { $connectedGids = $this->connectedGroups->getConnectedSpaceToGroupIds($gid); if ($connectedGids !== null && $user->isEnabled()) { @@ -118,6 +128,9 @@ public function getGroups($search = '', $limit = -1, $offset = 0) { */ public function groupExists($gid) { // @note : need to implement, but this backend doesn't manage existence of connected groups + if (str_starts_with($gid, 'SPACE-U-') && !$this->avoidRecurse_groups) { + return true; + } return $this->connectedGroups->hasConnectedGroups($gid); } @@ -134,12 +147,19 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { return []; } + $users = []; + if (str_starts_with($gid, 'SPACE-U-')) { + $spaceId = (int)substr($gid, 8); + if ($spaceId !== 0) { + $users[] = "SPACE-UWS-{$spaceId}"; + } + } + $groups = $this->connectedGroups->getConnectedGroupsToSpaceGroup($gid); if ($groups === null) { - return []; + return $users; } - $users = []; $avoid = $this->avoidRecurse_users; $this->avoidRecurse_users = true; foreach ($groups as $group) { @@ -152,6 +172,7 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { } } $this->avoidRecurse_users = $avoid; + return $users; } @@ -182,4 +203,12 @@ public function countUsersInGroup(string $gid, string $search = ''): int { } return $nbUsers; } + + public function disable() { + $this->avoidRecurse_users = $this->avoidRecurse_groups = true; + } + + public function enable() { + $this->avoidRecurse_users = $this->avoidRecurse_groups = false; + } }; diff --git a/lib/Service/Group/UserGroup.php b/lib/Service/Group/UserGroup.php index 3676b48b6..90b0f7668 100644 --- a/lib/Service/Group/UserGroup.php +++ b/lib/Service/Group/UserGroup.php @@ -26,17 +26,20 @@ use OCA\Workspace\Db\Space; use OCA\Workspace\Exceptions\CreateGroupException; +use OCA\Workspace\Group\GroupBackend; use OCP\AppFramework\Http; use OCP\AppFramework\Services\IAppConfig; use OCP\IGroup; use OCP\IGroupManager; +use OCP\Server; class UserGroup extends GroupsWorkspace { - private IGroupManager $groupManager; - public function __construct(IGroupManager $groupManager, IAppConfig $appConfig) { + public function __construct( + IAppConfig $appConfig, + private IGroupManager $groupManager, + ) { parent::__construct($appConfig); - $this->groupManager = $groupManager; } public static function get(int $spaceId): string { @@ -48,7 +51,10 @@ public static function getPrefix(): string { } public function create(Space $space): IGroup { + $groupBackend = Server::get(GroupBackend::class); + $groupBackend->disable(); $group = $this->groupManager->createGroup(self::PREFIX_GID_USERS . $space->getId()); + $groupBackend->enable(); if (is_null($group)) { throw new CreateGroupException('Error to create a Space Manager group.', Http::STATUS_CONFLICT); diff --git a/lib/User/Backend/UserBackend.php b/lib/User/Backend/UserBackend.php new file mode 100644 index 000000000..04cf7d313 --- /dev/null +++ b/lib/User/Backend/UserBackend.php @@ -0,0 +1,245 @@ + */ + private $_users = null; + + public function __construct( + protected INotificationManager $notificationManager, + protected LoggerInterface $logger, + private SpaceMapper $spaceMapper, + ) { + } + + private function initUsers(): void { + if ($this->_users !== null) { + return; + } + + $users = []; + $spaces = $this->spaceMapper->findAll(); + /** @var Space $space */ + foreach ($spaces as $space) { + $users['SPACE-UWS-' . $space->getSpaceId()] = [ + 'displayName' => $space->getSpaceName(), + ]; + } + $this->_users = $users; + } + + /** + * checks whether the user is allowed to change their avatar in Nextcloud + * + * @param string $uid the Nextcloud user name + * @return boolean either the user can or cannot + * @throws \Exception + */ + public function canChangeAvatar($uid) { + return false; + } + + /** + * Get a list of all users + * + * @param string $search + * @param integer $limit + * @param integer $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = 10, $offset = 0) { + $this->initUsers(); + $limit = (is_int($limit) && $limit >= 0) ? $limit : null; + if ($limit === null && $offset === 0 && $search === '') { + return array_keys($this->_users); + } + if ($search === '') { + return array_slice(array_keys($this->_users), $offset, $limit); + } + $search = strtolower($search); + $users = []; + $count = 0; + foreach ($this->_users as $uid => $user) { + if (str_contains(strtolower($user['displayName']), $search)) { + if ($count >= $offset) { + $users[] = $uid; + } + $count++; + if ($limit !== null && $count >= $offset + $limit) { + break; + } + } + } + return $users; + } + + /** + * check if a user exists + * @param string $uid the username + * @return boolean + * @throws \Exception when connection could not be established + */ + public function userExists($uid) { + $this->initUsers(); + return array_key_exists($uid, $this->_users); + } + + /** + * returns whether a user was deleted in LDAP + * + * @param string $uid The username of the user to delete + * @return bool + */ + public function deleteUser($uid) { + return false; + } + + /** + * get the user's home directory + * + * @param string $uid the username + * @return bool|string + * @throws NoUserException + * @throws \Exception + */ + public function getHome($uid) { + return false; + } + + /** + * get display name of the user + * @param string $uid user ID of the user + * @return string|false display name + */ + public function getDisplayName($uid) { + $this->initUsers(); + if (isset($this->_users[$uid]) && isset($this->_users[$uid]['displayName'])) { + return $this->_users[$uid]['displayName']; + } + return false; + } + + /** + * set display name of the user + * @param string $uid user ID of the user + * @param string $displayName new display name of the user + * @return string|false display name + */ + public function setDisplayName($uid, $displayName) { + return false; + } + + /** + * Get a list of all display names + * + * @param string $search + * @param int|null $limit + * @param int|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $this->initUsers(); + return array_combine( + array_keys($this->_users), + array_column($this->_users, 'displayName') + ); + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with \OC\User\Backend::CREATE_USER etc. + */ + public function implementsActions($actions) { + return (bool)( + // Backend::CHECK_PASSWORD + // | Backend::GET_HOME + (Backend::GET_DISPLAYNAME + // | (($this->access->connection->ldapUserAvatarRule !== 'none') ? Backend::PROVIDE_AVATAR : 0) + | Backend::COUNT_USERS) + // | (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0) + // | $this->userPluginManager->getImplementedActions()) + & $actions); + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } + + /** + * counts the users in LDAP + */ + public function countUsers(int $limit = 0): int|false { + $this->initUsers(); + return count($this->_users); + } + + + /** + * Backend name to be shown in user management + * @return string the name of the backend to be shown + */ + public function getBackendName() { + return 'WorkSpace'; + } + + /** + * @since 28.0.0 + * + * @param callable():bool $queryDatabaseValue A callable to query the enabled state from database + */ + public function isUserEnabled(string $uid, callable $queryDatabaseValue): bool { + if (str_starts_with($uid, 'SPACE-UWS-')) { + $spaceId = (int)substr($uid, 10); + if ($spaceId !== 0) { + $space = $this->spaceMapper->find($spaceId); + return $space !== null; + } + return false; + } + return true; + } + + /** + * @since 28.0.0 + * + * @param callable():bool $queryDatabaseValue A callable to query the enabled state from database + * @param callable(bool):void $setDatabaseValue A callable to set the enabled state in the database. + */ + public function setUserEnabled(string $uid, bool $enabled, callable $queryDatabaseValue, callable $setDatabaseValue): bool { + if (str_starts_with($uid, 'SPACE-UWS-')) { + return !$enabled; // refuse change + } + return true; + } + + /** + * Get the list of disabled users, to merge with the ones disabled in database + * + * @since 28.0.0 + * @since 30.0.0 $search parameter added + * + * @return string[] + */ + public function getDisabledUserList(?int $limit = null, int $offset = 0, string $search = ''): array { + return []; + } +}