Implement academy analytics, billing, and web stories updates

This commit is contained in:
2026-05-26 07:27:29 +02:00
parent 456c3d6bb0
commit 0b33a1b074
177 changed files with 27360 additions and 2685 deletions

View File

@@ -136,6 +136,8 @@ final class NewsService
$categoryId = (int) ($filters['category_id'] ?? 0);
$search = trim((string) ($filters['q'] ?? ''));
$perPage = max(10, min(50, (int) ($filters['per_page'] ?? 15)));
$order = trim((string) ($filters['order'] ?? ''));
$direction = trim((string) ($filters['direction'] ?? ''));
if ($status !== '') {
$query->where('editorial_status', $status);
@@ -158,6 +160,20 @@ final class NewsService
});
}
if ($order !== '') {
$map = [
'date' => 'published_at',
'title' => 'title',
'views' => 'views',
];
if (array_key_exists($order, $map)) {
$dir = in_array(Str::lower($direction), ['asc', 'desc'], true) ? Str::lower($direction) : 'desc';
// Replace any existing ordering (editorialOrder) with the user-specified ordering.
$query->reorder($map[$order], $dir);
}
}
$paginator = $query->paginate($perPage)->withQueryString();
return [
@@ -169,6 +185,8 @@ final class NewsService
'type' => $type,
'category_id' => $categoryId > 0 ? $categoryId : '',
'per_page' => $perPage,
'order' => $order,
'direction' => in_array(Str::lower($direction), ['asc', 'desc'], true) ? Str::lower($direction) : '',
],
];
}
@@ -179,7 +197,7 @@ final class NewsService
return [
'id' => (int) $article->id,
'title' => (string) $article->title,
'title' => $this->decodeLegacyHtml((string) $article->title),
'slug' => (string) $article->slug,
'excerpt' => (string) ($article->excerpt ?? ''),
'content' => (string) ($article->content ?? ''),
@@ -421,6 +439,8 @@ final class NewsService
$title = 'Untitled News Article';
}
$slug = $this->resolveSlug($title, $article, $data);
$previousCoverImage = trim((string) ($article->cover_image ?? ''));
$editorialStatus = $this->normalizeEditorialStatus((string) ($data['editorial_status'] ?? $article->editorial_status ?? NewsArticle::EDITORIAL_STATUS_DRAFT));
@@ -429,7 +449,7 @@ final class NewsService
$article->fill([
'title' => $title,
'slug' => $this->resolveSlug($title, $article, $data),
'slug' => $slug,
'excerpt' => $this->nullableText($data['excerpt'] ?? null),
'content' => (string) ($data['content'] ?? ''),
'cover_image' => $this->nullableText($data['cover_image'] ?? null),
@@ -445,7 +465,7 @@ final class NewsService
'meta_title' => $this->nullableText($data['meta_title'] ?? null),
'meta_description' => $this->nullableText($data['meta_description'] ?? null),
'meta_keywords' => $this->nullableText($data['meta_keywords'] ?? null),
'canonical_url' => $this->nullableText($data['canonical_url'] ?? null),
'canonical_url' => route('news.show', ['slug' => $slug]),
'og_title' => $this->nullableText($data['og_title'] ?? null),
'og_description' => $this->nullableText($data['og_description'] ?? null),
'og_image' => $this->nullableText($data['og_image'] ?? null),
@@ -472,7 +492,7 @@ final class NewsService
{
return [
'id' => (int) $article->id,
'title' => (string) $article->title,
'title' => $this->decodeLegacyHtml((string) $article->title),
'slug' => (string) $article->slug,
'type' => (string) ($article->type ?? NewsArticle::TYPE_ANNOUNCEMENT),
'type_label' => (string) $article->type_label,
@@ -598,6 +618,23 @@ final class NewsService
Storage::disk((string) config('uploads.object_storage.disk', 's3'))->delete($paths);
}
private function decodeLegacyHtml(string $value): string
{
$decoded = $value;
for ($pass = 0; $pass < 5; $pass++) {
$next = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5, 'UTF-8');
if ($next === $decoded) {
break;
}
$decoded = $next;
}
return str_replace(['´', '&acute;'], ["'", "'"], $decoded);
}
private function searchGroups(string $query, ?User $viewer): array
{
return Group::query()