$type,
'message' => (string) $message,
),
$extra
),
300
);
}
/**
* Store a one-time admin notice for the current user.
*
* @param string $context Notice context key.
* @param string $type success|error|info.
* @param string $message Notice message.
* @param array $extra Optional extra data.
* @return void
*/
public static function set_admin_notice($context, $type, $message, $extra = array())
{
self::set_admin_notice_for_user(get_current_user_id(), $context, $type, $message, $extra);
}
/**
* Read and clear a one-time admin notice for the current user.
*
* @param string $context Notice context key.
* @return array|null
*/
public static function consume_admin_notice($context)
{
$user_id = get_current_user_id();
if ($user_id <= 0) {
return null;
}
$key = 'bluteem_aba_notice_' . $context . '_' . $user_id;
$data = get_transient($key);
if (!is_array($data)) {
return null;
}
delete_transient($key);
return $data;
}
/**
* Register admin handler for clearing the error log.
*/
public static function init_error_handlers()
{
add_action('admin_post_bluteem_aba_clear_errors', array(__CLASS__, 'handle_clear_errors'));
}
/**
* Register AI connectivity test handler.
*/
public static function init_connectivity_handlers()
{
add_action('admin_post_bluteem_aba_test_ai_connectivity', array(__CLASS__, 'handle_test_ai_connectivity'));
}
/**
* Run AI provider connectivity test and redirect back to settings.
*/
public static function handle_test_ai_connectivity()
{
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You do not have sufficient permissions to perform this action.', 'ai-blog-automator'));
}
check_admin_referer('bluteem_aba_test_ai_connectivity');
$result = Bluteem_ABA_AI_Connectivity::test();
$message = '';
if (!empty($result['success'])) {
$message = sprintf(
/* translators: 1: provider name, 2: elapsed milliseconds */
__('%1$s connectivity test passed in %2$dms. %3$s', 'ai-blog-automator'),
isset($result['provider']) ? $result['provider'] : __('AI', 'ai-blog-automator'),
isset($result['elapsed_ms']) ? (int) $result['elapsed_ms'] : 0,
isset($result['message']) ? $result['message'] : ''
);
} else {
$message = sprintf(
/* translators: 1: provider name, 2: elapsed milliseconds, 3: error details */
__('%1$s connectivity test failed after %2$dms. %3$s', 'ai-blog-automator'),
isset($result['provider']) ? $result['provider'] : __('AI', 'ai-blog-automator'),
isset($result['elapsed_ms']) ? (int) $result['elapsed_ms'] : 0,
isset($result['message']) ? $result['message'] : __('Unknown error', 'ai-blog-automator')
);
}
self::set_admin_notice(
self::NOTICE_CONNECTIVITY,
!empty($result['success']) ? 'success' : 'error',
$message
);
wp_safe_redirect(
add_query_arg(
array(
'page' => 'ai-blog-automator',
),
admin_url('admin.php')
)
);
exit;
}
/**
* Get stored error log entries.
*
* @return array
*/
public static function get_recent_errors()
{
$errors = get_option(self::ERRORS_OPTION, array());
return is_array($errors) ? $errors : array();
}
/**
* URL for the Logs admin page.
*
* @return string
*/
public static function get_logs_page_url()
{
return admin_url('admin.php?page=' . self::LOGS_PAGE_SLUG);
}
/**
* Clear all stored errors.
*/
public static function handle_clear_errors()
{
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You do not have sufficient permissions to perform this action.', 'ai-blog-automator'));
}
check_admin_referer('bluteem_aba_clear_errors');
delete_option(self::ERRORS_OPTION);
wp_safe_redirect(
add_query_arg(
array(
'page' => self::LOGS_PAGE_SLUG,
'errors_cleared' => '1',
'_wpnonce' => wp_create_nonce('bluteem_aba_errors_feedback'),
),
admin_url('admin.php')
)
);
exit;
}
/**
* Add admin menu
*/
public static function add_admin_menu()
{
// Load SVG icon and encode it for WordPress menu
$icon_path = BLUTEEM_ABA_PLUGIN_DIR . 'assets/icon.svg';
$icon_svg = file_exists($icon_path) ? file_get_contents($icon_path) : '';
if ($icon_svg) {
// WordPress admin menu icons should use fill color that matches the admin color scheme
// Replace black fill with currentColor for proper theme integration
$icon_svg = str_replace('fill="#000000"', 'fill="currentColor"', $icon_svg);
$icon_svg = str_replace('fill="#000"', 'fill="currentColor"', $icon_svg);
// Encode SVG for use as data URI
$icon_data = 'data:image/svg+xml;base64,' . base64_encode($icon_svg);
} else {
// Fallback to dashicon if SVG not found
$icon_data = 'dashicons-edit';
}
add_menu_page(
__('AI Blog Automator', 'ai-blog-automator'), // Page title
__('AI Blog Automator', 'ai-blog-automator'), // Menu title
'manage_options', // Capability
'ai-blog-automator', // Menu slug
array(__CLASS__, 'render_settings_page'), // Callback
$icon_data, // Icon (custom SVG)
30 // Position (after Comments)
);
}
/**
* Add Logs submenu (runs after Pro submenu items).
*/
public static function add_logs_menu()
{
$error_count = count(self::get_recent_errors());
$logs_menu_title = sprintf(
' %s',
esc_html__('Logs', 'ai-blog-automator')
);
if ($error_count > 0) {
$logs_menu_title .= sprintf(
' %1$d',
$error_count
);
}
add_submenu_page(
'ai-blog-automator',
__('AI Blog Automator - Logs', 'ai-blog-automator'),
$logs_menu_title,
'manage_options',
self::LOGS_PAGE_SLUG,
array(__CLASS__, 'render_logs_page')
);
}
/**
* Render the error logs page.
*/
public static function render_logs_page()
{
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'ai-blog-automator'));
}
$template_path = BLUTEEM_ABA_PLUGIN_DIR . 'includes/templates/logs-page.php';
if (file_exists($template_path)) {
include $template_path;
}
}
/**
* Register settings
*/
public static function register_settings()
{
register_setting(
'bluteem_aba_settings_group',
self::OPTION_NAME,
array(__CLASS__, 'sanitize_settings')
);
}
/**
* Enqueue admin styles
*
* @param string $hook Current admin page hook
*/
public static function enqueue_admin_styles($hook)
{
$allowed_hooks = array(
'toplevel_page_ai-blog-automator',
'ai-blog-automator_page_' . self::LOGS_PAGE_SLUG,
);
if (!in_array($hook, $allowed_hooks, true)) {
return;
}
// Enqueue admin CSS file
wp_enqueue_style(
'bluteem-aba-admin',
BLUTEEM_ABA_PLUGIN_URL . 'assets/admin.css',
array(),
BLUTEEM_ABA_VERSION
);
wp_enqueue_script(
'bluteem-aba-admin',
BLUTEEM_ABA_PLUGIN_URL . 'assets/admin.js',
array(),
BLUTEEM_ABA_VERSION,
true
);
wp_localize_script(
'bluteem-aba-admin',
'bluteemAbaAdmin',
array(
'generatingLabel' => __('Generating...', 'ai-blog-automator'),
'connectivityAction' => admin_url('admin-post.php'),
'connectivityNonce' => wp_create_nonce('bluteem_aba_test_ai_connectivity'),
'connectivityTestingLabel' => __('Testing...', 'ai-blog-automator'),
)
);
}
/**
* Render settings page
*/
public static function render_settings_page()
{
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'ai-blog-automator'));
}
$template_path = BLUTEEM_ABA_PLUGIN_DIR . 'includes/templates/settings-page.php';
if (file_exists($template_path)) {
include $template_path;
}
}
/**
* Get all settings with defaults
*
* @return array Settings array
*/
public static function get_settings()
{
$defaults = array(
'api_key' => '',
'unsplash_api_key' => '',
'base_keywords' => '',
'post_length' => 'medium',
'frequency' => 'daily',
'auto_publish' => 'publish',
'prompt_template' => '',
'api_endpoint' => 'https://api.groq.com/openai/v1/chat/completions',
'category_mode' => 'smart_fallback',
'default_category_id' => 0,
'category_ids' => array(),
'last_run' => '',
'total_posts' => 0
);
$stored_settings = get_option(self::OPTION_NAME, $defaults);
$settings = wp_parse_args($stored_settings, $defaults);
// Existing installs without category_mode keep auto-create behavior.
if (is_array($stored_settings) && !array_key_exists('category_mode', $stored_settings)) {
$settings['category_mode'] = 'auto';
}
// Free version: Always use Groq endpoint (by design, not a locked feature)
if (!Bluteem_ABA_Premium::is_active()) {
$settings['api_endpoint'] = 'https://api.groq.com/openai/v1/chat/completions';
}
// Ensure default prompt template is set if empty (for all users)
if (empty($settings['prompt_template'])) {
$settings['prompt_template'] = "Write a comprehensive blog post about {keyword}.\n\nLength: {length} words\n\nStructure:\n- Engaging introduction with a hook\n- 3-5 main sections with subheadings\n- Each section should provide valuable, actionable information\n- Conclusion with key takeaways\n\nTone: Informative, conversational, and SEO-friendly\nStyle: Human-like writing with natural flow\nSEO: Include relevant keywords naturally throughout the content\n\nPlease format the output as follows:\nTitle: [Your engaging title here]\n\n[Rest of the content]";
}
// Allow Pro features to override settings (e.g., bulk generation)
$settings = apply_filters('bluteem_aba_override_settings', $settings);
return $settings;
}
/**
* Analyze existing content and generate smart keywords
*
* @return string Comma-separated keywords
*/
public static function generate_smart_keywords()
{
$keywords = array();
// Get categories
$categories = get_categories(array(
'hide_empty' => false,
'number' => 10,
'orderby' => 'count',
'order' => 'DESC'
));
foreach ($categories as $category) {
if ($category->slug !== 'uncategorized') {
$keywords[] = $category->name;
}
}
// Get tags
$tags = get_tags(array(
'hide_empty' => false,
'number' => 10,
'orderby' => 'count',
'order' => 'DESC'
));
foreach ($tags as $tag) {
$keywords[] = $tag->name;
}
// Analyze recent post titles for common topics
$recent_posts = get_posts(array(
'numberposts' => 20,
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC'
));
// Extract common words from titles (excluding stop words)
$stop_words = array('the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'is', 'it', 'this', 'that', 'by', 'from', 'as', 'are', 'was', 'were', 'be', 'been', 'has', 'have', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should');
$word_frequency = array();
foreach ($recent_posts as $post) {
$title = strtolower($post->post_title);
$words = preg_split('/\s+/', $title);
foreach ($words as $word) {
$word = trim($word, '.,!?;:()[]{}');
if (strlen($word) > 3 && !in_array($word, $stop_words) && !is_numeric($word)) {
if (!isset($word_frequency[$word])) {
$word_frequency[$word] = 0;
}
$word_frequency[$word]++;
}
}
}
// Get top 5 frequent words
arsort($word_frequency);
$top_words = array_slice(array_keys($word_frequency), 0, 5);
foreach ($top_words as $word) {
$keywords[] = ucfirst($word);
}
// Remove duplicates and limit to 15 keywords
$keywords = array_unique($keywords);
$keywords = array_slice($keywords, 0, 15);
// If no keywords found, provide defaults based on site niche
if (empty($keywords)) {
$site_name = get_bloginfo('name');
$site_description = get_bloginfo('description');
// Try to detect niche from site info
if (
stripos($site_name . ' ' . $site_description, 'tech') !== false ||
stripos($site_name . ' ' . $site_description, 'technology') !== false
) {
$keywords = array('Technology trends', 'Tech tips', 'Innovation', 'Digital transformation');
} elseif (
stripos($site_name . ' ' . $site_description, 'food') !== false ||
stripos($site_name . ' ' . $site_description, 'recipe') !== false
) {
$keywords = array('Recipes', 'Cooking tips', 'Food trends', 'Healthy eating');
} elseif (stripos($site_name . ' ' . $site_description, 'travel') !== false) {
$keywords = array('Travel destinations', 'Travel tips', 'Adventure', 'Tourism');
} elseif (stripos($site_name . ' ' . $site_description, 'business') !== false) {
$keywords = array('Business strategy', 'Entrepreneurship', 'Marketing', 'Growth');
} else {
// Generic defaults
$keywords = array('Tips and tricks', 'Best practices', 'How to guide', 'Latest trends');
}
}
return implode(', ', $keywords);
}
/**
* Get specific setting
*
* @param string $key Setting key
* @param mixed $default Default value
* @return mixed Setting value
*/
public static function get_setting($key, $default = '')
{
$settings = self::get_settings();
return isset($settings[$key]) ? $settings[$key] : $default;
}
/**
* Update specific setting
*
* @param string $key Setting key
* @param mixed $value Setting value
* @return bool True on success
*/
public static function update_setting($key, $value)
{
$settings = self::get_settings();
$settings[$key] = $value;
return update_option(self::OPTION_NAME, $settings);
}
/**
* Sanitize settings before saving
*
* @param array $input Raw input data
* @return array Sanitized data
*/
public static function sanitize_settings($input)
{
$sanitized = array();
// Allow Pro features to add additional sanitization
$sanitized = apply_filters('bluteem_aba_sanitize_settings', $sanitized, $input);
// API Key - sanitize as text
if (isset($input['api_key'])) {
$sanitized['api_key'] = sanitize_text_field($input['api_key']);
}
// Unsplash API Key - sanitize as text
if (isset($input['unsplash_api_key'])) {
$sanitized['unsplash_api_key'] = sanitize_text_field($input['unsplash_api_key']);
}
// Base Keywords - sanitize as textarea
if (isset($input['base_keywords'])) {
$sanitized['base_keywords'] = sanitize_textarea_field($input['base_keywords']);
}
// Post Length - validate against allowed values
$allowed_lengths = array('short', 'medium', 'long');
// Pro users can use custom length
if (Bluteem_ABA_Premium::is_active()) {
$allowed_lengths[] = 'custom';
}
if (isset($input['post_length']) && in_array($input['post_length'], $allowed_lengths, true)) {
// Validate against whitelist, then sanitize for extra safety
$sanitized['post_length'] = sanitize_text_field($input['post_length']);
} else {
$sanitized['post_length'] = 'medium';
}
// Custom Word Count - only for Pro users
if (Bluteem_ABA_Premium::is_active() && isset($input['custom_word_count'])) {
// Validate and sanitize: ensure it's a number, then clamp to valid range
$custom_count = absint($input['custom_word_count']);
// Clamp to valid range (300-5000)
$sanitized['custom_word_count'] = max(300, min(5000, $custom_count));
}
// Frequency - validate against allowed values
// Free version: daily, twicedaily, weekly (all shown in UI must work)
$allowed_frequencies = array('daily', 'twicedaily', 'weekly');
// Premium users get additional frequencies via filter
if (Bluteem_ABA_Premium::is_active()) {
$allowed_frequencies = array_merge($allowed_frequencies, array(
'hourly',
'every_two_hours',
'every_three_hours',
'every_six_hours'
));
}
// Allow premium plugin to add custom frequencies
$allowed_frequencies = apply_filters('bluteem_aba_allowed_frequencies', $allowed_frequencies);
if (isset($input['frequency']) && in_array($input['frequency'], $allowed_frequencies, true)) {
// Validate against whitelist, then sanitize for extra safety
$sanitized['frequency'] = sanitize_text_field($input['frequency']);
} else {
$sanitized['frequency'] = 'daily';
}
// Auto Publish - validate against allowed values
$allowed_statuses = array('publish', 'draft');
if (isset($input['auto_publish']) && in_array($input['auto_publish'], $allowed_statuses, true)) {
// Validate against whitelist, then sanitize for extra safety
$sanitized['auto_publish'] = sanitize_text_field($input['auto_publish']);
} else {
$sanitized['auto_publish'] = 'publish';
}
// Category mode
$allowed_category_modes = array('fixed', 'smart', 'smart_fallback', 'auto');
if (isset($input['category_mode']) && in_array($input['category_mode'], $allowed_category_modes, true)) {
$sanitized['category_mode'] = sanitize_text_field($input['category_mode']);
} else {
$sanitized['category_mode'] = 'smart_fallback';
}
// Default / fallback category
$current_settings = self::get_settings();
if (isset($input['default_category_id'])) {
$category_id = absint($input['default_category_id']);
if ($category_id > 0 && term_exists($category_id, 'category')) {
$sanitized['default_category_id'] = $category_id;
} else {
$sanitized['default_category_id'] = 0;
}
} else {
$sanitized['default_category_id'] = isset($current_settings['default_category_id'])
? absint($current_settings['default_category_id'])
: 0;
}
// Prompt Template - Pro users can edit via textarea, free users preserve via hidden field
if (isset($input['prompt_template'])) {
$sanitized['prompt_template'] = wp_kses_post($input['prompt_template']);
} else {
// If not provided, preserve existing value
$current_settings = self::get_settings();
$sanitized['prompt_template'] = $current_settings['prompt_template'];
}
// API Endpoint - Free: always Groq. Pro: the settings form does not post
// api_endpoint for preset/custom providers; Bluteem_ABA_Pro_AI_Providers sets it
// in bluteem_aba_sanitize_settings. Do not overwrite that with Groq here.
$current_settings = self::get_settings();
if (!Bluteem_ABA_Premium::is_active()) {
$sanitized['api_endpoint'] = 'https://api.groq.com/openai/v1/chat/completions';
} elseif (isset($input['api_endpoint']) && $input['api_endpoint'] !== '') {
$sanitized['api_endpoint'] = esc_url_raw($input['api_endpoint']);
} elseif (!empty($sanitized['api_endpoint'])) {
// Kept: already set by Pro filter from provider preset or custom_ai_endpoint.
} elseif (!empty($current_settings['api_endpoint'])) {
$sanitized['api_endpoint'] = esc_url_raw($current_settings['api_endpoint']);
} else {
$sanitized['api_endpoint'] = 'https://api.groq.com/openai/v1/chat/completions';
}
// Preserve system fields
if (!isset($sanitized['last_run'])) {
$sanitized['last_run'] = $current_settings['last_run'];
}
if (!isset($sanitized['total_posts'])) {
$sanitized['total_posts'] = $current_settings['total_posts'];
}
// Reschedule cron if frequency changed
if (isset($input['frequency']) && $input['frequency'] !== $current_settings['frequency']) {
Bluteem_ABA_Cron::reschedule_event($input['frequency']);
}
return $sanitized;
}
/**
* Get post length in words
*
* @param string $length Length identifier (short/medium/long)
* @return int Word count
*/
public static function get_word_count($length)
{
$word_counts = array(
'short' => 500,
'medium' => 1000,
'long' => 2000
);
return isset($word_counts[$length]) ? $word_counts[$length] : 1000;
}
/**
* Get available categories
*
* @return array Categories
*/
public static function get_categories()
{
$categories = get_categories(array(
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC'
));
return $categories;
}
/**
* Get Uncategorized category ID
*
* @return int
*/
public static function get_uncategorized_category_id()
{
$uncategorized = get_term_by('slug', 'uncategorized', 'category');
return $uncategorized ? (int) $uncategorized->term_id : 1;
}
/**
* Resolve fallback category ID from settings
*
* @param array $settings Plugin settings.
* @return int
*/
public static function get_fallback_category_id($settings = null)
{
if (null === $settings) {
$settings = self::get_settings();
}
$category_id = isset($settings['default_category_id']) ? absint($settings['default_category_id']) : 0;
if ($category_id > 0 && term_exists($category_id, 'category')) {
return $category_id;
}
return self::get_uncategorized_category_id();
}
/**
* Test API connection
*
* @return array Result with success status and message
*/
public static function test_api_connection()
{
$api_key = self::get_setting('api_key');
if (empty($api_key)) {
return array(
'success' => false,
'message' => __('API key is required.', 'ai-blog-automator')
);
}
// This is a placeholder - actual implementation depends on the API being used
return array(
'success' => true,
'message' => __('API configuration saved. Test the connection by generating a post.', 'ai-blog-automator')
);
}
}