| 1 |
<?php |
| 2 |
if ( ! defined( 'ABSPATH' ) ) { |
| 3 |
exit; |
| 4 |
} |
| 5 |
|
| 6 |
// Add an action to the 'admin_notices' hook to execute our function |
| 7 |
add_action('admin_notices', 'botwriter_create_alert'); |
| 8 |
|
| 9 |
// Function to create admin alerts in the WordPress dashboard |
| 10 |
function botwriter_create_alert() { |
| 11 |
// Get the alerts, settings, API key, and announcements |
| 12 |
$alerts = get_option('botwriter_alerts'); |
| 13 |
$settings = get_option('botwriter_settings'); |
| 14 |
$announcements = get_option('botwriter_announcements', []); |
| 15 |
$welcome_dismissed = get_option('botwriter_welcome_dismissed', false); |
| 16 |
|
| 17 |
// Unserialize the settings if they are not an array |
| 18 |
$settings = $settings ? maybe_unserialize($settings) : []; |
| 19 |
|
| 20 |
// Check for active announcements |
| 21 |
$has_announcement = !empty($announcements) && array_filter($announcements, function($announcement) { |
| 22 |
return isset($announcement['active']) && $announcement['active'] == "1"; |
| 23 |
}); |
| 24 |
|
| 25 |
// Check if ANY AI provider API key is configured (multi-provider support) |
| 26 |
$has_any_text_key = false; |
| 27 |
$text_providers = [ |
| 28 |
'botwriter_openai_api_key', |
| 29 |
'botwriter_anthropic_api_key', |
| 30 |
'botwriter_google_api_key', |
| 31 |
'botwriter_mistral_api_key', |
| 32 |
'botwriter_groq_api_key', |
| 33 |
'botwriter_openrouter_api_key' |
| 34 |
]; |
| 35 |
|
| 36 |
foreach ($text_providers as $provider_key) { |
| 37 |
$key_value = get_option($provider_key); |
| 38 |
if (!empty($key_value)) { |
| 39 |
if (function_exists('botwriter_decrypt_api_key')) { |
| 40 |
$decrypted = botwriter_decrypt_api_key($key_value); |
| 41 |
if (!empty($decrypted)) { |
| 42 |
$has_any_text_key = true; |
| 43 |
break; |
| 44 |
} |
| 45 |
} else { |
| 46 |
$has_any_text_key = true; |
| 47 |
break; |
| 48 |
} |
| 49 |
} |
| 50 |
} |
| 51 |
|
| 52 |
$api_key_missing = !$has_any_text_key; |
| 53 |
|
| 54 |
// Show welcome banner if API key is missing and not dismissed |
| 55 |
if ($api_key_missing && !$welcome_dismissed) { |
| 56 |
$settings_url = admin_url('admin.php?page=botwriter_settings'); |
| 57 |
$robot_icon = plugin_dir_url(dirname(__FILE__)) . 'assets/images/robot_icon2.png'; |
| 58 |
|
| 59 |
// Random tips to show |
| 60 |
$tips = [ |
| 61 |
__('💡 Pro Tip: Get a FREE Gemini API key for text + FREE Cloudflare Workers AI for images!', 'botwriter'), |
| 62 |
__('💡 Pro Tip: Groq offers blazing fast AI with generous free tier - perfect for testing!', 'botwriter'), |
| 63 |
__('💡 Pro Tip: Use Mistral AI for great quality at lower costs than OpenAI.', 'botwriter'), |
| 64 |
__('💡 Pro Tip: OpenRouter gives you access to 100+ AI models with one API key!', 'botwriter'), |
| 65 |
]; |
| 66 |
$random_tip = $tips[array_rand($tips)]; |
| 67 |
?> |
| 68 |
<div class="botwriter-welcome-banner" id="botwriter-welcome-banner"> |
| 69 |
<button type="button" class="botwriter-welcome-dismiss" id="botwriter-welcome-dismiss" data-nonce="<?php echo esc_attr( wp_create_nonce( 'botwriter_dismiss_welcome_nonce' ) ); ?>" title="<?php esc_attr_e('Dismiss', 'botwriter'); ?>"> |
| 70 |
<span class="dashicons dashicons-no-alt"></span> |
| 71 |
</button> |
| 72 |
|
| 73 |
<div class="botwriter-welcome-content"> |
| 74 |
<div class="botwriter-welcome-icon"> |
| 75 |
<img src="<?php echo esc_url($robot_icon); ?>" alt="BotWriter Robot" /> |
| 76 |
</div> |
| 77 |
|
| 78 |
<div class="botwriter-welcome-text"> |
| 79 |
<h2>🎉 <?php esc_html_e('Welcome to BotWriter!', 'botwriter'); ?></h2> |
| 80 |
<p class="botwriter-welcome-subtitle"> |
| 81 |
<?php esc_html_e('Your AI-powered content creation assistant is ready to help you generate amazing posts.', 'botwriter'); ?> |
| 82 |
</p> |
| 83 |
|
| 84 |
<div class="botwriter-welcome-steps"> |
| 85 |
<div class="botwriter-welcome-step"> |
| 86 |
<span class="step-number">1</span> |
| 87 |
<span class="step-text"><?php esc_html_e('Add your AI API key', 'botwriter'); ?></span> |
| 88 |
</div> |
| 89 |
<div class="botwriter-welcome-step"> |
| 90 |
<span class="step-number">2</span> |
| 91 |
<span class="step-text"><?php esc_html_e('Create your first task', 'botwriter'); ?></span> |
| 92 |
</div> |
| 93 |
<div class="botwriter-welcome-step"> |
| 94 |
<span class="step-number">3</span> |
| 95 |
<span class="step-text"><?php esc_html_e('Watch the magic happen!', 'botwriter'); ?></span> |
| 96 |
</div> |
| 97 |
</div> |
| 98 |
|
| 99 |
<div class="botwriter-welcome-tip"> |
| 100 |
<?php echo esc_html($random_tip); ?> |
| 101 |
</div> |
| 102 |
|
| 103 |
<div class="botwriter-welcome-actions"> |
| 104 |
<a href="<?php echo esc_url($settings_url); ?>" class="botwriter-welcome-btn primary"> |
| 105 |
<span class="dashicons dashicons-admin-settings"></span> |
| 106 |
<?php esc_html_e('Configure API Keys', 'botwriter'); ?> |
| 107 |
</a> |
| 108 |
<a href="https://ai.google.dev/" target="_blank" class="botwriter-welcome-btn secondary"> |
| 109 |
<?php esc_html_e('Get Free Gemini Key', 'botwriter'); ?> → |
| 110 |
</a> |
| 111 |
</div> |
| 112 |
</div> |
| 113 |
</div> |
| 114 |
</div> |
| 115 |
|
| 116 |
<?php |
| 117 |
} |
| 118 |
|
| 119 |
// Display announcements/info notice (separately) if any |
| 120 |
if ($has_announcement) { |
| 121 |
echo '<div class="notice notice-info is-dismissible">'; |
| 122 |
echo '<p><strong>' . esc_html__('BotWriter Announcement:', 'botwriter') . '</strong></p>'; |
| 123 |
if (!empty($alerts)) { |
| 124 |
echo '<p>' . esc_html($alerts) . '</p>'; |
| 125 |
} |
| 126 |
if (!empty($announcements)) { |
| 127 |
foreach ($announcements as $announcement_id => $announcement) { |
| 128 |
if (isset($announcement['active']) && $announcement['active'] == "1") { |
| 129 |
echo '<p>' . esc_html($announcement['title']) . ': ' . wp_kses_post($announcement['message']) . '</p>'; |
| 130 |
echo '<button data-announcement-id="' . esc_attr($announcement_id) . '" class="button botwriter-dismiss-announcement">' . esc_html__('Dismiss', 'botwriter') . '</button>'; |
| 131 |
} |
| 132 |
} |
| 133 |
} |
| 134 |
echo '</div>'; |
| 135 |
} |
| 136 |
} |
| 137 |
|
| 138 |
// AJAX handler for dismissing welcome banner |
| 139 |
add_action('wp_ajax_botwriter_dismiss_welcome', 'botwriter_dismiss_welcome_handler'); |
| 140 |
function botwriter_dismiss_welcome_handler() { |
| 141 |
check_ajax_referer('botwriter_dismiss_welcome_nonce', 'security'); |
| 142 |
|
| 143 |
if (!current_user_can('manage_options')) { |
| 144 |
wp_send_json_error(['message' => 'Permission denied']); |
| 145 |
} |
| 146 |
|
| 147 |
update_option('botwriter_welcome_dismissed', true); |
| 148 |
wp_send_json_success(); |
| 149 |
} |
| 150 |
|
| 151 |
// AJAX handler for dismissing announcements |
| 152 |
function botwriter_dismiss_announcement() { |
| 153 |
// Verify user has permission |
| 154 |
if (!current_user_can('manage_options')) { |
| 155 |
wp_send_json_error(['message' => 'Permission denied']); |
| 156 |
} |
| 157 |
|
| 158 |
check_ajax_referer('botwriter_dismiss_nonce', 'security'); |
| 159 |
|
| 160 |
if (isset($_POST['announcement_id'])) { |
| 161 |
$announcement_id = sanitize_text_field(wp_unslash($_POST['announcement_id'])); |
| 162 |
$announcements = get_option('botwriter_announcements', []); |
| 163 |
|
| 164 |
if (isset($announcements[$announcement_id])) { |
| 165 |
$announcements[$announcement_id]['active'] = 0; |
| 166 |
update_option('botwriter_announcements', $announcements); |
| 167 |
wp_send_json_success(); |
| 168 |
} else { |
| 169 |
wp_send_json_error(['message' => 'Invalid announcement ID']); |
| 170 |
} |
| 171 |
} else { |
| 172 |
wp_send_json_error(['message' => 'Missing announcement ID']); |
| 173 |
} |
| 174 |
} |
| 175 |
add_action('wp_ajax_botwriter_dismiss_announcement', 'botwriter_dismiss_announcement'); |
| 176 |
|
| 177 |
// Function to add new announcements |
| 178 |
function botwriter_announcements_add($title, $message) { |
| 179 |
$announcements = get_option('botwriter_announcements', []); |
| 180 |
$title = sanitize_text_field($title); |
| 181 |
$message = wp_kses_post($message); |
| 182 |
|
| 183 |
foreach ($announcements as $announcement) { |
| 184 |
if ($announcement['title'] === $title && $announcement['message'] === $message && $announcement['active'] == 1) { |
| 185 |
return; |
| 186 |
} |
| 187 |
} |
| 188 |
|
| 189 |
$announcements[] = [ |
| 190 |
'id' => md5($title . $message . time()), |
| 191 |
'title' => $title, |
| 192 |
'message' => $message, |
| 193 |
'active' => 1 |
| 194 |
]; |
| 195 |
update_option('botwriter_announcements', $announcements); |
| 196 |
} |
| 197 |
|
| 198 |
// ===================================================== |
| 199 |
// STOPFORMANY — Tasks paused due to too many consecutive errors |
| 200 |
// ===================================================== |
| 201 |
|
| 202 |
/** |
| 203 |
* Show a critical admin notice when STOPFORMANY is active. |
| 204 |
* The notice includes a "Reset & Resume" button that calls the server |
| 205 |
* endpoint to clear the consecutive-error counter and removes the local flag. |
| 206 |
*/ |
| 207 |
add_action('admin_notices', 'botwriter_stopformany_notice'); |
| 208 |
function botwriter_stopformany_notice() { |
| 209 |
if (!current_user_can('manage_options')) { |
| 210 |
return; |
| 211 |
} |
| 212 |
if (!get_option('botwriter_stopformany', false)) { |
| 213 |
return; |
| 214 |
} |
| 215 |
|
| 216 |
$logs_url = admin_url('admin.php?page=botwriter_logs'); |
| 217 |
$nonce = wp_create_nonce('botwriter_stopformany_reset_nonce'); |
| 218 |
?> |
| 219 |
<div class="notice notice-error" id="botwriter-stopformany-notice" style="border-left-color:#d63638;"> |
| 220 |
<p><strong>🛑 <?php esc_html_e('BotWriter: All tasks have been STOPPED due to too many consecutive errors!', 'botwriter'); ?></strong></p> |
| 221 |
<p><?php esc_html_e('The server detected 100+ consecutive task errors for your site and paused all task processing to prevent further failures.', 'botwriter'); ?></p> |
| 222 |
<p><?php esc_html_e('Please review your logs, fix the underlying issue (API key, provider configuration, etc.), then click the button below to resume.', 'botwriter'); ?></p> |
| 223 |
<p> |
| 224 |
<a class="button button-primary" href="<?php echo esc_url($logs_url); ?>"> |
| 225 |
<span class="dashicons dashicons-warning" style="vertical-align:middle; margin-right:4px;"></span> |
| 226 |
<?php esc_html_e('View Logs', 'botwriter'); ?> |
| 227 |
</a> |
| 228 |
<button type="button" class="button" id="botwriter-stopformany-reset" data-nonce="<?php echo esc_attr($nonce); ?>" data-success-message="<?php echo esc_attr(__('Tasks resumed! The error counter has been reset.', 'botwriter')); ?>" data-error-message="<?php echo esc_attr(__('Error resetting. Please try again.', 'botwriter')); ?>" data-network-error="<?php echo esc_attr(__('Network error. Please try again.', 'botwriter')); ?>" style="margin-left:8px;"> |
| 229 |
<?php esc_html_e('Reset & Resume Tasks', 'botwriter'); ?> |
| 230 |
</button> |
| 231 |
<span id="botwriter-stopformany-spinner" class="spinner" style="float:none; margin-top:0;"></span> |
| 232 |
</p> |
| 233 |
</div> |
| 234 |
<?php |
| 235 |
} |
| 236 |
|
| 237 |
/** |
| 238 |
* AJAX: Reset the STOPFORMANY flag (calls server + clears local option). |
| 239 |
*/ |
| 240 |
add_action('wp_ajax_botwriter_stopformany_reset', 'botwriter_stopformany_reset_handler'); |
| 241 |
function botwriter_stopformany_reset_handler() { |
| 242 |
check_ajax_referer('botwriter_stopformany_reset_nonce', 'security'); |
| 243 |
|
| 244 |
if (!current_user_can('manage_options')) { |
| 245 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 246 |
} |
| 247 |
|
| 248 |
// Call the server endpoint to reset the counter |
| 249 |
$remote_url = BOTWRITER_API_URL . 'redis_reset_stop.php'; |
| 250 |
$ssl_verify = get_option('botwriter_sslverify', 'yes') !== 'no'; |
| 251 |
|
| 252 |
$response = wp_remote_post($remote_url, array( |
| 253 |
'timeout' => 15, |
| 254 |
'sslverify' => $ssl_verify, |
| 255 |
'body' => array( |
| 256 |
'user_domainname' => esc_url(get_site_url()), |
| 257 |
'site_token' => get_option('botwriter_site_token', ''), |
| 258 |
), |
| 259 |
)); |
| 260 |
|
| 261 |
$server_ok = false; |
| 262 |
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) { |
| 263 |
$body = json_decode(wp_remote_retrieve_body($response), true); |
| 264 |
if (!empty($body['success'])) { |
| 265 |
$server_ok = true; |
| 266 |
} |
| 267 |
} |
| 268 |
|
| 269 |
// Always clear the local flag even if the server call failed |
| 270 |
// (the server will reject the next task if the counter is still high, |
| 271 |
// re-activating the flag automatically) |
| 272 |
delete_option('botwriter_stopformany'); |
| 273 |
|
| 274 |
botwriter_log('STOPFORMANY reset requested', [ |
| 275 |
'server_ok' => $server_ok, |
| 276 |
]); |
| 277 |
|
| 278 |
if ($server_ok) { |
| 279 |
wp_send_json_success(); |
| 280 |
} else { |
| 281 |
// Local flag cleared; warn that server reset may have failed |
| 282 |
wp_send_json_success(['message' => 'Local flag cleared. Server reset may have failed — tasks will resume but may be stopped again if errors persist.']); |
| 283 |
} |
| 284 |
} |
| 285 |
|
| 286 |
/** |
| 287 |
* Check for consecutive errors in logs and show warning notice |
| 288 |
* Shows when 5+ consecutive errors are found in the most recent logs |
| 289 |
* Dismissable but reappears if errors continue after a successful log |
| 290 |
*/ |
| 291 |
add_action('admin_notices', 'botwriter_consecutive_errors_notice'); |
| 292 |
function botwriter_consecutive_errors_notice() { |
| 293 |
if (!current_user_can('manage_options')) { |
| 294 |
return; |
| 295 |
} |
| 296 |
|
| 297 |
// Check if dismissed |
| 298 |
if (get_option('botwriter_errors_notice_dismissed', false)) { |
| 299 |
return; |
| 300 |
} |
| 301 |
|
| 302 |
// Check for consecutive errors |
| 303 |
$consecutive_errors = botwriter_count_consecutive_errors(); |
| 304 |
|
| 305 |
if ($consecutive_errors < 5) { |
| 306 |
return; |
| 307 |
} |
| 308 |
|
| 309 |
$logs_url = admin_url('admin.php?page=botwriter_logs'); |
| 310 |
$dismiss_nonce = wp_create_nonce('botwriter_dismiss_errors_nonce'); |
| 311 |
|
| 312 |
echo '<div class="notice notice-error" id="botwriter-errors-notice">'; |
| 313 |
echo '<p><strong>⚠️ ' . esc_html__('BotWriter: Multiple consecutive errors detected!', 'botwriter') . '</strong></p>'; |
| 314 |
echo '<p>' . sprintf( |
| 315 |
/* translators: %d is the number of consecutive errors */ |
| 316 |
esc_html__('The last %d log entries have errors. Please check your configuration and logs.', 'botwriter'), |
| 317 |
$consecutive_errors |
| 318 |
) . '</p>'; |
| 319 |
echo '<p>'; |
| 320 |
echo '<a class="button button-primary" href="' . esc_url($logs_url) . '">'; |
| 321 |
echo '<span class="dashicons dashicons-warning" style="vertical-align: middle; margin-right: 5px;"></span>'; |
| 322 |
echo esc_html__('View Logs', 'botwriter'); |
| 323 |
echo '</a> '; |
| 324 |
echo '<button type="button" class="button botwriter-dismiss-errors" data-nonce="' . esc_attr($dismiss_nonce) . '">'; |
| 325 |
echo esc_html__('Dismiss', 'botwriter'); |
| 326 |
echo '</button>'; |
| 327 |
echo '</p>'; |
| 328 |
echo '</div>'; |
| 329 |
|
| 330 |
} |
| 331 |
|
| 332 |
/** |
| 333 |
* Count consecutive errors from the most recent logs |
| 334 |
* |
| 335 |
* @return int Number of consecutive errors |
| 336 |
*/ |
| 337 |
function botwriter_count_consecutive_errors() { |
| 338 |
global $wpdb; |
| 339 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 340 |
|
| 341 |
// Get the last 20 logs ordered by most recent first |
| 342 |
$recent_logs = $wpdb->get_results( |
| 343 |
"SELECT task_status FROM {$table_name} ORDER BY id DESC LIMIT 20", |
| 344 |
ARRAY_A |
| 345 |
); |
| 346 |
|
| 347 |
if (empty($recent_logs)) { |
| 348 |
return 0; |
| 349 |
} |
| 350 |
|
| 351 |
$consecutive_errors = 0; |
| 352 |
|
| 353 |
foreach ($recent_logs as $log) { |
| 354 |
if ($log['task_status'] === 'error') { |
| 355 |
$consecutive_errors++; |
| 356 |
} else { |
| 357 |
// Stop counting when we hit a non-error |
| 358 |
break; |
| 359 |
} |
| 360 |
} |
| 361 |
|
| 362 |
return $consecutive_errors; |
| 363 |
} |
| 364 |
|
| 365 |
/** |
| 366 |
* AJAX handler for dismissing errors notice |
| 367 |
*/ |
| 368 |
add_action('wp_ajax_botwriter_dismiss_errors_notice', 'botwriter_dismiss_errors_notice_handler'); |
| 369 |
function botwriter_dismiss_errors_notice_handler() { |
| 370 |
check_ajax_referer('botwriter_dismiss_errors_nonce', 'security'); |
| 371 |
|
| 372 |
if (!current_user_can('manage_options')) { |
| 373 |
wp_send_json_error(['message' => 'Permission denied']); |
| 374 |
} |
| 375 |
|
| 376 |
update_option('botwriter_errors_notice_dismissed', true); |
| 377 |
wp_send_json_success(); |
| 378 |
} |
| 379 |
|
| 380 |
/** |
| 381 |
* Reset the errors notice dismissed flag |
| 382 |
* Called when a successful (non-error) log is registered |
| 383 |
*/ |
| 384 |
function botwriter_reset_errors_notice_dismissed() { |
| 385 |
delete_option('botwriter_errors_notice_dismissed'); |
| 386 |
} |
| 387 |
|
| 388 |
/** |
| 389 |
* Show a warning when WordPress cron is disabled and BotWriter has active tasks. |
| 390 |
*/ |
| 391 |
add_action('admin_notices', 'botwriter_wp_cron_disabled_notice'); |
| 392 |
function botwriter_wp_cron_disabled_notice() { |
| 393 |
if (!current_user_can('manage_options')) { |
| 394 |
return; |
| 395 |
} |
| 396 |
|
| 397 |
if (!function_exists('botwriter_should_show_wp_cron_disabled_warning') || !botwriter_should_show_wp_cron_disabled_warning()) { |
| 398 |
return; |
| 399 |
} |
| 400 |
|
| 401 |
$active_tasks = function_exists('botwriter_get_enabled_tasks_count') ? (int) botwriter_get_enabled_tasks_count() : 0; |
| 402 |
$settings_url = admin_url('admin.php?page=botwriter_settings'); |
| 403 |
|
| 404 |
echo '<div class="notice notice-warning">'; |
| 405 |
echo '<p><strong>' . esc_html__('BotWriter scheduling warning:', 'botwriter') . '</strong> '; |
| 406 |
echo esc_html(sprintf( |
| 407 |
/* translators: %d: number of active tasks */ |
| 408 |
_n( |
| 409 |
'WordPress cron is disabled (DISABLE_WP_CRON) and BotWriter has %d active task.', |
| 410 |
'WordPress cron is disabled (DISABLE_WP_CRON) and BotWriter has %d active tasks.', |
| 411 |
$active_tasks, |
| 412 |
'botwriter' |
| 413 |
), |
| 414 |
$active_tasks |
| 415 |
)); |
| 416 |
echo ' ' . esc_html__('Automatic task execution will not run unless a real server cron triggers wp-cron.php.', 'botwriter') . '</p>'; |
| 417 |
echo '<p>' . esc_html__('Please enable WP-Cron or ask your hosting administrator to configure server cron.', 'botwriter') . ' '; |
| 418 |
echo '<a href="' . esc_url($settings_url) . '">' . esc_html__('Open BotWriter Settings', 'botwriter') . '</a>.</p>'; |
| 419 |
echo '</div>'; |
| 420 |
} |
| 421 |
|
| 422 |
?> |