restore gpt seo copy pipeline and stabilize seo tkd output
This commit is contained in:
@@ -458,10 +458,14 @@ class SiteContext
|
||||
$strCategory = $this->Request->param('strCategory');
|
||||
$strSortType = $this->Request->param('strSortType');
|
||||
$intPage = $this->Request->param('intPage');
|
||||
$strOrderName = '';
|
||||
if (!empty($strSortType) && $strSortType !== 'all' && isset(StaticConfig::$arrVideoRankSortType[$strSortType])) {
|
||||
$strOrderName = StaticConfig::$arrVideoRankSortType[$strSortType];
|
||||
}
|
||||
ConverterMovel::setVal([
|
||||
'strVideoParentCategoryName' => empty($strParentCategory) || $strParentCategory == 'all' ? '' : StaticConfig::$arrVideoClass[$strParentCategory],
|
||||
'strVideoCategoryName' => empty($strCategory) || $strCategory == 'all' ? '' : VideoCategoryModel::$arrCategory[$strCategory],
|
||||
'strOrderName' => empty($strSortType) || $strSortType == 'all' ? '' : StaticConfig::$arrVideoRankSortType[$strSortType],
|
||||
'strOrderName' => $strOrderName,
|
||||
'intPage' => $intPage ?? 1,
|
||||
]);
|
||||
}
|
||||
@@ -746,23 +750,377 @@ class SiteContext
|
||||
*/
|
||||
public function getSeoTkd(string $strCode, string $strPage): string
|
||||
{
|
||||
// TpStyle 已在 initCfg() 中注入
|
||||
$TpStyle = $this->TpStyle;
|
||||
$strCode = strtolower(trim($strCode));
|
||||
$strPage = strtolower(trim($strPage));
|
||||
|
||||
if (empty($TpStyle)) {
|
||||
if (!in_array($strCode, ['title', 'keywords', 'description'], true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 1️⃣ 选 SEO 模板
|
||||
$renderer = new SeoRenderer($TpStyle);
|
||||
$tpl = $renderer->getTemplate($strCode, $strPage);
|
||||
$arrCandidates = [];
|
||||
|
||||
// 1. 新 GPT SEO 池
|
||||
$arrCandidates[] = $this->renderSeoFromPool($strCode, $strPage);
|
||||
|
||||
// 2. 旧 SubjectFomart key 兼容层
|
||||
$arrCandidates[] = $this->renderSeoFromLegacyKey($strCode, $strPage);
|
||||
|
||||
// 3. domain 表兜底
|
||||
$arrCandidates[] = $this->renderSeoFromDomainRecord($strCode, $strPage);
|
||||
|
||||
foreach ($arrCandidates as $strCandidate) {
|
||||
$strCandidate = $this->normalizeSeoText($strCandidate);
|
||||
if ($strCode === 'keywords') {
|
||||
$strCandidate = $this->normalizeSeoKeywords($strCandidate);
|
||||
} elseif ($strCode === 'description') {
|
||||
$strCandidate = $this->normalizeSeoDescription($strCandidate, $strPage);
|
||||
}
|
||||
if ($strCandidate !== '') {
|
||||
return $strCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function renderSeoFromPool(string $strCode, string $strPage): string
|
||||
{
|
||||
$TpStyle = $this->TpStyle;
|
||||
if (empty($TpStyle) || !is_array($TpStyle)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$renderer = new SeoRenderer($TpStyle);
|
||||
$tpl = trim((string)$renderer->getTemplate($strCode, $strPage));
|
||||
if ($tpl === '') {
|
||||
return '';
|
||||
}
|
||||
// var_dump($tpl);
|
||||
// 2️⃣ 统一走 Converter(你现有系统)
|
||||
return ConverterMovel::convert($tpl);
|
||||
|
||||
return $this->normalizeSeoText(ConverterMovel::convert($tpl));
|
||||
}
|
||||
|
||||
protected function renderSeoFromLegacyKey(string $strCode, string $strPage): string
|
||||
{
|
||||
$strLegacyKey = $this->resolveLegacySeoKey($strCode, $strPage);
|
||||
if ($strLegacyKey === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->normalizeSeoText($this->converterTemplate($strLegacyKey));
|
||||
} catch (\Throwable $Throwable) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected function renderSeoFromDomainRecord(string $strCode, string $strPage): string
|
||||
{
|
||||
if (!$this->DomainModel instanceof DomainModel) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strSiteName = trim((string)($this->DomainModel->d_name ?? ''));
|
||||
$strHost = trim((string)($this->DomainModel->d_domain ?? ''));
|
||||
|
||||
if ($strPage === 'home') {
|
||||
return match ($strCode) {
|
||||
'title' => trim((string)($this->DomainModel->d_index_title ?? $strSiteName)),
|
||||
'keywords' => trim((string)($this->DomainModel->d_index_keywords ?? $this->DomainModel->d_keywords ?? $strSiteName)),
|
||||
'description' => trim((string)($this->DomainModel->d_index_description ?? $this->DomainModel->d_description ?? $strSiteName)),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
if ($strCode === 'title') {
|
||||
$strEntityTitle = trim((string)(
|
||||
ConverterMovel::getVal('strVideoName')
|
||||
?? ConverterMovel::getVal('strVideoCategoryName')
|
||||
?? ConverterMovel::getVal('strVideoParentCategoryName')
|
||||
?? ''
|
||||
));
|
||||
|
||||
if ($strEntityTitle !== '' && $strSiteName !== '') {
|
||||
return $strEntityTitle . ' - ' . $strSiteName;
|
||||
}
|
||||
}
|
||||
|
||||
if ($strCode === 'keywords') {
|
||||
$arrKeywords = array_filter([
|
||||
trim((string)(ConverterMovel::getVal('strVideoName') ?? '')),
|
||||
trim((string)(ConverterMovel::getVal('strVideoCategoryName') ?? '')),
|
||||
trim((string)($this->DomainModel->d_index_keywords ?? '')),
|
||||
trim((string)($this->DomainModel->d_keywords ?? '')),
|
||||
], static function (string $strValue): bool {
|
||||
return $strValue !== '';
|
||||
});
|
||||
|
||||
if (!empty($arrKeywords)) {
|
||||
return implode(',', array_values(array_unique($arrKeywords)));
|
||||
}
|
||||
}
|
||||
|
||||
if ($strCode === 'description') {
|
||||
$strEntityDescription = trim((string)(
|
||||
ConverterMovel::getVal('strVideoDescription')
|
||||
?? ConverterMovel::getVal('strVideoCategoryName')
|
||||
?? ConverterMovel::getVal('strVideoParentCategoryName')
|
||||
?? ''
|
||||
));
|
||||
|
||||
if ($strEntityDescription !== '') {
|
||||
return $strEntityDescription;
|
||||
}
|
||||
|
||||
$strIndexDescription = trim((string)($this->DomainModel->d_index_description ?? ''));
|
||||
if ($strIndexDescription !== '') {
|
||||
return $strIndexDescription;
|
||||
}
|
||||
}
|
||||
|
||||
$arrGeneric = [
|
||||
'title' => $strSiteName !== '' ? $strSiteName : $strHost,
|
||||
'keywords' => trim((string)($this->DomainModel->d_keywords ?? $this->DomainModel->d_index_keywords ?? $strSiteName)),
|
||||
'description' => trim((string)($this->DomainModel->d_description ?? $this->DomainModel->d_index_description ?? $strSiteName)),
|
||||
];
|
||||
|
||||
return trim((string)($arrGeneric[$strCode] ?? ''));
|
||||
}
|
||||
|
||||
protected function normalizeSeoText(mixed $mValue): string
|
||||
{
|
||||
$strValue = trim((string)$mValue);
|
||||
if ($strValue === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strValue = html_entity_decode($strValue, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$strValue = strip_tags($strValue);
|
||||
$strValue = preg_replace('/\s+/u', ' ', $strValue) ?? $strValue;
|
||||
$strValue = preg_replace('/\{[A-Z0-9@_:-]+\}/iu', '', $strValue) ?? $strValue;
|
||||
$strValue = preg_replace('/^[\\s\\-|_,,。;;::]+/u', '', $strValue) ?? $strValue;
|
||||
$strValue = preg_replace('/[\\s\\-|_,,。;;::]+$/u', '', $strValue) ?? $strValue;
|
||||
|
||||
if ($strValue === '' || preg_match('/^[-|_,,。;;::\\s]+$/u', $strValue)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return trim($strValue);
|
||||
}
|
||||
|
||||
protected function normalizeSeoKeywords(string $strValue): string
|
||||
{
|
||||
$strValue = str_replace([',', '、', ';', ';', '|', '|'], ',', $strValue);
|
||||
$arrRawParts = preg_split('/\s*,\s*/u', $strValue) ?: [];
|
||||
$arrNoiseSuffixes = [
|
||||
'叙事模型',
|
||||
'英雄之旅',
|
||||
'成长母题',
|
||||
'精神图腾',
|
||||
'精神内核',
|
||||
'热血传奇',
|
||||
'荒诞美学',
|
||||
'流浪美学',
|
||||
'怀旧元素',
|
||||
'世界观构建',
|
||||
'文化符号',
|
||||
'末日隐喻',
|
||||
'双重叙事结构',
|
||||
'叙事策略',
|
||||
'氛围营造手法',
|
||||
'角色塑造',
|
||||
'诗化表达手法',
|
||||
'史诗建构',
|
||||
'疗愈力',
|
||||
'英雄塑造',
|
||||
'人性挖掘',
|
||||
];
|
||||
$arrStopWords = [
|
||||
'在线',
|
||||
'播放',
|
||||
'观看',
|
||||
'免费',
|
||||
'高清',
|
||||
'热播',
|
||||
'推荐',
|
||||
'完整',
|
||||
'完整版',
|
||||
'全集',
|
||||
'中字',
|
||||
'未删减',
|
||||
'在线播放',
|
||||
'在线观看',
|
||||
'免费高清',
|
||||
'高清免费视频',
|
||||
'高清免费',
|
||||
'免费播放',
|
||||
];
|
||||
|
||||
$arrParts = [];
|
||||
foreach ($arrRawParts as $strPart) {
|
||||
$strPart = trim((string)$strPart);
|
||||
if ($strPart === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($arrNoiseSuffixes as $strSuffix) {
|
||||
if ($strSuffix !== '' && str_ends_with($strPart, $strSuffix)) {
|
||||
$strPart = trim(mb_substr($strPart, 0, mb_strlen($strPart) - mb_strlen($strSuffix)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$strPart = trim($strPart, " \t\n\r\0\x0B-_|,。;、,;");
|
||||
|
||||
if (in_array($strPart, $arrStopWords, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mb_strlen($strPart) <= 2 && !preg_match('/^\d+$/u', $strPart)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($strPart, $arrParts, true)) {
|
||||
$arrParts[] = $strPart;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(',', $arrParts);
|
||||
}
|
||||
|
||||
protected function normalizeSeoDescription(string $strValue, string $strPage): string
|
||||
{
|
||||
$strValue = trim($strValue);
|
||||
if ($strValue === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$strSiteName = trim((string)($this->DomainModel->d_name ?? ''));
|
||||
$strSiteKeywords = trim((string)(ConverterMovel::getVal('strSiteKeywords') ?? ''));
|
||||
$strVideoName = trim((string)(ConverterMovel::getVal('strVideoName') ?? ''));
|
||||
$strVideoDescription = trim((string)(ConverterMovel::getVal('strVideoDescription') ?? ''));
|
||||
$strVideoCategoryName = trim((string)(ConverterMovel::getVal('strVideoCategoryName') ?? ''));
|
||||
|
||||
if ($strSiteKeywords !== '') {
|
||||
$strValue = str_replace($strSiteKeywords, '', $strValue);
|
||||
}
|
||||
|
||||
if ($strVideoName !== '') {
|
||||
$strQuotedVideoName = preg_quote($strVideoName, '/');
|
||||
|
||||
// 合并连续重复片名,如 “片名……片名”
|
||||
$strValue = preg_replace('/(' . $strQuotedVideoName . ')(?:\s*[,。;、,\-]?\s*\1){1,}/u', '$1', $strValue) ?? $strValue;
|
||||
// 清掉 “片名的热血传奇片名” 这类硬拼句式
|
||||
$strValue = preg_replace('/' . $strQuotedVideoName . '(的)?(热血传奇|精神图腾|叙事模型|英雄之旅|成长母题)' . $strQuotedVideoName . '/u', $strVideoName, $strValue) ?? $strValue;
|
||||
}
|
||||
|
||||
$arrNoisePhrases = [
|
||||
'的热血传奇',
|
||||
'的精神图腾',
|
||||
'的叙事模型',
|
||||
'的英雄之旅',
|
||||
'的成长母题',
|
||||
];
|
||||
$strValue = str_replace($arrNoisePhrases, '', $strValue);
|
||||
|
||||
$strValue = preg_replace('/([,。;、])\1+/u', '$1', $strValue) ?? $strValue;
|
||||
$strValue = preg_replace('/\s+/u', ' ', $strValue) ?? $strValue;
|
||||
$strValue = preg_replace('/\s*([,。;、])/u', '$1', $strValue) ?? $strValue;
|
||||
$strValue = preg_replace('/([,。;、])(?=[,。;、])/u', '', $strValue) ?? $strValue;
|
||||
$strValue = trim($strValue, " \t\n\r\0\x0B,。;、,;");
|
||||
|
||||
$boolDescriptionLooksWeak = false;
|
||||
if ($strVideoName !== '') {
|
||||
$intNameCount = preg_match_all('/' . preg_quote($strVideoName, '/') . '/u', $strValue);
|
||||
if ($intNameCount >= 2) {
|
||||
$boolDescriptionLooksWeak = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($strVideoDescription !== '' && $strVideoName !== '' && $strVideoDescription === $strVideoName) {
|
||||
$boolDescriptionLooksWeak = true;
|
||||
}
|
||||
|
||||
if (mb_strlen($strValue) < 20) {
|
||||
$boolDescriptionLooksWeak = true;
|
||||
}
|
||||
|
||||
if ($boolDescriptionLooksWeak) {
|
||||
return $this->buildSeoDescriptionFallback($strPage, $strSiteName, $strVideoName, $strVideoCategoryName);
|
||||
}
|
||||
|
||||
return $strValue;
|
||||
}
|
||||
|
||||
protected function buildSeoDescriptionFallback(string $strPage, string $strSiteName, string $strVideoName, string $strVideoCategoryName): string
|
||||
{
|
||||
$strSiteName = trim($strSiteName);
|
||||
$strVideoName = trim($strVideoName);
|
||||
$strVideoCategoryName = trim($strVideoCategoryName);
|
||||
|
||||
if ($strVideoName === '') {
|
||||
return trim((string)($this->DomainModel->d_index_description ?? $strSiteName));
|
||||
}
|
||||
|
||||
$strCategorySuffix = $strVideoCategoryName !== '' ? $strVideoCategoryName . '内容' : '相关内容';
|
||||
|
||||
return match ($strPage) {
|
||||
'play' => $strSiteName . '提供' . $strVideoName . '播放入口与剧集浏览信息,方便继续查看' . $strCategorySuffix . '与线路安排。',
|
||||
'detail' => $strSiteName . '整理' . $strVideoName . '的详情信息、演员资料与观看线索,方便继续了解' . $strCategorySuffix . '与播放入口。',
|
||||
default => $strSiteName . '提供' . $strVideoName . '相关介绍与浏览入口,方便继续查看' . $strCategorySuffix . '。',
|
||||
};
|
||||
}
|
||||
|
||||
protected function resolveLegacySeoKey(string $strCode, string $strPage): string
|
||||
{
|
||||
$arrMap = [
|
||||
'home' => [
|
||||
'title' => 'VIDEO@INDEX@INDEX@TITLE',
|
||||
'keywords' => 'VIDEO@INDEX@INDEX@KEYWORDS',
|
||||
'description' => 'VIDEO@INDEX@INDEX@DESCRIPTION',
|
||||
],
|
||||
'detail' => [
|
||||
'title' => 'VIDEO@GETVIDEOINFO@TITLE',
|
||||
'keywords' => 'VIDEO@GETVIDEOINFO@KEYWORDS',
|
||||
'description' => 'VIDEO@GETVIDEOINFO@DESCRIPTION',
|
||||
],
|
||||
'play' => [
|
||||
'title' => 'VIDEO@GETVIDEOPLAY@TITLE',
|
||||
'keywords' => 'VIDEO@GETVIDEOPLAY@KEYWORDS',
|
||||
'description' => 'VIDEO@GETVIDEOPLAY@DESCRIPTION',
|
||||
],
|
||||
'search' => [
|
||||
'title' => 'VIDEO@GETSEARCHVIDEO@TITLE',
|
||||
'keywords' => 'VIDEO@GETSEARCHVIDEO@KEYWORDS',
|
||||
'description' => 'VIDEO@GETSEARCHVIDEO@DESCRIPTION',
|
||||
],
|
||||
'rank_index' => [
|
||||
'title' => 'VIDEO@GETVIDEORANKINDEX@TITLE',
|
||||
'keywords' => 'VIDEO@GETVIDEORANKINDEX@KEYWORDS',
|
||||
'description' => 'VIDEO@GETVIDEORANKINDEX@DESCRIPTION',
|
||||
],
|
||||
'rank_list' => [
|
||||
'title' => 'VIDEO@GETVIDEORANKLIST@TITLE',
|
||||
'keywords' => 'VIDEO@GETVIDEORANKLIST@KEYWORDS',
|
||||
'description' => 'VIDEO@GETVIDEORANKLIST@DESCRIPTION',
|
||||
],
|
||||
'category_home' => [
|
||||
'title' => 'VIDEO@GETSEARCHVIDEO@TITLE',
|
||||
'keywords' => 'VIDEO@GETSEARCHVIDEO@KEYWORDS',
|
||||
'description' => 'VIDEO@GETSEARCHVIDEO@DESCRIPTION',
|
||||
],
|
||||
'category_index' => [
|
||||
'title' => 'VIDEO@GETCATEGORYINDEX@TITLE',
|
||||
'keywords' => 'VIDEO@GETCATEGORYINDEX@KEYWORDS',
|
||||
'description' => 'VIDEO@GETCATEGORYINDEX@DESCRIPTION',
|
||||
],
|
||||
'category_list' => [
|
||||
'title' => 'VIDEO@GETCATEGORY@TITLE',
|
||||
'keywords' => 'VIDEO@GETCATEGORY@KEYWORDS',
|
||||
'description' => 'VIDEO@GETCATEGORY@DESCRIPTION',
|
||||
],
|
||||
];
|
||||
|
||||
return (string)($arrMap[$strPage][$strCode] ?? '');
|
||||
}
|
||||
|
||||
public function getTemplate(): string
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace app\services;
|
||||
|
||||
use app\common\helper\SeoCopyFallbackBuilder;
|
||||
use app\common\helper\SeoCopySchema;
|
||||
use app\common\helper\SeoCopyStore;
|
||||
use app\model\CategoryModel;
|
||||
use app\model\ChapterModel;
|
||||
use app\model\ConverterMovel;
|
||||
@@ -48,14 +51,18 @@ class VideoService
|
||||
/**
|
||||
* 获取视频分类首页 URl
|
||||
*
|
||||
* @param string $strParentCategory
|
||||
* @param string|null $strParentCategory
|
||||
* @return string
|
||||
*/
|
||||
public function getVideoCategoryIndexUrl(string $strParentCategory): string
|
||||
public function getVideoCategoryIndexUrl(?string $strParentCategory): string
|
||||
{
|
||||
$strParentCategory = trim((string)$strParentCategory);
|
||||
$strTmpCode = $this->SiteContext->TemplatesModel['t_code'];
|
||||
|
||||
if ($strTmpCode == 'videoGpt1') {
|
||||
if ($strParentCategory === '') {
|
||||
return $this->SiteContext->UrlBuilder->categoryHome();
|
||||
}
|
||||
return $this->SiteContext->UrlBuilder->categoryParent($strParentCategory);
|
||||
} else {
|
||||
$strKey = 'VIDEO_CATEGORY_INDEX_URL';
|
||||
@@ -184,17 +191,23 @@ class VideoService
|
||||
/**
|
||||
* 排行榜 列表 url
|
||||
*
|
||||
* @param string $strCategory
|
||||
* @param string $strParentCategory
|
||||
* @param string $strSortType
|
||||
* @param integer $intPage
|
||||
* @param string|null $strCategory
|
||||
* @param string|null $strParentCategory
|
||||
* @param string|null $strSortType
|
||||
* @param integer|null $intPage
|
||||
* @return string
|
||||
*/
|
||||
public function getVideoRankUrl(string $strCategory, string $strParentCategory, string $strSortType, int $intPage): string
|
||||
public function getVideoRankUrl(?string $strCategory, ?string $strParentCategory, ?string $strSortType, ?int $intPage): string
|
||||
{
|
||||
$strCategory = trim((string)$strCategory);
|
||||
$strParentCategory = trim((string)$strParentCategory);
|
||||
$strSortType = trim((string)$strSortType);
|
||||
$strTmpCode = $this->SiteContext->TemplatesModel['t_code'];
|
||||
|
||||
if ($strTmpCode == 'videoGpt1') {
|
||||
if ($strSortType === '') {
|
||||
return $this->SiteContext->UrlBuilder->rankIndex();
|
||||
}
|
||||
return $this->SiteContext->UrlBuilder->rankList($strSortType);
|
||||
} else {
|
||||
$strKey = 'VIDEO_RANK_LIST_URL';
|
||||
@@ -244,7 +257,17 @@ class VideoService
|
||||
$strTmpCode = $this->SiteContext->TemplatesModel['t_code'];
|
||||
|
||||
if ($strTmpCode == 'videoGpt1') {
|
||||
return $this->SiteContext->UrlBuilder->searchResult($strKeyWords);
|
||||
$strUrl = $this->SiteContext->UrlBuilder->searchResult($strKeyWords);
|
||||
if (is_string($intPage) && strpos($intPage, '{page}') !== false) {
|
||||
return $strUrl . '&page={page}';
|
||||
}
|
||||
|
||||
$intPage = max(1, (int)$intPage);
|
||||
if ($intPage <= 1) {
|
||||
return $strUrl;
|
||||
}
|
||||
|
||||
return $strUrl . '&page=' . $intPage;
|
||||
} else {
|
||||
$strKey = 'VIDEO_SEARCH_LIST_URL';
|
||||
$strUrl = $this->SiteContext->DomainModel->getFomartUrl($strKey, [
|
||||
@@ -791,6 +814,11 @@ class VideoService
|
||||
throw new HttpException(404, '视频不存在');
|
||||
}
|
||||
|
||||
return $this->hydrateResolvedVideo($arrVideo, $intVId, $intVForgeId);
|
||||
}
|
||||
|
||||
protected function hydrateResolvedVideo(array $arrVideo, int $intRequestedVId, int|NULL $intVForgeId): array
|
||||
{
|
||||
if ($intVForgeId >= 0 && !empty($arrVideo['v_seo_words'])) {
|
||||
$intForgeOffset = max(0, $intVForgeId - 1);
|
||||
if (!empty($arrVideo['v_seo_words'][$intForgeOffset])) {
|
||||
@@ -809,14 +837,23 @@ class VideoService
|
||||
$strTopLang = '2025';
|
||||
|
||||
// 点击数
|
||||
$arrVideoClicks = $this->VideoClicksModel->getStats((int)($arrVideo['v_id'] ?? $intVId));
|
||||
$arrVideoClicks = $this->VideoClicksModel->getStats((int)($arrVideo['v_id'] ?? $intRequestedVId));
|
||||
|
||||
$strVideoParentCategoryName = trim((string)($arrVideo['v_parent_category'] ?? ''));
|
||||
$strVideoCategoryName = trim((string)($arrVideo['v_category'] ?? ''));
|
||||
|
||||
// 一部分视频数据里父分类与子分类会被写成同一个值,例如“伦理片 / 伦理片”。
|
||||
// 这里在注入 SEO/TKD 变量前做一次去重,避免标题拼出“伦理片伦理片”。
|
||||
if ($strVideoParentCategoryName !== '' && $strVideoParentCategoryName === $strVideoCategoryName) {
|
||||
$strVideoParentCategoryName = '';
|
||||
}
|
||||
|
||||
// 更新值
|
||||
ConverterMovel::setVal([
|
||||
'intPage' => 1,
|
||||
'strVideoName' => $arrVideo['v_name'],
|
||||
'strVideoParentCategoryName' => $arrVideo['v_parent_category'],
|
||||
'strVideoCategoryName' => $arrVideo['v_category'],
|
||||
'strVideoParentCategoryName' => $strVideoParentCategoryName,
|
||||
'strVideoCategoryName' => $strVideoCategoryName,
|
||||
'strVideoStatus' => $arrVideo['v_isend'],
|
||||
'strVideoDescription' => $arrVideo['v_description'],
|
||||
'strLangName' => $strTopLang,
|
||||
@@ -827,7 +864,27 @@ class VideoService
|
||||
|
||||
$arrVideo['arrVideoClicks'] = $arrVideoClicks;
|
||||
|
||||
return $arrVideo;
|
||||
return $arrVideo;
|
||||
}
|
||||
|
||||
public function getVideoByRouteContext(int|string|null $intVId, int|string|null $intVForgeId, string|null $strPinyin = '')
|
||||
{
|
||||
$intVId = (int)$intVId;
|
||||
$intVForgeId = $intVForgeId === null ? -1 : (int)$intVForgeId;
|
||||
$strPinyin = trim((string)$strPinyin);
|
||||
|
||||
if ($intVId > 0) {
|
||||
return $this->getVideoByVId($intVId, $intVForgeId);
|
||||
}
|
||||
|
||||
if ($strPinyin !== '') {
|
||||
$arrVideo = $this->VideoModel->getVideoByNameEn($strPinyin);
|
||||
if (!empty($arrVideo)) {
|
||||
return $this->hydrateResolvedVideo($arrVideo, (int)($arrVideo['v_id'] ?? 0), $intVForgeId);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getVideoByVId($intVId, $intVForgeId);
|
||||
}
|
||||
|
||||
protected function resolveFallbackVideoByRequest(int $requestedVId, bool $requirePlayable = false): ?array
|
||||
@@ -1103,6 +1160,82 @@ class VideoService
|
||||
return SiteStyle::buildDetailSeoAddon($arrTpStyle, $arrVideo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一获取页面补料块
|
||||
*
|
||||
* 优先读取 seo_copy 发布/本地文件;
|
||||
* 缺失时退回 fallback builder,保证页面不空。
|
||||
*/
|
||||
public function getSeoCopyBlock(string $strScene, array $arrOptions = []): array
|
||||
{
|
||||
$strScene = trim(strtolower($strScene));
|
||||
if ($strScene === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$strHost = trim((string)($this->SiteContext->DomainModel->d_domain ?? ''));
|
||||
if ($strHost === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$arrPageKeys = $this->buildSeoCopyPageKeys($strScene, $arrOptions);
|
||||
|
||||
if ($this->shouldSuppressDefaultOnlySeoCopy($strScene, $strHost, $arrPageKeys)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$arrData = !empty($arrPageKeys)
|
||||
? SeoCopyStore::getPreferredPageData($strHost, $strScene, $arrPageKeys)
|
||||
: [];
|
||||
|
||||
if (!empty($arrData)) {
|
||||
$arrData = $this->interpolateSeoCopyData($arrData, $this->buildSeoCopyTokens($strScene, $arrOptions));
|
||||
}
|
||||
|
||||
if (empty($arrData)) {
|
||||
$arrData = SeoCopyFallbackBuilder::build($strScene, $this->buildSeoCopyFacts($strScene, $arrOptions));
|
||||
}
|
||||
|
||||
return $this->normalizeSeoCopyBlock($strScene, $arrData);
|
||||
}
|
||||
|
||||
private function shouldSuppressDefaultOnlySeoCopy(string $strScene, string $strHost, array $arrPageKeys): bool
|
||||
{
|
||||
if (!in_array($strScene, ['detail', 'play'], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$arrSpecificKeys = array_values(array_filter($arrPageKeys, static function ($strPageKey): bool {
|
||||
$strPageKey = trim((string)$strPageKey);
|
||||
return $strPageKey !== '' && $strPageKey !== 'default';
|
||||
}));
|
||||
|
||||
if (empty($arrSpecificKeys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (SeoCopyStore::preferredRoots() as $strRoot) {
|
||||
$strRoot = rtrim(trim((string)$strRoot), '/');
|
||||
if ($strRoot === '' || !is_dir($strRoot)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($arrSpecificKeys as $strPageKey) {
|
||||
$strPath = SeoCopyStore::resolvePagePathFromRoot($strRoot, $strHost, $strScene, $strPageKey);
|
||||
if ($strPath !== '' && is_file($strPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$strDefaultPath = SeoCopyStore::resolvePagePathFromRoot($strRoot, $strHost, $strScene, 'default');
|
||||
if ($strDefaultPath !== '' && is_file($strDefaultPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* BSONDocument / 对象 / array 统一转数组(递归)
|
||||
*/
|
||||
@@ -1144,4 +1277,435 @@ class VideoService
|
||||
}
|
||||
return $mixVal;
|
||||
}
|
||||
|
||||
private function buildSeoCopyPageKeys(string $strScene, array $arrOptions): array
|
||||
{
|
||||
$arrKeys = [];
|
||||
|
||||
switch ($strScene) {
|
||||
case 'home':
|
||||
case 'rank_index':
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, 'index');
|
||||
break;
|
||||
|
||||
case 'category_index':
|
||||
$strParentCategory = trim((string)($arrOptions['parent_category'] ?? request()->route('strParentCategory')));
|
||||
if ($strParentCategory !== '') {
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$strParentCategory]);
|
||||
}
|
||||
$arrKeys[] = 'index';
|
||||
break;
|
||||
|
||||
case 'category_list':
|
||||
$strParentCategory = trim((string)($arrOptions['parent_category'] ?? request()->route('strParentCategory')));
|
||||
$strCategory = trim((string)($arrOptions['category'] ?? request()->route('strCategory')));
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$strParentCategory, $strCategory]);
|
||||
$arrKeys[] = 'default';
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
$strKeyword = trim((string)($arrOptions['keyword'] ?? request()->get('keyword', '')));
|
||||
if ($strKeyword !== '') {
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$strKeyword]);
|
||||
}
|
||||
$arrKeys[] = 'landing';
|
||||
break;
|
||||
|
||||
case 'rank_list':
|
||||
$strSortType = trim((string)($arrOptions['sort_type'] ?? request()->route('strSortType')));
|
||||
$strParentCategory = trim((string)($arrOptions['parent_category'] ?? request()->route('strParentCategory')));
|
||||
$strCategory = trim((string)($arrOptions['category'] ?? request()->route('strCategory')));
|
||||
if ($strSortType !== '') {
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$strSortType, $strParentCategory, $strCategory]);
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$strSortType]);
|
||||
}
|
||||
$arrKeys[] = 'default';
|
||||
break;
|
||||
|
||||
case 'detail':
|
||||
$arrVideo = $this->toArraySafe($arrOptions['video'] ?? []);
|
||||
$intVideoId = (int)($arrVideo['v_id'] ?? ($arrOptions['video_id'] ?? 0));
|
||||
if ($intVideoId > 0) {
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$intVideoId]);
|
||||
}
|
||||
$arrKeys[] = 'default';
|
||||
break;
|
||||
|
||||
case 'forge':
|
||||
$arrVideo = $this->toArraySafe($arrOptions['video'] ?? []);
|
||||
$intVideoId = (int)($arrVideo['v_id'] ?? ($arrOptions['video_id'] ?? 0));
|
||||
$intForgeId = (int)($arrOptions['forge_id'] ?? request()->route('intVForgeId'));
|
||||
if ($intVideoId > 0 && $intForgeId > 0) {
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$intVideoId, $intForgeId]);
|
||||
}
|
||||
$arrKeys[] = 'default';
|
||||
break;
|
||||
|
||||
case 'play':
|
||||
$arrVideo = $this->toArraySafe($arrOptions['video'] ?? []);
|
||||
$intVideoId = (int)($arrVideo['v_id'] ?? ($arrOptions['video_id'] ?? 0));
|
||||
$strPlayType = trim((string)($arrOptions['play_type'] ?? request()->route('strPlayType')));
|
||||
$intPlayIndex = (int)($arrOptions['play_index'] ?? request()->route('intPlayIndex'));
|
||||
if ($intVideoId > 0) {
|
||||
$arrKeys[] = SeoCopySchema::buildScenePageKey($strScene, [$intVideoId, $strPlayType, $intPlayIndex]);
|
||||
}
|
||||
$arrKeys[] = 'default';
|
||||
break;
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter($arrKeys, static function ($strKey): bool {
|
||||
return trim((string)$strKey) !== '';
|
||||
})));
|
||||
}
|
||||
|
||||
private function buildSeoCopyFacts(string $strScene, array $arrOptions): array
|
||||
{
|
||||
$arrVideo = $this->toArraySafe($arrOptions['video'] ?? []);
|
||||
$arrPagerData = $this->toArraySafe($arrOptions['pager_data'] ?? []);
|
||||
$arrPageStats = [
|
||||
'page' => (int)($arrPagerData['page'] ?? ($arrOptions['page'] ?? 1)),
|
||||
'pages' => (int)($arrPagerData['pages'] ?? ($arrOptions['pages'] ?? 1)),
|
||||
'total' => (int)($arrPagerData['total'] ?? ($arrOptions['total'] ?? 0)),
|
||||
'visible' => (int)($arrOptions['visible'] ?? 0),
|
||||
];
|
||||
|
||||
return match ($strScene) {
|
||||
'home' => [
|
||||
'site_name' => (string)($this->SiteContext->DomainModel->d_name ?? ''),
|
||||
'content_scopes' => $this->extractHomeContentScopes(),
|
||||
'navigation_paths' => ['首页 -> 分类频道 -> 详情页', '首页 -> 搜索页 -> 详情页 -> 播放页'],
|
||||
],
|
||||
'category_index' => [
|
||||
'parent_category_name' => $this->resolveCategoryLabel((string)($arrOptions['parent_category'] ?? request()->route('strParentCategory'))),
|
||||
'sub_category_examples' => $this->extractCategoryExamples((string)($arrOptions['parent_category'] ?? request()->route('strParentCategory'))),
|
||||
'search_path' => '如果题材仍然不够精确,可继续走站内搜索。',
|
||||
],
|
||||
'category_list' => [
|
||||
'parent_category_name' => $this->resolveCategoryLabel((string)($arrOptions['parent_category'] ?? request()->route('strParentCategory'))),
|
||||
'category_name' => $this->resolveCategoryLabel((string)($arrOptions['category'] ?? request()->route('strCategory'))),
|
||||
'page_stats' => $arrPageStats,
|
||||
],
|
||||
'search' => [
|
||||
'keyword' => (string)($arrOptions['keyword'] ?? request()->get('keyword', '')),
|
||||
'result_stats' => $arrPageStats,
|
||||
],
|
||||
'rank_index' => [
|
||||
'rank_periods' => $this->extractRankPeriods(),
|
||||
'rank_path' => '榜单首页 -> 榜单列表 -> 详情页 -> 播放页',
|
||||
],
|
||||
'rank_list' => [
|
||||
'rank_period_name' => $this->resolveRankSortLabel((string)($arrOptions['sort_type'] ?? request()->route('strSortType'))),
|
||||
'rank_scope_name' => $this->resolveRankScopeName(
|
||||
(string)($arrOptions['parent_category'] ?? request()->route('strParentCategory')),
|
||||
(string)($arrOptions['category'] ?? request()->route('strCategory'))
|
||||
),
|
||||
'page_stats' => $arrPageStats,
|
||||
],
|
||||
'detail' => [
|
||||
'video_name' => (string)($arrVideo['v_name'] ?? ''),
|
||||
'category_name' => (string)($arrVideo['v_category'] ?? ''),
|
||||
'year' => $this->normalizeSeoCopyYear($arrVideo),
|
||||
'core_plot_or_positioning' => (string)($arrVideo['v_description'] ?? ''),
|
||||
],
|
||||
'play' => [
|
||||
'video_name' => (string)($arrVideo['v_name'] ?? ''),
|
||||
'play_type' => $this->getConverterPlayLineVal((string)($arrOptions['play_type'] ?? request()->route('strPlayType'))),
|
||||
'episode_label_or_index' => (string)($arrOptions['play_index'] ?? request()->route('intPlayIndex') ?? 1),
|
||||
'play_index' => (int)($arrOptions['play_index'] ?? request()->route('intPlayIndex') ?? 1),
|
||||
'core_plot_or_positioning' => (string)($arrVideo['v_description'] ?? ''),
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
||||
private function normalizeSeoCopyBlock(string $strScene, array $arrData): array
|
||||
{
|
||||
$arrTemplate = SeoCopySchema::getSceneTemplate($strScene);
|
||||
$arrMerged = array_merge($arrTemplate, $arrData);
|
||||
|
||||
$arrGuideCards = [];
|
||||
foreach ((array)($arrMerged['guide_cards'] ?? []) as $arrCard) {
|
||||
if (!is_array($arrCard)) {
|
||||
continue;
|
||||
}
|
||||
$strTitle = trim((string)($arrCard['title'] ?? ''));
|
||||
$strText = trim((string)($arrCard['text'] ?? ''));
|
||||
$strHref = trim((string)($arrCard['href'] ?? ''));
|
||||
if ($strTitle === '' && $strText === '') {
|
||||
continue;
|
||||
}
|
||||
$arrItem = [
|
||||
'title' => $strTitle,
|
||||
'text' => $strText,
|
||||
];
|
||||
if ($strHref !== '') {
|
||||
$arrItem['href'] = $strHref;
|
||||
}
|
||||
$arrGuideCards[] = $arrItem;
|
||||
}
|
||||
$arrMerged['guide_cards'] = $arrGuideCards;
|
||||
$arrMerged['_scene'] = $strScene;
|
||||
|
||||
return $arrMerged;
|
||||
}
|
||||
|
||||
private function interpolateSeoCopyData(array $arrData, array $arrTokens): array
|
||||
{
|
||||
foreach ($arrData as $strKey => $mValue) {
|
||||
$arrData[$strKey] = $this->interpolateSeoCopyValue($mValue, $arrTokens);
|
||||
}
|
||||
|
||||
return $arrData;
|
||||
}
|
||||
|
||||
private function interpolateSeoCopyValue($mValue, array $arrTokens)
|
||||
{
|
||||
if (is_array($mValue)) {
|
||||
foreach ($mValue as $strKey => $mChildValue) {
|
||||
$mValue[$strKey] = $this->interpolateSeoCopyValue($mChildValue, $arrTokens);
|
||||
}
|
||||
|
||||
return $mValue;
|
||||
}
|
||||
|
||||
if (!is_string($mValue)) {
|
||||
return $mValue;
|
||||
}
|
||||
|
||||
return SeoCopyStore::interpolateTemplate($mValue, $arrTokens);
|
||||
}
|
||||
|
||||
private function buildSeoCopyTokens(string $strScene, array $arrOptions): array
|
||||
{
|
||||
$arrVideo = $this->toArraySafe($arrOptions['video'] ?? []);
|
||||
$strPlayType = trim((string)($arrOptions['play_type'] ?? request()->route('strPlayType')));
|
||||
$intPlayIndex = (int)($arrOptions['play_index'] ?? request()->route('intPlayIndex') ?? 1);
|
||||
$arrFacts = $this->buildSeoCopyFacts($strScene, $arrOptions);
|
||||
|
||||
$strSiteName = trim((string)($this->SiteContext->DomainModel->d_name ?? ''));
|
||||
$strVideoName = trim((string)($arrVideo['v_name'] ?? ''));
|
||||
$strYear = $this->normalizeSeoCopyYear($arrVideo);
|
||||
$strAreaName = $this->implodeSeoCopyScalar($arrVideo['v_area'] ?? '');
|
||||
$strLangName = $this->implodeSeoCopyScalar($arrVideo['v_lang'] ?? '');
|
||||
$strDirectorNames = $this->implodeSeoCopyScalar($arrVideo['v_director'] ?? '', 3);
|
||||
$strActorNames = $this->implodeSeoCopyScalar($arrVideo['v_actor'] ?? '', 4);
|
||||
$strRemarks = trim((string)($arrVideo['v_remarks'] ?? ''));
|
||||
$strVideoAlias = $this->implodeSeoCopyScalar($arrVideo['v_alias'] ?? ($arrVideo['v_aliases'] ?? ''), 3);
|
||||
$strPlayLine = $this->getConverterPlayLineVal($strPlayType);
|
||||
$strEpisodeName = $this->buildSeoCopyEpisodeLabel($arrVideo, $intPlayIndex);
|
||||
|
||||
$arrTokens = [
|
||||
'site_name' => $strSiteName,
|
||||
'video_name' => $strVideoName,
|
||||
'video_alias' => $strVideoAlias,
|
||||
'year' => $strYear,
|
||||
'area_name' => $strAreaName,
|
||||
'lang_name' => $strLangName,
|
||||
'director_names' => $strDirectorNames,
|
||||
'actor_names' => $strActorNames,
|
||||
'remarks' => $strRemarks,
|
||||
'play_line' => $strPlayLine,
|
||||
'play_type' => $strPlayType,
|
||||
'episode_name' => $strEpisodeName,
|
||||
'episode_label_or_index' => $strEpisodeName,
|
||||
'category_name' => trim((string)($arrVideo['v_category'] ?? '')),
|
||||
'scene' => $strScene,
|
||||
];
|
||||
|
||||
$arrKnownFactKeys = [
|
||||
'keyword',
|
||||
'parent_category_name',
|
||||
'category_name',
|
||||
'rank_period_name',
|
||||
'rank_scope_name',
|
||||
'search_path',
|
||||
'rank_path',
|
||||
];
|
||||
|
||||
foreach ($arrKnownFactKeys as $strFactKey) {
|
||||
if (!isset($arrFacts[$strFactKey])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrTokens[$strFactKey] = $this->implodeSeoCopyScalar($arrFacts[$strFactKey]);
|
||||
}
|
||||
|
||||
return array_map(static function ($mValue): string {
|
||||
return trim((string)$mValue);
|
||||
}, $arrTokens);
|
||||
}
|
||||
|
||||
private function normalizeSeoCopyYear(array $arrVideo): string
|
||||
{
|
||||
$strYear = trim((string)($arrVideo['v_year'] ?? ''));
|
||||
if ($strYear !== '') {
|
||||
if (preg_match('/\b(\d{4})\b/u', $strYear, $arrMatches) === 1) {
|
||||
return $arrMatches[1];
|
||||
}
|
||||
|
||||
return $strYear;
|
||||
}
|
||||
|
||||
$strPublishDate = trim((string)($arrVideo['v_publish_date'] ?? ''));
|
||||
if ($strPublishDate !== '' && preg_match('/\b(\d{4})\b/u', $strPublishDate, $arrMatches) === 1) {
|
||||
return $arrMatches[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function implodeSeoCopyScalar($mValue, int $intLimit = 0): string
|
||||
{
|
||||
if (is_string($mValue)) {
|
||||
return trim($mValue);
|
||||
}
|
||||
|
||||
if (!is_array($mValue)) {
|
||||
return trim((string)$mValue);
|
||||
}
|
||||
|
||||
$arrItems = array_values(array_filter(array_map(static function ($mItem): string {
|
||||
return trim((string)$mItem);
|
||||
}, $mValue), static function (string $strVal): bool {
|
||||
return $strVal !== '';
|
||||
}));
|
||||
|
||||
if ($intLimit > 0) {
|
||||
$arrItems = array_slice($arrItems, 0, $intLimit);
|
||||
}
|
||||
|
||||
return implode('、', $arrItems);
|
||||
}
|
||||
|
||||
private function buildSeoCopyEpisodeLabel(array $arrVideo, int $intPlayIndex): string
|
||||
{
|
||||
$intPlayIndex = max(1, $intPlayIndex);
|
||||
$arrPlayFrom = (array)($arrVideo['v_play_from'] ?? []);
|
||||
$arrPlayUrl = (array)($arrVideo['v_play_url'] ?? []);
|
||||
$strPlayType = trim((string)(request()->route('strPlayType') ?? ''));
|
||||
|
||||
if ($strPlayType !== '' && isset($arrPlayFrom[$strPlayType]) && !empty($arrPlayUrl[$strPlayType][$intPlayIndex - 1]['p_title'])) {
|
||||
return trim((string)$arrPlayUrl[$strPlayType][$intPlayIndex - 1]['p_title']);
|
||||
}
|
||||
|
||||
return '第' . $intPlayIndex . '集';
|
||||
}
|
||||
|
||||
private function extractHomeContentScopes(): array
|
||||
{
|
||||
$arrCategories = (array)($this->SiteContext->TpStyle['template_cfg']['pages']['home']['categories'] ?? []);
|
||||
$arrOut = [];
|
||||
foreach ($arrCategories as $arrCategory) {
|
||||
$strTitle = $this->resolveDisplayText($arrCategory['title_text'] ?? ($arrCategory['name'] ?? $arrCategory['key'] ?? ''));
|
||||
if ($strTitle !== '') {
|
||||
$arrOut[] = $strTitle;
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($arrOut) ? array_slice(array_values(array_unique($arrOut)), 0, 5) : ['电影', '电视剧', '综艺'];
|
||||
}
|
||||
|
||||
private function extractCategoryExamples(string $strParentCategory): array
|
||||
{
|
||||
$arrMap = (array)($this->SiteContext->TpStyle['template_cfg']['pages']['category']['cat1_map'][$strParentCategory]['subcat_slots'] ?? []);
|
||||
$arrOut = [];
|
||||
foreach ($arrMap as $arrItem) {
|
||||
$strTitle = $this->resolveDisplayText($arrItem['title_text'] ?? ($arrItem['name'] ?? $arrItem['key'] ?? ''));
|
||||
if ($strTitle !== '') {
|
||||
$arrOut[] = $strTitle;
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($arrOut) ? array_slice(array_values(array_unique($arrOut)), 0, 4) : [];
|
||||
}
|
||||
|
||||
private function extractRankPeriods(): array
|
||||
{
|
||||
$arrSlots = (array)($this->SiteContext->TpStyle['template_cfg']['pages']['rank_home']['slots'] ?? []);
|
||||
$arrOut = [];
|
||||
foreach ($arrSlots as $arrSlot) {
|
||||
$strSortType = trim((string)($arrSlot['sort_type'] ?? ''));
|
||||
if ($strSortType !== '') {
|
||||
$arrOut[] = $this->resolveRankSortLabel($strSortType);
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($arrOut) ? array_slice(array_values(array_unique($arrOut)), 0, 4) : ['日榜', '周榜', '月榜'];
|
||||
}
|
||||
|
||||
private function resolveCategoryLabel(string $strCategory): string
|
||||
{
|
||||
$strCategory = trim($strCategory);
|
||||
if ($strCategory === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$arrFlatMap = (array)VideoCategoryModel::$arrCategory;
|
||||
if (isset($arrFlatMap[$strCategory])) {
|
||||
return trim((string)$arrFlatMap[$strCategory]);
|
||||
}
|
||||
|
||||
foreach ((array)VideoCategoryModel::getCustomCategory('ONE') as $arrParentCategory) {
|
||||
if ((string)($arrParentCategory['v_category_en'] ?? '') === $strCategory) {
|
||||
return trim((string)($arrParentCategory['v_category'] ?? $strCategory));
|
||||
}
|
||||
|
||||
foreach ((array)($arrParentCategory['children'] ?? []) as $arrChildCategory) {
|
||||
if ((string)($arrChildCategory['v_category_en'] ?? '') === $strCategory) {
|
||||
return trim((string)($arrChildCategory['v_category'] ?? $strCategory));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $strCategory;
|
||||
}
|
||||
|
||||
private function resolveRankSortLabel(string $strSortType): string
|
||||
{
|
||||
return match (trim(strtolower($strSortType))) {
|
||||
'daily' => '日榜',
|
||||
'weekly' => '周榜',
|
||||
'monthly' => '月榜',
|
||||
default => $strSortType !== '' ? $strSortType : '榜单',
|
||||
};
|
||||
}
|
||||
|
||||
private function resolveRankScopeName(string $strParentCategory, string $strCategory): string
|
||||
{
|
||||
$strParentLabel = $this->resolveCategoryLabel($strParentCategory);
|
||||
$strCategoryLabel = $this->resolveCategoryLabel($strCategory);
|
||||
|
||||
if ($strParentLabel !== '' && $strCategoryLabel !== '' && $strCategoryLabel !== 'all') {
|
||||
return $strParentLabel . ' / ' . $strCategoryLabel;
|
||||
}
|
||||
|
||||
if ($strParentLabel !== '') {
|
||||
return $strParentLabel;
|
||||
}
|
||||
|
||||
return '当前范围';
|
||||
}
|
||||
|
||||
private function resolveDisplayText($mValue): string
|
||||
{
|
||||
if (is_array($mValue)) {
|
||||
foreach (['primary', 'seo', 'secondary', 'name', 'title', 'text'] as $strKey) {
|
||||
$strVal = trim((string)($mValue[$strKey] ?? ''));
|
||||
if ($strVal !== '') {
|
||||
return $strVal;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($mValue as $mItem) {
|
||||
$strVal = $this->resolveDisplayText($mItem);
|
||||
if ($strVal !== '') {
|
||||
return $strVal;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return trim((string)$mValue);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user