Files
laralog/plugins/neatstudio.membership/src/ServiceProvider.php
T
ak 5f74dd76e4 feat: 文章/分类/评论关联细节完善
- 文章列表:会员插件注入「付费」徽章列(会员/付费¥xx,停插件即消失)
- 分类:真实文章数(withCount)、名称点击跳文章列表并按分类筛选(ListPosts booted 预置筛选)
- 分类:新增封面(medialibrary)+ 介绍(description 字段/表单);前台分类页顶部展示介绍区;SEO description + BreadcrumbList 结构化数据(GEO)
- 文章编辑:MarkdownEditor 内容区加大(26rem);文章编辑页新增「评论」关系管理页(可审/拒)
- 评论列表:文章列带摘要
- 三套主题补 .category-intro 样式
- 测试更新(付费徽章列)
2026-08-12 22:44:16 +08:00

136 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Plugins\Neatstudio\Membership;
use App\Blog\Support\PluginManager;
use App\Blog\Support\PluginServiceProvider;
use App\Models\Post;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Plugins\Neatstudio\Membership\Models\MembershipPlan;
use Plugins\Neatstudio\Membership\Models\Subscription;
use Plugins\Neatstudio\Payment\Models\Payment;
class ServiceProvider extends PluginServiceProvider
{
protected function boot(PluginManager $manager): void
{
$this->loadRoutes(__DIR__.'/../routes/web.php');
// 支付成功:按购买实体分发(激活会员订阅 / 解锁单篇付费文章)
$manager->addAction('payment.paid', function (Payment $payment) {
$user = $payment->user;
$payable = $payment->payable;
if (! $user || ! $payable) {
return;
}
if ($payable instanceof MembershipPlan) {
$now = now();
Subscription::create([
'user_id' => $user->id,
'membership_plan_id' => $payable->id,
'status' => 'active',
'starts_at' => $now,
'ends_at' => $now->copy()->addDays($payable->duration_days),
]);
}
if ($payable instanceof Post) {
$meta = $payable->meta ?? [];
$unlocked = $meta['unlocked_user_ids'] ?? [];
$unlocked[] = $user->id;
$payable->update(['meta' => array_merge($meta, ['unlocked_user_ids' => array_values(array_unique($unlocked))])]);
}
}, 10);
// 付费文章:[paid]...[/paid] —— 无权限访客替换为 teaser;有权限替换为付费内容块(避免原始标签裸露在页面)
$manager->addFilter('post.rendered', function (string $html, \App\Models\Post $post) {
$service = app(Services\MembershipService::class);
$user = auth()->user();
if (preg_match('/\[paid\](.*?)\[\/paid\]/s', $html)) {
// Markdown 转换会给短代码包上 <p>,先去掉,避免替换后残留未闭合标签
$html = preg_replace('/<p>\s*\[(\/?paid)\]\s*<\/p>/i', '[$1]', $html);
$html = preg_replace_callback(
'/\[paid\](.*?)\[\/paid\]/s',
function (array $m) use ($service, $user, $post) {
if ($service->canReadPaidContent($user, $post)) {
return '<div class="paid-content"><div class="paid-content-label">付费内容</div>'.trim($m[1]).'</div>';
}
return view('membership.paid-teaser', ['post' => $post])->render();
},
$html
);
}
if (! $service->canReadPost($user, $post)) {
return view('membership.paywall', ['post' => $post])->render();
}
return $html;
}, 10);
// 后台文章表单注入:付费设置(meta.price 单篇价格 + meta.members_only 会员专享)
$manager->addAction('filament.post_form', function (Schema $schema) {
$schema->components([
...$schema->getComponents(),
Section::make('付费设置')
->description('需启用支付插件并配置支付渠道;0 = 免费')
->columns(3)
->collapsible()
->schema([
TextInput::make('meta.price')
->label('单篇价格(分)')
->numeric()
->minValue(0)
->default(0)
->helperText('例:9900 = ¥99;填 0 表示单篇免费'),
Toggle::make('meta.members_only')
->label('会员专享')
->helperText('整篇文章仅会员可读'),
]),
]);
});
// 后台文章表格注入:付费标识(价格/会员徽章,停用插件后消失)
$manager->addAction('filament.post_table', function (Table $table) {
$table->pushColumns([
TextColumn::make('meta.members_only')
->label('付费')
->badge()
->formatStateUsing(function ($state, $record) {
$meta = $record->meta ?? [];
$price = (int) ($meta['price'] ?? 0);
if (! empty($meta['members_only'])) {
return '会员';
}
return $price > 0 ? '付费 ¥'.number_format($price / 100, 2) : '—';
})
->color(function ($state, $record) {
$meta = $record->meta ?? [];
if (! empty($meta['members_only'])) {
return 'warning';
}
return (int) ($meta['price'] ?? 0) > 0 ? 'danger' : 'gray';
})
->placeholder('—'),
]);
});
}
}