diff options
Diffstat (limited to 'lib/public/Collaboration')
26 files changed, 1021 insertions, 286 deletions
diff --git a/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php b/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php index 503f31bfffb..1a225178310 100644 --- a/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php +++ b/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\AutoComplete; @@ -29,9 +12,9 @@ use OCP\EventDispatcher\GenericEvent; /** * @since 16.0.0 + * @deprecated 28.0.0 Use {@see AutoCompleteFilterEvent} instead */ class AutoCompleteEvent extends GenericEvent { - /** * @param array $arguments * @since 16.0.0 diff --git a/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php b/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php new file mode 100644 index 00000000000..0ae49a9a1f3 --- /dev/null +++ b/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php @@ -0,0 +1,90 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Collaboration\AutoComplete; + +use OCP\EventDispatcher\Event; + +/** + * @since 28.0.0 + */ +class AutoCompleteFilterEvent extends Event { + /** + * @since 28.0.0 + */ + public function __construct( + protected array $results, + protected string $search, + protected ?string $itemType, + protected ?string $itemId, + protected ?string $sorter, + protected array $shareTypes, + protected int $limit, + ) { + parent::__construct(); + } + + /** + * @since 28.0.0 + */ + public function getResults(): array { + return $this->results; + } + + /** + * @param array $results + * @since 28.0.0 + */ + public function setResults(array $results): void { + $this->results = $results; + } + + /** + * @since 28.0.0 + */ + public function getSearchTerm(): string { + return $this->search; + } + + /** + * @return int[] List of `\OCP\Share\IShare::TYPE_*` constants + * @since 28.0.0 + */ + public function getShareTypes(): array { + return $this->shareTypes; + } + + /** + * @since 28.0.0 + */ + public function getItemType(): ?string { + return $this->itemType; + } + + /** + * @since 28.0.0 + */ + public function getItemId(): ?string { + return $this->itemId; + } + + /** + * @return ?string List of desired sort identifiers, top priority first. When multiple are given they are joined with a pipe: `commenters|share-recipients` + * @since 28.0.0 + */ + public function getSorter(): ?string { + return $this->sorter; + } + + /** + * @since 28.0.0 + */ + public function getLimit(): int { + return $this->limit; + } +} diff --git a/lib/public/Collaboration/AutoComplete/IManager.php b/lib/public/Collaboration/AutoComplete/IManager.php index c4ca499b618..2d5443b921d 100644 --- a/lib/public/Collaboration/AutoComplete/IManager.php +++ b/lib/public/Collaboration/AutoComplete/IManager.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\AutoComplete; @@ -35,9 +19,9 @@ interface IManager { public function registerSorter($className); /** - * @param array $sorters list of sorter IDs, seperated by "|" - * @param array $sortArray array representation of OCP\Collaboration\Collaborators\ISearchResult - * @param array $context context info of the search, keys: itemType, itemId + * @param array $sorters list of sorter IDs, separated by "|" + * @param array $sortArray array representation of OCP\Collaboration\Collaborators\ISearchResult + * @param array{itemType: string, itemId: string, search?: string} $context context info of the search * @since 13.0.0 */ public function runSorters(array $sorters, array &$sortArray, array $context); diff --git a/lib/public/Collaboration/AutoComplete/ISorter.php b/lib/public/Collaboration/AutoComplete/ISorter.php index ddf39671d87..1009092af6a 100644 --- a/lib/public/Collaboration/AutoComplete/ISorter.php +++ b/lib/public/Collaboration/AutoComplete/ISorter.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\AutoComplete; @@ -30,7 +14,6 @@ namespace OCP\Collaboration\AutoComplete; * @since 13.0.0 */ interface ISorter { - /** * @return string The ID of the sorter, e.g. commenters * @since 13.0.0 @@ -41,7 +24,7 @@ interface ISorter { * executes the sort action * * @param array $sortArray the array to be sorted, provided as reference - * @param array $context carries key 'itemType' and 'itemId' of the source object (e.g. a file) + * @param array{itemType: string, itemId: string, search?: string} $context carries key 'itemType' and 'itemId' of the source object (e.g. a file) * @since 13.0.0 */ public function sort(array &$sortArray, array $context); diff --git a/lib/public/Collaboration/Collaborators/ISearch.php b/lib/public/Collaboration/Collaborators/ISearch.php index e6ef5ec894e..d2c5c85c07f 100644 --- a/lib/public/Collaboration/Collaborators/ISearch.php +++ b/lib/public/Collaboration/Collaborators/ISearch.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Collaborators; @@ -41,7 +25,7 @@ interface ISearch { /** * @param array $pluginInfo with keys 'shareType' containing the name of a corresponding constant in \OCP\Share and - * 'class' with the class name of the plugin + * 'class' with the class name of the plugin * @since 13.0.0 */ public function registerPlugin(array $pluginInfo); diff --git a/lib/public/Collaboration/Collaborators/ISearchPlugin.php b/lib/public/Collaboration/Collaborators/ISearchPlugin.php index 1965ef7d368..e55a095e2b6 100644 --- a/lib/public/Collaboration/Collaborators/ISearchPlugin.php +++ b/lib/public/Collaboration/Collaborators/ISearchPlugin.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Collaborators; diff --git a/lib/public/Collaboration/Collaborators/ISearchResult.php b/lib/public/Collaboration/Collaborators/ISearchResult.php index 15d14d656ed..bcc5a91ce99 100644 --- a/lib/public/Collaboration/Collaborators/ISearchResult.php +++ b/lib/public/Collaboration/Collaborators/ISearchResult.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Collaborators; @@ -34,7 +18,7 @@ interface ISearchResult { * @param array|null $exactMatches * @since 13.0.0 */ - public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null); + public function addResultSet(SearchResultType $type, array $matches, ?array $exactMatches = null); /** * @param SearchResultType $type diff --git a/lib/public/Collaboration/Collaborators/SearchResultType.php b/lib/public/Collaboration/Collaborators/SearchResultType.php index 74d10b02895..3fdbefdcf1f 100644 --- a/lib/public/Collaboration/Collaborators/SearchResultType.php +++ b/lib/public/Collaboration/Collaborators/SearchResultType.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Collaborators; @@ -30,7 +12,7 @@ namespace OCP\Collaboration\Collaborators; * @since 13.0.0 */ class SearchResultType { - /** @var string */ + /** @var string */ protected $label; /** diff --git a/lib/public/Collaboration/Reference/ADiscoverableReferenceProvider.php b/lib/public/Collaboration/Reference/ADiscoverableReferenceProvider.php new file mode 100644 index 00000000000..582f51beea3 --- /dev/null +++ b/lib/public/Collaboration/Reference/ADiscoverableReferenceProvider.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +use JsonSerializable; + +/** + * @since 26.0.0 + */ +abstract class ADiscoverableReferenceProvider implements IDiscoverableReferenceProvider, JsonSerializable { + /** + * @since 26.0.0 + */ + public function jsonSerialize(): array { + $json = [ + 'id' => $this->getId(), + 'title' => $this->getTitle(), + 'icon_url' => $this->getIconUrl(), + 'order' => $this->getOrder(), + ]; + if ($this instanceof ISearchableReferenceProvider) { + $json['search_providers_ids'] = $this->getSupportedSearchProviderIds(); + } + return $json; + } +} diff --git a/lib/public/Collaboration/Reference/IDiscoverableReferenceProvider.php b/lib/public/Collaboration/Reference/IDiscoverableReferenceProvider.php new file mode 100644 index 00000000000..af4c6b3b1f6 --- /dev/null +++ b/lib/public/Collaboration/Reference/IDiscoverableReferenceProvider.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +/** + * @since 26.0.0 + */ +interface IDiscoverableReferenceProvider extends IReferenceProvider { + /** + * @return string Unique id that identifies the reference provider + * @since 26.0.0 + */ + public function getId(): string; + + /** + * @return string User facing title of the widget + * @since 26.0.0 + */ + public function getTitle(): string; + + /** + * @return int Initial order for reference provider sorting + * @since 26.0.0 + */ + public function getOrder(): int; + + /** + * @return string url to an icon that can be displayed next to the reference provider title + * @since 26.0.0 + */ + public function getIconUrl(): string; + + /** + * @return array representation of the provider + * @since 26.0.0 + */ + public function jsonSerialize(): array; +} diff --git a/lib/public/Collaboration/Reference/IPublicReferenceProvider.php b/lib/public/Collaboration/Reference/IPublicReferenceProvider.php new file mode 100644 index 00000000000..db6c3d3828b --- /dev/null +++ b/lib/public/Collaboration/Reference/IPublicReferenceProvider.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +/** + * @since 30.0.0 + */ +interface IPublicReferenceProvider extends IReferenceProvider { + /** + * Return a reference with its metadata for a given reference identifier and sharingToken + * + * @since 30.0.0 + */ + public function resolveReferencePublic(string $referenceText, string $sharingToken): ?IReference; + + /** + * Return a custom cache key to be used for caching the metadata + * This could be for example the current sharingToken if the reference + * access permissions are different for each share + * + * Should return null, if the cache is only related to the + * reference id and has no further dependency + * + * @since 30.0.0 + */ + public function getCacheKeyPublic(string $referenceId, string $sharingToken): ?string; +} diff --git a/lib/public/Collaboration/Reference/IReference.php b/lib/public/Collaboration/Reference/IReference.php new file mode 100644 index 00000000000..eaa82c323b5 --- /dev/null +++ b/lib/public/Collaboration/Reference/IReference.php @@ -0,0 +1,120 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +use JsonSerializable; + +/** + * @since 25.0.0 + */ +interface IReference extends JsonSerializable { + /** + * @since 25.0.0 + */ + public function getId(): string; + + /** + * Accessible flag indicates if the user has access to the provided reference + * + * @since 25.0.0 + */ + public function setAccessible(bool $accessible): void; + + /** + * Accessible flag indicates if the user has access to the provided reference + * + * @since 25.0.0 + */ + public function getAccessible(): bool; + + /** + * @since 25.0.0 + */ + public function setTitle(string $title): void; + + /** + * @since 25.0.0 + */ + public function getTitle(): string; + + /** + * @since 25.0.0 + */ + public function setDescription(?string $description): void; + + /** + * @since 25.0.0 + */ + public function getDescription(): ?string; + + /** + * @since 25.0.0 + */ + public function setImageUrl(?string $imageUrl): void; + + /** + * @since 25.0.0 + */ + public function getImageUrl(): ?string; + + /** + * @since 25.0.0 + */ + public function setImageContentType(?string $contentType): void; + + /** + * @since 25.0.0 + */ + public function getImageContentType(): ?string; + + /** + * @since 25.0.0 + */ + public function setUrl(?string $url): void; + + /** + * @since 25.0.0 + */ + public function getUrl(): string; + + /** + * Set the reference specific rich object representation + * + * @since 25.0.0 + */ + public function setRichObject(string $type, ?array $richObject): void; + + /** + * Returns the type of the reference specific rich object + * + * @since 25.0.0 + */ + public function getRichObjectType(): string; + + /** + * Returns the reference specific rich object representation + * + * @since 25.0.0 + */ + public function getRichObject(): array; + + /** + * Returns the opengraph rich object representation + * + * @since 25.0.0 + */ + public function getOpenGraphObject(): array; + + /** + * @return array{richObjectType: string, richObject: array<string, mixed>, openGraphObject: array{id: string, name: string, description: ?string, thumb: ?string, link: string}, accessible: bool} + * + * @since 25.0.0 + */ + public function jsonSerialize(): array; +} diff --git a/lib/public/Collaboration/Reference/IReferenceManager.php b/lib/public/Collaboration/Reference/IReferenceManager.php new file mode 100644 index 00000000000..c3cf7ca8e7b --- /dev/null +++ b/lib/public/Collaboration/Reference/IReferenceManager.php @@ -0,0 +1,84 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +/** + * @since 25.0.0 + */ +interface IReferenceManager { + /** + * Return all reference identifiers within a string as an array + * + * @return string[] Array of found references (urls) + * @since 25.0.0 + */ + public function extractReferences(string $text): array; + + /** + * Resolve a given reference id to its metadata with all available providers + * + * This method has a fallback to always provide the open graph metadata, + * but may still return null in case this is disabled or the fetching fails + * + * @since 25.0.0 + * @since 30.0.0 optional arguments `$public` and `$sharingToken` + */ + public function resolveReference(string $referenceId, bool $public = false, string $sharingToken = ''): ?IReference; + + /** + * Get a reference by its cache key + * + * @since 25.0.0 + */ + public function getReferenceByCacheKey(string $cacheKey): ?IReference; + + /** + * Explicitly get a reference from the cache to avoid heavy fetches for cases + * the cache can then be filled with a separate request from the frontend + * + * @since 25.0.0 + * @since 30.0.0 optional arguments `$public` and `$sharingToken` + */ + public function getReferenceFromCache(string $referenceId, bool $public = false, string $sharingToken = ''): ?IReference; + + /** + * Invalidate all cache entries with a prefix or just one if the cache key is provided + * + * @since 25.0.0 + */ + public function invalidateCache(string $cachePrefix, ?string $cacheKey = null): void; + + /** + * Get information on discoverable reference providers (id, title, icon and order) + * If the provider is searchable, also get the list of supported unified search providers + * + * @return IDiscoverableReferenceProvider[] + * @since 26.0.0 + */ + public function getDiscoverableProviders(): array; + + /** + * Update or set the last used timestamp for a provider + * + * @param string $userId + * @param string $providerId + * @param int|null $timestamp use current timestamp if null + * @return bool + * @since 26.0.0 + */ + public function touchProvider(string $userId, string $providerId, ?int $timestamp = null): bool; + + /** + * Get all known last used timestamps for reference providers + * + * @return int[] + * @since 26.0.0 + */ + public function getUserProviderTimestamps(): array; +} diff --git a/lib/public/Collaboration/Reference/IReferenceProvider.php b/lib/public/Collaboration/Reference/IReferenceProvider.php new file mode 100644 index 00000000000..9ff9a728063 --- /dev/null +++ b/lib/public/Collaboration/Reference/IReferenceProvider.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +/** + * @since 25.0.0 + */ +interface IReferenceProvider { + /** + * Validate that a given reference identifier matches the current provider + * + * @since 25.0.0 + */ + public function matchReference(string $referenceText): bool; + + /** + * Return a reference with its metadata for a given reference identifier + * + * @since 25.0.0 + */ + public function resolveReference(string $referenceText): ?IReference; + + /** + * Return true if the reference metadata can be globally cached + * + * @since 25.0.0 + */ + public function getCachePrefix(string $referenceId): string; + + /** + * Return a custom cache key to be used for caching the metadata + * This could be for example the current user id if the reference + * access permissions are different for each user + * + * Should return null, if the cache is only related to the + * reference id and has no further dependency + * + * @since 25.0.0 + */ + public function getCacheKey(string $referenceId): ?string; +} diff --git a/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php b/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php new file mode 100644 index 00000000000..d66a9de9453 --- /dev/null +++ b/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +/** + * @since 26.0.0 + */ +interface ISearchableReferenceProvider extends IDiscoverableReferenceProvider { + /** + * @return string[] list of search provider IDs that can be used by the vue-richtext picker + * @since 26.0.0 + */ + public function getSupportedSearchProviderIds(): array; +} diff --git a/lib/public/Collaboration/Reference/LinkReferenceProvider.php b/lib/public/Collaboration/Reference/LinkReferenceProvider.php new file mode 100644 index 00000000000..65bdcecb577 --- /dev/null +++ b/lib/public/Collaboration/Reference/LinkReferenceProvider.php @@ -0,0 +1,227 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +use Fusonic\OpenGraph\Consumer; +use GuzzleHttp\Psr7\LimitStream; +use GuzzleHttp\Psr7\Utils; +use OC\Security\RateLimiting\Exception\RateLimitExceededException; +use OC\Security\RateLimiting\Limiter; +use OC\SystemConfig; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Files\NotFoundException; +use OCP\Http\Client\IClientService; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\IUserSession; +use Psr\Log\LoggerInterface; + +/** + * @since 29.0.0 + */ +class LinkReferenceProvider implements IReferenceProvider, IPublicReferenceProvider { + + /** + * for image size and webpage header + * @since 29.0.0 + */ + public const MAX_CONTENT_LENGTH = 5 * 1024 * 1024; + + /** + * @since 29.0.0 + */ + public const ALLOWED_CONTENT_TYPES = [ + 'image/png', + 'image/jpg', + 'image/jpeg', + 'image/gif', + 'image/svg+xml', + 'image/webp' + ]; + + /** + * @since 29.0.0 + */ + public function __construct( + private IClientService $clientService, + private LoggerInterface $logger, + private SystemConfig $systemConfig, + private IAppDataFactory $appDataFactory, + private IURLGenerator $urlGenerator, + private Limiter $limiter, + private IUserSession $userSession, + private IRequest $request, + ) { + } + + /** + * @inheritDoc + * @since 29.0.0 + */ + public function matchReference(string $referenceText): bool { + if ($this->systemConfig->getValue('reference_opengraph', true) !== true) { + return false; + } + + return (bool)preg_match(IURLGenerator::URL_REGEX, $referenceText); + } + + /** + * @inheritDoc + * @since 29.0.0 + */ + public function resolveReference(string $referenceText): ?IReference { + if ($this->matchReference($referenceText)) { + $reference = new Reference($referenceText); + $this->fetchReference($reference); + return $reference; + } + + return null; + } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function resolveReferencePublic(string $referenceText, string $sharingToken): ?IReference { + return $this->resolveReference($referenceText); + } + + /** + * Populates the reference with OpenGraph data + * + * @param Reference $reference + * @since 29.0.0 + */ + private function fetchReference(Reference $reference): void { + try { + $user = $this->userSession->getUser(); + if ($user) { + $this->limiter->registerUserRequest('opengraph', 10, 120, $user); + } else { + $this->limiter->registerAnonRequest('opengraph', 10, 120, $this->request->getRemoteAddress()); + } + } catch (RateLimitExceededException $e) { + return; + } + + $client = $this->clientService->newClient(); + try { + $headResponse = $client->head($reference->getId(), [ 'timeout' => 3 ]); + } catch (\Exception $e) { + $this->logger->debug('Failed to perform HEAD request to get target metadata', ['exception' => $e]); + return; + } + + $linkContentLength = $headResponse->getHeader('Content-Length'); + if (is_numeric($linkContentLength) && (int)$linkContentLength > self::MAX_CONTENT_LENGTH) { + $this->logger->debug('[Head] Skip resolving links pointing to content length > 5 MiB'); + return; + } + + $linkContentType = $headResponse->getHeader('Content-Type'); + $expectedContentTypeRegex = '/^text\/html;?/i'; + + // check the header begins with the expected content type + if (!preg_match($expectedContentTypeRegex, $linkContentType)) { + $this->logger->debug('Skip resolving links pointing to content type that is not "text/html"'); + return; + } + + try { + $response = $client->get($reference->getId(), [ 'timeout' => 3, 'stream' => true ]); + } catch (\Exception $e) { + $this->logger->debug('Failed to fetch link for obtaining open graph data', ['exception' => $e]); + return; + } + + $body = $response->getBody(); + if (is_resource($body)) { + $responseContent = fread($body, self::MAX_CONTENT_LENGTH); + if (!feof($body)) { + $this->logger->debug('[Get] Skip resolving links pointing to content length > 5 MiB'); + return; + } + } else { + $this->logger->error('[Get] Impossible to check content length'); + return; + } + + // OpenGraph handling + $consumer = new Consumer(); + $consumer->useFallbackMode = true; + $object = $consumer->loadHtml($responseContent); + + $reference->setUrl($reference->getId()); + + if ($object->title) { + $reference->setTitle($object->title); + } + + if ($object->description) { + $reference->setDescription($object->description); + } + + if ($object->images) { + try { + $host = parse_url($object->images[0]->url, PHP_URL_HOST); + if ($host === false || $host === null) { + $this->logger->warning('Could not detect host of open graph image URI for ' . $reference->getId()); + return; + } + + $appData = $this->appDataFactory->get('core'); + try { + $folder = $appData->getFolder('opengraph'); + } catch (NotFoundException $e) { + $folder = $appData->newFolder('opengraph'); + } + + $response = $client->get($object->images[0]->url, ['timeout' => 3]); + $contentType = $response->getHeader('Content-Type'); + $contentLength = $response->getHeader('Content-Length'); + + if (in_array($contentType, self::ALLOWED_CONTENT_TYPES, true) && $contentLength < self::MAX_CONTENT_LENGTH) { + $stream = Utils::streamFor($response->getBody()); + $bodyStream = new LimitStream($stream, self::MAX_CONTENT_LENGTH, 0); + $reference->setImageContentType($contentType); + $folder->newFile(md5($reference->getId()), $bodyStream->getContents()); + $reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute('core.Reference.preview', ['referenceId' => md5($reference->getId())])); + } + } catch (\Exception $e) { + $this->logger->debug('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]); + } + } + } + + /** + * @inheritDoc + * @since 29.0.0 + */ + public function getCachePrefix(string $referenceId): string { + return $referenceId; + } + + /** + * @inheritDoc + * @since 29.0.0 + */ + public function getCacheKey(string $referenceId): ?string { + return null; + } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getCacheKeyPublic(string $referenceId, string $sharingToken): ?string { + return null; + } +} diff --git a/lib/public/Collaboration/Reference/Reference.php b/lib/public/Collaboration/Reference/Reference.php new file mode 100644 index 00000000000..3698ae419b7 --- /dev/null +++ b/lib/public/Collaboration/Reference/Reference.php @@ -0,0 +1,237 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +/** + * @since 25.0.0 + * @psalm-type OpenGraphObject = array{id: string, name: string, description: ?string, thumb: ?string, link: string} + */ +class Reference implements IReference { + protected string $reference; + + protected bool $accessible = true; + + protected ?string $title = null; + protected ?string $description = null; + protected ?string $imageUrl = null; + protected ?string $contentType = null; + protected ?string $url = null; + + protected ?string $richObjectType = null; + protected ?array $richObject = null; + + /** + * @since 25.0.0 + */ + public function __construct(string $reference) { + $this->reference = $reference; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getId(): string { + return $this->reference; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function setAccessible(bool $accessible): void { + $this->accessible = $accessible; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getAccessible(): bool { + return $this->accessible; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function setTitle(string $title): void { + $this->title = $title; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getTitle(): string { + return $this->title ?? $this->reference; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function setDescription(?string $description): void { + $this->description = $description; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getDescription(): ?string { + return $this->description; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function setImageUrl(?string $imageUrl): void { + $this->imageUrl = $imageUrl; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getImageUrl(): ?string { + return $this->imageUrl; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function setImageContentType(?string $contentType): void { + $this->contentType = $contentType; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getImageContentType(): ?string { + return $this->contentType; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function setUrl(?string $url): void { + $this->url = $url; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getUrl(): string { + return $this->url ?? $this->reference; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function setRichObject(string $type, ?array $richObject): void { + $this->richObjectType = $type; + $this->richObject = $richObject; + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function getRichObjectType(): string { + if ($this->richObjectType === null) { + return 'open-graph'; + } + return $this->richObjectType; + } + + /** + * @inheritdoc + * @since 25.0.0 + * @return array<string, mixed> + */ + public function getRichObject(): array { + if ($this->richObject === null) { + return $this->getOpenGraphObject(); + } + return $this->richObject; + } + + /** + * @inheritdoc + * @since 25.0.0 + * @return OpenGraphObject + */ + public function getOpenGraphObject(): array { + return [ + 'id' => $this->getId(), + 'name' => $this->getTitle(), + 'description' => $this->getDescription(), + 'thumb' => $this->getImageUrl(), + 'link' => $this->getUrl() + ]; + } + + /** + * @param IReference $reference + * @return array + * @since 25.0.0 + */ + public static function toCache(IReference $reference): array { + return [ + 'id' => $reference->getId(), + 'title' => $reference->getTitle(), + 'imageUrl' => $reference->getImageUrl(), + 'imageContentType' => $reference->getImageContentType(), + 'description' => $reference->getDescription(), + 'link' => $reference->getUrl(), + 'accessible' => $reference->getAccessible(), + 'richObjectType' => $reference->getRichObjectType(), + 'richObject' => $reference->getRichObject(), + ]; + } + + /** + * @param array $cache + * @return IReference + * @since 25.0.0 + */ + public static function fromCache(array $cache): IReference { + $reference = new Reference($cache['id']); + $reference->setTitle($cache['title']); + $reference->setDescription($cache['description']); + $reference->setImageUrl($cache['imageUrl']); + $reference->setImageContentType($cache['imageContentType']); + $reference->setUrl($cache['link']); + $reference->setRichObject($cache['richObjectType'], $cache['richObject']); + $reference->setAccessible($cache['accessible']); + return $reference; + } + + /** + * @inheritdoc + * @since 25.0.0 + * @return array{richObjectType: string, richObject: array<string, mixed>, openGraphObject: OpenGraphObject, accessible: bool} + */ + public function jsonSerialize(): array { + return [ + 'richObjectType' => $this->getRichObjectType(), + 'richObject' => $this->getRichObject(), + 'openGraphObject' => $this->getOpenGraphObject(), + 'accessible' => $this->accessible + ]; + } +} diff --git a/lib/public/Collaboration/Reference/RenderReferenceEvent.php b/lib/public/Collaboration/Reference/RenderReferenceEvent.php new file mode 100644 index 00000000000..692098dbf60 --- /dev/null +++ b/lib/public/Collaboration/Reference/RenderReferenceEvent.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +use OCP\EventDispatcher\Event; + +/** + * Event emitted when apps might render references like link previews or smart picker widgets. + * + * This can be used to inject scripts for extending that. + * Further details can be found in the :ref:`Reference providers` deep dive. + * + * @since 25.0.0 + */ +class RenderReferenceEvent extends Event { +} diff --git a/lib/public/Collaboration/Resources/CollectionException.php b/lib/public/Collaboration/Resources/CollectionException.php index e5fd8fd4d22..da1c66352b0 100644 --- a/lib/public/Collaboration/Resources/CollectionException.php +++ b/lib/public/Collaboration/Resources/CollectionException.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Resources; diff --git a/lib/public/Collaboration/Resources/ICollection.php b/lib/public/Collaboration/Resources/ICollection.php index 5efae755f4d..6f9e14c52e2 100644 --- a/lib/public/Collaboration/Resources/ICollection.php +++ b/lib/public/Collaboration/Resources/ICollection.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Resources; @@ -31,7 +14,6 @@ use OCP\IUser; * @since 16.0.0 */ interface ICollection { - /** * @return int * @since 16.0.0 diff --git a/lib/public/Collaboration/Resources/IManager.php b/lib/public/Collaboration/Resources/IManager.php index f220a9c489f..b3f47676784 100644 --- a/lib/public/Collaboration/Resources/IManager.php +++ b/lib/public/Collaboration/Resources/IManager.php @@ -3,27 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Resources; @@ -33,7 +14,6 @@ use OCP\IUser; * @since 16.0.0 */ interface IManager extends IProvider { - /** * @param int $id * @return ICollection diff --git a/lib/public/Collaboration/Resources/IProvider.php b/lib/public/Collaboration/Resources/IProvider.php index 88a74496e55..f0e54b1ef6f 100644 --- a/lib/public/Collaboration/Resources/IProvider.php +++ b/lib/public/Collaboration/Resources/IProvider.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Resources; @@ -32,7 +14,6 @@ use OCP\IUser; * @since 16.0.0 */ interface IProvider { - /** * Get the resource type of the provider * diff --git a/lib/public/Collaboration/Resources/IProviderManager.php b/lib/public/Collaboration/Resources/IProviderManager.php index 5dcb3eeaca9..2f5a652dcef 100644 --- a/lib/public/Collaboration/Resources/IProviderManager.php +++ b/lib/public/Collaboration/Resources/IProviderManager.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019 Daniel Kesselberg <mail@danielkesselberg.de> - * - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Resources; @@ -29,7 +12,6 @@ namespace OCP\Collaboration\Resources; * @since 18.0.0 */ interface IProviderManager { - /** * @return IProvider[] list of resource providers * @since 18.0.0 diff --git a/lib/public/Collaboration/Resources/IResource.php b/lib/public/Collaboration/Resources/IResource.php index 20148090d64..ccea7acb4a7 100644 --- a/lib/public/Collaboration/Resources/IResource.php +++ b/lib/public/Collaboration/Resources/IResource.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Resources; @@ -32,7 +14,6 @@ use OCP\IUser; * @since 16.0.0 */ interface IResource { - /** * @return string * @since 16.0.0 diff --git a/lib/public/Collaboration/Resources/LoadAdditionalScriptsEvent.php b/lib/public/Collaboration/Resources/LoadAdditionalScriptsEvent.php new file mode 100644 index 00000000000..e83c7d4289f --- /dev/null +++ b/lib/public/Collaboration/Resources/LoadAdditionalScriptsEvent.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Resources; + +use OCP\EventDispatcher\Event; + +/** + * This event is used by apps to register their own frontend scripts for integrating + * projects in their app. Apps also need to dispatch the event in order to load + * scripts during page load + * + * @see https://docs.nextcloud.com/server/latest/developer_manual/digging_deeper/projects.html + * @since 25.0.0 + */ +class LoadAdditionalScriptsEvent extends Event { +} diff --git a/lib/public/Collaboration/Resources/ResourceException.php b/lib/public/Collaboration/Resources/ResourceException.php index 6dd154853b4..60d86f95f7a 100644 --- a/lib/public/Collaboration/Resources/ResourceException.php +++ b/lib/public/Collaboration/Resources/ResourceException.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Collaboration\Resources; |