wpmind/modules/geo/includes/AiSummaryManager.php
LinuxJoy c7108ed376 feat: GEO 模块 v3.10 — 3 个新功能 + 设置页子导航重构
新增组件:
- RobotsTxtManager: robots.txt AI 爬虫管理 (15 个爬虫 Allow/Disallow)
- AiSummaryManager: AI 摘要编辑器字段 + meta 标签 + Schema.org abstract
- EntityLinker: Wikidata 实体关联 + Schema.org about.sameAs

设置页重构:
- 平铺 section 改为 5 个 pill 子导航 (基础/内容/结构化数据/AI索引/爬虫管理)
- 右栏爬虫统计仅在基础设置 tab 显示,其他 tab 全宽布局
- 子 tab 状态通过 sessionStorage 保持

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-08 15:02:58 +08:00

194 lines
4.4 KiB
PHP

<?php
/**
* AI Summary Manager
*
* Provides per-post AI context summaries for better AI citation control.
*
* @package WPMind\Modules\Geo
* @since 3.10.0
*/
declare(strict_types=1);
namespace WPMind\Modules\Geo;
/**
* Class AiSummaryManager
*
* Adds an "AI Summary" field to the post editor and outputs it
* as a meta tag and Schema.org abstract for AI crawlers.
*/
class AiSummaryManager {
/**
* Post meta key.
*/
private const META_SUMMARY = '_wpmind_ai_summary';
/**
* Nonce action.
*/
private const NONCE_ACTION = 'wpmind_ai_summary_metabox';
/**
* Constructor.
*/
public function __construct() {
add_action( 'wp_head', [ $this, 'output_meta_tag' ], 3 );
add_action( 'add_meta_boxes', [ $this, 'register_metabox' ] );
add_action( 'save_post', [ $this, 'save_metabox' ] );
add_filter( 'wpmind_article_schema', [ $this, 'enrich_schema' ], 10, 2 );
}
/**
* Output AI summary meta tag in wp_head.
*/
public function output_meta_tag(): void {
if ( ! is_singular() ) {
return;
}
$post = get_queried_object();
if ( ! $post instanceof \WP_Post ) {
return;
}
$summary = $this->get_summary( $post->ID );
if ( empty( $summary ) ) {
return;
}
echo '<meta name="ai-summary" content="' . esc_attr( $summary ) . '">' . "\n";
}
/**
* Enrich Article schema with abstract property.
*
* @param array $schema Schema data.
* @param \WP_Post $post Post object.
* @return array Modified schema.
*/
public function enrich_schema( array $schema, \WP_Post $post ): array {
$summary = $this->get_summary( $post->ID );
if ( ! empty( $summary ) ) {
$schema['abstract'] = $summary;
}
return $schema;
}
/**
* Get AI summary for a post.
*
* Falls back to excerpt if no custom summary is set.
*
* @param int $post_id Post ID.
* @return string Summary text.
*/
public function get_summary( int $post_id ): string {
$summary = get_post_meta( $post_id, self::META_SUMMARY, true );
if ( ! empty( $summary ) && is_string( $summary ) ) {
return $summary;
}
// Fallback to excerpt.
$fallback = get_option( 'wpmind_ai_summary_fallback', 'excerpt' );
if ( 'excerpt' === $fallback ) {
$post = get_post( $post_id );
if ( $post instanceof \WP_Post ) {
$excerpt = $post->post_excerpt;
if ( ! empty( $excerpt ) ) {
return wp_strip_all_tags( $excerpt );
}
}
}
return '';
}
/**
* Register metabox on public post types.
*/
public function register_metabox(): void {
$post_types = get_post_types( [ 'public' => true ], 'names' );
foreach ( $post_types as $post_type ) {
if ( 'attachment' === $post_type ) {
continue;
}
add_meta_box(
'wpmind-ai-summary',
__( 'AI 摘要', 'wpmind' ),
[ $this, 'render_metabox' ],
$post_type,
'side',
'default'
);
}
}
/**
* Render metabox UI.
*
* @param \WP_Post $post Current post.
*/
public function render_metabox( \WP_Post $post ): void {
wp_nonce_field( self::NONCE_ACTION, '_wpmind_ai_summary_nonce' );
$summary = get_post_meta( $post->ID, self::META_SUMMARY, true );
if ( ! is_string( $summary ) ) {
$summary = '';
}
?>
<div class="wpmind-ai-summary-metabox">
<p class="description" style="margin-bottom:8px;">
<?php esc_html_e( '控制 AI 如何描述这篇文章。留空则使用摘要。', 'wpmind' ); ?>
</p>
<textarea name="wpmind_ai_summary" rows="4" style="width:100%;"
placeholder="<?php esc_attr_e( '为 AI 爬虫提供简洁的内容摘要...', 'wpmind' ); ?>"
><?php echo esc_textarea( $summary ); ?></textarea>
<p class="description">
<?php
printf(
/* translators: %d: character count */
esc_html__( '建议 50-160 字符。当前:%d 字符', 'wpmind' ),
mb_strlen( $summary )
);
?>
</p>
</div>
<?php
}
/**
* Save metabox data.
*
* @param int $post_id Post ID.
*/
public function save_metabox( int $post_id ): void {
if ( ! isset( $_POST['_wpmind_ai_summary_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce(
sanitize_text_field( wp_unslash( $_POST['_wpmind_ai_summary_nonce'] ) ),
self::NONCE_ACTION
) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$summary = isset( $_POST['wpmind_ai_summary'] )
? sanitize_textarea_field( wp_unslash( $_POST['wpmind_ai_summary'] ) )
: '';
update_post_meta( $post_id, self::META_SUMMARY, $summary );
}
}