Signed-off-by: Julius Härtl <jus@bitgrid.net>tags/v25.0.0beta4
<code>bool|mixed</code> | <code>bool|mixed</code> | ||||
</LessSpecificImplementedReturnType> | </LessSpecificImplementedReturnType> | ||||
</file> | </file> | ||||
<file src="lib/private/Collaboration/Reference/File/FileReferenceEventListener.php"> | |||||
<InvalidArgument occurrences="3"> | |||||
<code>addServiceListener</code> | |||||
<code>addServiceListener</code> | |||||
<code>addServiceListener</code> | |||||
</InvalidArgument> | |||||
</file> | |||||
<file src="lib/private/Command/CallableJob.php"> | <file src="lib/private/Command/CallableJob.php"> | ||||
<ParamNameMismatch occurrences="1"> | <ParamNameMismatch occurrences="1"> | ||||
<code>$serializedCallable</code> | <code>$serializedCallable</code> |
* Defaults to ``true`` | * Defaults to ``true`` | ||||
*/ | */ | ||||
'bulkupload.enabled' => true, | 'bulkupload.enabled' => true, | ||||
/** | |||||
* Enables fetching open graph metadata from remote urls | |||||
* | |||||
* Defaults to ``true`` | |||||
*/ | |||||
'reference_opengraph' => true, | |||||
]; | ]; |
$result = []; | $result = []; | ||||
$index = 0; | $index = 0; | ||||
foreach ($references as $reference) { | foreach ($references as $reference) { | ||||
if ($index++ < $limit) { | |||||
$result[$reference] = $resolve ? $this->referenceManager->resolveReference($reference) : null; | |||||
if ($index++ >= $limit) { | |||||
break; | |||||
} | } | ||||
$result[$reference] = $resolve ? $this->referenceManager->resolveReference($reference) : null; | |||||
} | } | ||||
return new DataResponse([ | return new DataResponse([ |
self::registerEncryptionWrapperAndHooks(); | self::registerEncryptionWrapperAndHooks(); | ||||
self::registerAccountHooks(); | self::registerAccountHooks(); | ||||
self::registerResourceCollectionHooks(); | self::registerResourceCollectionHooks(); | ||||
self::registerFileReferenceEventListener(); | |||||
self::registerAppRestrictionsHooks(); | self::registerAppRestrictionsHooks(); | ||||
// Make sure that the application class is not loaded before the database is setup | // Make sure that the application class is not loaded before the database is setup | ||||
\OC\Collaboration\Resources\Listener::register(Server::get(SymfonyAdapter::class), Server::get(IEventDispatcher::class)); | \OC\Collaboration\Resources\Listener::register(Server::get(SymfonyAdapter::class), Server::get(IEventDispatcher::class)); | ||||
} | } | ||||
private static function registerFileReferenceEventListener() { | |||||
\OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class)); | |||||
} | |||||
/** | /** | ||||
* register hooks for the filesystem | * register hooks for the filesystem | ||||
*/ | */ |
'OC\\Collaboration\\Collaborators\\Search' => $baseDir . '/lib/private/Collaboration/Collaborators/Search.php', | 'OC\\Collaboration\\Collaborators\\Search' => $baseDir . '/lib/private/Collaboration/Collaborators/Search.php', | ||||
'OC\\Collaboration\\Collaborators\\SearchResult' => $baseDir . '/lib/private/Collaboration/Collaborators/SearchResult.php', | 'OC\\Collaboration\\Collaborators\\SearchResult' => $baseDir . '/lib/private/Collaboration/Collaborators/SearchResult.php', | ||||
'OC\\Collaboration\\Collaborators\\UserPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/UserPlugin.php', | 'OC\\Collaboration\\Collaborators\\UserPlugin' => $baseDir . '/lib/private/Collaboration/Collaborators/UserPlugin.php', | ||||
'OC\\Collaboration\\Reference\\FileReferenceProvider' => $baseDir . '/lib/private/Collaboration/Reference/FileReferenceProvider.php', | |||||
'OC\\Collaboration\\Reference\\File\\FileReferenceEventListener' => $baseDir . '/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php', | |||||
'OC\\Collaboration\\Reference\\File\\FileReferenceProvider' => $baseDir . '/lib/private/Collaboration/Reference/File/FileReferenceProvider.php', | |||||
'OC\\Collaboration\\Reference\\LinkReferenceProvider' => $baseDir . '/lib/private/Collaboration/Reference/LinkReferenceProvider.php', | 'OC\\Collaboration\\Reference\\LinkReferenceProvider' => $baseDir . '/lib/private/Collaboration/Reference/LinkReferenceProvider.php', | ||||
'OC\\Collaboration\\Reference\\Reference' => $baseDir . '/lib/private/Collaboration/Reference/Reference.php', | 'OC\\Collaboration\\Reference\\Reference' => $baseDir . '/lib/private/Collaboration/Reference/Reference.php', | ||||
'OC\\Collaboration\\Reference\\ReferenceManager' => $baseDir . '/lib/private/Collaboration/Reference/ReferenceManager.php', | 'OC\\Collaboration\\Reference\\ReferenceManager' => $baseDir . '/lib/private/Collaboration/Reference/ReferenceManager.php', |
'OC\\Collaboration\\Collaborators\\Search' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/Search.php', | 'OC\\Collaboration\\Collaborators\\Search' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/Search.php', | ||||
'OC\\Collaboration\\Collaborators\\SearchResult' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/SearchResult.php', | 'OC\\Collaboration\\Collaborators\\SearchResult' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/SearchResult.php', | ||||
'OC\\Collaboration\\Collaborators\\UserPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/UserPlugin.php', | 'OC\\Collaboration\\Collaborators\\UserPlugin' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Collaborators/UserPlugin.php', | ||||
'OC\\Collaboration\\Reference\\FileReferenceProvider' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/FileReferenceProvider.php', | |||||
'OC\\Collaboration\\Reference\\File\\FileReferenceEventListener' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php', | |||||
'OC\\Collaboration\\Reference\\File\\FileReferenceProvider' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/File/FileReferenceProvider.php', | |||||
'OC\\Collaboration\\Reference\\LinkReferenceProvider' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/LinkReferenceProvider.php', | 'OC\\Collaboration\\Reference\\LinkReferenceProvider' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/LinkReferenceProvider.php', | ||||
'OC\\Collaboration\\Reference\\Reference' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/Reference.php', | 'OC\\Collaboration\\Reference\\Reference' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/Reference.php', | ||||
'OC\\Collaboration\\Reference\\ReferenceManager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/ReferenceManager.php', | 'OC\\Collaboration\\Reference\\ReferenceManager' => __DIR__ . '/../../..' . '/lib/private/Collaboration/Reference/ReferenceManager.php', |
<?php | |||||
declare(strict_types=1); | |||||
/** | |||||
* @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net> | |||||
* | |||||
* @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/>. | |||||
*/ | |||||
namespace OC\Collaboration\Reference\File; | |||||
use OCP\Collaboration\Reference\IReferenceManager; | |||||
use OCP\EventDispatcher\Event; | |||||
use OCP\EventDispatcher\IEventDispatcher; | |||||
use OCP\Files\Events\Node\NodeDeletedEvent; | |||||
use OCP\Share\Events\ShareCreatedEvent; | |||||
use OCP\Share\Events\ShareDeletedEvent; | |||||
class FileReferenceEventListener implements \OCP\EventDispatcher\IEventListener { | |||||
private IReferenceManager $manager; | |||||
public function __construct(IReferenceManager $manager) { | |||||
$this->manager = $manager; | |||||
} | |||||
public static function register(IEventDispatcher $eventDispatcher): void { | |||||
$eventDispatcher->addServiceListener(NodeDeletedEvent::class, FileReferenceEventListener::class); | |||||
$eventDispatcher->addServiceListener(ShareDeletedEvent::class, FileReferenceEventListener::class); | |||||
$eventDispatcher->addServiceListener(ShareCreatedEvent::class, FileReferenceEventListener::class); | |||||
} | |||||
/** | |||||
* @inheritDoc | |||||
*/ | |||||
public function handle(Event $event): void { | |||||
if ($event instanceof NodeDeletedEvent) { | |||||
$this->manager->invalidateCache((string)$event->getNode()->getId()); | |||||
} | |||||
if ($event instanceof ShareDeletedEvent) { | |||||
$this->manager->invalidateCache((string)$event->getShare()->getNodeId()); | |||||
} | |||||
if ($event instanceof ShareCreatedEvent) { | |||||
$this->manager->invalidateCache((string)$event->getShare()->getNodeId()); | |||||
} | |||||
} | |||||
} |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
namespace OC\Collaboration\Reference; | |||||
namespace OC\Collaboration\Reference\File; | |||||
use OC\Collaboration\Reference\Reference; | |||||
use OC\User\NoUserException; | use OC\User\NoUserException; | ||||
use OCP\Collaboration\Reference\IReference; | use OCP\Collaboration\Reference\IReference; | ||||
use OCP\Collaboration\Reference\IReferenceProvider; | use OCP\Collaboration\Reference\IReferenceProvider; | ||||
} | } | ||||
public function matchReference(string $referenceText): bool { | public function matchReference(string $referenceText): bool { | ||||
return str_starts_with($referenceText, $this->urlGenerator->getAbsoluteURL('/index.php/f/')) | |||||
|| str_starts_with($referenceText, $this->urlGenerator->getAbsoluteURL('/f/')); | |||||
return $this->getFilesAppLinkId($referenceText) !== null; | |||||
} | |||||
private function getFilesAppLinkId(string $referenceText): ?int { | |||||
$start = $this->urlGenerator->getAbsoluteURL('/apps/files'); | |||||
$startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/files'); | |||||
$fileId = null; | |||||
if (mb_strpos($referenceText, $start) === 0) { | |||||
$parts = parse_url($referenceText); | |||||
parse_str($parts['query'], $query); | |||||
$fileId = isset($query['fileid']) ? (int)$query['fileid'] : $fileId; | |||||
$fileId = isset($query['openfile']) ? (int)$query['openfile'] : $fileId; | |||||
} | |||||
if (mb_strpos($referenceText, $startIndex) === 0) { | |||||
$parts = parse_url($referenceText); | |||||
parse_str($parts['query'], $query); | |||||
$fileId = isset($query['fileid']) ? (int)$query['fileid'] : $fileId; | |||||
$fileId = isset($query['openfile']) ? (int)$query['openfile'] : $fileId; | |||||
} | |||||
if (mb_strpos($referenceText, $this->urlGenerator->getAbsoluteURL('/index.php/f/')) === 0) { | |||||
$fileId = str_replace($this->urlGenerator->getAbsoluteURL('/index.php/f/'), '', $referenceText); | |||||
} | |||||
if (mb_strpos($referenceText, $this->urlGenerator->getAbsoluteURL('/f/')) === 0) { | |||||
$fileId = str_replace($this->urlGenerator->getAbsoluteURL('/f/'), '', $referenceText); | |||||
} | |||||
return $fileId !== null ? (int)$fileId : null; | |||||
} | } | ||||
public function resolveReference(string $referenceText): ?IReference { | public function resolveReference(string $referenceText): ?IReference { | ||||
throw new NotFoundException(); | throw new NotFoundException(); | ||||
} | } | ||||
$fileId = str_replace($this->urlGenerator->getAbsoluteURL('/index.php/f/'), '', $reference->getId()); | |||||
$fileId = str_replace($this->urlGenerator->getAbsoluteURL('/f/'), '', $fileId); | |||||
$fileId = $this->getFilesAppLinkId($reference->getId()); | |||||
if ($fileId === null) { | |||||
throw new NotFoundException(); | |||||
} | |||||
try { | try { | ||||
$userFolder = $this->rootFolder->getUserFolder($this->userId); | $userFolder = $this->rootFolder->getUserFolder($this->userId); | ||||
$files = $userFolder->getById((int)$fileId); | |||||
$files = $userFolder->getById($fileId); | |||||
if (empty($files)) { | if (empty($files)) { | ||||
throw new NotFoundException(); | throw new NotFoundException(); | ||||
} | } | ||||
} | } | ||||
public function isGloballyCacheable(): bool { | |||||
return false; | |||||
public function getCachePrefix(string $referenceId): string { | |||||
return (string)$this->getFilesAppLinkId($referenceId); | |||||
} | } | ||||
public function getCacheKey(string $referenceId): string { | |||||
public function getCacheKey(string $referenceId): ?string { | |||||
return $this->userId ?? ''; | return $this->userId ?? ''; | ||||
} | } | ||||
} | } |
$responseBody = (string)$response->getBody(); | $responseBody = (string)$response->getBody(); | ||||
$reference->setUrl($reference->getId()); | |||||
// OpenGraph handling | // OpenGraph handling | ||||
$consumer = new Consumer(); | $consumer = new Consumer(); | ||||
$consumer->useFallbackMode = true; | |||||
$object = $consumer->loadHtml($responseBody); | $object = $consumer->loadHtml($responseBody); | ||||
$reference->setUrl($reference->getId()); | |||||
if ($object->title) { | if ($object->title) { | ||||
$reference->setTitle($object->title); | $reference->setTitle($object->title); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
public function isGloballyCacheable(): bool { | |||||
return true; | |||||
public function getCachePrefix(string $referenceId): string { | |||||
return $referenceId; | |||||
} | } | ||||
public function getCacheKey(string $referenceId): string { | |||||
return ''; | |||||
public function getCacheKey(string $referenceId): ?string { | |||||
return null; | |||||
} | } | ||||
} | } |
namespace OC\Collaboration\Reference; | namespace OC\Collaboration\Reference; | ||||
use OC\AppFramework\Bootstrap\Coordinator; | use OC\AppFramework\Bootstrap\Coordinator; | ||||
use OC\Collaboration\Reference\File\FileReferenceProvider; | |||||
use OCP\Collaboration\Reference\IReference; | use OCP\Collaboration\Reference\IReference; | ||||
use OCP\Collaboration\Reference\IReferenceManager; | use OCP\Collaboration\Reference\IReferenceManager; | ||||
use OCP\Collaboration\Reference\IReferenceProvider; | use OCP\Collaboration\Reference\IReferenceProvider; | ||||
}, $references); | }, $references); | ||||
} | } | ||||
public function getReferenceFromCache(string $referenceId): ?IReference { | |||||
$matchedProvider = $this->getMatchedProvider($referenceId); | |||||
if ($matchedProvider === null) { | |||||
return null; | |||||
} | |||||
$cacheKey = $this->getFullCacheKey($matchedProvider, $referenceId); | |||||
return $this->getReferenceByCacheKey($cacheKey); | |||||
} | |||||
public function getReferenceByCacheKey(string $cacheKey): ?IReference { | public function getReferenceByCacheKey(string $cacheKey): ?IReference { | ||||
$cached = $this->cache->get($cacheKey); | $cached = $this->cache->get($cacheKey); | ||||
if ($cached) { | if ($cached) { | ||||
return null; | return null; | ||||
} | } | ||||
$cacheKey = $this->getCacheKey($matchedProvider, $referenceId); | |||||
$cacheKey = $this->getFullCacheKey($matchedProvider, $referenceId); | |||||
$cached = $this->cache->get($cacheKey); | $cached = $this->cache->get($cacheKey); | ||||
if ($cached) { | if ($cached) { | ||||
return Reference::fromCache($cached); | return Reference::fromCache($cached); | ||||
return $matchedProvider; | return $matchedProvider; | ||||
} | } | ||||
private function getCacheKey(IReferenceProvider $provider, string $referenceId): string { | |||||
return md5($referenceId) . ( | |||||
$provider->isGloballyCacheable() | |||||
? '' | |||||
: '-' . md5($provider->getCacheKey($referenceId)) | |||||
private function getFullCacheKey(IReferenceProvider $provider, string $referenceId): string { | |||||
$cacheKey = $provider->getCacheKey($referenceId); | |||||
return md5($provider->getCachePrefix($referenceId)) . ( | |||||
$cacheKey !== null ? ('-' . md5($cacheKey)) : '' | |||||
); | ); | ||||
} | } | ||||
public function invalidateCache(string $referenceId, ?string $providerCacheKey = null): void { | |||||
$matchedProvider = $this->getMatchedProvider($referenceId); | |||||
if ($matchedProvider === null) { | |||||
return; | |||||
} | |||||
if ($providerCacheKey === null) { | |||||
$this->cache->clear(md5($referenceId)); | |||||
public function invalidateCache(string $cachePrefix, ?string $cacheKey = null): void { | |||||
if ($cacheKey === null) { | |||||
$this->cache->clear(md5($cachePrefix)); | |||||
return; | return; | ||||
} | } | ||||
$this->cache->remove($this->getCacheKey($matchedProvider, $referenceId)); | |||||
$this->cache->remove(md5($cachePrefix) . '-' . md5($cacheKey)); | |||||
} | } | ||||
/** | /** |
/** | /** | ||||
* Return all reference identifiers within a string as an array | * Return all reference identifiers within a string as an array | ||||
* | * | ||||
* @return string[] Array of found references (urls) | |||||
* @since 25.0.0 | * @since 25.0.0 | ||||
*/ | */ | ||||
public function extractReferences(string $text): array; | public function extractReferences(string $text): array; | ||||
* @since 25.0.0 | * @since 25.0.0 | ||||
*/ | */ | ||||
public function resolveReference(string $referenceId): ?IReference; | public function resolveReference(string $referenceId): ?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 | |||||
*/ | |||||
public function getReferenceFromCache(string $referenceId): ?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; | |||||
} | } |
* | * | ||||
* @since 25.0.0 | * @since 25.0.0 | ||||
*/ | */ | ||||
public function isGloballyCacheable(): bool; | |||||
public function getCachePrefix(string $referenceId): string; | |||||
/** | /** | ||||
* Return a custom cache key to be used for caching the metadata | * Return a custom cache key to be used for caching the metadata | ||||
* This could be for example the current user id if the reference | * This could be for example the current user id if the reference | ||||
* access permissions are different for each user | * 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 | * @since 25.0.0 | ||||
*/ | */ | ||||
public function getCacheKey(string $referenceId): string; | |||||
public function getCacheKey(string $referenceId): ?string; | |||||
} | } |