From abb9a465290afa9da81d2784ca57000c5dda4086 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Fri, 2 Dec 2022 13:06:59 +0100 Subject: [PATCH] extend the reference API for the new link picker - add 2 interfaces for discoverable and searchable reference providers - new OCS route to get info on discoverable/searchable reference providers - new abstract ADiscoverableReferenceProvider that only implements jsonSerialize - listen to RenderReferenceEvent to inject provider list with initial state Signed-off-by: Julien Veyssier --- core/Controller/ReferenceApiController.php | 14 +++++ core/routes.php | 1 + lib/base.php | 5 ++ .../Reference/ReferenceManager.php | 62 +++++++++++++++++++ .../RenderReferenceEventListener.php | 62 +++++++++++++++++++ .../ADiscoverableReferenceProvider.php | 47 ++++++++++++++ .../IDiscoverableReferenceProvider.php | 60 ++++++++++++++++++ .../Reference/IReferenceManager.php | 8 +++ .../ISearchableReferenceProvider.php | 36 +++++++++++ 9 files changed, 295 insertions(+) create mode 100644 lib/private/Collaboration/Reference/RenderReferenceEventListener.php create mode 100644 lib/public/Collaboration/Reference/ADiscoverableReferenceProvider.php create mode 100644 lib/public/Collaboration/Reference/IDiscoverableReferenceProvider.php create mode 100644 lib/public/Collaboration/Reference/ISearchableReferenceProvider.php diff --git a/core/Controller/ReferenceApiController.php b/core/Controller/ReferenceApiController.php index 266532113d888..098940187a815 100644 --- a/core/Controller/ReferenceApiController.php +++ b/core/Controller/ReferenceApiController.php @@ -25,6 +25,7 @@ namespace OC\Core\Controller; use OCP\AppFramework\Http\DataResponse; +use OCP\Collaboration\Reference\IDiscoverableReferenceProvider; use OCP\Collaboration\Reference\IReferenceManager; use OCP\IRequest; @@ -88,4 +89,17 @@ public function resolve(array $references, int $limit = 1): DataResponse { 'references' => array_filter($result) ]); } + + /** + * @NoAdminRequired + * + * @return DataResponse + */ + public function getProvidersInfo(): DataResponse { + $providers = $this->referenceManager->getDiscoverableProviders(); + $jsonProviders = array_map(static function (IDiscoverableReferenceProvider $provider) { + return $provider->jsonSerialize(); + }, $providers); + return new DataResponse($jsonProviders); + } } diff --git a/core/routes.php b/core/routes.php index a3fdfafd7bf08..4b67dee43f9a9 100644 --- a/core/routes.php +++ b/core/routes.php @@ -134,6 +134,7 @@ ['root' => '/references', 'name' => 'ReferenceApi#resolveOne', 'url' => '/resolve', 'verb' => 'GET'], ['root' => '/references', 'name' => 'ReferenceApi#extract', 'url' => '/extract', 'verb' => 'POST'], ['root' => '/references', 'name' => 'ReferenceApi#resolve', 'url' => '/resolve', 'verb' => 'POST'], + ['root' => '/references', 'name' => 'ReferenceApi#getProvidersInfo', 'url' => '/providers', 'verb' => 'GET'], ['root' => '/profile', 'name' => 'ProfileApi#setVisibility', 'url' => '/{targetUserId}', 'verb' => 'PUT'], diff --git a/lib/base.php b/lib/base.php index 6186e29bd6da7..b5c5845b5a0ca 100644 --- a/lib/base.php +++ b/lib/base.php @@ -762,6 +762,7 @@ public static function init(): void { self::registerAccountHooks(); self::registerResourceCollectionHooks(); self::registerFileReferenceEventListener(); + self::registerRenderReferenceEventListener(); self::registerAppRestrictionsHooks(); // Make sure that the application class is not loaded before the database is setup @@ -925,6 +926,10 @@ private static function registerFileReferenceEventListener(): void { \OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class)); } + private static function registerRenderReferenceEventListener() { + \OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class)); + } + /** * register hooks for sharing */ diff --git a/lib/private/Collaboration/Reference/ReferenceManager.php b/lib/private/Collaboration/Reference/ReferenceManager.php index a11d92fe2a9ff..1b00b9325ca33 100644 --- a/lib/private/Collaboration/Reference/ReferenceManager.php +++ b/lib/private/Collaboration/Reference/ReferenceManager.php @@ -26,6 +26,7 @@ use OC\AppFramework\Bootstrap\Coordinator; use OC\Collaboration\Reference\File\FileReferenceProvider; +use OCP\Collaboration\Reference\IDiscoverableReferenceProvider; use OCP\Collaboration\Reference\IReference; use OCP\Collaboration\Reference\IReferenceManager; use OCP\Collaboration\Reference\IReferenceProvider; @@ -56,6 +57,12 @@ public function __construct(LinkReferenceProvider $linkReferenceProvider, ICache $this->logger = $logger; } + /** + * Extract a list of URLs from a text + * + * @param string $text + * @return string[] + */ public function extractReferences(string $text): array { preg_match_all(IURLGenerator::URL_REGEX, $text, $matches); $references = $matches[0] ?? []; @@ -64,6 +71,12 @@ public function extractReferences(string $text): array { }, $references); } + /** + * Try to get a cached reference object from a reference string + * + * @param string $referenceId + * @return IReference|null + */ public function getReferenceFromCache(string $referenceId): ?IReference { $matchedProvider = $this->getMatchedProvider($referenceId); @@ -75,6 +88,12 @@ public function getReferenceFromCache(string $referenceId): ?IReference { return $this->getReferenceByCacheKey($cacheKey); } + /** + * Try to get a cached reference object from a full cache key + * + * @param string $cacheKey + * @return IReference|null + */ public function getReferenceByCacheKey(string $cacheKey): ?IReference { $cached = $this->cache->get($cacheKey); if ($cached) { @@ -84,6 +103,13 @@ public function getReferenceByCacheKey(string $cacheKey): ?IReference { return null; } + /** + * Get a reference object from a reference string with a matching provider + * Use a cached reference if possible + * + * @param string $referenceId + * @return IReference|null + */ public function resolveReference(string $referenceId): ?IReference { $matchedProvider = $this->getMatchedProvider($referenceId); @@ -106,6 +132,13 @@ public function resolveReference(string $referenceId): ?IReference { return null; } + /** + * Try to match a reference string with all the registered providers + * Fallback to the link reference provider (using OpenGraph) + * + * @param string $referenceId + * @return IReferenceProvider|null the first matching provider + */ private function getMatchedProvider(string $referenceId): ?IReferenceProvider { $matchedProvider = null; foreach ($this->getProviders() as $provider) { @@ -122,6 +155,13 @@ private function getMatchedProvider(string $referenceId): ?IReferenceProvider { return $matchedProvider; } + /** + * Get a hashed full cache key from a key and prefix given by a provider + * + * @param IReferenceProvider $provider + * @param string $referenceId + * @return string + */ private function getFullCacheKey(IReferenceProvider $provider, string $referenceId): string { $cacheKey = $provider->getCacheKey($referenceId); return md5($provider->getCachePrefix($referenceId)) . ( @@ -129,6 +169,13 @@ private function getFullCacheKey(IReferenceProvider $provider, string $reference ); } + /** + * Remove a specific cache entry from its key+prefix + * + * @param string $cachePrefix + * @param string|null $cacheKey + * @return void + */ public function invalidateCache(string $cachePrefix, ?string $cacheKey = null): void { if ($cacheKey === null) { $this->cache->clear(md5($cachePrefix)); @@ -167,4 +214,19 @@ public function getProviders(): array { return $this->providers; } + + /** + * 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[] + */ + public function getDiscoverableProviders(): array { + // preserve 0 based index to avoid returning an object in data responses + return array_values( + array_filter($this->getProviders(), static function (IReferenceProvider $provider) { + return $provider instanceof IDiscoverableReferenceProvider; + }) + ); + } } diff --git a/lib/private/Collaboration/Reference/RenderReferenceEventListener.php b/lib/private/Collaboration/Reference/RenderReferenceEventListener.php new file mode 100644 index 0000000000000..172dca3d7f2d4 --- /dev/null +++ b/lib/private/Collaboration/Reference/RenderReferenceEventListener.php @@ -0,0 +1,62 @@ + + * + * @author Julien Veyssier + * + * @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\Collaboration\Reference; + +use OCP\Collaboration\Reference\IDiscoverableReferenceProvider; +use OCP\Collaboration\Reference\IReferenceManager; +use OCP\Collaboration\Reference\RenderReferenceEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\EventDispatcher\IEventListener; +use OCP\IInitialStateService; + +class RenderReferenceEventListener implements IEventListener { + private IReferenceManager $manager; + private IInitialStateService $initialStateService; + + public function __construct(IReferenceManager $manager, IInitialStateService $initialStateService) { + $this->manager = $manager; + $this->initialStateService = $initialStateService; + } + + public static function register(IEventDispatcher $eventDispatcher): void { + $eventDispatcher->addServiceListener(RenderReferenceEvent::class, RenderReferenceEventListener::class); + } + + /** + * @inheritDoc + */ + public function handle(Event $event): void { + if (!($event instanceof RenderReferenceEvent)) { + return; + } + + $providers = $this->manager->getDiscoverableProviders(); + $jsonProviders = array_map(static function (IDiscoverableReferenceProvider $provider) { + return $provider->jsonSerialize(); + }, $providers); + $this->initialStateService->provideInitialState('core', 'reference-provider-list', $jsonProviders); + } +} diff --git a/lib/public/Collaboration/Reference/ADiscoverableReferenceProvider.php b/lib/public/Collaboration/Reference/ADiscoverableReferenceProvider.php new file mode 100644 index 0000000000000..56b97957a0238 --- /dev/null +++ b/lib/public/Collaboration/Reference/ADiscoverableReferenceProvider.php @@ -0,0 +1,47 @@ + + * + * @author Julien Veyssier + * + * @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\Collaboration\Reference; + +/** + * @since 26.0.0 + */ +abstract class ADiscoverableReferenceProvider implements IDiscoverableReferenceProvider { + + /** + * @inheritDoc + */ + 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 0000000000000..847e03c602ec2 --- /dev/null +++ b/lib/public/Collaboration/Reference/IDiscoverableReferenceProvider.php @@ -0,0 +1,60 @@ + + * + * @author Julien Veyssier + * + * @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\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/IReferenceManager.php b/lib/public/Collaboration/Reference/IReferenceManager.php index 487e243c7ed5e..31977be4d747d 100644 --- a/lib/public/Collaboration/Reference/IReferenceManager.php +++ b/lib/public/Collaboration/Reference/IReferenceManager.php @@ -67,4 +67,12 @@ public function getReferenceFromCache(string $referenceId): ?IReference; * @since 25.0.0 */ public function invalidateCache(string $cachePrefix, ?string $cacheKey = null): void; + + /** + * Get list of providers that implement IDiscoverableReferenceProvider + * + * @return IDiscoverableReferenceProvider[] + * @since 26.0.0 + */ + public function getDiscoverableProviders(): array; } diff --git a/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php b/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php new file mode 100644 index 0000000000000..b863de468a89a --- /dev/null +++ b/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php @@ -0,0 +1,36 @@ + + * + * @author Julien Veyssier + * + * @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\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; +}