*/
private function config(): array {
static $cache = null;
if ($cache !== null) {
return $cache;
}
if (class_exists('\AKSRT\Settings\Settings')) {
$s = \AKSRT\Settings\Settings::get_all();
} else {
$s = [];
}
$cache = [
'connector' => trim((string) ($s['seo_connector'] ?? '-')) ?: '-',
'site_title' => trim((string) ($s['seo_title'] ?? '')),
'description' => trim((string) ($s['seo_description'] ?? ($s['site_description'] ?? ''))),
'keywords' => trim((string) ($s['seo_keywords'] ?? '')),
'canonical' => !isset($s['canonical_tag']) || !empty($s['canonical_tag']),
'og_enabled' => empty($s['disable_og_meta']),
'jsonld' => !isset($s['seo_jsonld']) || !empty($s['seo_jsonld']),
'jsonld_single' => !isset($s['seo_jsonld_single']) || !empty($s['seo_jsonld_single']),
'jsonld_page' => !isset($s['seo_jsonld_page']) || !empty($s['seo_jsonld_page']),
'default_image' => trim((string) ($s['seo_image_default'] ?? '')),
'always_default' => !empty($s['seo_image_always_default']),
];
return $cache;
}
/**
* 解析当前请求的规范 SEO 负载。
*
* @return array{title:string,description:string,keywords:string,canonical:string,image:string,og_type:string,object:?\WP_Post}
*/
private function resolve_payload(): array {
$cfg = $this->config();
$site_name = get_bloginfo('name');
$payload = [
'title' => wp_get_document_title(),
'description' => $cfg['description'],
'keywords' => $cfg['keywords'],
'canonical' => home_url(isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '/'),
'image' => $cfg['default_image'],
'og_type' => 'website',
'object' => null,
];
if (is_singular()) {
$post = get_queried_object();
if ($post instanceof \WP_Post) {
$payload['object'] = $post;
$payload['canonical'] = (string) get_permalink($post);
$payload['og_type'] = ($post->post_type === 'post') ? 'article' : 'website';
// 单篇 SEO 覆盖:per-post meta → 文章字段 → 站点默认。
$meta = $this->post_seo_meta($post->ID);
$payload['title'] = $meta['title'] !== '' ? $meta['title'] : wp_strip_all_tags(get_the_title($post));
if ($meta['description'] !== '') {
$payload['description'] = $meta['description'];
} else {
$excerpt = has_excerpt($post)
? wp_strip_all_tags(get_the_excerpt($post))
: wp_trim_words(wp_strip_all_tags((string) $post->post_content), 40, '…');
$payload['description'] = trim((string) $excerpt);
}
if ($meta['keywords'] !== '') {
$payload['keywords'] = $meta['keywords'];
} elseif ($payload['keywords'] === '') {
$payload['keywords'] = $this->terms_to_keywords($post);
}
$payload['image'] = $this->resolve_image($post, $cfg);
}
} elseif (is_home() || is_front_page()) {
if ($cfg['site_title'] !== '') {
$payload['title'] = $cfg['site_title'];
}
$payload['canonical'] = home_url('/');
} elseif (is_category() || is_tag() || is_tax()) {
$term = get_queried_object();
if ($term instanceof \WP_Term) {
$payload['canonical'] = (string) get_term_link($term);
$meta = $this->term_seo_meta((int) $term->term_id);
if ($meta['title'] !== '') {
$payload['title'] = $meta['title'];
}
if ($meta['description'] !== '') {
$payload['description'] = $meta['description'];
} else {
$term_desc = trim(wp_strip_all_tags((string) term_description($term)));
if ($term_desc !== '') {
$payload['description'] = $term_desc;
}
}
if ($meta['keywords'] !== '') {
$payload['keywords'] = $meta['keywords'];
}
}
}
if ($cfg['always_default'] && $cfg['default_image'] !== '') {
$payload['image'] = $cfg['default_image'];
}
/**
* 允许扩展(如 AI SEO)在最终输出前调整 head 负载。
*
* @param array $payload 规范 SEO 负载。
*/
return apply_filters('aksrt_seo_head_payload', $payload);
}
/**
* 读取单篇 SEO 覆盖(per-post meta)。
*
* @param int $post_id Post ID.
* @return array{title:string,description:string,keywords:string}
*/
private function post_seo_meta(int $post_id): array {
$get = static function (string $key) use ($post_id): string {
return trim((string) get_post_meta($post_id, $key, true));
};
return [
'title' => $get('_aksrt_seo_title'),
'description' => $get('_aksrt_seo_description'),
'keywords' => $get('_aksrt_seo_keywords'),
];
}
/**
* 读取 term SEO 覆盖。
*
* @return array{title:string,description:string,keywords:string}
*/
private function term_seo_meta(int $term_id): array {
$get = static function (string $key) use ($term_id): string {
return trim((string) get_term_meta($term_id, $key, true));
};
return [
'title' => $get('_aksrt_seo_title'),
'description' => $get('_aksrt_seo_description'),
'keywords' => $get('_aksrt_seo_keywords'),
];
}
/**
* 由分类/标签生成关键词回退。
*/
private function terms_to_keywords(\WP_Post $post): string {
$names = [];
foreach ((array) get_the_category($post->ID) as $cat) {
if ($cat instanceof \WP_Term) {
$names[] = $cat->name;
}
}
$tags = get_the_terms($post, 'post_tag');
if (is_array($tags)) {
foreach ($tags as $tag) {
$names[] = $tag->name;
}
}
return implode(',', array_slice(array_values(array_unique(array_filter($names))), 0, 10));
}
/**
* 解析单篇缩略图:特色图 → 内容首图 → 站点默认 → logo。
*/
private function resolve_image(\WP_Post $post, array $cfg): string {
if (has_post_thumbnail($post)) {
$image = (string) get_the_post_thumbnail_url($post, 'large');
if ($image !== '') {
return $image;
}
}
if (function_exists('aksrt_get_first_content_image_url')) {
$image = (string) aksrt_get_first_content_image_url($post->ID);
if ($image !== '') {
return $image;
}
}
if ($cfg['default_image'] !== '') {
return $cfg['default_image'];
}
if (class_exists('\AKSRT\Settings\Settings')) {
return (string) \AKSRT\Settings\Settings::logo_url();
}
return '';
}
/**
* dl token 页面 noindex。
*/
public function render_robots(): void {
if (function_exists('get_query_var') && get_query_var('aksrt_dl_token')) {
echo '' . "\n";
}
}
/**
* 输出 head(favicon + canonical + description/keywords + OG/Twitter + JSON-LD)。
*/
public function render(): void {
$cfg = $this->config();
$payload = $this->resolve_payload();
echo "\n\n";
// Favicon(无站点图标时回退)。
if (function_exists('aksrt_get_favicon_url')) {
$favicon = (string) aksrt_get_favicon_url();
if ($favicon !== '' && !has_site_icon()) {
printf('' . "\n", esc_url($favicon));
}
}
// Canonical。
if ($cfg['canonical'] && !is_404() && $payload['canonical'] !== '') {
printf('' . "\n", esc_url($payload['canonical']));
}
// Description / Keywords。
if ($payload['description'] !== '') {
printf('' . "\n", esc_attr($payload['description']));
}
if ($payload['keywords'] !== '') {
printf('' . "\n", esc_attr($payload['keywords']));
}
// OpenGraph + Twitter。
if ($cfg['og_enabled']) {
$this->render_open_graph($payload);
}
// JSON-LD。
if ($cfg['jsonld']) {
$this->render_jsonld($payload, $cfg);
}
echo "\n";
}
/**
* 输出 OpenGraph + Twitter Card。
*/
private function render_open_graph(array $payload): void {
$site_name = get_bloginfo('name');
$locale = str_replace('-', '_', get_locale());
$og = [
['property' => 'og:type', 'content' => $payload['og_type']],
['property' => 'og:locale', 'content' => $locale],
['property' => 'og:site_name', 'content' => $site_name],
['property' => 'og:title', 'content' => $payload['title']],
['property' => 'og:url', 'content' => $payload['canonical']],
];
if ($payload['description'] !== '') {
$og[] = ['property' => 'og:description', 'content' => $payload['description']];
}
if ($payload['image'] !== '') {
$og[] = ['property' => 'og:image', 'content' => $payload['image']];
}
if ($payload['og_type'] === 'article' && $payload['object'] instanceof \WP_Post) {
$post = $payload['object'];
$published = get_the_date('c', $post);
$modified = get_the_modified_date('c', $post);
$author = get_the_author_meta('display_name', (int) $post->post_author);
if ($published) $og[] = ['property' => 'article:published_time', 'content' => $published];
if ($modified) $og[] = ['property' => 'article:modified_time', 'content' => $modified];
if ($author) $og[] = ['property' => 'article:author', 'content' => $author];
$cats = get_the_category($post->ID);
if (!empty($cats)) {
$og[] = ['property' => 'article:section', 'content' => $cats[0]->name];
}
$tags = get_the_terms($post, 'post_tag');
if (is_array($tags)) {
foreach ($tags as $tag) {
$og[] = ['property' => 'article:tag', 'content' => $tag->name];
}
}
}
foreach ($og as $tag) {
printf('' . "\n", esc_attr($tag['property']), esc_attr($tag['content']));
}
$twitter = [
['name' => 'twitter:card', 'content' => $payload['image'] !== '' ? 'summary_large_image' : 'summary'],
['name' => 'twitter:title', 'content' => $payload['title']],
];
if ($payload['description'] !== '') {
$twitter[] = ['name' => 'twitter:description', 'content' => $payload['description']];
}
if ($payload['image'] !== '') {
$twitter[] = ['name' => 'twitter:image', 'content' => $payload['image']];
}
foreach ($twitter as $tag) {
printf('' . "\n", esc_attr($tag['name']), esc_attr($tag['content']));
}
}
/**
* 输出 JSON-LD 结构化数据。
*/
private function render_jsonld(array $payload, array $cfg): void {
$graph = [];
// WebSite(全站)。
$graph[] = [
'@type' => 'WebSite',
'@id' => home_url('/#website'),
'url' => home_url('/'),
'name' => get_bloginfo('name'),
'inLanguage' => get_locale(),
];
if (is_singular('post') && $cfg['jsonld_single'] && $payload['object'] instanceof \WP_Post) {
$post = $payload['object'];
$node = [
'@type' => 'Article',
'headline' => $payload['title'],
'url' => $payload['canonical'],
'datePublished' => get_the_date('c', $post),
'dateModified' => get_the_modified_date('c', $post),
'author' => [
'@type' => 'Person',
'name' => get_the_author_meta('display_name', (int) $post->post_author),
],
'publisher' => [
'@type' => 'Organization',
'name' => get_bloginfo('name'),
],
];
if ($payload['description'] !== '') {
$node['description'] = $payload['description'];
}
if ($payload['image'] !== '') {
$node['image'] = $payload['image'];
}
$graph[] = $node;
} elseif (is_page() && $cfg['jsonld_page'] && $payload['object'] instanceof \WP_Post) {
$graph[] = [
'@type' => 'WebPage',
'url' => $payload['canonical'],
'name' => $payload['title'],
];
}
if (empty($graph)) {
return;
}
$doc = [
'@context' => 'https://schema.org',
'@graph' => $graph,
];
echo '' . "\n";
}
}