From c231f4af967f1a82c97173356078a124034a745a Mon Sep 17 00:00:00 2001 From: ak Date: Wed, 12 Aug 2026 17:48:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20[paid]=20=E8=A3=B8=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E6=B3=84=E6=BC=8F=E2=80=94=E2=80=94=E6=A0=B8=E5=BF=83=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=99=A8=E5=85=9C=E5=BA=95=E5=89=A5=E7=A6=BB=20+=20?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=20dev=20=E5=BA=93=E6=8F=92=E4=BB=B6=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因:dev 数据库 membership 插件被停用(PluginRecord enabled 为空),其 post.rendered 过滤器未运行,[paid] 标签对所有访客裸露;与缓存无关 - 已恢复 membership 启用并清页面缓存,实测游客看到 teaser - 防御:PostContentRenderer 渲染后兜底剥离裸 [paid]/[/paid] 标签,即使会员插件被停用也不会泄漏到页面 - 回归测试:渲染结果永不含 [paid] --- app/Blog/Services/PostContentRenderer.php | 5 ++++- tests/Feature/MembershipFlowTest.php | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/Blog/Services/PostContentRenderer.php b/app/Blog/Services/PostContentRenderer.php index ee2e558..c01a381 100644 --- a/app/Blog/Services/PostContentRenderer.php +++ b/app/Blog/Services/PostContentRenderer.php @@ -43,7 +43,10 @@ class PostContentRenderer $html = $this->renderShortcodes($content, $post); // 插件钩子:付费内容过滤、AI 处理等 - return app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post); + $html = app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post); + + // 兜底:任何情况下不把裸 [paid] 标签输出到页面(如会员插件被停用时) + return preg_replace('/\[(\/?paid)\]/i', '', $html); } /** diff --git a/tests/Feature/MembershipFlowTest.php b/tests/Feature/MembershipFlowTest.php index 50db5b5..83f8907 100644 --- a/tests/Feature/MembershipFlowTest.php +++ b/tests/Feature/MembershipFlowTest.php @@ -218,6 +218,23 @@ class MembershipFlowTest extends TestCase ->assertSee('隐藏付费块'); } + public function test_rendered_paid_content_never_leaks_raw_tags(): void + { + $post = Post::create([ + 'title' => '渲染防泄漏', + 'slug' => 'render-no-leak', + 'content' => "公开\n\n[paid]\n隐藏内容\n[/paid]", + 'content_format' => 'markdown', + 'status' => 'published', + 'published_at' => now(), + ]); + + $html = app(\App\Blog\Services\PostContentRenderer::class)->render($post); + + $this->assertStringNotContainsString('[paid]', $html); + $this->assertStringNotContainsString('[/paid]', $html); + } + public function test_members_only_post_shows_paywall_to_guest(): void { $post = Post::create([