ID )
: array();
// Fallback to legacy scoring if Ultimate Score not available
if ( ! empty( $ultimate_score ) ) {
$unified_data = array(
'unified_score' => $ultimate_score['score'],
'color' => $ultimate_score['color'],
'label' => $ultimate_score['label'],
'status' => '',
'content_data' => $ultimate_score,
'content_score' => $ultimate_score['score'],
'content_weight' => 70,
'visibility_score' => 0,
'visibility_weight' => 30,
);
} else {
$unified_data = AI_Answer_Toolkit_Score::calculate_unified_score( $post->ID );
}
// Extract individual components
$manual_summary = get_post_meta( $post->ID, '_aitk_summary', true );
$auto_summary = AI_Answer_Toolkit_Summary::generate_auto_summary( $post->ID );
$final_summary = AI_Answer_Toolkit_Summary::get_final_summary( $post->ID );
$summary_type = AI_Answer_Toolkit_Summary::get_summary_type( $post->ID );
$faq_count = AI_Answer_Toolkit_Score::get_faq_count( $post->ID );
// Get stats
$content = get_post_field( 'post_content', $post->ID );
$word_count = str_word_count( wp_strip_all_tags( $content ) );
// Get warnings from content data
$warnings = self::get_warnings( $post->ID, $unified_data['content_data'] );
// Allow filtering sections
$sections = apply_filters(
'ai_answer_toolkit_meta_box_sections',
array(
'summary' => true,
'score' => true,
'stats' => true,
),
$post->ID
);
?>
'aitk_summary',
'textarea_rows' => 5,
'media_buttons' => false,
'teeny' => true,
'quicktags' => false,
'tinymce' => array(
'toolbar1' => 'bold,italic,link,unlink,undo,redo',
'toolbar2' => '',
),
)
);
?>
0
ID ); ?>
post_content ) );
$title = trim( $post->post_title );
$has_content = ! empty( $content ) || ! empty( $title );
// Get content guidance analysis
$guidance = AI_Answer_Toolkit_Content_Guidance::analyze_content( $post_id );
?>
'dashicons-yes-alt',
'warning' => 'dashicons-warning',
'fail' => 'dashicons-dismiss',
);
return isset( $icons[ $status ] ) ? $icons[ $status ] : 'dashicons-info';
}
/**
* Get visibility check label.
*
* @param string $check_name Check name.
* @return string Check label.
*/
private static function get_visibility_check_label( $check_name ) {
$labels = array(
'summary' => __( 'Summary Visibility', 'ai-answer-toolkit' ),
'article' => __( 'Article Schema', 'ai-answer-toolkit' ),
'faq' => __( 'FAQ Schema', 'ai-answer-toolkit' ),
'placement' => __( 'Summary Placement', 'ai-answer-toolkit' ),
);
return isset( $labels[ $check_name ] ) ? $labels[ $check_name ] : ucfirst( $check_name );
}
/**
* Get warnings for the post.
*
* @param int $post_id Post ID.
* @param array $score_data Score data.
* @return array Warnings.
*/
private static function get_warnings( $post_id, $score_data ) {
$warnings = array();
// Check for issues in score data
if ( ! empty( $score_data['issues'] ) ) {
foreach ( $score_data['issues'] as $issue ) {
$warnings[] = array(
'type' => 'warning',
'message' => $issue,
);
}
}
// Critical warning is now displayed directly after score status, not in warnings list
// Allow filtering
return apply_filters( 'ai_answer_toolkit_meta_box_warnings', $warnings, $post_id );
}
/**
* Save meta box data.
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
*/
public static function save_meta_box( $post_id, $post ) {
// Check nonce
if ( ! isset( $_POST['AI_Answer_Toolkit_meta_box_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['AI_Answer_Toolkit_meta_box_nonce'] ) ), 'AI_Answer_Toolkit_meta_box' ) ) {
return;
}
// Check autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Check permissions
$post_type = get_post_type_object( $post->post_type );
if ( ! current_user_can( $post_type->cap->edit_post, $post_id ) ) {
return;
}
// Save manual summary
if ( isset( $_POST['aitk_summary'] ) ) {
$summary = wp_kses_post( wp_unslash( $_POST['aitk_summary'] ) );
update_post_meta( $post_id, '_aitk_summary', $summary );
// Fire action hook
do_action( 'ai_answer_toolkit_meta_box_summary_saved', $post_id, $summary );
}
}
/**
* Handle AJAX request to refresh content guidance.
*/
public static function ajax_refresh_content_guidance() {
// Verify nonce
check_ajax_referer( 'aitk_ai_suggestions', 'nonce' );
// Get post ID and content
$post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
$content = isset( $_POST['content'] ) ? wp_kses_post( wp_unslash( $_POST['content'] ) ) : '';
if ( ! $post_id ) {
wp_send_json_error( array( 'message' => 'Invalid post ID' ) );
}
// Get post object
// Create temporary post object with updated content
$post = get_post( $post_id );
if ( ! $post ) {
wp_send_json_error( array( 'message' => 'Post not found' ) );
}
// Temporarily update post content for analysis
$original_content = $post->post_content;
$post->post_content = $content;
// Update global post temporarily
$GLOBALS['post'] = $post;
// Get fresh guidance analysis
$guidance = AI_Answer_Toolkit_Content_Guidance::analyze_content( $post_id );
// Restore original content
$post->post_content = $original_content;
$GLOBALS['post'] = $post;
// Generate HTML
ob_start();
?>
$html ) );
}
/**
* Handle unified AJAX request to recalculate all AEO components.
*
* Returns: score, tips, visibility, and guidance in a single response.
* Phase 1: Quick Win - Batch all updates into one request.
*/
public static function ajax_recalculate_all() {
// Verify nonce
check_ajax_referer( 'aitk_ai_suggestions', 'nonce' );
// Get post ID and content
$post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
$content = isset( $_POST['content'] ) ? wp_kses_post( wp_unslash( $_POST['content'] ) ) : '';
$title = isset( $_POST['title'] ) ? sanitize_text_field( wp_unslash( $_POST['title'] ) ) : '';
$summary = isset( $_POST['summary'] ) ? wp_kses_post( wp_unslash( $_POST['summary'] ) ) : '';
if ( ! $post_id ) {
wp_send_json_error( array( 'message' => 'Invalid post ID' ) );
}
// Verify user can edit this post
if ( ! current_user_can( 'edit_post', $post_id ) ) {
wp_send_json_error( array( 'message' => 'Permission denied' ) );
}
// Get post object
$post = get_post( $post_id );
if ( ! $post ) {
wp_send_json_error( array( 'message' => 'Post not found' ) );
}
// Temporarily update post data for analysis
$original_content = $post->post_content;
$original_title = $post->post_title;
$original_summary = get_post_meta( $post_id, '_aitk_summary', true );
$post->post_content = $content;
if ( ! empty( $title ) ) {
$post->post_title = $title;
}
if ( ! empty( $summary ) ) {
update_post_meta( $post_id, '_aitk_summary', $summary );
}
$GLOBALS['post'] = $post;
// Calculate all components
$response = array();
// 1. Overall AEO Score
$score_data = AI_Answer_Toolkit_Score::calculate_score( $post_id );
$response['score'] = array(
'unified_score' => $score_data['score'],
'label' => $score_data['label'],
'color' => $score_data['color'],
'status' => $score_data['status'],
'breakdown' => $score_data['breakdown'],
);
// 2. Improvement Tips
$tips = AI_Answer_Toolkit_AI_Suggestions::get_improvement_hints( $post_id );
$response['tips'] = self::render_improvement_tips_html( $tips );
// 3. Visibility Checks
$visibility_data = AI_Answer_Toolkit_Visibility::analyze_visibility( $post_id );
$response['visibility'] = array(
'score' => $visibility_data['score'],
'checks' => $visibility_data['checks'],
'warnings' => $visibility_data['warnings'],
'html' => self::render_visibility_checks_html( $visibility_data ),
);
// 4. Content Guidance
$guidance = AI_Answer_Toolkit_Content_Guidance::analyze_content( $post_id );
$response['guidance'] = array(
'html' => self::render_content_guidance_html( $guidance ),
);
// 5. Content Insights Stats
$word_count = str_word_count( wp_strip_all_tags( $post->post_content ) );
$faq_count = self::count_faq_blocks( $post->post_content );
$final_summary = get_post_meta( $post_id, '_aitk_summary', true );
$response['stats'] = array(
'html' => self::render_stats_html( $post_id, $word_count, $faq_count, $final_summary ),
);
// Restore original post data
$post->post_content = $original_content;
$post->post_title = $original_title;
if ( ! empty( $summary ) ) {
update_post_meta( $post_id, '_aitk_summary', $original_summary );
}
$GLOBALS['post'] = $post;
wp_send_json_success( $response );
}
/**
* Render improvement tips HTML for AJAX response.
*
* @param array $tips Improvement tips array.
* @return string HTML output.
*/
private static function render_improvement_tips_html( $tips ) {
if ( empty( $tips ) ) {
return '
' . esc_html__( 'Great job! No improvement tips at this time.', 'ai-answer-toolkit' ) . '
';
}
// Group tips by category
$grouped = array();
foreach ( $tips as $tip ) {
$cat = isset( $tip['category'] ) ? $tip['category'] : 'general';
if ( ! isset( $grouped[ $cat ] ) ) {
$grouped[ $cat ] = array();
}
$grouped[ $cat ][] = $tip;
}
ob_start();
foreach ( $grouped as $category => $category_tips ) {
$cat_label = ucwords( str_replace( '_', ' ', $category ) );
$is_collapsed = ( $category !== 'critical' && $category !== 'content_quality' ) ? '1' : '0';
?>
admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'aitk_ai_suggestions' ),
)
);
}
/**
* Render stats HTML for AJAX response.
*
* @param int $post_id Post ID.
* @param int $word_count Word count.
* @param int $faq_count FAQ count.
* @param string $final_summary Summary text.
* @return string HTML output.
*/
private static function render_stats_html( $post_id, $word_count, $faq_count, $final_summary ) {
$post = get_post( $post_id );
ob_start();
?>
post_type === 'service' ) : ?>
/i';
preg_match_all( $faq_pattern, $content, $matches );
return count( $matches[0] );
}
}
AI_Answer_Toolkit_Unified_Meta_Box::init();