{!! SEOMeta::generate() !!} {!! OpenGraph::generate() !!} {!! Twitter::generate() !!} {{-- ĐƯA CSS LÊN TRƯỚC ĐỂ TRÁNH MẤT GIAO DIỆN --}} @include('partials.css') @php // 1. Lấy dữ liệu thực thể chuẩn từ Controller $currentData = $data ?? $article ?? $post ?? $category ?? null; // 2. Dùng đúng SEOService đang chạy $seoService = app(\Modules\Core\Services\SEOService::class); // 3. Mỗi trang chỉ lấy MỘT nguồn JSON-LD: // - Trang chủ : GlobalSchema // - Trang khác : PageSchema $isHomePage = request()->is('/'); $schema = $isHomePage ? $seoService->generateGlobalSchemas() : $seoService->generatePageSchemas($currentData); /** * Chuẩn hóa JSON-LD ở lớp cuối cùng. * * Kiến trúc canonical: * - Organization duy nhất: https://bongda.dev/#organization * - Person duy nhất : https://bongda.dev/nguyen-tu-dev/#person * - WebSite duy nhất : https://bongda.dev/#website * * Trang chủ: * - Organization/Person/WebSite được giữ đầy đủ thông tin entity. * * Trang trong: * - Các entity dùng @id tham chiếu tới node canonical. * - Không tạo NewsMediaOrganization trùng với #organization. * * Quan trọng: * - Không xóa các schema page-specific như Article, NewsArticle, * ProfilePage, BreadcrumbList, CollectionPage... * - Không tự ý thay đổi title/description của page schema. */ $normalizeJsonLd = function ($schemaHtml) use ($isHomePage) { if (empty($schemaHtml) || !is_string($schemaHtml)) { return ''; } $baseUrl = rtrim(url('/'), '/') . '/'; $personId = $baseUrl . 'nguyen-tu-dev/#person'; $organizationId = $baseUrl . '#organization'; $websiteId = $baseUrl . '#website'; $logoId = $baseUrl . '#logo'; $siteName = \Modules\System\Entities\Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu'; $siteDescription = \Modules\System\Entities\Setting::getValue('site_description') ?: 'BongDaDev là nền tảng số hóa dữ liệu và phân tích bóng đá, cung cấp thông tin thời gian thực, thống kê phong độ và dữ liệu bóng đá chuyên sâu.'; // Canonical Person fallback / homepage entity. $personNode = [ '@type' => 'Person', '@id' => $personId, 'name' => 'Nguyễn Tú Dev', 'alternateName' => [ 'Nguyễn Văn Tú', 'Nguyen Tu Dev', 'Admin Bongda.dev' ], 'url' => $baseUrl . 'nguyen-tu-dev/', 'jobTitle' => 'Founder & Chief Football Data Analyst', 'image' => [ '@type' => 'ImageObject', '@id' => $baseUrl . 'nguyen-tu-dev/#author-image', 'url' => $baseUrl . 'zoom/156x0/uploads/images/admin/2026/07/18/nguyen-tu-dev-1784357378.jpeg', 'contentUrl' => $baseUrl . 'zoom/156x0/uploads/images/admin/2026/07/18/nguyen-tu-dev-1784357378.jpeg', 'caption' => 'Nguyễn Tú Dev - Founder & Chief Football Data Analyst' ], 'worksFor' => [ '@id' => $organizationId ], 'knowsAbout' => [ 'Football Data Architecture', 'Sports Analytics', 'Predictive Modeling', 'Tactical Performance Tracking' ], 'sameAs' => [ 'https://www.facebook.com/nguyentudev/' ] ]; // Canonical Organization. // Không dùng NewsMediaOrganization cho node #organization. $organizationNode = [ '@type' => 'Organization', '@id' => $organizationId, 'name' => $siteName, 'alternateName' => [ 'BongDaDev', 'BDD' ], 'legalName' => 'Công ty TNHH Thương mại và Đầu tư Nguyễn Tú', 'url' => $baseUrl, 'description' => $siteDescription, 'mainEntityOfPage' => [ '@id' => $websiteId ], 'logo' => [ '@type' => 'ImageObject', '@id' => $logoId, 'url' => $baseUrl . 'uploads/images/setting/admin/2026/07/11/logo-bongdadev-1783726178.webp', 'contentUrl' => $baseUrl . 'uploads/images/setting/admin/2026/07/11/logo-bongdadev-1783726178.webp', 'width' => 600, 'height' => 60, 'caption' => 'BongDaDev Logo' ], 'telephone' => '+84-906818182', 'email' => 'nguyentudev@bongda.dev', 'vatID' => '3702568166', 'address' => [ '@type' => 'PostalAddress', 'streetAddress' => '01 Hùng Vương, Khu phố Mỹ Phước, Xã Đại Phước', 'addressLocality' => 'Thành phố Hồ Chí Minh', 'addressRegion' => 'Hồ Chí Minh', 'postalCode' => '700000', 'addressCountry' => 'VN' ], 'contactPoint' => [ '@type' => 'ContactPoint', 'telephone' => '+84-906818182', 'contactType' => 'customer service', 'email' => 'nguyentudev@bongda.dev', 'availableLanguage' => [ 'vi', 'en' ] ], 'founder' => [ '@id' => $personId ], 'knowsAbout' => [ 'Football Analytics', 'Sports Data Science', 'Expected Goals (xG)', 'Tactical Analysis', 'Premier League Statistics', 'Big Data Football Metrics' ], 'publishingPrinciples' => $baseUrl . 'chinh-sach-quan-ly/', 'sameAs' => [ 'https://www.tiktok.com/@bongdadev', 'https://www.linkedin.com/in/bongda-dev-686660420/', 'https://www.pinterest.com/bongdadev/', 'https://x.com/bongdadev', 'https://www.youtube.com/@Bongdadev', 'https://www.facebook.com/bongdadev' ] ]; // Canonical WebSite. $websiteNode = [ '@type' => 'WebSite', '@id' => $websiteId, 'url' => $baseUrl, 'name' => $siteName, 'alternateName' => [ 'BongDaDev', 'BDD' ], 'description' => $siteDescription, 'inLanguage' => 'vi-VN', 'publisher' => [ '@id' => $organizationId ] ]; $scripts = []; preg_match_all( '~]*type=["\']application/ld\+json["\'][^>]*>(.*?)~is', $schemaHtml, $matches ); if (!empty($matches[1])) { foreach ($matches[1] as $jsonText) { $payload = json_decode(trim($jsonText), true); if (!is_array($payload) || !isset($payload['@graph']) || !is_array($payload['@graph'])) { continue; } $scripts[] = $payload; } } if (empty($scripts)) { return ''; } $graph = []; $nodeIndex = []; foreach ($scripts as $payload) { foreach ($payload['@graph'] as $node) { if (!is_array($node)) { continue; } $nodeId = $node['@id'] ?? null; if ($nodeId) { if (!isset($nodeIndex[$nodeId])) { $nodeIndex[$nodeId] = count($graph); $graph[] = $node; } else { $existingIndex = $nodeIndex[$nodeId]; // Gộp node trùng @id để giữ thông tin đầy đủ. $graph[$existingIndex] = array_replace_recursive( $graph[$existingIndex], $node ); } } else { $graph[] = $node; } } } $hasPerson = false; $hasOrganization = false; $hasWebsite = false; foreach ($graph as &$node) { if (!is_array($node)) { continue; } $nodeId = $node['@id'] ?? null; $type = $node['@type'] ?? null; $types = is_array($type) ? $type : [$type]; /* * Canonical Organization: * luôn là Organization, kể cả SEOService cũ còn trả * NewsMediaOrganization. */ if ($nodeId === $organizationId) { $node['@type'] = 'Organization'; $types = ['Organization']; } if (in_array('Person', $types, true) && $nodeId === $personId) { $hasPerson = true; } if (in_array('Organization', $types, true) && $nodeId === $organizationId) { $hasOrganization = true; } if (in_array('WebSite', $types, true) && $nodeId === $websiteId) { $hasWebsite = true; } // Không cho phép NewsMediaOrganization tồn tại như một // entity canonical trùng #organization. if ($nodeId === $organizationId) { $node['@type'] = 'Organization'; } // WebSite luôn publish bởi Organization canonical. if (in_array('WebSite', $types, true) && $nodeId === $websiteId) { $node['publisher'] = [ '@id' => $organizationId ]; } // NewsArticle luôn dùng canonical Person + Organization. if (in_array('NewsArticle', $types, true)) { $node['author'] = [ '@id' => $personId ]; $node['publisher'] = [ '@id' => $organizationId ]; } // Chuẩn hóa các reference canonical nếu node khác đang // nhúng đầy đủ Person/Organization. if (isset($node['author']) && is_array($node['author']) && ($node['author']['@id'] ?? null) === $personId) { $node['author'] = [ '@id' => $personId ]; } if (isset($node['publisher']) && is_array($node['publisher']) && ($node['publisher']['@id'] ?? null) === $organizationId) { $node['publisher'] = [ '@id' => $organizationId ]; } // Founder/worksFor cũng dùng reference canonical. if (isset($node['founder']) && is_array($node['founder']) && ($node['founder']['@id'] ?? null) === $personId) { $node['founder'] = [ '@id' => $personId ]; } if (isset($node['worksFor']) && is_array($node['worksFor']) && ($node['worksFor']['@id'] ?? null) === $organizationId) { $node['worksFor'] = [ '@id' => $organizationId ]; } } unset($node); /* * Trang chủ: canonical Organization/Person/WebSite được làm giàu * bằng bộ thuộc tính entity chuẩn ở trên. * * array_replace_recursive theo hướng canonical -> existing giúp * giữ dữ liệu page/service thực tế khi có giá trị riêng. */ if ($isHomePage) { if ($hasOrganization) { foreach ($graph as $index => $node) { if (($node['@id'] ?? null) === $organizationId) { $graph[$index] = array_replace_recursive( $organizationNode, $node ); $graph[$index]['@type'] = 'Organization'; break; } } } else { $graph[] = $organizationNode; } if ($hasPerson) { foreach ($graph as $index => $node) { if (($node['@id'] ?? null) === $personId) { $graph[$index] = array_replace_recursive( $personNode, $node ); $graph[$index]['@type'] = 'Person'; break; } } } else { $graph[] = $personNode; } if ($hasWebsite) { foreach ($graph as $index => $node) { if (($node['@id'] ?? null) === $websiteId) { $graph[$index] = array_replace_recursive( $websiteNode, $node ); $graph[$index]['@type'] = 'WebSite'; $graph[$index]['publisher'] = [ '@id' => $organizationId ]; break; } } } else { $graph[] = $websiteNode; } } else { /* * Trang trong: * Chỉ thêm fallback tối thiểu nếu SEOService không tạo entity. * Không bơm toàn bộ thông tin Organization vào mọi trang. */ if (!$hasOrganization) { $graph[] = [ '@type' => 'Organization', '@id' => $organizationId, 'name' => $siteName, 'url' => $baseUrl ]; } if (!$hasPerson) { $graph[] = [ '@type' => 'Person', '@id' => $personId, 'name' => 'Nguyễn Tú Dev', 'url' => $baseUrl . 'nguyen-tu-dev/', 'worksFor' => [ '@id' => $organizationId ] ]; } if (!$hasWebsite) { $graph[] = $websiteNode; } } $normalizedPayload = [ '@context' => 'https://schema.org', '@graph' => array_values($graph) ]; $json = json_encode( $normalizedPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS ); if ($json === false) { return ''; } return ''; }; // 4. Chuẩn hóa đúng một lần. $schema = $normalizeJsonLd($schema); @endphp @endphp {{-- 5. Render DUY NHẤT một JSON-LD --}} @if(!empty($schema)) {!! $schema !!} @endif @stack('head_js') @if(config('app.env') === 'production') @include('partials.ga') @endif Hệ thống đang nâng cấp. Vui lòng quay lại sau!
500
Hệ thống đang nâng cấp. Vui lòng quay lại sau!