diff --git a/core/Controller/TeamsApiController.php b/core/Controller/TeamsApiController.php new file mode 100644 index 0000000000000..f937bcb847c9d --- /dev/null +++ b/core/Controller/TeamsApiController.php @@ -0,0 +1,97 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OC\Core\Controller; + +use OCA\Core\ResponseDefinitions; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\ApiRoute; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\DataResponse; +use OCP\IRequest; +use OCP\Teams\ITeamManager; +use OCP\Teams\Team; + +/** + * @psalm-import-type CoreTeamResource from ResponseDefinitions + * @psalm-import-type CoreTeam from ResponseDefinitions + * @property $userId string + */ +class TeamsApiController extends \OCP\AppFramework\OCSController { + public function __construct( + string $appName, + IRequest $request, + private ITeamManager $teamManager, + private ?string $userId, + ) { + parent::__construct($appName, $request); + } + + /** + * Get all resources of a team + * + * @param string $teamId Unique id of the team + * @return DataResponse + * + * 200: Resources returned + */ + #[NoAdminRequired] + #[ApiRoute(verb: 'GET', url: '/{teamId}/resources', root: '/teams')] + public function resolveOne(string $teamId): DataResponse { + /** + * @var CoreTeamResource[] $resolvedResources + * @psalm-suppress PossiblyNullArgument The route is limited to logged-in users + */ + $resolvedResources = $this->teamManager->getSharedWith($teamId, $this->userId); + + return new DataResponse(['resources' => $resolvedResources]); + } + + /** + * Get all teams of a resource + * + * @param string $providerId Identifier of the provider (e.g. deck, talk, collectives) + * @param string $resourceId Unique id of the resource to list teams for (e.g. deck board id) + * @return DataResponse + * + * 200: Teams returned + */ + #[NoAdminRequired] + #[ApiRoute(verb: 'GET', url: '/resources/{providerId}/{resourceId}', root: '/teams')] + public function listTeams(string $providerId, string $resourceId): DataResponse { + /** @psalm-suppress PossiblyNullArgument The route is limited to logged-in users */ + $teams = $this->teamManager->getTeamsForResource($providerId, $resourceId, $this->userId); + /** @var CoreTeam[] $teams */ + $teams = array_map(function (Team $team) { + $response = $team->jsonSerialize(); + /** @psalm-suppress PossiblyNullArgument The route is limited to logged in users */ + $response['resources'] = $this->teamManager->getSharedWith($team->getId(), $this->userId); + return $response; + }, $teams); + + return new DataResponse([ + 'teams' => $teams, + ]); + } +} diff --git a/core/ResponseDefinitions.php b/core/ResponseDefinitions.php index b8f73bcdcdacd..4a79c3ad3ec85 100644 --- a/core/ResponseDefinitions.php +++ b/core/ResponseDefinitions.php @@ -161,6 +161,21 @@ * numberOfImages: int, * completionExpectedAt: ?int, * } + * + * @psalm-type CoreTeam = array{ + * id: string, + * name: string, + * icon: string, + * } + * + * @psalm-type CoreTeamResource = array{ + * id: int, + * label: string, + * url: string, + * iconSvg: ?string, + * iconURL: ?string, + * iconEmoji: ?string, + * } */ class ResponseDefinitions { } diff --git a/core/openapi.json b/core/openapi.json index 2c6b5f28ef247..8bb7beca406f9 100644 --- a/core/openapi.json +++ b/core/openapi.json @@ -406,6 +406,60 @@ } } }, + "Team": { + "type": "object", + "required": [ + "id", + "name", + "icon" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "icon": { + "type": "string" + } + } + }, + "TeamResource": { + "type": "object", + "required": [ + "id", + "label", + "url", + "iconSvg", + "iconURL", + "iconEmoji" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string" + }, + "iconSvg": { + "type": "string", + "nullable": true + }, + "iconURL": { + "type": "string", + "nullable": true + }, + "iconEmoji": { + "type": "string", + "nullable": true + } + } + }, "TextProcessingTask": { "type": "object", "required": [ @@ -3009,6 +3063,177 @@ } } }, + "/ocs/v2.php/teams/{teamId}/resources": { + "get": { + "operationId": "teams_api-resolve-one", + "summary": "Get all resources of a team", + "tags": [ + "teams_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "teamId", + "in": "path", + "description": "Unique id of the team", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Resources returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "resources" + ], + "properties": { + "resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamResource" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/teams/resources/{providerId}/{resourceId}": { + "get": { + "operationId": "teams_api-list-teams", + "summary": "Get all teams of a resource", + "tags": [ + "teams_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "providerId", + "in": "path", + "description": "Identifier of the provider (e.g. deck, talk, collectives)", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "resourceId", + "in": "path", + "description": "Unique id of the resource to list teams for (e.g. deck board id)", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Teams returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "teams" + ], + "properties": { + "teams": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Team" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/textprocessing/tasktypes": { "get": { "operationId": "text_processing_api-task-types", diff --git a/lib/composer/composer/LICENSE b/lib/composer/composer/LICENSE index 62ecfd8d0046b..f27399a042d95 100644 --- a/lib/composer/composer/LICENSE +++ b/lib/composer/composer/LICENSE @@ -1,3 +1,4 @@ + Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy @@ -17,3 +18,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index d5c89d0237f52..e32e509e3eeb7 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -691,6 +691,10 @@ 'OCP\\Talk\\IConversation' => $baseDir . '/lib/public/Talk/IConversation.php', 'OCP\\Talk\\IConversationOptions' => $baseDir . '/lib/public/Talk/IConversationOptions.php', 'OCP\\Talk\\ITalkBackend' => $baseDir . '/lib/public/Talk/ITalkBackend.php', + 'OCP\\Teams\\ITeamManager' => $baseDir . '/lib/public/Teams/ITeamManager.php', + 'OCP\\Teams\\ITeamResourceProvider' => $baseDir . '/lib/public/Teams/ITeamResourceProvider.php', + 'OCP\\Teams\\Team' => $baseDir . '/lib/public/Teams/Team.php', + 'OCP\\Teams\\TeamResource' => $baseDir . '/lib/public/Teams/TeamResource.php', 'OCP\\Template' => $baseDir . '/lib/public/Template.php', 'OCP\\TextProcessing\\Events\\AbstractTextProcessingEvent' => $baseDir . '/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php', 'OCP\\TextProcessing\\Events\\TaskFailedEvent' => $baseDir . '/lib/public/TextProcessing/Events/TaskFailedEvent.php', @@ -1158,6 +1162,7 @@ 'OC\\Core\\Controller\\ReferenceController' => $baseDir . '/core/Controller/ReferenceController.php', 'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php', 'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php', + 'OC\\Core\\Controller\\TeamsApiController' => $baseDir . '/core/Controller/TeamsApiController.php', 'OC\\Core\\Controller\\TextProcessingApiController' => $baseDir . '/core/Controller/TextProcessingApiController.php', 'OC\\Core\\Controller\\TextToImageApiController' => $baseDir . '/core/Controller/TextToImageApiController.php', 'OC\\Core\\Controller\\TranslationApiController' => $baseDir . '/core/Controller/TranslationApiController.php', @@ -1794,6 +1799,7 @@ 'OC\\Tags' => $baseDir . '/lib/private/Tags.php', 'OC\\Talk\\Broker' => $baseDir . '/lib/private/Talk/Broker.php', 'OC\\Talk\\ConversationOptions' => $baseDir . '/lib/private/Talk/ConversationOptions.php', + 'OC\\Teams\\TeamManager' => $baseDir . '/lib/private/Teams/TeamManager.php', 'OC\\TempManager' => $baseDir . '/lib/private/TempManager.php', 'OC\\TemplateLayout' => $baseDir . '/lib/private/TemplateLayout.php', 'OC\\Template\\Base' => $baseDir . '/lib/private/Template/Base.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 906a5ac199611..2e8a3c71c0acb 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -724,6 +724,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Talk\\IConversation' => __DIR__ . '/../../..' . '/lib/public/Talk/IConversation.php', 'OCP\\Talk\\IConversationOptions' => __DIR__ . '/../../..' . '/lib/public/Talk/IConversationOptions.php', 'OCP\\Talk\\ITalkBackend' => __DIR__ . '/../../..' . '/lib/public/Talk/ITalkBackend.php', + 'OCP\\Teams\\ITeamManager' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamManager.php', + 'OCP\\Teams\\ITeamResourceProvider' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamResourceProvider.php', + 'OCP\\Teams\\Team' => __DIR__ . '/../../..' . '/lib/public/Teams/Team.php', + 'OCP\\Teams\\TeamResource' => __DIR__ . '/../../..' . '/lib/public/Teams/TeamResource.php', 'OCP\\Template' => __DIR__ . '/../../..' . '/lib/public/Template.php', 'OCP\\TextProcessing\\Events\\AbstractTextProcessingEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php', 'OCP\\TextProcessing\\Events\\TaskFailedEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/TaskFailedEvent.php', @@ -1191,6 +1195,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Controller\\ReferenceController' => __DIR__ . '/../../..' . '/core/Controller/ReferenceController.php', 'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php', 'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php', + 'OC\\Core\\Controller\\TeamsApiController' => __DIR__ . '/../../..' . '/core/Controller/TeamsApiController.php', 'OC\\Core\\Controller\\TextProcessingApiController' => __DIR__ . '/../../..' . '/core/Controller/TextProcessingApiController.php', 'OC\\Core\\Controller\\TextToImageApiController' => __DIR__ . '/../../..' . '/core/Controller/TextToImageApiController.php', 'OC\\Core\\Controller\\TranslationApiController' => __DIR__ . '/../../..' . '/core/Controller/TranslationApiController.php', @@ -1827,6 +1832,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Tags' => __DIR__ . '/../../..' . '/lib/private/Tags.php', 'OC\\Talk\\Broker' => __DIR__ . '/../../..' . '/lib/private/Talk/Broker.php', 'OC\\Talk\\ConversationOptions' => __DIR__ . '/../../..' . '/lib/private/Talk/ConversationOptions.php', + 'OC\\Teams\\TeamManager' => __DIR__ . '/../../..' . '/lib/private/Teams/TeamManager.php', 'OC\\TempManager' => __DIR__ . '/../../..' . '/lib/private/TempManager.php', 'OC\\TemplateLayout' => __DIR__ . '/../../..' . '/lib/private/TemplateLayout.php', 'OC\\Template\\Base' => __DIR__ . '/../../..' . '/lib/private/Template/Base.php', diff --git a/lib/composer/composer/installed.json b/lib/composer/composer/installed.json index f20a6c47c6d4f..13ea12dca2a27 100644 --- a/lib/composer/composer/installed.json +++ b/lib/composer/composer/installed.json @@ -1,5 +1,68 @@ { - "packages": [], - "dev": false, - "dev-package-names": [] + "packages": [ + { + "name": "bamarni/composer-bin-plugin", + "version": "1.8.2", + "version_normalized": "1.8.2.0", + "source": { + "type": "git", + "url": "https://github.com/bamarni/composer-bin-plugin.git", + "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880", + "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "ext-json": "*", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" + }, + "time": "2022-10-31T08:38:03+00:00", + "type": "composer-plugin", + "extra": { + "class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Bamarni\\Composer\\Bin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "No conflicts for your bin dependencies", + "keywords": [ + "composer", + "conflict", + "dependency", + "executable", + "isolation", + "tool" + ], + "support": { + "issues": "https://github.com/bamarni/composer-bin-plugin/issues", + "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2" + }, + "install-path": "../bamarni/composer-bin-plugin" + } + ], + "dev": true, + "dev-package-names": [ + "bamarni/composer-bin-plugin" + ] } diff --git a/lib/composer/composer/installed.php b/lib/composer/composer/installed.php index ada5df14e4f1f..88d9c302532ce 100644 --- a/lib/composer/composer/installed.php +++ b/lib/composer/composer/installed.php @@ -3,21 +3,30 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '559a758533026559cf632ed1b3d74f6b1ebfb481', + 'reference' => 'b6abfc4cba2d1ef4fdd8f2c22bbff46796b9485e', 'type' => 'library', 'install_path' => __DIR__ . '/../../../', 'aliases' => array(), - 'dev' => false, + 'dev' => true, ), 'versions' => array( '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '559a758533026559cf632ed1b3d74f6b1ebfb481', + 'reference' => 'b6abfc4cba2d1ef4fdd8f2c22bbff46796b9485e', 'type' => 'library', 'install_path' => __DIR__ . '/../../../', 'aliases' => array(), 'dev_requirement' => false, ), + 'bamarni/composer-bin-plugin' => array( + 'pretty_version' => '1.8.2', + 'version' => '1.8.2.0', + 'reference' => '92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../bamarni/composer-bin-plugin', + 'aliases' => array(), + 'dev_requirement' => true, + ), ), ); diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index 120ee7ea9fae8..6c51aafff9bf7 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -54,6 +54,7 @@ use OCP\SpeechToText\ISpeechToTextProvider; use OCP\Support\CrashReport\IReporter; use OCP\Talk\ITalkBackend; +use OCP\Teams\ITeamResourceProvider; use OCP\TextProcessing\IProvider as ITextProcessingProvider; use OCP\Translation\ITranslationProvider; use OCP\UserMigration\IMigrator as IUserMigrator; @@ -158,6 +159,9 @@ class RegistrationContext { /** @var PreviewProviderRegistration[] */ private array $previewProviders = []; + /** @var ServiceRegistration[] */ + private array $teamResourceProviders = []; + public function __construct(LoggerInterface $logger) { $this->logger = $logger; } @@ -357,6 +361,13 @@ public function registerCalendarResourceBackend(string $class): void { ); } + public function registerTeamResourceProvider(string $class) : void { + $this->context->registerTeamResourceProvider( + $this->appId, + $class + ); + } + public function registerCalendarRoomBackend(string $class): void { $this->context->registerCalendarRoomBackend( $this->appId, @@ -531,6 +542,17 @@ public function registerCalendarRoomBackend(string $appId, string $class) { ); } + + /** + * @psalm-param class-string $class + */ + public function registerTeamResourceProvider(string $appId, string $class) { + $this->teamResourceProviders[] = new ServiceRegistration( + $appId, + $class + ); + } + /** * @psalm-param class-string $migratorClass */ @@ -870,4 +892,12 @@ public function getPublicShareTemplateProviders(): array { public function getSetupChecks(): array { return $this->setupChecks; } + + + /** + * @return ServiceRegistration[] + */ + public function getTeamResourceProviders(): array { + return $this->teamResourceProviders; + } } diff --git a/lib/private/Server.php b/lib/private/Server.php index 1aedd7d06ac56..df267839160ee 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -158,6 +158,7 @@ use OC\SystemTag\ManagerFactory as SystemTagManagerFactory; use OC\Tagging\TagMapper; use OC\Talk\Broker; +use OC\Teams\TeamManager; use OC\Template\JSCombiner; use OC\Translation\TranslationManager; use OC\User\AvailabilityCoordinator; @@ -265,6 +266,7 @@ use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\Talk\IBroker; +use OCP\Teams\ITeamManager; use OCP\Translation\ITranslationManager; use OCP\User\Events\BeforeUserDeletedEvent; use OCP\User\Events\BeforeUserLoggedInEvent; @@ -1297,6 +1299,7 @@ public function __construct($webRoot, \OC\Config $config) { $this->registerAlias(\OCP\Collaboration\Resources\IManager::class, \OC\Collaboration\Resources\Manager::class); $this->registerAlias(IReferenceManager::class, ReferenceManager::class); + $this->registerAlias(ITeamManager::class, TeamManager::class); $this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class); $this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class); diff --git a/lib/private/Teams/TeamManager.php b/lib/private/Teams/TeamManager.php new file mode 100644 index 0000000000000..6651d3ce67652 --- /dev/null +++ b/lib/private/Teams/TeamManager.php @@ -0,0 +1,119 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OC\Teams; + +use OC\AppFramework\Bootstrap\Coordinator; +use OCA\Circles\CirclesManager; +use OCA\Circles\Exceptions\CircleNotFoundException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\Member; +use OCP\IURLGenerator; +use OCP\Server; +use OCP\Teams\ITeamManager; +use OCP\Teams\ITeamResourceProvider; +use OCP\Teams\Team; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +class TeamManager implements ITeamManager { + + /** @var ?ITeamResourceProvider[] */ + private ?array $providers = null; + + public function __construct( + private Coordinator $bootContext, + private IURLGenerator $urlGenerator, + private ?CirclesManager $circlesManager, + ) { + } + + public function hasTeamSupport(): bool { + return $this->circlesManager !== null; + } + + public function getProviders(): array { + if ($this->providers !== null) { + return $this->providers; + } + + $this->providers = []; + foreach ($this->bootContext->getRegistrationContext()->getTeamResourceProviders() as $providerRegistration) { + try { + /** @var ITeamResourceProvider $provider */ + $provider = Server::get($providerRegistration->getService()); + $this->providers[$provider->getId()] = $provider; + } catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) { + } + } + return $this->providers; + } + + public function getProvider(string $providerId): ITeamResourceProvider { + $providers = $this->getProviders(); + if (isset($providers[$providerId])) { + return $providers[$providerId]; + } + + throw new \RuntimeException('No provider found for id ' .$providerId); + } + + public function getSharedWith(string $teamId, string $userId): array { + if ($this->getTeam($teamId, $userId) === null) { + return []; + } + + $resources = []; + + foreach ($this->getProviders() as $provider) { + array_push($resources, ...$provider->getSharedWith($teamId)); + } + + return $resources; + } + + public function getTeamsForResource(string $providerId, string $resourceId, string $userId): array { + $provider = $this->getProvider($providerId); + return array_values(array_filter(array_map(function ($teamId) use ($userId) { + $team = $this->getTeam($teamId, $userId); + if ($team === null) { + return null; + } + + return new Team( + $teamId, + $team->getDisplayName(), + $this->urlGenerator->linkToRouteAbsolute('contacts.contacts.directcircle', ['singleId' => $teamId]), + ); + }, $provider->getTeamsForResource($resourceId)))); + } + + private function getTeam(string $teamId, string $userId): ?Circle { + try { + $federatedUser = $this->circlesManager->getFederatedUser($userId, Member::TYPE_USER); + $this->circlesManager->startSession($federatedUser); + return $this->circlesManager->getCircle($teamId); + } catch (CircleNotFoundException) { + return null; + } + } +} diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index b39c8591a7ef0..f515180cef59f 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -351,6 +351,14 @@ public function registerCalendarResourceBackend(string $class): void; */ public function registerCalendarRoomBackend(string $class): void; + /** + * @param string $class + * @psalm-param class-string<\OCP\Calendar\Room\IBackend> $actionClass + * @return void + * @since 29.0.0 + */ + public function registerTeamResourceProvider(string $class): void; + /** * Register an implementation of \OCP\UserMigration\IMigrator that * will handle the implementation of a migrator diff --git a/lib/public/Teams/ITeamManager.php b/lib/public/Teams/ITeamManager.php new file mode 100644 index 0000000000000..51d8a1feb5a98 --- /dev/null +++ b/lib/public/Teams/ITeamManager.php @@ -0,0 +1,58 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCP\Teams; + +/** + * @since 29.0.0 + */ +interface ITeamManager { + /** + * Get all providers that have registered as a team resource provider + * + * @return ITeamResourceProvider[] + * @since 29.0.0 + */ + public function getProviders(): array; + + /** + * Get a specific team resource provider by its id + * + * @since 29.0.0 + */ + public function getProvider(string $providerId): ITeamResourceProvider; + + /** + * Returns all team resources for a given team and user + * + * @return TeamResource[] + * @since 29.0.0 + */ + public function getSharedWith(string $teamId, string $userId): array; + + /** + * Returns all teams for a given resource and user + * + * @since 29.0.0 + */ + public function getTeamsForResource(string $providerId, string $resourceId, string $userId): array; +} diff --git a/lib/public/Teams/ITeamResourceProvider.php b/lib/public/Teams/ITeamResourceProvider.php new file mode 100644 index 0000000000000..722c877555eb0 --- /dev/null +++ b/lib/public/Teams/ITeamResourceProvider.php @@ -0,0 +1,76 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCP\Teams; + +/** + * Implement a provider of resources that are shared or owned by a team + * + * @since 29.0.0 + */ +interface ITeamResourceProvider { + + /** + * Unique identifier used to identify the provider (app id) + * + * @since 29.0.0 + */ + public function getId(): string; + + /** + * User visible name of the provider (app name) + * + * @since 29.0.0 + */ + public function getName(): string; + + /** + * Svg icon to show next to the provider (app icon) + * + * @since 29.0.0 + */ + public function getIconSvg(): string; + + /** + * Return all resources that are shared to the given team id for the current provider + * + * @param string $teamId + * @return TeamResource[] + * @since 29.0.0 + */ + public function getSharedWith(string $teamId): array; + + /** + * Check if a resource is shared with the given team + * + * @since 29.0.0 + */ + public function isSharedWithTeam(string $teamId, string $resourceId): bool; + + /** + * Return team ids that a resource is shared with or owned by + * + * @return string[] + * @since 29.0.0 + */ + public function getTeamsForResource(string $resourceId): array; +} diff --git a/lib/public/Teams/Team.php b/lib/public/Teams/Team.php new file mode 100644 index 0000000000000..d3d6c2d143da5 --- /dev/null +++ b/lib/public/Teams/Team.php @@ -0,0 +1,73 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCP\Teams; + +/** + * Simple abstraction to represent a team in the public API + * + * In the backend a team is a circle identified by the circles singleId + * + * @since 29.0.0 + */ +class Team implements \JsonSerializable { + + /** + * @since 29.0.0 + */ + public function __construct(private string $teamId, private string $displayName, private ?string $link) { + } + + /** + * Unique identifier of the team (singleId of the circle) + * + * @since 29.0.0 + */ + public function getId(): string { + return $this->teamId; + } + + /** + * @since 29.0.0 + */ + public function getDisplayName(): string { + return $this->displayName; + } + + /** + * @since 29.0.0 + */ + public function getLink(): ?string { + return $this->link; + } + + /** + * @since 29.0.0 + */ + public function jsonSerialize(): array { + return [ + 'teamId' => $this->teamId, + 'displayName' => $this->displayName, + 'link' => $this->link, + ]; + } +} diff --git a/lib/public/Teams/TeamResource.php b/lib/public/Teams/TeamResource.php new file mode 100644 index 0000000000000..569583bb39317 --- /dev/null +++ b/lib/public/Teams/TeamResource.php @@ -0,0 +1,129 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCP\Teams; + +/** + * @since 29.0.0 + */ +class TeamResource implements \JsonSerializable { + /** + * @since 29.0.0 + */ + public function __construct( + private ITeamResourceProvider $teamResourceProvider, + private string $resourceId, + private string $label, + private string $url, + private ?string $iconSvg = null, + private ?string $iconURL = null, + private ?string $iconEmoji = null, + ) { + } + + /** + * Returns the provider details for the current resource + * + * @since 29.0.0 + */ + public function getProvider(): ITeamResourceProvider { + return $this->teamResourceProvider; + } + + /** + * Unique id of the resource (e.g. primary key id) + * @since 29.0.0 + */ + public function getId(): string { + return $this->resourceId; + } + + /** + * User visible label when listing resources + * + * @since 29.0.0 + */ + public function getLabel(): string { + return $this->label; + } + + /** + * Absolute url to navigate the user to the resource + * + * @since 29.0.0 + */ + public function getUrl(): string { + return $this->url; + } + + /** + * Svg icon to show next to the name for the resource + * + * From all icons the first one returning not null will be picked in order: iconEmoji, iconSvg, iconUrl + * + * @since 29.0.0 + */ + public function getIconSvg(): ?string { + return $this->iconSvg; + } + + /** + * Image url of the icon to show next to the name for the resource + * + * From all icons the first one returning not null will be picked in order: iconEmoji, iconSvg, iconUrl + * + * @since 29.0.0 + */ + public function getIconURL(): ?string { + return $this->iconURL; + } + + /** + * Emoji show next to the name for the resource + * + * From all icons the first one returning not null will be picked in order: iconEmoji, iconSvg, iconUrl + * + * @since 29.0.0 + */ + public function getIconEmoji(): ?string { + return $this->iconEmoji; + } + + /** + * @since 29.0.0 + */ + public function jsonSerialize(): array { + return [ + 'id' => $this->resourceId, + 'label' => $this->label, + 'url' => $this->url, + 'iconSvg' => $this->iconSvg, + 'iconURL' => $this->iconURL, + 'iconEmoji' => $this->iconEmoji, + 'provider' => [ + 'id' => $this->teamResourceProvider->getId(), + 'name' => $this->teamResourceProvider->getName(), + 'icon' => $this->teamResourceProvider->getIconSvg(), + ] + ]; + } +} diff --git a/psalm.xml b/psalm.xml index ed840f89059d0..7ef8405407434 100644 --- a/psalm.xml +++ b/psalm.xml @@ -91,6 +91,10 @@ + + + +