';
}
}
/**
* Validate options.
*
* @param array $input Raw input data.
* @return array Validated options.
*/
public function validate_options($input) {
// Start with existing options to preserve values not in current form
$existing_options = get_option('ai_smart_excerpt_options', array());
$validated = $existing_options;
// Determine which form section was submitted
$form_section = isset($input['ai_smart_excerpt_form_section']) ? $input['ai_smart_excerpt_form_section'] : 'unknown';
// Define which fields belong to each form section
$section_fields = array(
'general' => array('is_active', 'admin_only'),
'ai_provider' => array('llm_provider', 'api_key', 'claude_model', 'openai_model', 'gemini_model'),
'excerpts' => array('excerpt_type', 'max_length', 'min_content_length'),
'seo' => array('seo_integration_enabled', 'seo_focus_keyword', 'auto_assign_categories', 'create_new_categories', 'auto_assign_tags', 'max_auto_tags', 'taxonomy_confidence_threshold', 'keywords_enabled', 'keyword_type', 'max_keywords', 'keywords_as_tags', 'keywords_display_meta'),
'display' => array('featured_image', 'featured_size', 'featured_align', 'search_featured', 'more_link_text'),
'advanced' => array('skip_manual', 'fallback_enabled', 'auto_excerpt_on_schedule', 'auto_seo_on_schedule')
);
// Boolean fields
$boolean_fields = array('is_active', 'admin_only', 'featured_image', 'search_featured', 'skip_manual', 'fallback_enabled', 'seo_integration_enabled', 'seo_focus_keyword', 'auto_assign_categories', 'create_new_categories', 'auto_assign_tags', 'keywords_enabled', 'keywords_as_tags', 'keywords_display_meta', 'auto_excerpt_on_schedule', 'auto_seo_on_schedule');
// Only update fields that belong to the submitted form section
if (isset($section_fields[$form_section])) {
$fields_to_update = $section_fields[$form_section];
foreach ($fields_to_update as $field) {
if (in_array($field, $boolean_fields)) {
// Handle boolean fields - now they will always be present due to hidden field
$validated[$field] = isset($input[$field]) && $input[$field] === '1';
} else {
// Handle other fields if present in input
if (isset($input[$field])) {
$validated[$field] = $input[$field]; // Will be sanitized below
}
}
}
} else {
// Fallback: if no section specified, handle all boolean fields for backward compatibility
foreach ($boolean_fields as $field) {
if (array_key_exists($field, $input)) {
$validated[$field] = isset($input[$field]) && $input[$field] === '1';
}
}
// Also handle non-boolean fields if they exist in input
$all_fields = array_merge(
$section_fields['general'] ?? array(),
$section_fields['ai_provider'] ?? array(),
$section_fields['excerpts'] ?? array(),
$section_fields['keywords'] ?? array(),
$section_fields['display'] ?? array(),
$section_fields['advanced'] ?? array()
);
foreach ($all_fields as $field) {
if (!in_array($field, $boolean_fields) && isset($input[$field])) {
$validated[$field] = $input[$field];
}
}
}
// Apply proper sanitization to validated fields
// Text fields
$text_fields = array('api_key', 'more_link_text');
foreach ($text_fields as $field) {
if (isset($validated[$field]) && $validated[$field] !== $existing_options[$field]) {
$validated[$field] = sanitize_text_field($validated[$field]);
}
}
// Select fields with specific options
$select_fields = array(
'llm_provider' => array_keys(LLM_Service::get_providers()),
'claude_model' => array_keys(LLM_Service::get_models_for_provider('claude')),
'openai_model' => array_keys(LLM_Service::get_models_for_provider('openai')),
'gemini_model' => array_keys(LLM_Service::get_models_for_provider('gemini')),
'excerpt_type' => array('seo', 'social', 'summary', 'teaser'),
'keyword_type' => array('seo', 'tags', 'focus'),
'featured_size' => array('thumbnail', 'medium', 'large', 'full'),
'featured_align' => array('alignleft', 'alignright', 'aligncenter')
);
foreach ($select_fields as $field => $valid_options) {
if (isset($validated[$field])) {
if (!in_array($validated[$field], $valid_options)) {
// Invalid value, revert to existing or default
if (isset($existing_options[$field]) && in_array($existing_options[$field], $valid_options)) {
$validated[$field] = $existing_options[$field];
} else {
$validated[$field] = $valid_options[0]; // Use first valid option as default
}
}
// Value is valid, keep it as is
} else if (isset($input[$field]) && in_array($input[$field], $valid_options)) {
// Field wasn't processed by section logic but exists in input and is valid
$validated[$field] = $input[$field];
}
}
// Number fields
$number_fields = array(
'max_length' => array('min' => 50, 'max' => 500),
'min_content_length' => array('min' => 50, 'max' => 1000),
'max_keywords' => array('min' => 1, 'max' => 20),
);
foreach ($number_fields as $field => $limits) {
if (isset($validated[$field])) {
if ($validated[$field] !== $existing_options[$field]) {
$value = intval($validated[$field]);
$validated[$field] = max($limits['min'], min($limits['max'], $value));
}
} else if (isset($input[$field])) {
// Field wasn't processed by section logic but exists in input
$value = intval($input[$field]);
$validated[$field] = max($limits['min'], min($limits['max'], $value));
}
}
// Validate API key format
if (!empty($validated['api_key']) && !empty($validated['llm_provider'])) {
if (!$this->llm_service->validate_api_key_format($validated['llm_provider'], $validated['api_key'])) {
add_settings_error(
'ai_smart_excerpt_options',
'invalid_api_key',
esc_html__('API key format is invalid for the selected provider.', 'ai-smart-excerpt')
);
}
}
return $validated;
}
/**
* Admin page display.
*/
public function admin_page() {
if (!current_user_can('manage_options')) {
return;
}
// Reload options to ensure we have the latest values
$this->load_options();
$active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'overview';
?>
render_overview_tab();
break;
case 'ai-provider':
$this->render_ai_provider_tab();
break;
case 'excerpts':
$this->render_excerpt_settings_tab();
break;
case 'seo':
$this->render_seo_settings_tab();
break;
case 'display':
$this->render_display_settings_tab();
break;
case 'advanced':
$this->render_advanced_settings_tab();
break;
case 'status':
$this->render_status_tab();
break;
case 'recommendations':
$this->render_recommendations_tab();
break;
default:
$this->render_overview_tab();
break;
}
?>
admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ai_smart_excerpt_ajax'),
'has_api_key' => !empty($this->options['api_key']),
'strings' => array(
'testing' => esc_html__('Testing...', 'ai-smart-excerpt'),
'success' => esc_html__('Success!', 'ai-smart-excerpt'),
'error' => esc_html__('Error occurred', 'ai-smart-excerpt'),
'generating' => esc_html__('Generating...', 'ai-smart-excerpt'),
'generated' => esc_html__('Generated successfully', 'ai-smart-excerpt'),
'test_connection' => esc_html__('Test Connection', 'ai-smart-excerpt'),
'please_enter_api_key' => esc_html__('Please enter an API key first.', 'ai-smart-excerpt'),
'bulk_confirm' => esc_html__('This will generate AI excerpts for all posts that don\'t have them yet. This process runs in the background and may take some time. Continue?', 'ai-smart-excerpt'),
'starting' => esc_html__('Starting...', 'ai-smart-excerpt'),
'initializing' => esc_html__('Initializing...', 'ai-smart-excerpt'),
'calculating' => esc_html__('Calculating...', 'ai-smart-excerpt'),
'processing' => esc_html__('Processing...', 'ai-smart-excerpt'),
'restarting' => esc_html__('Restarting...', 'ai-smart-excerpt'),
'resetting' => esc_html__('Resetting...', 'ai-smart-excerpt'),
'debugging' => esc_html__('Debugging...', 'ai-smart-excerpt'),
'forcing' => esc_html__('Forcing...', 'ai-smart-excerpt'),
'generate_all_missing' => esc_html__('Generate All Missing Excerpts', 'ai-smart-excerpt'),
'restart_processing' => esc_html__('Restart Processing', 'ai-smart-excerpt'),
'reset_bulk_progress' => esc_html__('Reset Bulk Progress', 'ai-smart-excerpt'),
'debug_test' => esc_html__('Debug Test', 'ai-smart-excerpt'),
'bulk_seo_processing' => esc_html__('Bulk SEO Processing', 'ai-smart-excerpt'),
'debug_seo_bulk' => esc_html__('Debug SEO Bulk', 'ai-smart-excerpt'),
'force_next_batch' => esc_html__('Force Next Batch', 'ai-smart-excerpt'),
'processing_seo' => esc_html__('Processing SEO...', 'ai-smart-excerpt'),
'seo_completed' => esc_html__('SEO Completed', 'ai-smart-excerpt'),
'total_posts' => esc_html__('Total Posts:', 'ai-smart-excerpt'),
'with_ai_excerpts' => esc_html__('With AI Excerpts:', 'ai-smart-excerpt'),
'without_excerpts' => esc_html__('Without Excerpts:', 'ai-smart-excerpt'),
'progress' => esc_html__('Progress:', 'ai-smart-excerpt'),
'pending_jobs' => esc_html__('Pending Jobs:', 'ai-smart-excerpt'),
'status' => esc_html__('Status:', 'ai-smart-excerpt'),
'bulk_processing_active' => esc_html__('Bulk processing active', 'ai-smart-excerpt'),
'error_loading_status' => esc_html__('Error loading status:', 'ai-smart-excerpt'),
'failed_load_status' => esc_html__('Failed to load excerpt status. Please check your server logs.', 'ai-smart-excerpt'),
'processing_stalled' => esc_html__('Bulk processing appears stalled. Last update was %d minutes ago. Try "Restart Processing" button.', 'ai-smart-excerpt'),
'processing_stopped' => esc_html__('Bulk processing stopped unexpectedly. You can start a new bulk operation or check the Status & Tools tab for more information.', 'ai-smart-excerpt'),
'seo_processing_stopped' => esc_html__('SEO processing stopped unexpectedly.', 'ai-smart-excerpt'),
'seo_plugin' => esc_html__('SEO Plugin:', 'ai-smart-excerpt'),
'ai_categories' => esc_html__('AI Categories:', 'ai-smart-excerpt'),
'ai_tags' => esc_html__('AI Tags:', 'ai-smart-excerpt'),
'focus_keywords' => esc_html__('Focus Keywords:', 'ai-smart-excerpt'),
'seo_integration' => esc_html__('SEO Integration:', 'ai-smart-excerpt'),
'error_loading_seo_status' => esc_html__('Error loading SEO status:', 'ai-smart-excerpt'),
'failed_load_seo_status' => esc_html__('Failed to load SEO status. AJAX Error:', 'ai-smart-excerpt'),
'restart_confirm' => esc_html__('This will restart the current bulk processing operation. Continue?', 'ai-smart-excerpt'),
'reset_confirm' => esc_html__('This will reset any stuck bulk processing operations. Are you sure?', 'ai-smart-excerpt'),
'seo_confirm' => esc_html__('This will add missing tags and focus keywords to all your posts. This process runs in the background and may take some time. Continue?', 'ai-smart-excerpt'),
'starting_seo' => esc_html__('Starting SEO Processing...', 'ai-smart-excerpt'),
'debug_result' => esc_html__('Debug test result: ', 'ai-smart-excerpt'),
'debug_failed' => esc_html__('Debug test failed: ', 'ai-smart-excerpt'),
'debug_error' => esc_html__('Debug Error: ', 'ai-smart-excerpt'),
'force_error' => esc_html__('Force Error: ', 'ai-smart-excerpt'),
'force_failed' => esc_html__('Force failed: ', 'ai-smart-excerpt'),
'batch_forced' => esc_html__('Batch forced. Progress: ', 'ai-smart-excerpt'),
'unknown_error' => esc_html__('Unknown error occurred', 'ai-smart-excerpt'),
'failed_update_bulk' => esc_html__('Failed to update bulk progress', 'ai-smart-excerpt'),
'failed_update_seo_bulk' => esc_html__('Failed to update SEO bulk progress', 'ai-smart-excerpt'),
'could_not_check_bulk' => esc_html__('Could not check existing bulk progress', 'ai-smart-excerpt'),
'could_not_check_seo_bulk' => esc_html__('Could not check existing SEO bulk progress', 'ai-smart-excerpt'),
'bulk_completed' => esc_html__('Bulk generation completed! Processed: ', 'ai-smart-excerpt'),
'seo_bulk_completed' => esc_html__('Bulk SEO processing completed! Processed: ', 'ai-smart-excerpt'),
'failed_generate_excerpt' => esc_html__('Failed to generate excerpt', 'ai-smart-excerpt'),
'last_generated' => esc_html__('Last generated: ', 'ai-smart-excerpt'),
'copied' => esc_html__('Copied!', 'ai-smart-excerpt'),
'copy' => esc_html__('Copy', 'ai-smart-excerpt'),
'choose_excerpt_type' => esc_html__('Choose the type of excerpt to generate', 'ai-smart-excerpt'),
'max_length_chars' => esc_html__('Maximum length in characters', 'ai-smart-excerpt'),
'failed_generate_keywords' => esc_html__('Failed to generate keywords', 'ai-smart-excerpt'),
'choose_keyword_type' => esc_html__('Choose the type of keywords to generate', 'ai-smart-excerpt'),
'max_num_keywords' => esc_html__('Maximum number of keywords', 'ai-smart-excerpt'),
'api_key_claude' => esc_html__('Enter your Claude API key (starts with sk-ant-)', 'ai-smart-excerpt'),
'api_key_openai' => esc_html__('Enter your OpenAI API key (starts with sk-)', 'ai-smart-excerpt'),
'api_key_gemini' => esc_html__('Enter your Google Gemini API key', 'ai-smart-excerpt')
)
);
// Add post ID for post editor pages
if (in_array($hook, array('post.php', 'post-new.php'))) {
global $post;
if ($post) {
$localize_data['post_id'] = $post->ID;
}
}
wp_localize_script('ai-smart-excerpt-admin', 'aiSmartExcerpt', $localize_data);
}
/**
* AJAX handler for testing API connection.
*/
public function ajax_test_api_connection() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
$provider = sanitize_text_field($_POST['provider']);
$api_key = sanitize_text_field($_POST['api_key']);
$result = $this->llm_service->test_api_connection($provider, $api_key);
wp_send_json($result);
}
/**
* AJAX handler for regenerating single excerpt.
*/
public function ajax_regenerate_excerpt() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('edit_posts')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
$post_id = intval($_POST['post_id']);
$post = get_post($post_id);
if (!$post) {
wp_send_json_error(esc_html__('Post not found', 'ai-smart-excerpt'));
}
$plugin = AI_Smart_Excerpt::get_instance();
$plugin->generate_excerpt_background($post_id, true);
$excerpt = get_post_meta($post_id, '_ai_smart_excerpt', true);
if ($excerpt) {
wp_send_json_success(array(
'message' => esc_html__('Excerpt regenerated successfully', 'ai-smart-excerpt'),
'excerpt' => wp_trim_words($excerpt, 20)
));
} else {
wp_send_json_error(esc_html__('Failed to generate excerpt', 'ai-smart-excerpt'));
}
}
/**
* AJAX handler for bulk generating excerpts.
*/
public function ajax_bulk_generate_excerpts() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
// Check if bulk processing is already running
$bulk_progress = get_option('ai_smart_excerpt_bulk_progress', array('status' => 'idle'));
if ($bulk_progress['status'] === 'running') {
wp_send_json_error(esc_html__('Bulk processing is already running', 'ai-smart-excerpt'));
return;
}
// Reset progress for new bulk operation
update_option('ai_smart_excerpt_bulk_progress', array(
'status' => 'idle',
'total' => 0,
'processed' => 0,
'current_batch' => 0,
'errors' => 0,
'started_at' => 0
));
// Schedule bulk processing to start immediately
wp_schedule_single_event(time() + 5, 'ai_smart_excerpt_bulk_process');
// Also schedule a backup trigger in case the first one fails
wp_schedule_single_event(time() + 60, 'ai_smart_excerpt_bulk_process_backup');
wp_send_json_success(array(
'message' => esc_html__('Bulk excerpt generation started. Progress will be shown below.', 'ai-smart-excerpt')
));
}
/**
* AJAX handler for getting bulk generation progress.
*/
public function ajax_get_bulk_progress() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
$bulk_progress = get_option('ai_smart_excerpt_bulk_progress', array(
'status' => 'idle',
'total' => 0,
'processed' => 0,
'current_batch' => 0,
'errors' => 0,
'started_at' => 0
));
// Calculate additional progress info
$percentage = 0;
if ($bulk_progress['total'] > 0) {
$percentage = round(($bulk_progress['processed'] / $bulk_progress['total']) * 100, 1);
}
$remaining = max(0, $bulk_progress['total'] - $bulk_progress['processed']);
// Calculate estimated time remaining
$estimated_time = '';
if ($bulk_progress['status'] === 'running' && $bulk_progress['processed'] > 0) {
$elapsed = time() - $bulk_progress['started_at'];
$rate = $bulk_progress['processed'] / $elapsed; // posts per second
if ($rate > 0 && $remaining > 0) {
$estimated_seconds = $remaining / $rate;
if ($estimated_seconds < 60) {
// translators: %d is the number of seconds remaining
$estimated_time = sprintf(esc_html__('%d seconds', 'ai-smart-excerpt'), ceil($estimated_seconds));
} else {
// translators: %d is the number of minutes remaining
$estimated_time = sprintf(esc_html__('%d minutes', 'ai-smart-excerpt'), ceil($estimated_seconds / 60));
}
}
}
// Check if bulk processing is scheduled
$is_scheduled = wp_next_scheduled('ai_smart_excerpt_bulk_process') ? true : false;
wp_send_json_success(array(
'status' => $bulk_progress['status'],
'total' => $bulk_progress['total'],
'processed' => $bulk_progress['processed'],
'remaining' => $remaining,
'percentage' => $percentage,
'current_batch' => $bulk_progress['current_batch'],
'errors' => $bulk_progress['errors'],
'estimated_time' => $estimated_time,
'is_scheduled' => $is_scheduled,
'last_updated' => isset($bulk_progress['last_updated']) ? $bulk_progress['last_updated'] : 0,
'completion_reason' => isset($bulk_progress['completion_reason']) ? $bulk_progress['completion_reason'] : null,
'last_error' => isset($bulk_progress['last_error']) ? $bulk_progress['last_error'] : null,
'last_post_time' => isset($bulk_progress['last_post_time']) ? $bulk_progress['last_post_time'] : null,
'started_at' => isset($bulk_progress['started_at']) ? $bulk_progress['started_at'] : 0
));
}
/**
* AJAX handler for resetting bulk generation progress.
*/
public function ajax_reset_bulk_progress() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
// Clear all scheduled bulk processing events
wp_clear_scheduled_hook('ai_smart_excerpt_bulk_process');
// Reset progress
delete_option('ai_smart_excerpt_bulk_progress');
wp_send_json_success(array(
'message' => esc_html__('Bulk processing has been reset. You can now start a new bulk generation.', 'ai-smart-excerpt')
));
}
/**
* AJAX handler for restarting bulk processing.
*/
public function ajax_restart_bulk_process() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
// Clear any existing scheduled events
wp_clear_scheduled_hook('ai_smart_excerpt_bulk_process');
wp_clear_scheduled_hook('ai_smart_excerpt_bulk_process_backup');
// Schedule immediate restart
wp_schedule_single_event(time() + 5, 'ai_smart_excerpt_bulk_process');
wp_schedule_single_event(time() + 60, 'ai_smart_excerpt_bulk_process_backup');
wp_send_json_success(array(
'message' => esc_html__('Bulk processing restarted. Monitoring will resume.', 'ai-smart-excerpt')
));
}
/**
* AJAX handler for getting excerpt status.
*/
public function ajax_get_excerpt_status() {
// Skip nonce check temporarily for debugging
// check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('edit_posts')) {
wp_send_json_error('Insufficient permissions');
return;
}
try {
// Simple database queries
$posts = get_posts(array(
'post_type' => array('post', 'page'),
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids'
));
$total_posts = count($posts);
// Count posts with AI excerpts
$with_excerpts = 0;
foreach ($posts as $post_id) {
$excerpt = get_post_meta($post_id, '_ai_smart_excerpt', true);
if (!empty($excerpt)) {
$with_excerpts++;
}
}
$without_excerpts = $total_posts - $with_excerpts;
$percentage = $total_posts > 0 ? round(($with_excerpts / $total_posts) * 100, 1) : 0;
wp_send_json_success(array(
'total_posts' => $total_posts,
'with_excerpts' => $with_excerpts,
'without_excerpts' => $without_excerpts,
'pending_jobs' => 0,
'bulk_running' => false,
'percentage' => $percentage
));
} catch (Exception $e) {
wp_send_json_error('Exception: ' . $e->getMessage());
}
}
/**
* Simple AJAX debug test.
*/
public function ajax_debug_test() {
wp_send_json_success(array(
'message' => 'AJAX is working!',
'timestamp' => current_time('mysql'),
'user_id' => get_current_user_id()
));
}
/**
* Add excerpt meta box to post editor.
*/
public function add_excerpt_meta_box() {
$post_types = array('post', 'page');
foreach ($post_types as $post_type) {
add_meta_box(
'ai-smart-excerpt-meta-box',
esc_html__('AI Smart Excerpt & Keywords', 'ai-smart-excerpt'),
array($this, 'excerpt_meta_box_callback'),
$post_type,
'normal',
'high'
);
}
}
/**
* Meta box callback for excerpt generation.
*
* @param WP_Post $post Current post object.
*/
public function excerpt_meta_box_callback($post) {
// Add nonce for security
wp_nonce_field('ai_smart_excerpt_meta_box', 'ai_smart_excerpt_meta_box_nonce');
// Get current AI excerpt
$ai_excerpt = get_post_meta($post->ID, '_ai_smart_excerpt', true);
$generated_at = get_post_meta($post->ID, '_ai_excerpt_generated_at', true);
$is_active = $this->options['is_active'];
// Get current AI keywords
$ai_keywords = get_post_meta($post->ID, '_ai_smart_keywords', true);
$keywords_generated_at = get_post_meta($post->ID, '_ai_keywords_generated_at', true);
$keywords_enabled_setting = $this->options['keywords_enabled'];
$keywords_display_meta = $this->options['keywords_display_meta'];
?>
prepare_content($post);
if (empty($content)) {
wp_send_json_error(esc_html__('Post content is too short or empty', 'ai-smart-excerpt'));
}
// Generate excerpt
$llm_service = new LLM_Service();
$excerpt = $llm_service->generate_excerpt(
$content,
$post->post_title,
$excerpt_type,
$max_length
);
if ($excerpt) {
wp_send_json_success(array(
'excerpt' => $excerpt,
'type' => $excerpt_type,
'length' => strlen($excerpt)
));
} else {
wp_send_json_error(esc_html__('Failed to generate excerpt', 'ai-smart-excerpt'));
}
}
/**
* AJAX handler for saving manual excerpt.
*/
public function ajax_save_excerpt_manual() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('edit_posts')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
$post_id = intval($_POST['post_id']);
$excerpt = sanitize_textarea_field($_POST['excerpt']);
if (!get_post($post_id)) {
wp_send_json_error(esc_html__('Post not found', 'ai-smart-excerpt'));
}
if (!empty($excerpt)) {
update_post_meta($post_id, '_ai_smart_excerpt', $excerpt);
update_post_meta($post_id, '_ai_excerpt_generated_at', current_time('timestamp'));
wp_send_json_success(array(
'message' => esc_html__('Excerpt saved successfully', 'ai-smart-excerpt')
));
} else {
delete_post_meta($post_id, '_ai_smart_excerpt');
delete_post_meta($post_id, '_ai_excerpt_generated_at');
wp_send_json_success(array(
'message' => esc_html__('Excerpt removed', 'ai-smart-excerpt')
));
}
}
/**
* AJAX handler for generating keyword preview.
*/
public function ajax_generate_keywords_preview() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('edit_posts')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
$post_id = intval($_POST['post_id']);
$keyword_type = sanitize_text_field($_POST['keyword_type']);
$max_keywords = intval($_POST['max_keywords']);
$post = get_post($post_id);
if (!$post) {
wp_send_json_error(esc_html__('Post not found', 'ai-smart-excerpt'));
}
// Get post content
$plugin = AI_Smart_Excerpt::get_instance();
$content = $plugin->prepare_content($post);
if (empty($content)) {
wp_send_json_error(esc_html__('Post content is too short or empty', 'ai-smart-excerpt'));
}
// Generate keywords based on type
$llm_service = new LLM_Service();
$keywords = null;
try {
if ($keyword_type === 'tags') {
$keywords = $llm_service->generate_tags($content, $post->post_title, $max_keywords);
} elseif ($keyword_type === 'focus_keyword') {
$focus_keyword = $llm_service->generate_focus_keyword($content, $post->post_title);
$keywords = $focus_keyword ? array($focus_keyword) : null;
} else {
// Default to tags if type is unknown
$keywords = $llm_service->generate_tags($content, $post->post_title, $max_keywords);
}
} catch (Exception $e) {
error_log('AI Smart Excerpt: Keyword generation error: ' . $e->getMessage());
wp_send_json_error('API error: ' . $e->getMessage());
}
if ($keywords && is_array($keywords)) {
wp_send_json_success(array(
'keywords' => $keywords,
'type' => $keyword_type,
'count' => count($keywords),
'keywords_text' => implode(', ', $keywords)
));
} else {
wp_send_json_error(esc_html__('Failed to generate keywords', 'ai-smart-excerpt'));
}
}
/**
* AJAX handler for saving manual keywords.
*/
public function ajax_save_keywords_manual() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('edit_posts')) {
wp_die(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
}
$post_id = intval($_POST['post_id']);
$keywords_text = sanitize_textarea_field($_POST['keywords']);
if (!get_post($post_id)) {
wp_send_json_error(esc_html__('Post not found', 'ai-smart-excerpt'));
}
if (!empty($keywords_text)) {
// Parse keywords from comma-separated text
$keywords = array_map('trim', explode(',', $keywords_text));
$keywords = array_filter($keywords, function($keyword) {
return !empty($keyword);
});
// Save to plugin meta fields
update_post_meta($post_id, '_ai_smart_keywords', $keywords);
update_post_meta($post_id, '_ai_keywords_generated_at', current_time('timestamp'));
// IMPORTANT: Also create WordPress tags (same as bulk process)
$clean_tags = array();
foreach ($keywords as $tag) {
$clean_tag = trim($tag);
if (!empty($clean_tag) && strlen($clean_tag) <= 50) {
$clean_tags[] = $clean_tag;
}
}
$tags_created = 0;
if (!empty($clean_tags)) {
wp_set_post_tags($post_id, $clean_tags, true); // Append, don't replace
$tags_created = count($clean_tags);
}
// IMPORTANT: Also integrate with Yoast SEO
$ai_plugin = AI_Smart_Excerpt::get_instance();
$yoast_success = $ai_plugin->set_seo_focus_keywords($post_id, $keywords);
wp_send_json_success(array(
'message' => esc_html__('Keywords saved successfully', 'ai-smart-excerpt'),
'count' => count($keywords),
'wordpress_tags_created' => $tags_created,
'yoast_integration' => $yoast_success ? 'SUCCESS' : 'FAILED'
));
} else {
delete_post_meta($post_id, '_ai_smart_keywords');
delete_post_meta($post_id, '_ai_keywords_generated_at');
wp_send_json_success(array(
'message' => esc_html__('Keywords removed', 'ai-smart-excerpt')
));
}
}
/**
* Render Overview & Settings tab.
*/
private function render_overview_tab() {
// Ensure we have the latest options
$this->load_options();
?>
';
switch ($type) {
case 'checkbox':
// Add hidden field to ensure unchecked value is sent
echo '';
echo '';
break;
case 'text':
echo '';
if ($description) {
echo '
' . esc_html($description) . '
';
}
break;
case 'password':
echo '';
if ($description) {
echo '
';
}
break;
case 'select':
echo '';
if ($description) {
echo '
' . esc_html($description) . '
';
}
break;
}
echo '
';
echo '
';
}
/**
* AJAX handler for getting SEO status.
*/
public function ajax_get_seo_status() {
// Skip nonce check like excerpt status (following existing pattern)
// check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('edit_posts')) {
wp_send_json_error('Insufficient permissions');
return;
}
try {
$ai_plugin = AI_Smart_Excerpt::get_instance();
$stats = $ai_plugin->get_seo_impact_stats();
// Calculate percentages for better display
$seo_percentage = 0;
if ($stats['total_posts'] > 0) {
$seo_percentage = round((($stats['posts_with_ai_categories'] + $stats['posts_with_ai_tags'] + $stats['focus_keywords_set']) / ($stats['total_posts'] * 3)) * 100, 1);
}
wp_send_json_success(array(
'seo_plugin' => $stats['seo_plugin_detected'] ?: 'None detected',
'total_posts' => (int) $stats['total_posts'],
'posts_with_categories' => (int) $stats['posts_with_ai_categories'],
'posts_with_tags' => (int) $stats['posts_with_ai_tags'],
'focus_keywords_set' => (int) $stats['focus_keywords_set'],
'seo_percentage' => $seo_percentage
));
} catch (Exception $e) {
wp_send_json_error('Failed to load SEO status: ' . $e->getMessage());
}
}
/**
* AJAX handler for bulk SEO processing.
*/
public function ajax_bulk_seo_process() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
return;
}
try {
$ai_plugin = AI_Smart_Excerpt::get_instance();
// Reset any existing progress
delete_option('ai_smart_excerpt_seo_bulk_progress');
// Start the bulk SEO processing immediately and continue with scheduling
$ai_plugin->process_bulk_seo();
// Schedule immediate follow-up processing
wp_schedule_single_event(time() + 3, 'ai_smart_excerpt_seo_bulk_process');
// Get updated progress after initial processing
$progress = get_option('ai_smart_excerpt_seo_bulk_progress', array('status' => 'idle'));
wp_send_json_success(array(
'message' => 'Bulk SEO processing started successfully',
'status' => 'running',
'initial_progress' => $progress
));
} catch (Exception $e) {
wp_send_json_error('Failed to start bulk SEO processing: ' . $e->getMessage());
}
}
/**
* AJAX handler for getting bulk SEO processing progress.
*/
public function ajax_get_seo_bulk_progress() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
return;
}
try {
$seo_bulk_progress = get_option('ai_smart_excerpt_seo_bulk_progress', array(
'status' => 'idle',
'total' => 0,
'processed' => 0,
'start_time' => 0,
'errors' => 0
));
$percentage = 0;
$eta = '';
if ($seo_bulk_progress['total'] > 0) {
$percentage = round(($seo_bulk_progress['processed'] / $seo_bulk_progress['total']) * 100, 1);
// Calculate ETA
if ($seo_bulk_progress['processed'] > 0 && $seo_bulk_progress['start_time'] > 0) {
$elapsed = time() - $seo_bulk_progress['start_time'];
$rate = $seo_bulk_progress['processed'] / $elapsed;
$remaining = $seo_bulk_progress['total'] - $seo_bulk_progress['processed'];
$eta_seconds = $rate > 0 ? $remaining / $rate : 0;
$eta = $eta_seconds > 0 ? gmdate('i:s', $eta_seconds) : '';
}
}
wp_send_json_success(array(
'status' => $seo_bulk_progress['status'],
'total' => (int) $seo_bulk_progress['total'],
'processed' => (int) $seo_bulk_progress['processed'],
'percentage' => $percentage,
'eta' => $eta,
'errors' => (int) $seo_bulk_progress['errors']
));
} catch (Exception $e) {
wp_send_json_error('Failed to get SEO bulk progress: ' . $e->getMessage());
}
}
/**
* Debug AJAX handler for SEO bulk processing.
*/
public function ajax_debug_seo_bulk() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
return;
}
try {
// Get posts that need SEO processing
$posts = get_posts(array(
'post_type' => array('post', 'page'),
'post_status' => 'publish',
'numberposts' => 5, // Just first 5 for debugging
'fields' => 'ids'
));
$ai_plugin = AI_Smart_Excerpt::get_instance();
$debug_info = array();
foreach ($posts as $post_id) {
$post = get_post($post_id);
$tags = wp_get_post_tags($post_id);
$needs_seo = $ai_plugin->post_needs_seo_processing($post_id);
$debug_info[] = array(
'id' => $post_id,
'title' => $post->post_title,
'tags_count' => count($tags),
'needs_seo' => $needs_seo
);
}
$current_progress = get_option('ai_smart_excerpt_seo_bulk_progress', array('status' => 'idle'));
// Test LLM connectivity with a simple call
$llm_test_result = 'Not tested';
try {
$ai_plugin = AI_Smart_Excerpt::get_instance();
$llm_service = new LLM_Service();
$test_tags = $llm_service->generate_tags('This is a test article about health and wellness.', 'Test Article', 3);
$llm_test_result = $test_tags ? 'SUCCESS - Got ' . count($test_tags) . ' tags' : 'FAILED - No tags returned';
} catch (Exception $e) {
$llm_test_result = 'ERROR: ' . $e->getMessage();
}
wp_send_json_success(array(
'total_posts' => count($posts),
'debug_posts' => $debug_info,
'current_progress' => $current_progress,
'llm_connectivity_test' => $llm_test_result,
'yoast_integration_check' => $this->check_yoast_integration($posts[0])
));
} catch (Exception $e) {
wp_send_json_error('Debug failed: ' . $e->getMessage());
}
}
/**
* Check Yoast SEO integration for a specific post.
*/
private function check_yoast_integration($post_id) {
if (empty($post_id)) {
return 'No post ID provided';
}
$result = array();
// Check if Yoast is active
$result['yoast_active'] = defined('WPSEO_VERSION') ? 'YES (' . WPSEO_VERSION . ')' : 'NO';
// Check Yoast meta fields
$result['focus_keyword'] = get_post_meta($post_id, '_yoast_wpseo_focuskw', true) ?: 'EMPTY';
$result['keyword_synonyms'] = get_post_meta($post_id, '_yoast_wpseo_keywordsynonyms', true) ?: 'EMPTY';
// Check our AI assignments
$ai_assignments = get_post_meta($post_id, '_ai_seo_assignments', true);
$result['ai_assignments'] = $ai_assignments ? $ai_assignments : 'NONE';
return $result;
}
/**
* Force next SEO batch processing.
*/
public function ajax_force_seo_batch() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
return;
}
try {
$ai_plugin = AI_Smart_Excerpt::get_instance();
// Force process the next batch
$ai_plugin->process_bulk_seo();
$progress = get_option('ai_smart_excerpt_seo_bulk_progress', array('status' => 'idle'));
wp_send_json_success(array(
'message' => 'Forced next batch processing',
'progress' => $progress
));
} catch (Exception $e) {
wp_send_json_error('Failed to force batch: ' . $e->getMessage());
}
}
/**
* AJAX handler for force regenerating all excerpts (skips all checks).
*/
public function ajax_force_regenerate_excerpts() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
return;
}
// Check if bulk processing is already running
$bulk_progress = get_option('ai_smart_excerpt_bulk_progress', array('status' => 'idle'));
if ($bulk_progress['status'] === 'running') {
wp_send_json_error(esc_html__('Bulk processing is already running', 'ai-smart-excerpt'));
return;
}
// Reset progress for new bulk operation
update_option('ai_smart_excerpt_bulk_progress', array(
'status' => 'idle',
'total' => 0,
'processed' => 0,
'current_batch' => 0,
'errors' => 0,
'started_at' => 0,
'force_regenerate' => true // Flag to skip all checks
));
// Schedule bulk processing to start immediately
wp_schedule_single_event(time() + 5, 'ai_smart_excerpt_bulk_process');
// Also schedule a backup trigger in case the first one fails
wp_schedule_single_event(time() + 60, 'ai_smart_excerpt_bulk_process_backup');
wp_send_json_success(array(
'message' => esc_html__('Force excerpt regeneration started. This will regenerate ALL excerpts.', 'ai-smart-excerpt')
));
}
/**
* AJAX handler for force regenerating all SEO content (skips all checks).
*/
public function ajax_force_regenerate_seo() {
check_ajax_referer('ai_smart_excerpt_ajax', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(esc_html__('Insufficient permissions', 'ai-smart-excerpt'));
return;
}
// Check if SEO bulk processing is already running
$seo_bulk_progress = get_option('ai_smart_excerpt_seo_bulk_progress', array('status' => 'idle'));
if ($seo_bulk_progress['status'] === 'running') {
wp_send_json_error(esc_html__('SEO bulk processing is already running', 'ai-smart-excerpt'));
return;
}
// Reset SEO progress for new bulk operation with force flag
update_option('ai_smart_excerpt_seo_bulk_progress', array(
'status' => 'idle',
'total' => 0,
'processed' => 0,
'current_batch' => 0,
'batch_size' => 3,
'start_time' => 0,
'last_updated' => 0,
'errors' => 0,
'force_regenerate' => true // Flag to skip all checks
));
// Schedule SEO bulk processing to start immediately
wp_schedule_single_event(time() + 5, 'ai_smart_excerpt_seo_bulk_process');
wp_send_json_success(array(
'message' => esc_html__('Force SEO regeneration started. This will regenerate ALL SEO content for all posts.', 'ai-smart-excerpt')
));
}
/**
* Render recommendations tab.
*/
private function render_recommendations_tab() {
?>
Link Automator Pro
Save hours of manual work with automatic link generation!
Link Automator Pro automatically converts keywords into affiliate links, internal links, or any custom URLs across your entire website. Perfect for affiliate marketers, bloggers, and anyone looking to improve their internal linking structure.
🚀 Key Features:
✅ Automatic keyword to link conversion
✅ Perfect for affiliate marketing campaigns
✅ Improves SEO with smart internal linking
✅ Bulk management of hundreds of keywords
✅ Safe processing that respects existing links
✅ Performance optimized for large sites
💡 Perfect For:
Affiliate Marketing: Set "iPhone 15" to automatically link to your Amazon affiliate URL across all posts
SEO Improvement: Build topic clusters by linking related keywords to pillar pages
Content Monetization: Convert product mentions into commission-earning links
Transform your site into an intelligent knowledge hub!
Knowledge AI Bot adds an intelligent AI chatbot to your website, specifically designed to help visitors understand and engage with your content. Unlike generic chatbots, it focuses on your site's actual content, perfect for knowledge-based websites.
✨ Key Features:
🤖 Multi-AI platform support (OpenAI, Claude, Gemini)
🎯 Contextually aware responses based on your content
📱 Fully customizable appearance and positioning
🌍 Mobile-responsive design with smooth animations
🛡️ Security-focused architecture with API encryption
⚡ Performance optimized, loads asynchronously
🎯 Perfect For:
Knowledge Base Sites: Help users understand complex documentation
Educational Websites: Explain concepts and answer student questions
Content Blogs: Engage readers with deeper explanations and discussions