absoluteUrl(route($name, $parameters, false)); } public function absoluteUrl(string $path): string { if (Str::startsWith($path, ['https://', 'http://'])) { return $path; } return rtrim(config('app.url'), '/').'/'.ltrim($path, '/'); } /** @return array */ public function forRequest(Request $request): array { $name = config('moonwell.realm.server_name', 'MoonWell'); $name = strcasecmp($name, 'MoonWell') === 0 ? 'MoonWell' : $name; $version = config('moonwell.realm.client_version', '1.12.1'); $rate = config('moonwell.realm.rates.experience', 'x1'); $home = $this->routeUrl('landing.index'); $route = $request->route()?->getName(); $article = $request->route('news'); $indexable = in_array($route, ['landing.index', 'news.index', 'news.show'], true); $title = match ($route) { 'landing.index' => "{$name} — PvP сервер WoW Vanilla {$version} {$rate}", 'news.index' => "Новости сервера WoW {$version} — {$name}", 'news.show' => $article instanceof News ? "{$article->title} — {$name}" : $name, 'legal.offer' => "Публичная оферта — {$name}", 'legal.privacy' => "Политика обработки персональных данных — {$name}", 'legal.rules' => "Правила проекта — {$name}", 'login' => "Вход в личный кабинет — {$name}", 'cabinet.index' => "Личный кабинет — {$name}", 'admin.accounts.index' => "Аккаунты и инвайты — {$name}", 'admin.balances.index' => "Слёзы Элуны — {$name}", 'admin.shop.index' => "Магазин — {$name}", 'admin.news.index' => "Новости — {$name}", 'admin.login-screen-news.index' => "Новости WoW — {$name}", default => $name, }; $description = match ($route) { 'landing.index' => "{$name} — русскоязычный PvP сервер World of Warcraft Vanilla {$version} с рейтами {$rate}. Классический Азерот, подземелья и рейды. Создай аккаунт и начни играть.", 'news.index' => "Новости PvP сервера WoW Vanilla {$version} {$name}: обновления, события, патчноуты и объявления проекта.", 'news.show' => $article instanceof News ? Str::limit(Str::squish(html_entity_decode(strip_tags($article->body), ENT_QUOTES | ENT_HTML5, 'UTF-8')), 160) : '', 'legal.offer' => "Публичная оферта о заключении договора возмездного оказания цифровых услуг {$name}.", 'legal.privacy' => "Политика обработки и защиты данных пользователей {$name}.", 'legal.rules' => "Запрещённые действия, меры ответственности и порядок обжалования санкций в проекте {$name}.", 'login' => "Вход в личный кабинет игрового сервера {$name}.", 'cabinet.index' => "Персонажи, баланс и настройки игрового аккаунта {$name}.", default => '', }; $canonical = match ($route) { 'landing.index' => $home, 'news.index' => $this->routeUrl('news.index', Paginator::resolveCurrentPage() > 1 ? ['page' => Paginator::resolveCurrentPage()] : []), 'news.show' => $article instanceof News ? $this->routeUrl('news.show', $article) : null, default => null, }; if ($route === 'news.index' && Paginator::resolveCurrentPage() > 1) { $title .= ' — страница '.Paginator::resolveCurrentPage(); } $image = $this->absoluteUrl($article instanceof News && $article->image_url ? $article->image_url : '/landing.png'); $graph = []; if ($indexable) { $graph[] = [ '@type' => 'WebSite', '@id' => $home.'#website', 'url' => $home, 'name' => $name, 'alternateName' => 'Лунный колодец', 'inLanguage' => 'ru-RU', ]; $graph[] = [ '@type' => $route === 'news.index' ? 'CollectionPage' : 'WebPage', '@id' => $canonical.'#webpage', 'url' => $canonical, 'name' => $title, 'description' => $description, 'inLanguage' => 'ru-RU', 'isPartOf' => ['@id' => $home.'#website'], ]; if ($route === 'news.show' && $article instanceof News) { $graph[] = array_filter([ '@type' => 'NewsArticle', 'headline' => $article->title, 'description' => $description, 'image' => $image, 'datePublished' => $article->created_at?->toIso8601String(), 'dateModified' => $article->updated_at?->toIso8601String(), 'inLanguage' => 'ru-RU', 'mainEntityOfPage' => ['@id' => $canonical.'#webpage'], 'author' => ['@type' => 'Organization', 'name' => $name, 'url' => $home], 'publisher' => ['@type' => 'Organization', 'name' => $name, 'url' => $home], ], static fn (mixed $value): bool => $value !== null); } } $seo = [ 'title' => $title, 'description' => $description, 'canonical' => $canonical, 'robots' => $indexable && config('seo.indexable') ? 'index, follow, max-image-preview:large' : 'noindex, follow', 'type' => $route === 'news.show' ? 'article' : 'website', 'image' => $image, 'site_name' => $name, 'published_at' => $route === 'news.show' && $article instanceof News ? $article->created_at?->toIso8601String() : null, 'modified_at' => $route === 'news.show' && $article instanceof News ? $article->updated_at?->toIso8601String() : null, 'structured_data' => $graph ? ['@context' => 'https://schema.org', '@graph' => $graph] : null, ]; // Inertia manages these same server-rendered tags on subsequent visits. $seo['head'] = array_map('trim', preg_split('/\R(?=<)/', trim(view('seo.head', ['seo' => $seo])->render()))); return $seo; } }