Allow heading tags (h1-h6) in ContentSanitizer so news editor headings render

This commit is contained in:
2026-06-04 07:52:57 +02:00
parent 0b33a1b074
commit 15870ddb1f
191 changed files with 15453 additions and 1786 deletions

View File

@@ -92,6 +92,121 @@ final class AcademyAccessService
return $this->activeAcademySubscription($user) instanceof Subscription;
}
/**
* @return array<string, mixed>
*/
public function accessSummary(?User $user): array
{
if (! $user instanceof User) {
return [
'signedIn' => false,
'tier' => 'free',
'tierLabel' => 'Guest',
'hasPaidAccess' => false,
'status' => 'guest',
'statusLabel' => 'Preview access only',
'expiresAt' => null,
'dateLabel' => null,
'renewsAutomatically' => false,
'source' => 'none',
];
}
if ($this->isAcademyAdmin($user)) {
return [
'signedIn' => true,
'tier' => 'admin',
'tierLabel' => 'Admin',
'hasPaidAccess' => true,
'status' => 'staff_access',
'statusLabel' => 'Full staff access',
'expiresAt' => null,
'dateLabel' => null,
'renewsAutomatically' => false,
'source' => 'admin',
];
}
$tier = $this->currentTier($user);
$subscription = $this->activeAcademySubscription($user);
if ($subscription instanceof Subscription) {
$trialEndsAt = $subscription->trial_ends_at?->toISOString();
$endsAt = $subscription->ends_at?->toISOString();
if ($subscription->onGracePeriod()) {
return [
'signedIn' => true,
'tier' => $tier,
'tierLabel' => $this->tierLabel($tier),
'hasPaidAccess' => $tier !== 'free',
'status' => 'grace_period',
'statusLabel' => 'Cancels soon',
'expiresAt' => $endsAt,
'dateLabel' => 'Access ends',
'renewsAutomatically' => false,
'source' => 'subscription',
];
}
if ($subscription->onTrial()) {
return [
'signedIn' => true,
'tier' => $tier,
'tierLabel' => $this->tierLabel($tier),
'hasPaidAccess' => $tier !== 'free',
'status' => 'trialing',
'statusLabel' => 'Trial active',
'expiresAt' => $trialEndsAt,
'dateLabel' => 'Trial ends',
'renewsAutomatically' => ! $subscription->cancelled(),
'source' => 'subscription',
];
}
return [
'signedIn' => true,
'tier' => $tier,
'tierLabel' => $this->tierLabel($tier),
'hasPaidAccess' => $tier !== 'free',
'status' => 'active',
'statusLabel' => 'Renews automatically',
'expiresAt' => null,
'dateLabel' => null,
'renewsAutomatically' => true,
'source' => 'subscription',
];
}
if ($tier !== 'free') {
return [
'signedIn' => true,
'tier' => $tier,
'tierLabel' => $this->tierLabel($tier),
'hasPaidAccess' => true,
'status' => 'active',
'statusLabel' => 'Full access active',
'expiresAt' => null,
'dateLabel' => null,
'renewsAutomatically' => false,
'source' => 'legacy_role',
];
}
return [
'signedIn' => true,
'tier' => 'free',
'tierLabel' => 'Free',
'hasPaidAccess' => false,
'status' => 'free',
'statusLabel' => 'Free access',
'expiresAt' => null,
'dateLabel' => null,
'renewsAutomatically' => false,
'source' => 'none',
];
}
public function canAccessLesson(?User $user, AcademyLesson $lesson): bool
{
return $this->canAccessContent($user, (string) $lesson->access_level);
@@ -633,6 +748,16 @@ final class AcademyAccessService
};
}
private function tierLabel(string $tier): string
{
return match ($this->normalizeAccessLevel($tier)) {
'admin' => 'Admin',
'pro' => 'Pro',
'creator' => 'Creator',
default => 'Free',
};
}
private function isAcademyAdmin(User $user): bool
{
return $user->hasStaffAccess() || $user->isModerator();

View File

@@ -42,6 +42,9 @@ final class AcademyAnalyticsContentResolver
if (! $contentId) {
return match ($contentType) {
AcademyAnalyticsContentType::HOME => 'Academy Home',
AcademyAnalyticsContentType::PROMPT_LIBRARY => 'Prompt Library',
AcademyAnalyticsContentType::PROMPT_POPULAR => 'Popular Prompts',
AcademyAnalyticsContentType::PROMPT_PACK_LIBRARY => 'Prompt Pack Library',
AcademyAnalyticsContentType::SEARCH => 'Academy Search',
AcademyAnalyticsContentType::UPGRADE => 'Academy Upgrade',
default => 'Unknown Academy Content',

View File

@@ -45,7 +45,7 @@ final class AcademyPopularityService
public function queryBetween(Carbon $from, Carbon $to): Builder
{
return AcademyContentMetricDaily::query()
->whereBetween('date', [$from->toDateString(), $to->toDateString()]);
->whereBetween('date', [$from->copy()->startOfDay(), $to->copy()->endOfDay()]);
}
public function topContent(Carbon $from, Carbon $to, int $limit = 10): Collection