From 111915af8d06ea6eabf990d7199712aff9ea21ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ca=C5=82ka?= Date: Fri, 16 Jun 2023 15:59:36 +0200 Subject: [PATCH] feat: Purge updated discussion tags only --- composer.json | 11 ++- extend.php | 11 +++ src/Abstract/PurgeMiddleware.php | 82 +++++++++++++++++++ .../FlarumTags/FlarumTagsPurgeMiddleware.php | 82 +++++++++++++++++++ .../FofMasquerade/Middleware.php | 36 ++++++++ src/Middleware/LSCachePurgeMiddleware.php | 63 ++++---------- src/Middleware/LSTagsMiddleware.php | 42 ++++++++-- 7 files changed, 269 insertions(+), 58 deletions(-) create mode 100644 src/Abstract/PurgeMiddleware.php create mode 100644 src/Compatibility/FlarumTags/FlarumTagsPurgeMiddleware.php create mode 100644 src/Compatibility/FofMasquerade/Middleware.php diff --git a/composer.json b/composer.json index 5fed748..0d36a9e 100644 --- a/composer.json +++ b/composer.json @@ -21,11 +21,12 @@ } ], "require": { - "flarum/core": "^1.7", + "flarum/core": "^1.8", "php": ">=8.0" }, "require-dev": { - "flarum/phpstan": "^1.7" + "flarum/phpstan": "^1.8", + "flarum/tags": "*" }, "suggest": { "blomstra/flarum-redis": "This library allows using Redis as cache, session and for the queue. https://github.com/blomstra/flarum-redis#set-up" @@ -50,7 +51,11 @@ "name": "fas fa-bolt", "backgroundColor": "#2b82d9", "color": "#fff" - } + }, + "optional-dependencies": [ + "flarum/tags", + "fof/masquerade" + ] }, "flarum-cli": { "modules": { diff --git a/extend.php b/extend.php index 0195f6b..6e6b661 100644 --- a/extend.php +++ b/extend.php @@ -14,6 +14,8 @@ use ACPL\FlarumCache\Api\Controller\LSCacheCsrfResponseController; use ACPL\FlarumCache\Api\Controller\PurgeLSCacheController; use ACPL\FlarumCache\Command\LSCacheClearCommand; +use ACPL\FlarumCache\Compatibility\FlarumTags\FlarumTagsPurgeMiddleware; +use ACPL\FlarumCache\Compatibility\FofMasquerade\Middleware as FofMasqueradeMiddleware; use ACPL\FlarumCache\Listener\ClearingCacheListener; use ACPL\FlarumCache\Middleware\LoginMiddleware; use ACPL\FlarumCache\Middleware\LogoutMiddleware; @@ -68,4 +70,13 @@ (new Extend\Routes('api'))->get('/lscache-purge', 'lscache.purge', PurgeLSCacheController::class), (new Extend\Console())->command(LSCacheClearCommand::class), (new Extend\Event())->listen(ClearingCache::class, ClearingCacheListener::class), + + // Extensions + (new Extend\Conditional) + ->whenExtensionEnabled('flarum-tags', [ + (new Extend\Middleware('api'))->add(FlarumTagsPurgeMiddleware ::class), + ]) + ->whenExtensionEnabled('fof-masquerade', [ + (new Extend\Middleware('api'))->add(FofMasqueradeMiddleware::class), + ]) ]; diff --git a/src/Abstract/PurgeMiddleware.php b/src/Abstract/PurgeMiddleware.php new file mode 100644 index 0000000..92e5624 --- /dev/null +++ b/src/Abstract/PurgeMiddleware.php @@ -0,0 +1,82 @@ +settings = $settings; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $response = $handler->handle($request); + + if ( + ! in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE']) + || $response->getStatusCode() >= 400 + ) { + return $response; + } + + $this->currentRouteName = $request->getAttribute('routeName'); + $this->isDiscussion = str_starts_with($this->currentRouteName, 'discussions'); + $this->isPost = str_starts_with($this->currentRouteName, 'posts'); + + // If this is just an update of the last read post, there is no point in clearing the public cache + if ($this->isDiscussion && Arr::get( + $request->getParsedBody(), + 'data.attributes.lastReadPostNumber') + ) { + return $response; + } + + + return $this->processPurge($request, $handler, $response); + } + + abstract protected function processPurge( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ResponseInterface $response + ): ResponseInterface; + + + protected function addPurgeParamsToResponse(ResponseInterface $response, array $newPurgeParams): ResponseInterface + { + if ($response->hasHeader(LSCacheHeadersEnum::PURGE)) { + $existingPurgeParams = explode(',', $response->getHeaderLine(LSCacheHeadersEnum::PURGE)); + $newPurgeParams = array_unique(array_merge($existingPurgeParams, $newPurgeParams)); + } + + if (count($newPurgeParams) < 1) { + return $response; + } + + if ($this->settings->get('acpl-lscache.serve_stale') && ! array_key_exists('stale', $newPurgeParams)) { + array_unshift($newPurgeParams, 'stale'); + } + + return $response->withHeader(LSCacheHeadersEnum::PURGE, implode(',', $newPurgeParams)); + } + + protected function getRouteParams($request): array + { + return $request->getAttribute('routeParameters'); + } +} diff --git a/src/Compatibility/FlarumTags/FlarumTagsPurgeMiddleware.php b/src/Compatibility/FlarumTags/FlarumTagsPurgeMiddleware.php new file mode 100644 index 0000000..d82e2bc --- /dev/null +++ b/src/Compatibility/FlarumTags/FlarumTagsPurgeMiddleware.php @@ -0,0 +1,82 @@ +url = $url; + parent::__construct($settings); + } + + protected function processPurge( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ResponseInterface $response + ): ResponseInterface { + $isDiscussion = $this->isDiscussion; + $isPost = $this->isPost; + + if (! $isDiscussion && ! $isPost) { + return $response; + } + + $body = $request->getParsedBody(); + $routeName = $this->currentRouteName; + + // When a post is edited, there is no need to purge tags cache unless the post is being hidden + if ($routeName === 'posts.update' && ! Arr::has($body, 'data.attributes.isHidden')) { + return $response; + } + + $payload = $response->getBody()->getContents(); + $payload = json_decode($payload, true); + + if ($isDiscussion) { + $discussionId = Arr::get($payload, 'data.id'); + } else { + $discussionId = Arr::get($payload, 'data.relationships.discussion.data.id'); + if (! $discussionId) { + $postId = Arr::get($payload, 'data.id'); + $discussionId = Post::find($postId)->discussion_id; + } + } + + $discussion = Discussion::find($discussionId); + if (! $discussion) { + return $response; + } + + /** + * @var Tag[] $tags + * @phpstan-ignore-next-line + */ + $tags = $discussion->tags; + + if (! $tags) { + return $response; + } + + $purgeParams = ['tags.index', 'tags']; + + foreach ($tags as $tag) { + $purgeParams[] = "tag_$tag->slug"; + } + + return $this->addPurgeParamsToResponse($response, $purgeParams); + } +} diff --git a/src/Compatibility/FofMasquerade/Middleware.php b/src/Compatibility/FofMasquerade/Middleware.php new file mode 100644 index 0000000..288229c --- /dev/null +++ b/src/Compatibility/FofMasquerade/Middleware.php @@ -0,0 +1,36 @@ +currentRouteName === 'masquerade.api.configure.save') { + $user = RequestUtil::getActor($request); + return $this->addPurgeParamsToResponse( + $response, + [ + "tag=user_$user->id", + "tag=users_$user->id", + "tag=user_$user->username", + "tag=users_$user->username", + "tag=masquerade_$user->id", + ] + ); + } + + return $response; + } +} diff --git a/src/Middleware/LSCachePurgeMiddleware.php b/src/Middleware/LSCachePurgeMiddleware.php index e7cc3a3..4576ee3 100644 --- a/src/Middleware/LSCachePurgeMiddleware.php +++ b/src/Middleware/LSCachePurgeMiddleware.php @@ -2,8 +2,10 @@ namespace ACPL\FlarumCache\Middleware; +use ACPL\FlarumCache\Abstract\PurgeMiddleware; use ACPL\FlarumCache\LSCache; use ACPL\FlarumCache\LSCacheHeadersEnum; +use Flarum\Discussion\Discussion; use Flarum\Http\RequestUtil; use Flarum\Post\Post; use Flarum\Settings\SettingsRepositoryInterface; @@ -14,53 +16,34 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -class LSCachePurgeMiddleware implements MiddlewareInterface +class LSCachePurgeMiddleware extends PurgeMiddleware { - private SettingsRepositoryInterface $settings; + protected function processPurge( + ServerRequestInterface $request, + RequestHandlerInterface $handler, + ResponseInterface $response + ): ResponseInterface { + $routeName = $this->currentRouteName; - public function __construct(SettingsRepositoryInterface $settings) - { - $this->settings = $settings; - } - - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - $response = $handler->handle($request); + $purgeParams = []; - if ( - ! in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE']) || - $response->hasHeader(LSCacheHeadersEnum::PURGE) || - $response->getStatusCode() >= 400 - ) { - return $response; - } + $params = $this->getRouteParams($request); - $routeName = $request->getAttribute('routeName'); + $isDiscussion = $this->isDiscussion; + $isPost = $this->isPost; - $isDiscussion = Str::startsWith($routeName, 'discussions'); $body = $request->getParsedBody(); - // If this is just an update of the last read post, there is no point in clearing the public cache - if ($isDiscussion && Arr::get($body, 'data.attributes.lastReadPostNumber')) { - return $response; - } - - $purgeParams = []; - - $params = $request->getAttribute('routeParameters'); - - $isPost = Str::startsWith($routeName, 'posts'); - if ($isDiscussion || $isPost) { $purgeList = $this->settings->get('acpl-lscache.purge_on_discussion_update'); if (! empty($purgeList)) { $purgeList = explode("\n", $purgeList); // Get only valid items - $purgeList = array_filter($purgeList, fn ($item) => Str::startsWith($item, ['/', 'tag='])); + $purgeList = array_filter($purgeList, fn($item) => Str::startsWith($item, ['/', 'tag='])); $purgeParams = array_merge($purgeParams, $purgeList); } - // If this is a post update, we don't need to clear the home page cache + // If this is a post update, we don't need to clear the home page cache unless the post is hidden $isPostUpdate = $routeName === 'posts.update'; if (($isPostUpdate && Arr::has($body, 'data.attributes.isHidden')) || ! $isPostUpdate) { array_push($purgeParams, 'tag=default', 'tag=index', 'tag=discussions.index'); @@ -97,20 +80,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } } - //Clear user profile cache when updating FriendsOfFlarum/masquerade fields - if ($routeName === 'masquerade.api.configure.save') { - $user = RequestUtil::getActor($request); - $purgeParams[] = "tag=users$user->id,tag=masquerade$user->id"; - } - - if (count($purgeParams) < 1) { - return $response; - } - - if ($this->settings->get('acpl-lscache.serve_stale')) { - array_unshift($purgeParams, 'stale'); - } - - return $response->withHeader(LSCacheHeadersEnum::PURGE, implode(',', $purgeParams)); + return $this->addPurgeParamsToResponse($response, $purgeParams); } } diff --git a/src/Middleware/LSTagsMiddleware.php b/src/Middleware/LSTagsMiddleware.php index f03c6ae..26fe96e 100644 --- a/src/Middleware/LSTagsMiddleware.php +++ b/src/Middleware/LSTagsMiddleware.php @@ -4,6 +4,7 @@ use ACPL\FlarumCache\LSCache; use ACPL\FlarumCache\LSCacheHeadersEnum; +use Laminas\Diactoros\Response\HtmlResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -15,23 +16,48 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface { $response = $handler->handle($request); - if (! in_array($request->getMethod(), ['GET', 'HEAD']) || $response->hasHeader(LSCacheHeadersEnum::TAG)) { + if (! in_array($request->getMethod(), ['GET', 'HEAD'])) { return $response; } $routeName = $request->getAttribute('routeName'); - $rootRouteName = LSCache::extractRootRouteName($routeName); $params = $request->getAttribute('routeParameters'); - $lsTagsString = $routeName; + $tagParams = [$routeName]; + + if (! empty($params)) { + $rootRouteName = LSCache::extractRootRouteName($routeName); + + // Discussion + if (! empty($params['id'])) { + // The id parameter contains the slug. We only need id (int) + $id = explode('-', $params['id'], 2)[0]; + if (! empty($id)) { + $tagParams[] = "{$rootRouteName}_$id"; + } + } + + // User profile + if (! empty($params['username'])) { + $tagParams[] = "{$rootRouteName}_{$params['username']}"; + } + + // Slugs, eg. tag slug + if (! empty($params['slug'])) { + $tagParams[] = "{$rootRouteName}_{$params['slug']}"; + } + } - if (! empty($params) && ! empty($params['id'])) { - // The id parameter contains the slug. We only need the id (int) - $id = explode('-', $params['id'], 2)[0]; - $lsTagsString .= ",$rootRouteName".$id; + if ($response->hasHeader(LSCacheHeadersEnum::TAG)) { + $tagParams = array_merge( + explode(',', $response->getHeaderLine(LSCacheHeadersEnum::TAG)), + $tagParams + ); } - return $response->withHeader(LSCacheHeadersEnum::TAG, $lsTagsString); + $tagParams = array_unique($tagParams); + + return $response->withHeader(LSCacheHeadersEnum::TAG, implode(',', $tagParams)); } }