PluginProbe
BotWriter – AI Writer & SEO Content Generator / 3.2.8
BotWriter – AI Writer & SEO Content Generator v3.2.8
3.4.10 3.4.9 3.4.8 3.4.7 3.4.6 3.4.4 3.4.2 3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 3.3.0 3.2.8 trunk 1.3.0 1.3.1 1.3.2 1.3.3 All 51 releases
botwriter / includes / announcements.php

announcements.php in BotWriter – AI Writer & SEO Content Generator 3.2.8, at includes/announcements.php

422 lines 17.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 ?>