| 1 |
<?php |
| 2 |
if (!defined('ABSPATH')) { exit; } |
| 3 |
|
| 4 |
// AJAX: create task and enqueue immediately |
| 5 |
add_action('wp_ajax_botwriter_quick_create', 'botwriter_quick_create'); |
| 6 |
function botwriter_quick_create() { |
| 7 |
if (!current_user_can('manage_options')) { wp_send_json_error('forbidden'); } |
| 8 |
check_ajax_referer('botwriter_quickpost_nonce', 'nonce'); |
| 9 |
|
| 10 |
global $wpdb; |
| 11 |
$table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 12 |
|
| 13 |
// Reuse the same schema/validation as normal form |
| 14 |
// Force defaults for Write Now: today and 1 time |
| 15 |
// Use gmdate to satisfy WPCS (day name in English); current_time('timestamp') already accounts for WP tz |
| 16 |
$today = gmdate('l', current_time('timestamp')); |
| 17 |
$days = $today; |
| 18 |
$times_per_day = 1; |
| 19 |
|
| 20 |
// Whitelist for post_status |
| 21 |
$raw_status = isset($_POST['post_status']) ? sanitize_text_field(wp_unslash($_POST['post_status'])) : 'publish'; |
| 22 |
$allowed_status = array('publish','draft','pending','private','future'); |
| 23 |
$safe_status = in_array($raw_status, $allowed_status, true) ? $raw_status : 'publish'; |
| 24 |
|
| 25 |
$item = array( |
| 26 |
'task_name' => isset($_POST['task_name']) ? sanitize_text_field(wp_unslash($_POST['task_name'])) : '', |
| 27 |
'post_status' => $safe_status, |
| 28 |
'days' => $days, |
| 29 |
'times_per_day' => $times_per_day, |
| 30 |
'writer' => isset($_POST['writer']) ? sanitize_text_field(wp_unslash($_POST['writer'])) : 'orion', |
| 31 |
'narration' => isset($_POST['narration']) ? sanitize_text_field(wp_unslash($_POST['narration'])) : 'Descriptive', |
| 32 |
'post_length' => isset($_POST['post_length']) ? sanitize_text_field(wp_unslash($_POST['post_length'])) : '800', |
| 33 |
'custom_post_length'=> isset($_POST['custom_post_length']) ? sanitize_text_field(wp_unslash($_POST['custom_post_length'])) : '', |
| 34 |
'custom_style' => isset($_POST['custom_style']) ? sanitize_text_field(wp_unslash($_POST['custom_style'])) : '', |
| 35 |
'post_language' => isset($_POST['post_language']) ? sanitize_text_field(wp_unslash($_POST['post_language'])) : '', |
| 36 |
'website_type' => isset($_POST['website_type']) ? sanitize_text_field(wp_unslash($_POST['website_type'])) : 'ai', |
| 37 |
'website_name' => '', |
| 38 |
'domain_name' => isset($_POST['domain_name']) ? esc_url_raw(wp_unslash($_POST['domain_name'])) : '', |
| 39 |
'post_type' => isset($_POST['task_post_type']) ? sanitize_text_field(wp_unslash($_POST['task_post_type'])) : 'post', |
| 40 |
'category_id' => isset($_POST['category_id']) ? array_map('intval', (array) wp_unslash($_POST['category_id'])) : array(), |
| 41 |
'taxonomy_data' => isset($_POST['taxonomy_data']) ? sanitize_text_field(wp_unslash($_POST['taxonomy_data'])) : '', |
| 42 |
'website_category_id'=> isset($_POST['website_category_id']) ? array_map('intval', (array) wp_unslash($_POST['website_category_id'])) : array(), |
| 43 |
'website_category_name'=> isset($_POST['website_category_name']) ? sanitize_text_field(wp_unslash($_POST['website_category_name'])) : '', |
| 44 |
'aigenerated_title' => '', |
| 45 |
'aigenerated_content'=> '', |
| 46 |
'aigenerated_tags' => '', |
| 47 |
'aigenerated_image' => '', |
| 48 |
'post_count' => 1, |
| 49 |
'post_order' => '', |
| 50 |
'title_prompt' => isset($_POST['title_prompt']) ? sanitize_text_field(wp_unslash($_POST['title_prompt'])) : '', |
| 51 |
'content_prompt' => isset($_POST['content_prompt']) ? sanitize_text_field(wp_unslash($_POST['content_prompt'])) : '', |
| 52 |
'tags_prompt' => isset($_POST['tags_prompt']) ? sanitize_text_field(wp_unslash($_POST['tags_prompt'])) : '', |
| 53 |
'image_prompt' => isset($_POST['image_prompt']) ? sanitize_text_field(wp_unslash($_POST['image_prompt'])) : '', |
| 54 |
'image_generating_status' => '', |
| 55 |
'author_selection' => isset($_POST['author_selection']) ? sanitize_text_field(wp_unslash($_POST['author_selection'])) : '', |
| 56 |
'task_type' => 'writenow', |
| 57 |
'news_keyword' => isset($_POST['news_keyword']) ? sanitize_text_field(wp_unslash($_POST['news_keyword'])) : '', |
| 58 |
'news_country' => isset($_POST['news_country']) ? sanitize_text_field(wp_unslash($_POST['news_country'])) : '', |
| 59 |
'news_language' => isset($_POST['news_language']) ? sanitize_text_field(wp_unslash($_POST['news_language'])) : '', |
| 60 |
'news_time_published'=> isset($_POST['news_time_published']) ? sanitize_text_field(wp_unslash($_POST['news_time_published'])) : '', |
| 61 |
'news_source' => isset($_POST['news_source']) ? sanitize_text_field(wp_unslash($_POST['news_source'])) : '', |
| 62 |
'rss_source' => isset($_POST['rss_source']) ? sanitize_text_field(wp_unslash($_POST['rss_source'])) : '', |
| 63 |
'ai_keywords' => isset($_POST['ai_keywords']) ? sanitize_text_field(wp_unslash($_POST['ai_keywords'])) : '', |
| 64 |
'disable_ai_images' => isset($_POST['disable_ai_images']) ? intval(wp_unslash($_POST['disable_ai_images'])) : 0, |
| 65 |
'template_id' => isset($_POST['template_id']) && !empty($_POST['template_id']) ? intval(wp_unslash($_POST['template_id'])) : null, |
| 66 |
); |
| 67 |
|
| 68 |
// Convert arrays to comma strings |
| 69 |
$item['category_id'] = implode(',', $item['category_id']); |
| 70 |
$item['website_category_id'] = implode(',', $item['website_category_id']); |
| 71 |
|
| 72 |
// Process taxonomy terms and build taxonomy_data JSON if not already provided |
| 73 |
if (empty($item['taxonomy_data']) && isset($_POST['taxonomy_terms'])) { |
| 74 |
$taxonomy_terms = wp_unslash($_POST['taxonomy_terms']); |
| 75 |
$taxonomy_data = array(); |
| 76 |
foreach ($taxonomy_terms as $taxonomy_name => $term_ids) { |
| 77 |
$taxonomy_name = sanitize_key($taxonomy_name); |
| 78 |
$taxonomy_data[$taxonomy_name] = array_map('intval', (array)$term_ids); |
| 79 |
} |
| 80 |
$item['taxonomy_data'] = wp_json_encode($taxonomy_data); |
| 81 |
|
| 82 |
// For backward compatibility, extract category IDs if 'category' taxonomy is present |
| 83 |
if (isset($taxonomy_data['category'])) { |
| 84 |
$item['category_id'] = implode(',', $taxonomy_data['category']); |
| 85 |
} |
| 86 |
} |
| 87 |
|
| 88 |
// Validate |
| 89 |
if (!function_exists('botwriter_validate_website')) { wp_send_json_error('validator-missing'); } |
| 90 |
$validation = botwriter_validate_website($item, true); |
| 91 |
if ($validation !== true) { |
| 92 |
wp_send_json_error($validation); |
| 93 |
} |
| 94 |
|
| 95 |
// Insert task |
| 96 |
$inserted = $wpdb->insert($table_name, $item); |
| 97 |
if ($inserted === false) { |
| 98 |
wp_send_json_error('db-error: ' . $wpdb->last_error); |
| 99 |
} |
| 100 |
$task_id = intval($wpdb->insert_id); |
| 101 |
|
| 102 |
// Immediately mark it as executed today so cron won't pick it and create another log |
| 103 |
$wpdb->update( |
| 104 |
$table_name, |
| 105 |
array( |
| 106 |
'execution_count' => 1, |
| 107 |
'last_execution_date'=> current_time('Y-m-d'), |
| 108 |
'last_execution_time'=> current_time('Y-m-d H:i:s'), |
| 109 |
), |
| 110 |
array('id' => $task_id), |
| 111 |
array('%d','%s','%s'), |
| 112 |
array('%d') |
| 113 |
); |
| 114 |
|
| 115 |
// Build initial event/log and kick phase 1 |
| 116 |
$event = $item; |
| 117 |
$event['task_status'] = 'pending'; |
| 118 |
$event['id_task'] = $task_id; |
| 119 |
$event['intentosfase1'] = 0; |
| 120 |
|
| 121 |
if (!function_exists('botwriter_logs_register')) { wp_send_json_error('logs-missing'); } |
| 122 |
$log_id = botwriter_logs_register($event); |
| 123 |
$event['id'] = $log_id; |
| 124 |
|
| 125 |
if (!function_exists('botwriter_send1_data_to_server')) { wp_send_json_error('send1-missing'); } |
| 126 |
botwriter_send1_data_to_server($event); |
| 127 |
|
| 128 |
wp_send_json_success(array('task_id' => $task_id, 'log_id' => $log_id)); |
| 129 |
} |
| 130 |
|
| 131 |
// AJAX: poll status and try advancing phase 2 |
| 132 |
add_action('wp_ajax_botwriter_quick_poll', 'botwriter_quick_poll'); |
| 133 |
function botwriter_quick_poll() { |
| 134 |
if (!current_user_can('manage_options')) { wp_send_json_error('forbidden'); } |
| 135 |
check_ajax_referer('botwriter_quickpost_nonce', 'nonce'); |
| 136 |
|
| 137 |
$log_id = isset($_POST['log_id']) ? intval($_POST['log_id']) : 0; |
| 138 |
if ($log_id <= 0) { wp_send_json_error('invalid-log'); } |
| 139 |
|
| 140 |
if (!function_exists('botwriter_logs_get')) { wp_send_json_error('logs-missing'); } |
| 141 |
$log = botwriter_logs_get($log_id); |
| 142 |
if (!$log) { wp_send_json_error('log-not-found'); } |
| 143 |
|
| 144 |
$status = $log['task_status']; |
| 145 |
|
| 146 |
// Try to advance phase 2 when pending/inqueue |
| 147 |
if (in_array($status, array('pending', 'inqueue'), true)) { |
| 148 |
if (!function_exists('botwriter_send2_data_to_server')) { wp_send_json_error('send2-missing'); } |
| 149 |
botwriter_send2_data_to_server($log); |
| 150 |
// reload |
| 151 |
$log = botwriter_logs_get($log_id); |
| 152 |
$status = $log['task_status']; |
| 153 |
} |
| 154 |
|
| 155 |
$progress = 10; // default |
| 156 |
if ($status === 'inqueue') { $progress = 60; } |
| 157 |
if ($status === 'direct_queued') { $progress = 30; } |
| 158 |
if ($status === 'processing') { $progress = 60; } |
| 159 |
if ($status === 'completed') { $progress = 100; } |
| 160 |
if ($status === 'error') { $progress = 100; } |
| 161 |
|
| 162 |
$edit_link = ''; |
| 163 |
$view_link = ''; |
| 164 |
if (!empty($log['id_post_published'])) { |
| 165 |
$pid = intval($log['id_post_published']); |
| 166 |
$edit_link = get_edit_post_link($pid, ''); |
| 167 |
$view_link = get_permalink($pid); |
| 168 |
} |
| 169 |
|
| 170 |
$response = array( |
| 171 |
'status' => $status, |
| 172 |
'progress' => $progress, |
| 173 |
'id_post_published' => isset($log['id_post_published']) ? intval($log['id_post_published']) : 0, |
| 174 |
'error' => isset($log['error']) ? $log['error'] : '', |
| 175 |
'edit_link' => $edit_link, |
| 176 |
'view_link' => $view_link, |
| 177 |
); |
| 178 |
|
| 179 |
wp_send_json_success($response); |
| 180 |
} |
| 181 |
|
| 182 |
// AJAX: retry phase 1 for an errored quickpost log |
| 183 |
add_action('wp_ajax_botwriter_quick_retry', 'botwriter_quick_retry'); |
| 184 |
function botwriter_quick_retry() { |
| 185 |
if (!current_user_can('manage_options')) { wp_send_json_error('forbidden'); } |
| 186 |
check_ajax_referer('botwriter_quickpost_nonce', 'nonce'); |
| 187 |
|
| 188 |
$log_id = isset($_POST['log_id']) ? intval($_POST['log_id']) : 0; |
| 189 |
if ($log_id <= 0) { wp_send_json_error('invalid-log'); } |
| 190 |
|
| 191 |
if (!function_exists('botwriter_logs_get')) { wp_send_json_error('logs-missing'); } |
| 192 |
$log = botwriter_logs_get($log_id); |
| 193 |
if (!$log) { wp_send_json_error('log-not-found'); } |
| 194 |
|
| 195 |
// Ensure we resend full task data (prompts, ai_keywords, etc.) by merging with source task |
| 196 |
global $wpdb; |
| 197 |
$table_tasks = $wpdb->prefix . 'botwriter_tasks'; |
| 198 |
if (!empty($log['id_task'])) { |
| 199 |
$task_row = $wpdb->get_row( |
| 200 |
$wpdb->prepare( |
| 201 |
'SELECT * FROM ' . $table_tasks . ' WHERE id = %d', |
| 202 |
intval($log['id_task']) |
| 203 |
), |
| 204 |
ARRAY_A |
| 205 |
); |
| 206 |
if (is_array($task_row)) { |
| 207 |
// Avoid overriding log id with task id |
| 208 |
unset($task_row['id']); |
| 209 |
// Task fields should be present; let log engine fields override when colliding |
| 210 |
$log = array_merge($task_row, $log); |
| 211 |
} |
| 212 |
} |
| 213 |
|
| 214 |
// Prepare for a new phase 1 dispatch: clear error and mark pending |
| 215 |
$log['task_status'] = 'pending'; |
| 216 |
$log['error'] = ''; |
| 217 |
// Optionally clear server id so a fresh queue id is assigned |
| 218 |
$log['id_task_server'] = 0; |
| 219 |
|
| 220 |
if (!function_exists('botwriter_logs_register')) { wp_send_json_error('logs-missing'); } |
| 221 |
botwriter_logs_register($log, $log_id); |
| 222 |
|
| 223 |
if (!function_exists('botwriter_send1_data_to_server')) { wp_send_json_error('send1-missing'); } |
| 224 |
botwriter_send1_data_to_server($log); |
| 225 |
|
| 226 |
wp_send_json_success(array('log_id' => $log_id)); |
| 227 |
} |
| 228 |
|
| 229 |
// AJAX: cancel and delete the quickpost task and its log |
| 230 |
add_action('wp_ajax_botwriter_quick_cancel', 'botwriter_quick_cancel'); |
| 231 |
function botwriter_quick_cancel() { |
| 232 |
if (!current_user_can('manage_options')) { wp_send_json_error('forbidden'); } |
| 233 |
check_ajax_referer('botwriter_quickpost_nonce', 'nonce'); |
| 234 |
|
| 235 |
$task_id = isset($_POST['task_id']) ? intval($_POST['task_id']) : 0; |
| 236 |
$log_id = isset($_POST['log_id']) ? intval($_POST['log_id']) : 0; |
| 237 |
if ($task_id <= 0 || $log_id <= 0) { wp_send_json_error('invalid-ids'); } |
| 238 |
|
| 239 |
global $wpdb; |
| 240 |
$table_tasks = $wpdb->prefix . 'botwriter_tasks'; |
| 241 |
$table_logs = $wpdb->prefix . 'botwriter_logs'; |
| 242 |
|
| 243 |
// Only allow cancel for Write now tasks for safety |
| 244 |
$task = $wpdb->get_row( |
| 245 |
$wpdb->prepare( |
| 246 |
'SELECT id, task_type FROM ' . $table_tasks . ' WHERE id = %d', |
| 247 |
$task_id |
| 248 |
), |
| 249 |
ARRAY_A |
| 250 |
); |
| 251 |
if (!$task || (isset($task['task_type']) && $task['task_type'] !== 'writenow')) { |
| 252 |
wp_send_json_error('not-writenow'); |
| 253 |
} |
| 254 |
|
| 255 |
// Delete log and task |
| 256 |
$wpdb->delete($table_logs, array('id' => $log_id), array('%d')); |
| 257 |
$wpdb->delete($table_tasks, array('id' => $task_id), array('%d')); |
| 258 |
|
| 259 |
wp_send_json_success(array('deleted' => true)); |
| 260 |
} |
| 261 |
|
| 262 |
// Page handler rendering identical form and AJAX UI |
| 263 |
function botwriter_quick_post_page_handler() { |
| 264 |
if (!current_user_can('manage_options')) { return; } |
| 265 |
|
| 266 |
global $wpdb; |
| 267 |
$table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 268 |
|
| 269 |
// Defaults similar to posts.php $default |
| 270 |
$item = array( |
| 271 |
'id' => 0, |
| 272 |
/* translators: %d: next quick post number */ |
| 273 |
'task_name' => sprintf(__('Quick Post %d', 'botwriter'), $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE website_type = 'ai'") + 1), |
| 274 |
'writer' => 'orion', |
| 275 |
'narration' => 'Descriptive', |
| 276 |
'custom_style' => '', |
| 277 |
'post_language' => '', |
| 278 |
'post_length' => '800', |
| 279 |
'custom_post_length' => '', |
| 280 |
'post_status' => 'publish', |
| 281 |
'days' => '', |
| 282 |
'times_per_day' => 1, |
| 283 |
'execution_count' => 0, |
| 284 |
'last_execution_date' => null, |
| 285 |
'website_type' => 'ai', |
| 286 |
'website_name' => '', |
| 287 |
'domain_name' => '', |
| 288 |
'post_type' => 'post', |
| 289 |
'category_id' => '', |
| 290 |
'taxonomy_data' => '', |
| 291 |
'website_category_id' => '', |
| 292 |
'website_category_name' => '', |
| 293 |
'aigenerated_title' => '', |
| 294 |
'aigenerated_content' => '', |
| 295 |
'aigenerated_tags' => '', |
| 296 |
'aigenerated_image' => '', |
| 297 |
'post_count' => '', |
| 298 |
'post_order' => '', |
| 299 |
'title_prompt' => '', |
| 300 |
'content_prompt' => '', |
| 301 |
'tags_prompt' => '', |
| 302 |
'image_prompt' => '', |
| 303 |
'image_generating_status' => '', |
| 304 |
'author_selection' => '', |
| 305 |
'news_keyword' => '', |
| 306 |
'news_country' => '', |
| 307 |
'news_language' => '', |
| 308 |
'news_time_published' => '', |
| 309 |
'news_source' => '', |
| 310 |
'rss_source' => '', |
| 311 |
'ai_keywords' => '', |
| 312 |
); |
| 313 |
|
| 314 |
// Register the same meta box handler for this page render |
| 315 |
add_meta_box('botwriter_post_form_meta_box', __('Task Form', 'botwriter'), 'botwriter_post_form_meta_box_handler', 'botwriter_automatic_post_new', 'normal', 'default'); |
| 316 |
|
| 317 |
echo '<div class="wrap">'; |
| 318 |
echo '<h2>' . esc_html__('Write now', 'botwriter') . '</h2>'; |
| 319 |
echo '<p class="description" style="max-width:800px;">' . |
| 320 |
esc_html__('This page is for writing a single article immediately. For recurring schedules or multiple runs, use', 'botwriter') . |
| 321 |
' <a href="' . esc_url(admin_url('admin.php?page=botwriter_addnew_page')) . '">' . esc_html__('New task', 'botwriter') . '</a>.' . |
| 322 |
'</p>'; |
| 323 |
|
| 324 |
echo '<form id="botwriter-quick-form" method="post">'; |
| 325 |
echo '<input type="hidden" name="nonce" value="' . esc_attr(wp_create_nonce('botwriter_quickpost_nonce')) . '">'; |
| 326 |
echo '<div class="metabox-holder" id="poststuff"><div id="post-body"><div id="post-body-content">'; |
| 327 |
do_meta_boxes('botwriter_automatic_post_new', 'normal', $item); |
| 328 |
// Progress bar should appear above the buttons |
| 329 |
$bw_video_url = esc_url( BOTWRITER_URL . 'assets/images/bot_working.mp4'); |
| 330 |
$bw_done_url = esc_url( BOTWRITER_URL . 'assets/images/robot_icon2.png'); |
| 331 |
echo '<div id="bwqp-progress" style="display:none;margin-top:10px;max-width:900px;">'; |
| 332 |
// Row: left media, right steps/messages |
| 333 |
echo '<div id="bwqp-row" style="display:flex; align-items:flex-start; gap:16px;">'; |
| 334 |
// Left media (no borders, left aligned) |
| 335 |
echo '<div id="bwqp-media" style="display:none; width:256px;">'; |
| 336 |
echo '<video id="bwqp-video" width="256" height="256" muted playsinline loop style="display:none;">'; |
| 337 |
echo '<source src="' . esc_url( $bw_video_url ) . '" type="video/mp4" />'; |
| 338 |
echo '</video>'; |
| 339 |
echo '<img id="bwqp-done" src="' . esc_url( $bw_done_url ) . '" width="256" height="256" alt="Done" style="display:none;" />'; |
| 340 |
echo '</div>'; |
| 341 |
// Right messages/steps container |
| 342 |
echo '<div id="bwqp-steps" style="flex:1 1 auto; min-height:256px; padding-top:6px;">'; |
| 343 |
echo '</div>'; |
| 344 |
echo '</div>'; // close row |
| 345 |
// Progress bar and status inside the progress container |
| 346 |
echo '<div style="background:#e5e5e5;border-radius:4px;overflow:hidden;">'; |
| 347 |
echo '<div id="bwqp-bar" style="width:0%;background:#2271b1;color:#fff;padding:6px 10px;transition:width .3s;">0%</div>'; |
| 348 |
echo '</div>'; |
| 349 |
echo '<p id="bwqp-status" style="margin-top:6px;"></p>'; |
| 350 |
echo '</div>'; // close #bwqp-progress |
| 351 |
echo '<p class="submit" style="margin-top:12px;">'; |
| 352 |
echo '<button type="button" id="botwriter-quick-create" class="button button-primary">' . esc_html__('Create Now', 'botwriter') . '</button> '; |
| 353 |
echo '<a class="button" href="' . esc_url(admin_url('admin.php?page=botwriter_automatic_posts')) . '">' . esc_html__('Back to Tasks', 'botwriter') . '</a>'; |
| 354 |
echo '</p>'; |
| 355 |
echo '</div></div></div>'; |
| 356 |
echo '</form>'; |
| 357 |
echo '</div>'; |
| 358 |
} |
| 359 |
|