namespace Modules\Core\Services; use Artesaos\SEOTools\Facades\OpenGraph; use Artesaos\SEOTools\Facades\SEOMeta; use Artesaos\SEOTools\Facades\TwitterCard; use Illuminate\Support\Facades\Route; use Modules\System\Entities\Setting; use Carbon\Carbon; class SEOService { private $title; private $description; private $site_name; private $keywords; private $url; private $fb_app_id = '481736152602858'; private $fb_image; private $google_client; private $is_product = false; private $product; private $robots = 'index,follow'; private $canonical; private $seo_meta; private $published_date; private $modified_date; private $breadcrumbList = []; private $robots_overwrite; private $type = 'website'; private $google_verification; private $favicon; private $fb_fanpage_id; public function getSeoMeta() { return $this->seo_meta; } public function setSeoMeta($seo_meta) { $this->seo_meta = $seo_meta; return $this; } public function getCanonical() { return $this->canonical; } public function setCanonical($canonical) { $this->canonical = $this->ensureTrailingSlash($canonical); return $this; } public function getType() { return $this->type; } public function setType($type) { $this->type = $type; return $this; } private function myHtmlentities($text) { if (empty($text)) { return ''; } $decoded = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); return chop(preg_replace('/\"/', '\'', $decoded)); } /** * Đảm bảo URL luôn có dấu gạch chéo ở cuối (trừ khi kết thúc bằng .html) * Xử lý an toàn cả URL có query string (?page=2) và hash fragment (#) */ private function ensureTrailingSlash($url) { if (empty($url) || strpos($url, '.html') !== false) { return $url; } $parts = explode('?', $url, 2); $mainUrl = $parts[0]; $queryString = isset($parts[1]) ? '?' . $parts[1] : ''; $partsHash = explode('#', $mainUrl, 2); $pathUrl = $partsHash[0]; $hashString = isset($partsHash[1]) ? '#' . $partsHash[1] : ''; if (!str_ends_with($pathUrl, '/')) { $pathUrl .= '/'; } return $pathUrl . $hashString . $queryString; } /** * Làm sạch đoạn Description, tự động cắt bỏ câu Headline bị lặp lại ở đầu văn bản */ private function cleanDescription($description, $headline = '') { if (empty($description)) { return ''; } $desc = html_entity_decode(strip_tags($description), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $desc = trim(preg_replace('/\s+/u', ' ', $desc)); if (!empty($headline)) { $cleanHeadline = html_entity_decode(strip_tags($headline), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $cleanHeadline = trim(preg_replace('/\s+/u', ' ', $cleanHeadline)); $normalizedDesc = preg_replace('/^["\'”’“\s]+/u', '', $desc); $normalizedHeadline = preg_replace('/^["\'”’“\s]+/u', '', $cleanHeadline); if (!empty($normalizedHeadline) && mb_stripos($normalizedDesc, $normalizedHeadline) === 0) { $desc = trim(mb_substr($normalizedDesc, mb_strlen($normalizedHeadline))); $desc = preg_replace('/^[\s\:\-\–\—\?\.\"\'' . '”’“]+/u', '', $desc); } } return $this->myHtmlentities($desc); } public function getRobots() { return $this->robots; } public function setRobots($robots): self { $this->robots = $robots; return $this; } public function getOverwriteRobots() { return $this->robots_overwrite; } public function setOverwriteRobots($robots_overwrite): self { $this->robots_overwrite = $robots_overwrite; return $this; } public function getProduct() { return $this->product; } public function setProduct($product) { $this->product = $product; return $this; } public function getIsProduct() { return $this->is_product; } public function setIsProduct($is_product) { $this->is_product = $is_product; return $this; } public function getGoogleClient() { return $this->google_client; } public function setGoogleClient($google_client) { $this->google_client = $google_client; return $this; } public function getGoogleVerification() { return $this->google_verification; } public function setGoogleVerification($google_verification) { $this->google_verification = $google_verification; return $this; } public function getFavicon() { return $this->favicon; } public function setFavicon($favicon) { $this->favicon = $favicon; return $this; } public function getFbImage() { return $this->fb_image; } public function setFbImage($fb_image) { $cdn_url = config('filesystems.default') == 'cdn' ? config('app.cdn_url') : ''; $fb_image = str_replace(config('filesystems.default') == 'cdn' ? config('app.cdn_url') : config('app.url'), '', $fb_image); $this->fb_image = $fb_image ? ($cdn_url ? $cdn_url . $fb_image : url($fb_image)) : ''; return $this; } public function getFbAppId() { return $this->fb_app_id; } public function setFbAppId($fb_app_id) { $this->fb_app_id = $fb_app_id; return $this; } public function getFbFanpageId() { return $this->fb_fanpage_id; } public function setFbFanpageId($fb_fanpage_id) { $this->fb_fanpage_id = $fb_fanpage_id; return $this; } public function __construct() { $current_url = url()->current(); if (config('webnew.hierarchical_url') && substr($current_url, -1) != '/' && strpos($current_url, wn_setting('article_slug_suffix', '.html')) === false) { $current_url .= '/'; } $this->setUrl($current_url); $this->setSiteName(Setting::getValue('site_name')); $this->setKeywords(Setting::getValue('meta_keywords')); $this->setDescription(Setting::getValue('meta_description')); $this->setFavicon(Setting::getValue('favicon')); $this->setGoogleVerification(Setting::getValue('google_verification')); $this->setGoogleClient(Setting::getValue('google_client')); $this->setFbAppId(Setting::getValue('fb_app_id')); $this->setFbFanpageId(Setting::getValue('fb_fanpage_id')); $this->setFbImage(Setting::getValue('fb_image')); } public function getSiteName() { return $this->site_name; } public function setSiteName($site_name) { $this->site_name = $site_name; return $this; } public function getUrl() { return $this->ensureTrailingSlash($this->url); } public function setUrl($url) { $this->url = $this->ensureTrailingSlash($url); return $this; } static private $_instance = NULL; static function getInstance() : self { if (self::$_instance == NULL) { self::$_instance = new self(); } return self::$_instance; } public function getDescription() { return $this->cleanDescription($this->description, $this->title); } public function setDescription($description) { $this->description = $description; return $this; } public function getKeywords() { return $this->keywords; } public function setKeywords($keywords) { $this->keywords = $keywords; return $this; } public function getTitle() { if(empty($this->title)) return $this->getSiteName(); return $this->myHtmlentities($this->title); } public function setTitle($title) { $this->title = $title; return $this; } public function getPublishedDate() { return $this->published_date; } public function setPublishedDate($date) { $this->published_date = $date; return $this; } public function getModifiedDate() { return $this->modified_date; } public function setModifiedDate($date) { $this->modified_date = $date; return $this; } public function getBreadcrumbList() { return $this->breadcrumbList; } public function setBreadcrumbList($breadcrumbList): self { $this->breadcrumbList = $breadcrumbList; return $this; } public function render() { if (!$this->getSeoMeta()) { $segments = request()->segments(); $mainSegment = $segments[0] ?? ''; $subSegment = array_slice($segments, 1); if ($mainSegment === 'ket-qua') { if (!empty($subSegment)) { $subName = ucwords(str_replace('-', ' ', implode(' ', $subSegment))); $this->setTitle('Kết quả bóng đá ' . $subName . ' - ' . $this->getSiteName()); $this->setDescription('Cập nhật kết quả bóng đá ' . $subName . ' trực tuyến nhanh và chính xác nhất.'); if (empty($this->breadcrumbList)) { $crumbs = [['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Kết quả bóng đá', 'url' => url('/ket-qua')]]; $built = '/ket-qua'; foreach($subSegment as $seg) { $built .= '/' . $seg; $crumbs[] = ['name' => ucwords(str_replace('-', ' ', $seg)), 'url' => url($built)]; } $this->setBreadcrumbList($crumbs); } } else { $this->setTitle('Kết quả bóng đá trực tuyến hôm nay - ' . $this->getSiteName()); $this->setDescription('Cập nhật kết quả bóng đá trực tuyến hôm nay nhanh và chính xác nhất tỷ số các trận đấu Ngoại Hạng Anh, Cúp C1, La Liga, Serie A và các giải đấu lớn.'); if (empty($this->breadcrumbList)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Kết quả bóng đá', 'url' => $this->getUrl()] ]); } } } elseif ($mainSegment === 'lich-thi-dau') { if (!empty($subSegment)) { $subName = ucwords(str_replace('-', ' ', implode(' ', $subSegment))); $this->setTitle('Lịch thi đấu bóng đá ' . $subName . ' mới nhất - ' . $this->getSiteName()); $this->setDescription('Xem lịch thi đấu bóng đá ' . $subName . ' chi tiết được cập nhật liên tục 24/7.'); if (empty($this->breadcrumbList)) { $crumbs = [['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Lịch thi đấu bóng đá', 'url' => url('/lich-thi-dau')]]; $built = '/lich-thi-dau'; foreach($subSegment as $seg) { $built .= '/' . $seg; $crumbs[] = ['name' => ucwords(str_replace('-', ' ', $seg)), 'url' => url($built)]; } $this->setBreadcrumbList($crumbs); } } else { $this->setTitle('Lịch thi đấu bóng đá hôm nay và ngày mai mới nhất - ' . $this->getSiteName()); $this->setDescription('Xem lịch thi đấu bóng đá hôm nay và ngày mai chi tiết của các giải vô địch quốc gia và cúp châu Âu được cập nhật liên tục 24/7.'); if (empty($this->breadcrumbList)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Lịch thi đấu bóng đá', 'url' => $this->getUrl()] ]); } } } elseif ($mainSegment === 'bang-xep-hang') { if (!empty($subSegment)) { $subName = ucwords(str_replace('-', ' ', implode(' ', $subSegment))); $this->setTitle('Bảng xếp hạng bóng đá ' . $subName . ' - ' . $this->getSiteName()); $this->setDescription('Bảng xếp hạng bóng đá ' . $subName . ' cập nhật chi tiết điểm số, hiệu số bàn thắng bại và thứ hạng.'); if (empty($this->breadcrumbList)) { $crumbs = [['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Bảng xếp hạng bóng đá', 'url' => url('/bang-xep-hang')]]; $built = '/bang-xep-hang'; foreach($subSegment as $seg) { $built .= '/' . $seg; $crumbs[] = ['name' => ucwords(str_replace('-', ' ', $seg)), 'url' => url($built)]; } $this->setBreadcrumbList($crumbs); } } else { $this->setTitle('Bảng xếp hạng bóng đá các giải đấu hàng đầu - ' . $this->getSiteName()); $this->setDescription('Bảng xếp hạng bóng đá cập nhật chi tiết điểm số, hiệu số bàn thắng bại, số trận và thứ hạng các câu lạc bộ bóng đá hàng đầu thế giới.'); if (empty($this->breadcrumbList)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Bảng xếp hạng bóng đá', 'url' => $this->getUrl()] ]); } } } elseif ($mainSegment === 'top-ghi-ban') { $this->setTitle('Top ghi bàn bóng đá - Vua phá lưới các giải đấu hàng đầu - ' . $this->getSiteName()); $this->setDescription('Thống kê danh sách top ghi bàn, vua phá lưới Ngoại Hạng Anh, Cúp C1, La Liga và các giải đấu lớn cập nhật liên tục.'); if (empty($this->breadcrumbList)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Top ghi bàn', 'url' => $this->getUrl()] ]); } } elseif ($mainSegment === 'lich-phat-song') { $this->setTitle('Lịch phát sóng bóng đá trực tiếp hôm nay - ' . $this->getSiteName()); $this->setDescription('Xem lịch phát sóng trực tiếp bóng đá hôm nay trên các kênh truyền hình và nền tảng trực tuyến nhanh và chính xác nhất.'); if (empty($this->breadcrumbList)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Lịch phát sóng', 'url' => $this->getUrl()] ]); } } elseif ($mainSegment === 'du-doan') { $this->setTitle('Dự đoán bóng đá - Nhận định tỷ số chính xác từ chuyên gia - ' . $this->getSiteName()); $this->setDescription('Tổng hợp dự đoán bóng đá, phân tích tỷ lệ kèo và dự đoán tỷ số các trận cầu tâm điểm từ chuyên gia dữ liệu.'); if (empty($this->breadcrumbList)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Dự đoán bóng đá', 'url' => $this->getUrl()] ]); } } elseif ($mainSegment === 'nhan-dinh') { $this->setTitle('Nhận định bóng đá chuyên sâu, soi kèo các trận đấu hot - ' . $this->getSiteName()); $this->setDescription('Nhận định bóng đá trước trận đấu, phân tích phong độ, chiến thuật và soi kèo chuẩn xác từ chuyên gia phân tích dữ liệu.'); if (empty($this->breadcrumbList)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => 'Nhận định bóng đá', 'url' => $this->getUrl()] ]); } } else { if (empty($this->breadcrumbList) && !empty($mainSegment)) { $this->setBreadcrumbList([ ['name' => 'Trang chủ', 'url' => url('/')], ['name' => $this->getTitle(), 'url' => $this->getUrl()] ]); } } } $currentTitle = $this->getTitle(); $currentDesc = $this->getDescription() ?: Setting::getValue('meta_description'); $currentUrl = $this->getCanonical() ?: $this->getUrl(); $shareImage = $this->getFbImage(); SEOMeta::setTitle($currentTitle); SEOMeta::setDescription($currentDesc); SEOMeta::setCanonical($currentUrl); SEOMeta::addMeta('robots', $this->getRobots()); SEOMeta::addMeta('fb:app_id', $this->getFbAppId(), 'property'); if ($this->getGoogleVerification()) { SEOMeta::addMeta('google-site-verification', $this->getGoogleVerification()); } OpenGraph::setSiteName($this->getSiteName()); OpenGraph::addProperty('type', $this->getType()); OpenGraph::addProperty('locale', 'vi_VN'); if(request()->route() && request()->route()->getName() != "amp_article"){ SEOMeta::addAlternateLanguages([ ['lang' => 'vi-VN', 'url' => $this->getUrl()], ['lang' => 'x-default', 'url' => $this->getUrl()] ]); } SEOMeta::addMeta('author', 'Nguyễn Tú Dev'); if($this->getPublishedDate()){ SEOMeta::addMeta('pubdate', Carbon::parse($this->published_date)->format('Y-m-d\TH:i:s.uP')); SEOMeta::addMeta('article:published_time', Carbon::parse($this->published_date)->format('Y-m-d\TH:i:s.uP'), 'property'); } if($this->getModifiedDate()){ SEOMeta::addMeta('lastmod', Carbon::parse($this->modified_date)->format('Y-m-d\TH:i:s.uP')); SEOMeta::addMeta('article:modified_time', Carbon::parse($this->modified_date)->format('Y-m-d\TH:i:s.uP'), 'property'); } if ($this->getSeoMeta()) { $oSeoMeta = $this->getSeoMeta(); if($this->getSeoMeta()->title) { $currentTitle = $this->getSeoMeta()->title; SEOMeta::setTitle($currentTitle); } $desc = $this->getSeoMeta()->description; if(empty($desc)){ $desc = $this->getDescription(); } if(empty($desc)){ $desc = Setting::getValue('meta_description'); } $currentDesc = $this->cleanDescription($desc, $currentTitle); SEOMeta::setDescription($currentDesc); OpenGraph::addProperty('image:alt', $currentTitle); if($this->getSeoMeta()->robots && Route::currentRouteName() != 'article.preview'){ SEOMeta::addMeta('robots', $this->getSeoMeta()->robots); } $shareImage = !empty($oSeoMeta->image) ? $oSeoMeta->image : $this->getFbImage(); if (config('webnew.cdn_share.enable')) { $shareImage = config('webnew.cdn_share.url').'/1200x630/'.$shareImage; } OpenGraph::setTitle(htmlspecialchars($currentTitle, ENT_COMPAT, 'UTF-8')); OpenGraph::setDescription($currentDesc); OpenGraph::addImage($shareImage, ['height' => 630, 'width' => 1200]); OpenGraph::setUrl($this->getUrl()); } else { OpenGraph::addImage($shareImage, ['height' => 630, 'width' => 1200]); OpenGraph::setTitle($currentTitle); OpenGraph::setDescription($currentDesc); OpenGraph::setUrl($this->getUrl()); } TwitterCard::setType('summary_large_image'); TwitterCard::setTitle($currentTitle); TwitterCard::setDescription($currentDesc); TwitterCard::setImage($shareImage); TwitterCard::setUrl($this->getUrl()); if(wn_setting('noindex') || (config('webnew.hierarchical_url') && request()->getHost() == 'cms.meeyland.com')){ SEOMeta::addMeta('robots', 'noindex, nofollow'); } if($this->getOverwriteRobots()){ SEOMeta::addMeta('robots', $this->getOverwriteRobots()); } } /** * Tạo title/description chuẩn cho CollectionPage theo URL hiện tại. * generatePageSchemas() có thể được gọi trực tiếp từ Blade mà không qua render(), * nên không được phụ thuộc vào $this->title/$this->description đã được set trước đó. */ private function buildCollectionSeoMeta($rootSegment, array $subSegments, $siteName): array { $labels = [ 'giai-dau' => 'Giải đấu', 'cau-lac-bo' => 'Câu lạc bộ', 'cau-thu' => 'Cầu thủ', 'ket-qua' => 'Kết quả bóng đá', 'lich-thi-dau' => 'Lịch thi đấu bóng đá', 'bang-xep-hang' => 'Bảng xếp hạng bóng đá', 'top-ghi-ban' => 'Top ghi bàn bóng đá', 'lich-phat-song' => 'Lịch phát sóng bóng đá', 'du-doan' => 'Dự đoán bóng đá', 'nhan-dinh' => 'Nhận định bóng đá', ]; $baseLabel = $labels[$rootSegment] ?? null; $parts = []; foreach ($subSegments as $segment) { $segment = trim($segment); if ($segment === '') { continue; } $name = ucwords(str_replace('-', ' ', $segment)); $parts[] = $name; } if (!$baseLabel) { return [ 'title' => $siteName, 'description' => Setting::getValue('meta_description') ?: $siteName, ]; } $suffix = trim(implode(' ', $parts)); $title = $baseLabel . ($suffix !== '' ? ' ' . $suffix : ''); $description = ''; switch ($rootSegment) { case 'lich-thi-dau': $description = 'Cập nhật lịch thi đấu bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ' mới nhất, đầy đủ ngày giờ, cặp đấu và thông tin trận đấu.'; break; case 'ket-qua': $description = 'Cập nhật kết quả bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ' mới nhất, chính xác và đầy đủ tỷ số các trận đấu.'; break; case 'bang-xep-hang': $description = 'Cập nhật bảng xếp hạng bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ' với điểm số, hiệu số và thứ hạng các đội bóng.'; break; case 'top-ghi-ban': $description = 'Cập nhật top ghi bàn' . ($suffix !== '' ? ' ' . $suffix : '') . ', vua phá lưới và thống kê bàn thắng mới nhất.'; break; case 'lich-phat-song': $description = 'Cập nhật lịch phát sóng bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ' hôm nay và các trận đấu sắp diễn ra.'; break; case 'du-doan': $description = 'Tổng hợp dự đoán bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ', phân tích trận đấu, phong độ và dữ liệu trước trận.'; break; case 'nhan-dinh': $description = 'Nhận định bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ', phân tích phong độ, chiến thuật và dữ liệu trước trận.'; break; case 'giai-dau': $description = 'Thông tin và dữ liệu các giải đấu bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ', lịch thi đấu, kết quả và bảng xếp hạng mới nhất.'; break; case 'cau-lac-bo': $description = 'Thông tin câu lạc bộ bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ', đội hình, thành tích và dữ liệu thi đấu mới nhất.'; break; case 'cau-thu': $description = 'Thông tin cầu thủ bóng đá' . ($suffix !== '' ? ' ' . $suffix : '') . ', thống kê, phong độ và dữ liệu thi đấu mới nhất.'; break; default: $description = $siteName; break; } return [ 'title' => $title, 'description' => $description, ]; } /** * Chuẩn hóa và render toàn bộ JSON-LD. * * Quy tắc duy nhất: * - NewsArticle.author luôn là Person đầy đủ. * - NewsArticle.publisher luôn là NewsMediaOrganization đầy đủ. * - WebSite.publisher luôn là NewsMediaOrganization đầy đủ. * - Đảm bảo Person / Organization / WebSite tồn tại trong @graph. */ private function normalizeSchemaGraph(array $schemas, $baseUrl): array { $baseUrl = $this->ensureTrailingSlash($baseUrl); $personId = $baseUrl . 'nguyen-tu-dev/#person'; $organizationId = $baseUrl . '#organization'; $websiteId = $baseUrl . '#website'; $siteName = Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu'; $personNode = [ '@type' => 'Person', '@id' => $personId, 'name' => 'Nguyễn Tú Dev', 'url' => $baseUrl . 'nguyen-tu-dev/', 'worksFor' => [ '@id' => $organizationId ] ]; $organizationNode = [ '@type' => 'NewsMediaOrganization', '@id' => $organizationId, 'name' => $siteName, 'url' => $baseUrl ]; $websiteNode = [ '@type' => 'WebSite', '@id' => $websiteId, 'url' => $baseUrl, 'name' => $siteName, 'inLanguage' => 'vi-VN', 'publisher' => $organizationNode ]; $hasPerson = false; $hasOrganization = false; $hasWebsite = false; foreach ($schemas as &$schema) { if (!is_array($schema)) { continue; } $type = $schema['@type'] ?? null; $types = is_array($type) ? $type : [$type]; if (in_array('Person', $types, true) && ($schema['@id'] ?? null) === $personId) { $hasPerson = true; } if (in_array('NewsMediaOrganization', $types, true) && ($schema['@id'] ?? null) === $organizationId) { $hasOrganization = true; } if (in_array('WebSite', $types, true) && ($schema['@id'] ?? null) === $websiteId) { $hasWebsite = true; } if (in_array('NewsArticle', $types, true)) { $schema['author'] = $personNode; $schema['publisher'] = $organizationNode; } if (in_array('WebSite', $types, true)) { $schema['publisher'] = $organizationNode; } // Chuẩn hóa các reference đang chỉ có @id. if (isset($schema['author']) && is_array($schema['author'])) { if (($schema['author']['@id'] ?? null) === $personId) { $schema['author'] = array_merge( $personNode, $schema['author'] ); $schema['author']['@type'] = 'Person'; } } if (isset($schema['publisher']) && is_array($schema['publisher'])) { if (($schema['publisher']['@id'] ?? null) === $organizationId) { $schema['publisher'] = array_merge( $organizationNode, $schema['publisher'] ); $schema['publisher']['@type'] = 'NewsMediaOrganization'; } } } unset($schema); // Bổ sung canonical nodes nếu graph hiện tại bị thiếu. if (!$hasOrganization) { $schemas[] = $organizationNode; } if (!$hasPerson) { $schemas[] = $personNode; } if (!$hasWebsite) { $schemas[] = $websiteNode; } return array_values($schemas); } /** * Render JSON-LD duy nhất cho toàn bộ SEOService. */ private function renderSchemaGraph(array $schemas, $baseUrl): string { $schemas = $this->normalizeSchemaGraph($schemas, $baseUrl); $json = json_encode([ '@context' => 'https://schema.org', '@graph' => $schemas ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS); if ($json === false) { return ''; } return ''; } public function generateGlobalSchemas() { $schemas = []; $currentDesc = $this->getDescription() ?: Setting::getValue('meta_description'); $currentLogo = Setting::getValue('logo') ?: '/uploads/images/setting/admin/2026/07/11/logo-bongdadev-1783726178.webp'; $baseUrl = $this->ensureTrailingSlash(url('/')); // 1. Organization Schema $schemas[] = [ '@type' => 'NewsMediaOrganization', '@id' => $baseUrl . '#organization', 'additionalType' => 'https://schema.org/SportsOrganization', 'name' => Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu', 'alternateName' => ['BongDaDev', 'BDD'], 'legalName' => 'Công ty TNHH Thương mại và Đầu tư Nguyễn Tú', 'url' => $baseUrl, 'logo' => [ '@type' => 'ImageObject', '@id' => $baseUrl . '#logo', 'url' => url($currentLogo), 'contentUrl' => url($currentLogo), 'width' => 600, 'height' => 60, 'caption' => 'BongDaDev Logo' ], 'image' => [ '@id' => $baseUrl . '#logo' ], 'description' => $currentDesc, 'vatID' => '3702568166', 'telephone' => '+84-906818182', 'email' => 'nguyentudev@bongda.dev', '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' => $this->ensureTrailingSlash(url('/nguyen-tu-dev/')) . '#person' ], 'knowsAbout' => [ 'Football Analytics', 'Sports Data Science', 'Expected Goals (xG)', 'Tactical Analysis', 'Premier League Statistics', 'Big Data Football Metrics' ], 'publishingPrinciples' => $this->ensureTrailingSlash(url('/chinh-sach-quan-ly/')), 'sameAs' => [ 'https://maps.app.goo.gl/X6FYpDtrBstGyoVL8', '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' ], 'subjectOf' => [ [ '@type' => 'NewsArticle', 'headline' => 'BongDaDev launches next-gen real-time football data platform', 'url' => 'https://finance.yahoo.com/media-advertising/articles/bongda-dev-launches-next-gen-164100125.html' ], [ '@type' => 'NewsArticle', 'url' => 'https://apnews.com/press-release/globenewswire-mobile/press-release-8c90bb7cd155f87ebd42a58aa5e4143f' ], [ '@type' => 'NewsArticle', 'url' => 'https://www.manilatimes.net/2026/07/18/tmt-newswire/globenewswire/bongdadev-launches-next-gen-real-time-football-data-platform/2386748' ], [ '@type' => 'NewsArticle', 'url' => 'https://www.benzinga.com/pressreleases/26/07/g60531085/bongda-dev-launches-next-gen-real-time-football-data-platform' ], [ '@type' => 'NewsArticle', 'url' => 'https://www.globenewswire.com/news-release/2026/07/17/3329227/0/en/bongdadev-launches-next-gen-real-time-football-data-platform.html' ], [ '@type' => 'NewsArticle', 'url' => 'https://markets.businessinsider.com/news/stocks/bongda-dev-launches-next-gen-real-time-football-data-platform-1036337287' ] ] ]; // 2. Person Schema $personUrl = $this->ensureTrailingSlash(url('/nguyen-tu-dev/')); $schemas[] = [ '@type' => 'Person', '@id' => $personUrl . '#person', 'name' => 'Nguyễn Tú Dev', 'alternateName' => ['Nguyễn Văn Tú', 'Nguyen Tu Dev', 'Admin Bongda.dev'], 'jobTitle' => 'Founder & Chief Football Data Analyst', 'birthDate' => '1986-03-18', 'gender' => 'Male', 'image' => [ '@type' => 'ImageObject', '@id' => $personUrl . '#author-image', 'url' => url('/zoom/156x0/uploads/images/admin/2026/07/18/nguyen-tu-dev-1784357378.jpeg'), 'contentUrl' => url('/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' => $baseUrl . '#organization' ], 'url' => $personUrl, 'knowsAbout' => [ 'Football Data Architecture', 'Sports Analytics', 'Predictive Modeling', 'Tactical Performance Tracking' ], 'sameAs' => [ 'https://www.facebook.com/nguyentudev/' ], 'subjectOf' => [ [ '@type' => 'Article', 'url' => 'https://www.msn.com/en-us/news/other/the-economics-and-engineering-of-global-football-inside-the-world%E2%80%99s-top-12-competitions-and-realtime-data-pipelines/ar-AA2cscuL' ], [ '@type' => 'Article', 'url' => 'https://www.dynamomania.com/news/821132-the-tactical-evolution-and-analytics-revolution-in-modern-european-football' ], [ '@type' => 'Article', 'url' => 'https://presseagence.fr/paris-le-pitch-algorithmique-comment-le-big-data-et-lanalyse-predictive-redefinissent-levaluation-moderne-des-matchs-de-football-et-les-marches-des-transferts/' ], [ '@type' => 'Article', 'url' => 'https://www.feedinco.com/blog/scouting-advanced-spatial-analytics' ] ] ]; // 3. WebSite Schema $schemas[] = [ '@type' => 'WebSite', '@id' => $baseUrl . '#website', 'url' => $baseUrl, 'name' => Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu', 'description' => $currentDesc, 'inLanguage' => 'vi-VN', 'publisher' => [ '@id' => $baseUrl . '#organization' ], 'potentialAction' => [ '@type' => 'SearchAction', 'target' => [ '@type' => 'EntryPoint', 'urlTemplate' => $this->ensureTrailingSlash(url('/tim-kiem.html')) . '?keyword={search_term_string}' ], 'query-input' => 'required name=search_term_string' ] ]; if (empty($schemas)) { return ''; } return $this->renderSchemaGraph($schemas, $baseUrl); } public function generatePageSchemas($data = null) { $schemas = []; $currentPath = trim(parse_url($this->getUrl(), PHP_URL_PATH), '/'); $segments = array_filter(explode('/', $currentPath)); $segmentCount = count($segments); $rootSegment = $segments[0] ?? ''; $baseUrl = $this->ensureTrailingSlash(url('/')); $personUrl = $this->ensureTrailingSlash(url('/nguyen-tu-dev/')); $currentLogo = Setting::getValue('logo') ?: '/uploads/images/setting/admin/2026/07/11/logo-bongdadev-1783726178.webp'; // KHAI BÁO NODE THỰC THỂ ĐẦY ĐỦ THUỘC TÍNH (Khắc phục hoàn toàn Unresolved Reference/Thing) $personEntity = [ '@type' => 'Person', '@id' => $personUrl . '#person', 'name' => 'Nguyễn Tú Dev', 'alternateName' => ['Nguyễn Văn Tú', 'Nguyen Tu Dev', 'Admin Bongda.dev'], 'jobTitle' => 'Founder & Chief Football Data Analyst', 'birthDate' => '1986-03-18', 'gender' => 'Male', 'url' => $personUrl, 'image' => [ '@type' => 'ImageObject', '@id' => $personUrl . '#author-image', 'url' => url('/zoom/156x0/uploads/images/admin/2026/07/18/nguyen-tu-dev-1784357378.jpeg'), 'contentUrl' => url('/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' => $baseUrl . '#organization' ], 'sameAs' => [ 'https://www.facebook.com/nguyentudev/' ] ]; $orgEntity = [ '@type' => 'NewsMediaOrganization', '@id' => $baseUrl . '#organization', 'additionalType' => 'https://schema.org/SportsOrganization', 'name' => Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu', 'alternateName' => ['BongDaDev', 'BDD'], 'legalName' => 'Công ty TNHH Thương mại và Đầu tư Nguyễn Tú', 'url' => $baseUrl, 'logo' => [ '@type' => 'ImageObject', '@id' => $baseUrl . '#logo', 'url' => url($currentLogo), 'contentUrl' => url($currentLogo), 'width' => 600, 'height' => 60, 'caption' => 'BongDaDev Logo' ] ]; $websiteEntity = [ '@type' => 'WebSite', '@id' => $baseUrl . '#website', 'url' => $baseUrl, 'name' => Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu', 'description' => Setting::getValue('meta_description'), 'inLanguage' => 'vi-VN', 'publisher' => [ '@id' => $baseUrl . '#organization' ] ]; $specialStaticPages = [ 've-bongdadev' => ['type' => 'AboutPage', 'name' => 'Về BongDaDev'], 'nguyen-tu-dev' => ['type' => 'ProfilePage', 'name' => 'Hồ sơ Nguyễn Tú Dev'], 'chung-chi-chung-nhan' => ['type' => 'WebPage', 'name' => 'Chứng chỉ & Chứng nhận'], 'chinh-sach-quan-ly' => ['type' => 'WebPage', 'name' => 'Chính sách quản lý'], 'thong-bao' => ['type' => 'WebPage', 'name' => 'Thông báo hệ thống'] ]; if (array_key_exists($currentPath, $specialStaticPages)) { $pageInfo = $specialStaticPages[$currentPath]; $pageUrl = $this->ensureTrailingSlash($this->getUrl()); $siteName = Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu'; $pageTitle = trim($this->getTitle()); $pageDesc = trim($this->getDescription()); // Khi generatePageSchemas() được gọi trực tiếp từ Blade, render() // có thể chưa chạy và title/description vẫn là site mặc định. if ($pageTitle === '' || $pageTitle === $siteName) { $collectionSeo = $this->buildCollectionSeoMeta( $rootSegment, array_values(array_slice($segments, 1)), $siteName ); $pageTitle = $collectionSeo['title']; if ($pageDesc === '' || $pageDesc === Setting::getValue('meta_description')) { $pageDesc = $collectionSeo['description']; } } if ($pageDesc === '') { $pageDesc = Setting::getValue('meta_description') ?: $siteName; } $breadcrumbId = $pageUrl . '#breadcrumb'; $webPageId = $pageUrl . '#webpage'; $webPageType = array_unique(['WebPage', $pageInfo['type']]); $schemas[] = $personEntity; $schemas[] = $orgEntity; $schemas[] = $websiteEntity; $schemas[] = [ '@type' => count($webPageType) > 1 ? array_values($webPageType) : $webPageType[0], '@id' => $webPageId, 'url' => $pageUrl, 'name' => $pageTitle, 'description' => strip_tags($pageDesc), 'inLanguage' => 'vi-VN', 'isPartOf' => [ '@id' => $baseUrl . '#website' ], 'about' => [ '@id' => ($currentPath === 'nguyen-tu-dev') ? $personUrl . '#person' : $baseUrl . '#organization' ], 'mainEntity' => [ '@id' => ($currentPath === 'nguyen-tu-dev') ? $personUrl . '#person' : $baseUrl . '#organization' ], 'breadcrumb' => [ '@id' => $breadcrumbId ] ]; $schemas[] = [ '@type' => 'BreadcrumbList', '@id' => $breadcrumbId, 'itemListElement' => [ [ '@type' => 'ListItem', 'position' => 1, 'name' => 'Trang chủ', 'item' => $baseUrl ], [ '@type' => 'ListItem', 'position' => 2, 'name' => $pageInfo['name'], 'item' => $pageUrl ] ] ]; return $this->renderSchemaGraph($schemas, $baseUrl); } $isHtmlArticle = str_ends_with($currentPath, '.html') || strpos($currentPath, '.html') !== false; $hubRoots = ['cau-lac-bo', 'giai-dau', 'cau-thu', 'ket-qua', 'lich-thi-dau', 'bang-xep-hang', 'top-ghi-ban', 'lich-phat-song', 'du-doan', 'nhan-dinh']; $isParentHub = ($segmentCount === 1 && in_array($rootSegment, $hubRoots)); $isLevel1Detail = ($segmentCount === 2 && in_array($rootSegment, ['cau-lac-bo', 'giai-dau', 'cau-thu'])); $isMultiLevelSub = (!$isHtmlArticle && $segmentCount >= 2 && in_array($rootSegment, $hubRoots)); $isHubPageOrCategory = !$isHtmlArticle && ($isParentHub || $isMultiLevelSub || in_array($currentPath, $hubRoots) || request()->routeIs('category.detail')); // 1. NewsArticle Schema $isArticle = $isHtmlArticle || (!$isHubPageOrCategory && !$isLevel1Detail && $data && ( request()->routeIs('*article*') || request()->routeIs('*news*') || request()->routeIs('*detail*') || isset($data->name) || isset($data->summary) || isset($data->published_at) )); if ($isArticle) { $articleUrl = method_exists($data, 'getLink') ? $data->getLink() : ($data->link ?? $this->getUrl()); $title = $data->name ?? $data->title ?? $this->getTitle(); $rawDesc = $data->summary ?? $data->description ?? $data->teaser ?? $this->getDescription() ?: Setting::getValue('meta_description'); $articleDesc = $this->cleanDescription($rawDesc, $title); $imageUrl = !empty($data->image) ? url($data->image) : (!empty($data->thumbnail) ? url($data->thumbnail) : $this->getFbImage()); $datePublished = null; if (!empty($data->published_at)) { $datePublished = Carbon::parse($data->published_at)->toIso8601String(); } elseif (!empty($data->created_at)) { $datePublished = Carbon::parse($data->created_at)->toIso8601String(); } elseif (!empty($this->getPublishedDate())) { $datePublished = Carbon::parse($this->getPublishedDate())->toIso8601String(); } $dateModified = null; if (!empty($data->updated_at)) { $dateModified = Carbon::parse($data->updated_at)->toIso8601String(); } elseif (!empty($this->getModifiedDate())) { $dateModified = Carbon::parse($this->getModifiedDate())->toIso8601String(); } $breadcrumbId = $articleUrl . '#breadcrumb'; $imageObject = [ '@type' => 'ImageObject', '@id' => $imageUrl . '#primaryimage', 'url' => $imageUrl, 'contentUrl' => $imageUrl, 'width' => 1200, 'height' => 630, 'caption' => $title ]; $sectionName = 'Tin tức'; if ($rootSegment === 'giai-dau') $sectionName = 'Giải đấu'; if ($rootSegment === 'cau-lac-bo') $sectionName = 'Câu lạc bộ'; if ($rootSegment === 'cau-thu') $sectionName = 'Cầu thủ'; if ($rootSegment === 'nhan-dinh') $sectionName = 'Nhận định bóng đá'; if ($rootSegment === 'du-doan') $sectionName = 'Dự đoán bóng đá'; $articleSchema = [ '@type' => 'NewsArticle', '@id' => $articleUrl . '#article', 'mainEntityOfPage' => [ '@id' => $articleUrl . '#webpage' ], 'headline' => $title, 'description' => $articleDesc, 'image' => $imageObject, 'articleSection' => $sectionName, 'isAccessibleForFree' => true, 'inLanguage' => 'vi-VN', 'author' => [ '@type' => 'Person', '@id' => $personUrl . '#person', 'name' => 'Nguyễn Tú Dev' ], 'publisher' => [ '@type' => 'NewsMediaOrganization', '@id' => $baseUrl . '#organization', 'name' => Setting::getValue('site_name') ?: 'BongDaDev - Nền tảng Dữ liệu & Phân tích Bóng đá Chuyên sâu' ] ]; if ($datePublished) { $articleSchema['datePublished'] = $datePublished; } if ($dateModified) { $articleSchema['dateModified'] = $dateModified; } if (isset($data->entity_name)) { $articleSchema['about'] = [ [ '@type' => $data->entity_type ?? 'Thing', 'name' => $data->entity_name, 'sameAs' => $data->entity_same_as ?? null ] ]; if (empty($articleSchema['about'][0]['sameAs'])) { unset($articleSchema['about'][0]['sameAs']); } } // Bơm đầy đủ các Node thực thể vào cùng mảng @graph $schemas[] = $websiteEntity; $schemas[] = $orgEntity; $schemas[] = $personEntity; $schemas[] = $articleSchema; $schemas[] = [ '@type' => 'WebPage', '@id' => $articleUrl . '#webpage', 'url' => $articleUrl, 'name' => $title, 'inLanguage' => 'vi-VN', 'isPartOf' => [ '@id' => $baseUrl . '#website' ], 'breadcrumb' => [ '@id' => $breadcrumbId ] ]; $itemListElement = []; $itemListElement[] = [ '@type' => 'ListItem', 'position' => 1, 'name' => 'Trang chủ', 'item' => $baseUrl ]; $builtUrl = ''; foreach ($segments as $index => $segment) { $builtUrl .= '/' . $segment; if ($index === count($segments) - 1 && strpos($segment, '.html') !== false) { $segUrl = $articleUrl; } else { $segUrl = $this->ensureTrailingSlash(url($builtUrl)); } $segName = ucwords(str_replace('-', ' ', $segment)); if ($segment === 'giai-dau') $segName = 'Giải đấu'; if ($segment === 'cup-c2') $segName = 'Cup C2'; if ($segment === 'cup-c1') $segName = 'Cup C1'; if ($segment === 'ngoai-hang-anh') $segName = 'Ngoại Hạng Anh'; if ($segment === 'cau-lac-bo') $segName = 'Câu lạc bộ'; if ($segment === 'cau-thu') $segName = 'Cầu thủ'; if ($segment === 'ket-qua') $segName = 'Kết quả bóng đá'; if ($segment === 'lich-thi-dau') $segName = 'Lịch thi đấu'; if ($segment === 'bang-xep-hang') $segName = 'Bảng xếp hạng'; if ($segment === 'top-ghi-ban') $segName = 'Top ghi bàn'; if ($segment === 'lich-phat-song') $segName = 'Lịch phát sóng'; if ($segment === 'du-doan') $segName = 'Dự đoán bóng đá'; if ($segment === 'nhan-dinh') $segName = 'Nhận định bóng đá'; $itemListElement[] = [ '@type' => 'ListItem', 'position' => count($itemListElement) + 1, 'name' => $segName, 'item' => $segUrl ]; } if (count($itemListElement) > 1) { $itemListElement[count($itemListElement) - 1]['name'] = $title; $itemListElement[count($itemListElement) - 1]['item'] = $articleUrl; } $schemas[] = [ '@type' => 'BreadcrumbList', '@id' => $breadcrumbId, 'itemListElement' => $itemListElement ]; } // 2. SportsTeam Schema if ($isLevel1Detail && $rootSegment === 'cau-lac-bo') { $pageUrl = $this->ensureTrailingSlash($this->getUrl()); $pageTitle = $data->name ?? $data->title ?? $this->getTitle(); $pageDesc = $this->cleanDescription($data->description ?? $this->getDescription() ?: Setting::getValue('meta_description'), $pageTitle); $imageUrl = !empty($data->image) ? url($data->image) : $this->getFbImage(); $breadcrumbId = $pageUrl . '#breadcrumb'; $sportsTeam = [ '@type' => 'SportsTeam', '@id' => $pageUrl . '#sportsteam', 'mainEntityOfPage' => [ '@id' => $pageUrl . '#webpage' ], 'name' => $pageTitle, 'description' => $pageDesc, 'url' => $pageUrl, 'image' => [ '@type' => 'ImageObject', 'url' => $imageUrl ], 'sport' => 'Football', 'coach' => [ '@type' => 'Person', 'name' => $data->coach ?? 'Đang cập nhật' ], 'memberOf' => [ '@type' => 'SportsOrganization', 'name' => $data->league ?? 'Premier League' ] ]; $schemas[] = $websiteEntity; $schemas[] = $orgEntity; $schemas[] = $personEntity; $schemas[] = $sportsTeam; $schemas[] = [ '@type' => 'WebPage', '@id' => $pageUrl . '#webpage', 'url' => $pageUrl, 'name' => $pageTitle, 'inLanguage' => 'vi-VN', 'isPartOf' => [ '@id' => $baseUrl . '#website' ], 'breadcrumb' => [ '@id' => $breadcrumbId ] ]; $clbUrl = $this->ensureTrailingSlash(url('/cau-lac-bo')); $schemas[] = [ '@type' => 'BreadcrumbList', '@id' => $breadcrumbId, 'itemListElement' => [ ['@type' => 'ListItem', 'position' => 1, 'name' => 'Trang chủ', 'item' => $baseUrl], ['@type' => 'ListItem', 'position' => 2, 'name' => 'Câu lạc bộ', 'item' => $clbUrl], ['@type' => 'ListItem', 'position' => 3, 'name' => $pageTitle, 'item' => $pageUrl] ] ]; } // 3. CollectionPage Schema if ($isHubPageOrCategory || ($isLevel1Detail && $rootSegment !== 'cau-lac-bo') || $rootSegment === 'du-doan' || $rootSegment === 'nhan-dinh') { $pageUrl = $this->ensureTrailingSlash($this->getUrl()); $pageTitle = $this->getTitle(); $pageDesc = $this->getDescription() ?: Setting::getValue('meta_description'); $breadcrumbId = $pageUrl . '#breadcrumb'; $collectionPageId = $pageUrl . '#webpage'; $collectionPage = [ '@type' => 'CollectionPage', '@id' => $collectionPageId, 'url' => $pageUrl, 'name' => $pageTitle, 'description' => $this->cleanDescription($pageDesc, $pageTitle), 'inLanguage' => 'vi-VN', 'isPartOf' => [ '@id' => $baseUrl . '#website' ], 'breadcrumb' => [ '@id' => $breadcrumbId ] ]; $schemas[] = $websiteEntity; $schemas[] = $orgEntity; $schemas[] = $personEntity; $schemas[] = $collectionPage; $itemListElement = [ [ '@type' => 'ListItem', 'position' => 1, 'name' => 'Trang chủ', 'item' => $baseUrl ] ]; $builtUrl = ''; foreach ($segments as $index => $segment) { $builtUrl .= '/' . $segment; $currentSegmentUrl = $this->ensureTrailingSlash(url($builtUrl)); $segName = ucwords(str_replace('-', ' ', $segment)); if ($segment === 'giai-dau') $segName = 'Giải đấu'; if ($segment === 'cup-c2') $segName = 'Cup C2'; if ($segment === 'cup-c1') $segName = 'Cup C1'; if ($segment === 'ngoai-hang-anh') $segName = 'Ngoại Hạng Anh'; if ($segment === 'cau-lac-bo') $segName = 'Câu lạc bộ'; if ($segment === 'cau-thu') $segName = 'Cầu thủ'; if ($segment === 'ket-qua') $segName = 'Kết quả bóng đá'; if ($segment === 'lich-thi-dau') $segName = 'Lịch thi đấu'; if ($segment === 'bang-xep-hang') $segName = 'Bảng xếp hạng'; if ($segment === 'top-ghi-ban') $segName = 'Top ghi bàn'; if ($segment === 'lich-phat-song') $segName = 'Lịch phát sóng'; if ($segment === 'du-doan') $segName = 'Dự đoán bóng đá'; if ($segment === 'nhan-dinh') $segName = 'Nhận định bóng đá'; $itemListElement[] = [ '@type' => 'ListItem', 'position' => count($itemListElement) + 1, 'name' => $segName, 'item' => $currentSegmentUrl ]; } $schemas[] = [ '@type' => 'BreadcrumbList', '@id' => $breadcrumbId, 'itemListElement' => $itemListElement ]; } // 4. BreadcrumbList Schema dự phòng if ($currentPath !== '' && !$isArticle && !$isHubPageOrCategory && !$isLevel1Detail && $rootSegment !== 'du-doan' && $rootSegment !== 'nhan-dinh') { $itemListElement = [ [ '@type' => 'ListItem', 'position' => 1, 'name' => 'Trang chủ', 'item' => $baseUrl ] ]; $builtUrl = ''; foreach ($segments as $index => $segment) { $builtUrl .= '/' . $segment; if ($index === count($segments) - 1 && strpos($segment, '.html') !== false) { $segUrl = url($builtUrl); } else { $segUrl = $this->ensureTrailingSlash(url($builtUrl)); } $segName = ucwords(str_replace('-', ' ', $segment)); if ($segment === 'ket-qua') $segName = 'Kết quả bóng đá'; if ($segment === 'lich-thi-dau') $segName = 'Lịch thi đấu'; if ($segment === 'bang-xep-hang') $segName = 'Bảng xếp hạng'; if ($segment === 'top-ghi-ban') $segName = 'Top ghi bàn'; if ($segment === 'lich-phat-song') $segName = 'Lịch phát sóng'; if ($segment === 'du-doan') $segName = 'Dự đoán bóng đá'; if ($segment === 'nhan-dinh') $segName = 'Nhận định bóng đá'; $itemListElement[] = [ '@type' => 'ListItem', 'position' => count($itemListElement) + 1, 'name' => $segName, 'item' => $segUrl ]; } $breadcrumbId = $this->ensureTrailingSlash($this->getUrl()) . '#breadcrumb'; $schemas[] = $websiteEntity; $schemas[] = $orgEntity; $schemas[] = $personEntity; $schemas[] = [ '@type' => 'BreadcrumbList', '@id' => $breadcrumbId, 'itemListElement' => $itemListElement ]; } if (empty($schemas)) { return ''; } return $this->renderSchemaGraph($schemas, $baseUrl); } }