| 1 |
<?php |
| 2 |
/* |
| 3 |
Plugin Name: BotWriter – AI Writer & Content Generator |
| 4 |
Plugin URI: https://www.wpbotwriter.com |
| 5 |
Description: Plugin for automatically generating posts using artificial intelligence. Create content from scratch with AI and generate custom images. Optimize content for SEO, including tags, titles, and image descriptions. Advanced features like ChatGPT, automatic content creation, image generation, SEO optimization, and AI training make this plugin a complete tool for writers and content creators. |
| 6 |
Version: 3.2.8 |
| 7 |
Author: estebandezafra |
| 8 |
Requires PHP: 7.0 |
| 9 |
License: GPL v2 or later |
| 10 |
License URI: https://www.gnu.org/licenses/gpl-2.0.html |
| 11 |
Text Domain: botwriter |
| 12 |
Domain Path: /languages |
| 13 |
*/ |
| 14 |
|
| 15 |
// Prevent direct access to the file |
| 16 |
if (!defined('ABSPATH')) { |
| 17 |
exit; |
| 18 |
} |
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
if (!defined('BOTWRITER_VERSION')) { |
| 24 |
define('BOTWRITER_VERSION', '3.2.8'); |
| 25 |
} |
| 26 |
|
| 27 |
// Plugin directory path (with trailing slash) |
| 28 |
if (!defined('BOTWRITER_PLUGIN_DIR')) { |
| 29 |
define('BOTWRITER_PLUGIN_DIR', plugin_dir_path(__FILE__)); |
| 30 |
} |
| 31 |
|
| 32 |
define('BOTWRITER_URL', plugin_dir_url(__FILE__)); |
| 33 |
|
| 34 |
define('BOTWRITER_API_URL', "https://api.wpbotwriter.com/"); |
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
// Debugging constant for development |
| 39 |
if (!defined('BOTWRITER_DEBUG')) { |
| 40 |
define('BOTWRITER_DEBUG', false); |
| 41 |
} |
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
if (!function_exists('botwriter_log')) { |
| 47 |
function botwriter_log($message, array $context = []) { |
| 48 |
$botwriter_debug = defined('BOTWRITER_DEBUG') && BOTWRITER_DEBUG === true; |
| 49 |
$wp_debug_log = defined('WP_DEBUG') && WP_DEBUG === true && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG; |
| 50 |
if (!$botwriter_debug && !$wp_debug_log) { |
| 51 |
return; |
| 52 |
} |
| 53 |
|
| 54 |
if (!is_string($message)) { |
| 55 |
$encoded = wp_json_encode($message); |
| 56 |
if ($encoded !== false) { |
| 57 |
$message = $encoded; |
| 58 |
} else { |
| 59 |
// Fallback to safe string/serialization without using print_r |
| 60 |
if (is_scalar($message)) { |
| 61 |
$message = (string) $message; |
| 62 |
} else { |
| 63 |
$message = maybe_serialize($message); |
| 64 |
} |
| 65 |
} |
| 66 |
} |
| 67 |
|
| 68 |
if (!empty($context)) { |
| 69 |
$encoded_context = wp_json_encode($context); |
| 70 |
if ($encoded_context !== false) { |
| 71 |
$message .= ' ' . $encoded_context; |
| 72 |
} |
| 73 |
} |
| 74 |
|
| 75 |
error_log('[BotWriter] ' . $message); |
| 76 |
} |
| 77 |
} |
| 78 |
|
| 79 |
|
| 80 |
/** |
| 81 |
* Add plugin action links (Settings link next to Deactivate) |
| 82 |
*/ |
| 83 |
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'botwriter_plugin_action_links'); |
| 84 |
function botwriter_plugin_action_links($links) { |
| 85 |
$settings_link = '<a href="' . admin_url('admin.php?page=botwriter_settings') . '">' . __('Settings', 'botwriter') . '</a>'; |
| 86 |
array_unshift($links, $settings_link); |
| 87 |
return $links; |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Add plugin row meta links (Website, FAQ, Support below plugin description) |
| 92 |
*/ |
| 93 |
add_filter('plugin_row_meta', 'botwriter_plugin_row_meta', 10, 2); |
| 94 |
function botwriter_plugin_row_meta($links, $file) { |
| 95 |
if (plugin_basename(__FILE__) === $file) { |
| 96 |
$row_meta = array( |
| 97 |
'website' => '<a href="https://www.wpbotwriter.com" target="_blank" rel="noopener">' . __('Website', 'botwriter') . '</a>', |
| 98 |
'faq' => '<a href="https://www.wpbotwriter.com/faq.html" target="_blank" rel="noopener">' . __('FAQ', 'botwriter') . '</a>', |
| 99 |
'support' => '<a href="https://wordpress.org/support/plugin/botwriter/" target="_blank" rel="noopener">' . __('Support', 'botwriter') . '</a>', |
| 100 |
); |
| 101 |
return array_merge($links, $row_meta); |
| 102 |
} |
| 103 |
return $links; |
| 104 |
} |
| 105 |
|
| 106 |
|
| 107 |
require plugin_dir_path( __FILE__ ) . 'includes/posts.php'; |
| 108 |
require plugin_dir_path( __FILE__ ) . 'includes/functions.php'; |
| 109 |
require plugin_dir_path( __FILE__ ) . 'includes/settings.php'; |
| 110 |
require plugin_dir_path( __FILE__ ) . 'includes/logs.php'; |
| 111 |
require plugin_dir_path( __FILE__ ) . 'includes/announcements.php'; |
| 112 |
require plugin_dir_path( __FILE__ ) . 'includes/super.php'; |
| 113 |
require plugin_dir_path( __FILE__ ) . 'includes/addnew.php'; |
| 114 |
require plugin_dir_path( __FILE__ ) . 'includes/quickpost.php'; |
| 115 |
require plugin_dir_path( __FILE__ ) . 'includes/rewriter.php'; |
| 116 |
require plugin_dir_path( __FILE__ ) . 'includes/siterewriter.php'; |
| 117 |
require plugin_dir_path( __FILE__ ) . 'includes/templates.php'; |
| 118 |
require plugin_dir_path( __FILE__ ) . 'includes/default-templates.php'; |
| 119 |
|
| 120 |
// WooCommerce AI Content Optimizer (loads only when WooCommerce is active) |
| 121 |
require plugin_dir_path( __FILE__ ) . 'includes/woocommerce-ai/class-bw-woo-ai.php'; |
| 122 |
add_action( 'plugins_loaded', function () { |
| 123 |
$bw_woo_ai = new BW_Woo_AI(); |
| 124 |
$bw_woo_ai->init(); |
| 125 |
} ); |
| 126 |
|
| 127 |
|
| 128 |
// Enqueque JS Files |
| 129 |
function botwriter_enqueue_scripts() { |
| 130 |
$my_plugin_dir = plugin_dir_url(__FILE__); |
| 131 |
$screen = get_current_screen(); |
| 132 |
$slug = $screen->id; |
| 133 |
|
| 134 |
|
| 135 |
|
| 136 |
wp_register_script( 'bootstrapjs',$my_plugin_dir.'/assets/js/bootstrap.min.js' , array('jquery'), false, true ); |
| 137 |
wp_enqueue_script( 'bootstrapjs' ); |
| 138 |
|
| 139 |
|
| 140 |
wp_register_script( 'botwriter_bootstrap_bundle',$my_plugin_dir.'/assets/js/bootstrap.bundle.min.js' , array('jquery'), false, true ); |
| 141 |
wp_enqueue_script( 'botwriter_bootstrap_bundle' ); |
| 142 |
|
| 143 |
|
| 144 |
wp_register_script( 'botwriter_botwriter',$my_plugin_dir.'/assets/js/botwriter.js' , array('jquery'), false, true ); |
| 145 |
wp_enqueue_script( 'botwriter_botwriter' ); |
| 146 |
wp_localize_script('botwriter_botwriter', 'botwriter_ajax', array( |
| 147 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 148 |
'nonce' => wp_create_nonce('botwriter_super_nonce'), |
| 149 |
'rss_nonce' => wp_create_nonce('botwriter_check_rss_nonce'), |
| 150 |
'wp_categories_nonce' => wp_create_nonce('botwriter_wp_categories_nonce'), |
| 151 |
)); |
| 152 |
|
| 153 |
wp_enqueue_script('botwriter-admin-ajax-status', $my_plugin_dir.'/assets/js/admin-ajax-status.js', ['jquery'], null, true); |
| 154 |
wp_localize_script('botwriter-admin-ajax-status', 'botwriter_ajax_object', [ |
| 155 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 156 |
'nonce' => wp_create_nonce('botwriter_cambiar_status_nonce') |
| 157 |
]); |
| 158 |
|
| 159 |
|
| 160 |
wp_enqueue_script('botwriter-dismiss-script', $my_plugin_dir . '/assets/js/botwriter_dismiss.js', array('jquery'), null, true); |
| 161 |
wp_localize_script('botwriter-dismiss-script','botwriterData', |
| 162 |
array( |
| 163 |
'nonce' => wp_create_nonce('botwriter_dismiss_nonce'), |
| 164 |
'ajaxurl' => admin_url('admin-ajax.php') |
| 165 |
) |
| 166 |
); |
| 167 |
|
| 168 |
|
| 169 |
if ($slug=="botwriter_page_botwriter_automatic_post_new" || $slug === 'botwriter_page_botwriter_super_page' || $slug === 'botwriter_page_botwriter_write_now' || $slug === 'botwriter_page_botwriter_rewriter_page' || $slug === 'botwriter_page_botwriter_siterewriter_page') { |
| 170 |
wp_register_script('botwriter_automatic_posts', $my_plugin_dir . 'assets/js/posts.js', array('jquery'), false, true); |
| 171 |
wp_enqueue_script('botwriter_automatic_posts'); |
| 172 |
wp_localize_script('botwriter_automatic_posts', 'botwriter_posts_ajax', array( |
| 173 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 174 |
'taxonomies_nonce' => wp_create_nonce('botwriter_taxonomies_nonce'), |
| 175 |
)); |
| 176 |
} |
| 177 |
|
| 178 |
|
| 179 |
if ($slug==="botwriter_page_botwriter_logs") { |
| 180 |
wp_register_script('botwriter_logs', $my_plugin_dir . 'assets/js/logs.js', array('jquery'), false, true); |
| 181 |
wp_enqueue_script('botwriter_logs'); |
| 182 |
wp_localize_script('botwriter_logs', 'botwriter_logs_vars', array( |
| 183 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 184 |
'nonce' => wp_create_nonce('botwriter_logs_delete_nonce'), |
| 185 |
'confirm_delete' => __('Are you sure you want to delete this log entry? This action cannot be undone.', 'botwriter'), |
| 186 |
'confirm_bulk_delete' => __('Are you sure you want to delete the selected log entries? This action cannot be undone.', 'botwriter'), |
| 187 |
'error_delete' => __('Error deleting log. Please try again.', 'botwriter'), |
| 188 |
)); |
| 189 |
} |
| 190 |
|
| 191 |
|
| 192 |
|
| 193 |
if ($slug === 'botwriter_page_botwriter_super_page') { |
| 194 |
wp_register_script('botwriter_super', $my_plugin_dir . 'assets/js/super.js', array('jquery'), false, true); |
| 195 |
wp_enqueue_script('botwriter_super'); |
| 196 |
wp_localize_script('botwriter_super', 'botwriter_super_ajax', array( |
| 197 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 198 |
'nonce' => wp_create_nonce('botwriter_super_nonce') |
| 199 |
)); |
| 200 |
} |
| 201 |
|
| 202 |
if ($slug === 'botwriter_page_botwriter_rewriter_page') { |
| 203 |
wp_register_script('botwriter_rewriter', $my_plugin_dir . 'assets/js/rewriter.js', array('jquery'), false, true); |
| 204 |
wp_enqueue_script('botwriter_rewriter'); |
| 205 |
wp_localize_script('botwriter_rewriter', 'botwriter_rewriter_ajax', array( |
| 206 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 207 |
'nonce' => wp_create_nonce('botwriter_rewriter_nonce'), |
| 208 |
'logs_url' => admin_url('admin.php?page=botwriter_logs'), |
| 209 |
)); |
| 210 |
} |
| 211 |
|
| 212 |
if ($slug === 'botwriter_page_botwriter_siterewriter_page') { |
| 213 |
wp_register_script('botwriter_siterewriter', $my_plugin_dir . 'assets/js/siterewriter.js', array('jquery'), false, true); |
| 214 |
wp_enqueue_script('botwriter_siterewriter'); |
| 215 |
wp_localize_script('botwriter_siterewriter', 'botwriter_siterewriter_ajax', array( |
| 216 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 217 |
'nonce' => wp_create_nonce('botwriter_siterewriter_nonce'), |
| 218 |
'logs_url' => admin_url('admin.php?page=botwriter_logs'), |
| 219 |
)); |
| 220 |
} |
| 221 |
|
| 222 |
if ($slug === 'botwriter_page_botwriter_settings') { |
| 223 |
wp_register_script('botwriter_settings', $my_plugin_dir . 'assets/js/botwriter-settings.js', array('jquery'), false, true); |
| 224 |
wp_enqueue_script('botwriter_settings'); |
| 225 |
wp_localize_script('botwriter_settings', 'botwriter_settings', array( |
| 226 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 227 |
'nonce' => wp_create_nonce('botwriter_settings_nonce'), |
| 228 |
'i18n' => array( |
| 229 |
'saving' => __('Saving...', 'botwriter'), |
| 230 |
'saved' => __('Saved', 'botwriter'), |
| 231 |
'error' => __('Error', 'botwriter'), |
| 232 |
'connection_error' => __('Connection error', 'botwriter'), |
| 233 |
'connection_failed' => __('Connection failed', 'botwriter'), |
| 234 |
'hide' => __('Hide', 'botwriter'), |
| 235 |
'show' => __('Show', 'botwriter'), |
| 236 |
'active' => __('Active', 'botwriter'), |
| 237 |
'enter_api_key' => __('Please enter an API key first.', 'botwriter'), |
| 238 |
'enter_api_url' => __('Please enter an API URL', 'botwriter'), |
| 239 |
'testing' => __('Testing...', 'botwriter'), |
| 240 |
'models_found' => __('Models found:', 'botwriter'), |
| 241 |
'configure_openai_key' => __('Configure OpenAI API key in Text AI tab first.', 'botwriter'), |
| 242 |
'confirm_reset_models' => __('Are you sure you want to reset all model lists to factory defaults?', 'botwriter'), |
| 243 |
) |
| 244 |
)); |
| 245 |
} |
| 246 |
|
| 247 |
if ($slug === 'botwriter_page_botwriter_write_now') { |
| 248 |
wp_register_script('botwriter_quickpost', $my_plugin_dir . 'assets/js/quickpost.js', array('jquery'), false, true); |
| 249 |
wp_enqueue_script('botwriter_quickpost'); |
| 250 |
wp_localize_script('botwriter_quickpost', 'botwriter_quickpost_ajax', array( |
| 251 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 252 |
'nonce' => wp_create_nonce('botwriter_quickpost_nonce') |
| 253 |
)); |
| 254 |
} |
| 255 |
|
| 256 |
// Regenerate featured image modal (post editor) |
| 257 |
if ($screen && $screen->base === 'post' && current_user_can('manage_options')) { |
| 258 |
wp_register_script('botwriter_post_image_regeneration', $my_plugin_dir . 'assets/js/post-image-regeneration.js', array('jquery'), BOTWRITER_VERSION, true); |
| 259 |
wp_enqueue_script('botwriter_post_image_regeneration'); |
| 260 |
wp_localize_script('botwriter_post_image_regeneration', 'botwriter_post_image_regeneration', array( |
| 261 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 262 |
'nonce' => wp_create_nonce('botwriter_regenerate_image_nonce'), |
| 263 |
'i18n' => array( |
| 264 |
'link_text' => __('Regenerate', 'botwriter'), |
| 265 |
'modal_title' => __('BotWriter Image Regeneration', 'botwriter'), |
| 266 |
'modal_subtitle' => __('Generate and preview a featured image before applying it.', 'botwriter'), |
| 267 |
'provider' => __('Current provider', 'botwriter'), |
| 268 |
'model' => __('Current model', 'botwriter'), |
| 269 |
'prompt_label' => __('Image Prompt', 'botwriter'), |
| 270 |
'cleanup_label' => __('Previous featured image', 'botwriter'), |
| 271 |
'cleanup_keep' => __('Keep in media library', 'botwriter'), |
| 272 |
'cleanup_delete' => __('Delete permanently (if not used elsewhere)', 'botwriter'), |
| 273 |
'btn_regenerate' => __('Regenerate', 'botwriter'), |
| 274 |
'btn_accept' => __('Accept', 'botwriter'), |
| 275 |
'btn_close' => __('Close', 'botwriter'), |
| 276 |
'loading_context'=> __('Loading data...', 'botwriter'), |
| 277 |
'generating' => __('Generating image preview...', 'botwriter'), |
| 278 |
'applying' => __('Applying featured image...', 'botwriter'), |
| 279 |
'missing_log' => __('No saved image prompt was found for this post. Please write your prompt manually.', 'botwriter'), |
| 280 |
'provider_none' => __('Image provider is currently set to "none" in settings. Select an image provider first.', 'botwriter'), |
| 281 |
'empty_prompt' => __('Please enter an image prompt before regenerating.', 'botwriter'), |
| 282 |
'working' => __('Regenerating image...', 'botwriter'), |
| 283 |
'generic_error'=> __('Could not regenerate the image. Please try again.', 'botwriter'), |
| 284 |
), |
| 285 |
)); |
| 286 |
} |
| 287 |
|
| 288 |
|
| 289 |
|
| 290 |
} |
| 291 |
add_action('admin_enqueue_scripts','botwriter_enqueue_scripts'); |
| 292 |
|
| 293 |
/** |
| 294 |
* Retrieve the latest BotWriter log associated with a published post. |
| 295 |
* |
| 296 |
* @param int $post_id Post ID. |
| 297 |
* @return array|null |
| 298 |
*/ |
| 299 |
function botwriter_get_latest_log_by_post_id($post_id) { |
| 300 |
global $wpdb; |
| 301 |
|
| 302 |
$post_id = intval($post_id); |
| 303 |
if ($post_id <= 0) { |
| 304 |
return null; |
| 305 |
} |
| 306 |
|
| 307 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 308 |
$log = $wpdb->get_row( |
| 309 |
$wpdb->prepare( |
| 310 |
"SELECT * FROM {$table_name} WHERE id_post_published = %d ORDER BY id DESC LIMIT 1", |
| 311 |
$post_id |
| 312 |
), |
| 313 |
ARRAY_A |
| 314 |
); |
| 315 |
|
| 316 |
botwriter_log('Image prompt lookup: latest log by post', array( |
| 317 |
'post_id' => $post_id, |
| 318 |
'found' => is_array($log), |
| 319 |
'log_id' => is_array($log) ? intval($log['id'] ?? 0) : 0, |
| 320 |
'id_post_published' => is_array($log) ? intval($log['id_post_published'] ?? 0) : 0, |
| 321 |
'image_prompt_len' => is_array($log) ? strlen(trim((string) ($log['image_prompt'] ?? ''))) : 0, |
| 322 |
)); |
| 323 |
|
| 324 |
return is_array($log) ? $log : null; |
| 325 |
} |
| 326 |
|
| 327 |
/** |
| 328 |
* Retrieve the latest BotWriter log for a post that has a non-empty image_prompt. |
| 329 |
* |
| 330 |
* @param int $post_id Post ID. |
| 331 |
* @return array|null |
| 332 |
*/ |
| 333 |
function botwriter_get_latest_log_with_image_prompt_by_post_id($post_id) { |
| 334 |
global $wpdb; |
| 335 |
|
| 336 |
$post_id = intval($post_id); |
| 337 |
if ($post_id <= 0) { |
| 338 |
return null; |
| 339 |
} |
| 340 |
|
| 341 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 342 |
$log = $wpdb->get_row( |
| 343 |
$wpdb->prepare( |
| 344 |
"SELECT * FROM {$table_name} WHERE id_post_published = %d AND image_prompt IS NOT NULL AND TRIM(image_prompt) <> '' ORDER BY id DESC LIMIT 1", |
| 345 |
$post_id |
| 346 |
), |
| 347 |
ARRAY_A |
| 348 |
); |
| 349 |
|
| 350 |
botwriter_log('Image prompt lookup: latest log WITH prompt by post', array( |
| 351 |
'post_id' => $post_id, |
| 352 |
'found' => is_array($log), |
| 353 |
'log_id' => is_array($log) ? intval($log['id'] ?? 0) : 0, |
| 354 |
'id_post_published' => is_array($log) ? intval($log['id_post_published'] ?? 0) : 0, |
| 355 |
'image_prompt_len' => is_array($log) ? strlen(trim((string) ($log['image_prompt'] ?? ''))) : 0, |
| 356 |
)); |
| 357 |
|
| 358 |
return is_array($log) ? $log : null; |
| 359 |
} |
| 360 |
|
| 361 |
/** |
| 362 |
* Resolve current image model from provider settings. |
| 363 |
* |
| 364 |
* @param string $provider Provider slug. |
| 365 |
* @return string |
| 366 |
*/ |
| 367 |
function botwriter_get_current_image_model_by_provider($provider) { |
| 368 |
$provider = sanitize_key((string) $provider); |
| 369 |
|
| 370 |
$image_model_defaults = array( |
| 371 |
'dalle' => 'gpt-image-1', |
| 372 |
'gemini' => 'gemini-2.5-flash-image', |
| 373 |
'fal' => 'fal-ai/flux-pro/v1.1', |
| 374 |
'replicate' => 'black-forest-labs/flux-1.1-pro', |
| 375 |
'stability' => 'sd3.5-large-turbo', |
| 376 |
'cloudflare' => 'flux-1-schnell', |
| 377 |
'stockphoto' => 'stockphoto', |
| 378 |
); |
| 379 |
|
| 380 |
if ($provider === 'gemini') { |
| 381 |
return (string) get_option('botwriter_gemini_image_model', $image_model_defaults[$provider]); |
| 382 |
} |
| 383 |
|
| 384 |
return (string) get_option("botwriter_{$provider}_model", $image_model_defaults[$provider] ?? 'gpt-image-1'); |
| 385 |
} |
| 386 |
|
| 387 |
/** |
| 388 |
* Return current image settings used for regeneration. |
| 389 |
* Uses active plugin settings at execution time. |
| 390 |
* |
| 391 |
* @return array |
| 392 |
*/ |
| 393 |
function botwriter_get_current_image_generation_settings() { |
| 394 |
$provider = (string) get_option('botwriter_image_provider', 'dalle'); |
| 395 |
$model = botwriter_get_current_image_model_by_provider($provider); |
| 396 |
|
| 397 |
return array( |
| 398 |
'provider' => $provider, |
| 399 |
'model' => $model, |
| 400 |
'size' => (string) get_option('botwriter_ai_image_size', 'square'), |
| 401 |
'quality' => (string) get_option('botwriter_ai_image_quality', 'medium'), |
| 402 |
'style' => (string) get_option('botwriter_ai_image_style', 'realistic'), |
| 403 |
'style_custom' => (string) get_option('botwriter_ai_image_style_custom', ''), |
| 404 |
'stockphoto_preferred' => (string) get_option('botwriter_stockphoto_preferred', 'pixabay'), |
| 405 |
'stockphoto_selection' => (string) get_option('botwriter_stockphoto_selection', 'random_top10'), |
| 406 |
'stockphoto_attribution' => (string) get_option('botwriter_stockphoto_attribution', 'caption'), |
| 407 |
); |
| 408 |
} |
| 409 |
|
| 410 |
/** |
| 411 |
* Return post meta keys used to persist image prompts. |
| 412 |
* |
| 413 |
* @return array |
| 414 |
*/ |
| 415 |
function botwriter_get_image_prompt_meta_keys() { |
| 416 |
return array( |
| 417 |
'ai' => 'botwriter_image_prompt', |
| 418 |
'stock' => 'botwriter_stockphoto_prompt', |
| 419 |
'last' => 'botwriter_image_prompt_last', |
| 420 |
'provider' => 'botwriter_image_prompt_last_provider', |
| 421 |
); |
| 422 |
} |
| 423 |
|
| 424 |
/** |
| 425 |
* Persist image prompt metadata on the post itself. |
| 426 |
* |
| 427 |
* @param int $post_id Post ID. |
| 428 |
* @param string $prompt Prompt text. |
| 429 |
* @param string $provider Provider used for generation. |
| 430 |
* @return bool |
| 431 |
*/ |
| 432 |
function botwriter_save_post_image_prompt_meta($post_id, $prompt, $provider = '') { |
| 433 |
$post_id = intval($post_id); |
| 434 |
if ($post_id <= 0) { |
| 435 |
botwriter_log('Image prompt meta save skipped: invalid post_id', array('post_id' => $post_id)); |
| 436 |
return false; |
| 437 |
} |
| 438 |
|
| 439 |
$prompt = trim(sanitize_textarea_field((string) $prompt)); |
| 440 |
if ($prompt === '') { |
| 441 |
botwriter_log('Image prompt meta save skipped: empty prompt', array( |
| 442 |
'post_id' => $post_id, |
| 443 |
'provider' => $provider, |
| 444 |
)); |
| 445 |
return false; |
| 446 |
} |
| 447 |
|
| 448 |
$provider = sanitize_key((string) $provider); |
| 449 |
$keys = botwriter_get_image_prompt_meta_keys(); |
| 450 |
|
| 451 |
update_post_meta($post_id, $keys['last'], $prompt); |
| 452 |
|
| 453 |
if ($provider === 'stockphoto') { |
| 454 |
update_post_meta($post_id, $keys['stock'], $prompt); |
| 455 |
} else { |
| 456 |
update_post_meta($post_id, $keys['ai'], $prompt); |
| 457 |
} |
| 458 |
|
| 459 |
if ($provider !== '') { |
| 460 |
update_post_meta($post_id, $keys['provider'], $provider); |
| 461 |
} |
| 462 |
|
| 463 |
botwriter_log('Image prompt meta saved', array( |
| 464 |
'post_id' => $post_id, |
| 465 |
'provider' => $provider, |
| 466 |
'prompt_len' => strlen($prompt), |
| 467 |
'saved_ai_meta' => ($provider !== 'stockphoto'), |
| 468 |
'saved_stock_meta' => ($provider === 'stockphoto'), |
| 469 |
'meta_key_last' => $keys['last'], |
| 470 |
)); |
| 471 |
|
| 472 |
return true; |
| 473 |
} |
| 474 |
|
| 475 |
/** |
| 476 |
* Resolve the best prompt saved on post meta for current provider context. |
| 477 |
* |
| 478 |
* @param int $post_id Post ID. |
| 479 |
* @param string $provider Current provider. |
| 480 |
* @return array |
| 481 |
*/ |
| 482 |
function botwriter_get_post_image_prompt_from_meta($post_id, $provider = '') { |
| 483 |
$post_id = intval($post_id); |
| 484 |
$provider = sanitize_key((string) $provider); |
| 485 |
$keys = botwriter_get_image_prompt_meta_keys(); |
| 486 |
|
| 487 |
$ai_prompt = trim((string) get_post_meta($post_id, $keys['ai'], true)); |
| 488 |
$stock_prompt = trim((string) get_post_meta($post_id, $keys['stock'], true)); |
| 489 |
$last_prompt = trim((string) get_post_meta($post_id, $keys['last'], true)); |
| 490 |
|
| 491 |
botwriter_log('Image prompt lookup: post meta snapshot', array( |
| 492 |
'post_id' => $post_id, |
| 493 |
'provider' => $provider, |
| 494 |
'ai_len' => strlen($ai_prompt), |
| 495 |
'stock_len' => strlen($stock_prompt), |
| 496 |
'last_len' => strlen($last_prompt), |
| 497 |
'meta_ai_key' => $keys['ai'], |
| 498 |
'meta_stock_key' => $keys['stock'], |
| 499 |
'meta_last_key' => $keys['last'], |
| 500 |
)); |
| 501 |
|
| 502 |
if ($provider === 'stockphoto') { |
| 503 |
if ($stock_prompt !== '') { |
| 504 |
botwriter_log('Image prompt lookup: selected meta_stock', array('post_id' => $post_id, 'len' => strlen($stock_prompt))); |
| 505 |
return array('prompt' => $stock_prompt, 'source' => 'meta_stock'); |
| 506 |
} |
| 507 |
if ($ai_prompt !== '') { |
| 508 |
botwriter_log('Image prompt lookup: selected meta_ai for stock provider', array('post_id' => $post_id, 'len' => strlen($ai_prompt))); |
| 509 |
return array('prompt' => $ai_prompt, 'source' => 'meta_ai'); |
| 510 |
} |
| 511 |
} else { |
| 512 |
if ($ai_prompt !== '') { |
| 513 |
botwriter_log('Image prompt lookup: selected meta_ai', array('post_id' => $post_id, 'len' => strlen($ai_prompt))); |
| 514 |
return array('prompt' => $ai_prompt, 'source' => 'meta_ai'); |
| 515 |
} |
| 516 |
} |
| 517 |
|
| 518 |
if ($last_prompt !== '') { |
| 519 |
botwriter_log('Image prompt lookup: selected meta_last', array('post_id' => $post_id, 'len' => strlen($last_prompt))); |
| 520 |
return array('prompt' => $last_prompt, 'source' => 'meta_last'); |
| 521 |
} |
| 522 |
|
| 523 |
if ($stock_prompt !== '') { |
| 524 |
botwriter_log('Image prompt lookup: selected stock fallback', array('post_id' => $post_id, 'len' => strlen($stock_prompt))); |
| 525 |
return array('prompt' => $stock_prompt, 'source' => 'meta_stock'); |
| 526 |
} |
| 527 |
|
| 528 |
botwriter_log('Image prompt lookup: no prompt found in post meta', array('post_id' => $post_id)); |
| 529 |
|
| 530 |
return array('prompt' => '', 'source' => 'none'); |
| 531 |
} |
| 532 |
|
| 533 |
/** |
| 534 |
* Resolve the best available image prompt from generated post data. |
| 535 |
* |
| 536 |
* @param array $data Data used to generate/publish the post. |
| 537 |
* @return string |
| 538 |
*/ |
| 539 |
function botwriter_resolve_image_prompt_from_post_data($data) { |
| 540 |
if (!is_array($data)) { |
| 541 |
botwriter_log('Image prompt resolve from post data: invalid payload'); |
| 542 |
return ''; |
| 543 |
} |
| 544 |
|
| 545 |
$explicit_prompt = isset($data['image_prompt']) ? trim(sanitize_textarea_field((string) $data['image_prompt'])) : ''; |
| 546 |
if ($explicit_prompt !== '') { |
| 547 |
botwriter_log('Image prompt resolve from post data: using explicit image_prompt', array( |
| 548 |
'len' => strlen($explicit_prompt), |
| 549 |
'has_image_provider' => isset($data['image_provider']), |
| 550 |
)); |
| 551 |
return $explicit_prompt; |
| 552 |
} |
| 553 |
|
| 554 |
// Edge/legacy fallback: when image_prompt is not returned, the title is the usual fallback basis. |
| 555 |
$title_based_prompt = isset($data['aigenerated_title']) ? trim(sanitize_text_field((string) $data['aigenerated_title'])) : ''; |
| 556 |
if ($title_based_prompt !== '') { |
| 557 |
botwriter_log('Image prompt resolve from post data: fallback to generated title', array( |
| 558 |
'title_len' => strlen($title_based_prompt), |
| 559 |
'has_image_prompt_key' => array_key_exists('image_prompt', $data), |
| 560 |
)); |
| 561 |
return $title_based_prompt; |
| 562 |
} |
| 563 |
|
| 564 |
botwriter_log('Image prompt resolve from post data: fallback to hardcoded default'); |
| 565 |
return 'Blog post illustration'; |
| 566 |
} |
| 567 |
|
| 568 |
/** |
| 569 |
* Build UI context for image regeneration modal. |
| 570 |
* |
| 571 |
* @param int $post_id Post ID. |
| 572 |
* @return array |
| 573 |
*/ |
| 574 |
function botwriter_get_image_regeneration_context($post_id) { |
| 575 |
$post_id = intval($post_id); |
| 576 |
$settings = botwriter_get_current_image_generation_settings(); |
| 577 |
$provider = (string) ($settings['provider'] ?? ''); |
| 578 |
|
| 579 |
$prompt_context = botwriter_get_post_image_prompt_from_meta($post_id, $provider); |
| 580 |
$prefill_prompt = (string) ($prompt_context['prompt'] ?? ''); |
| 581 |
$prompt_source = (string) ($prompt_context['source'] ?? 'none'); |
| 582 |
|
| 583 |
botwriter_log('Image regeneration context: after meta lookup', array( |
| 584 |
'post_id' => $post_id, |
| 585 |
'provider' => $provider, |
| 586 |
'prompt_source' => $prompt_source, |
| 587 |
'prompt_len' => strlen($prefill_prompt), |
| 588 |
)); |
| 589 |
|
| 590 |
$latest_log = null; |
| 591 |
if ($prefill_prompt === '') { |
| 592 |
// Prefer the newest log that actually contains an image prompt. |
| 593 |
$latest_log = botwriter_get_latest_log_with_image_prompt_by_post_id($post_id); |
| 594 |
|
| 595 |
// Backward-compat fallback: if no prompt-carrying log exists, inspect latest log anyway. |
| 596 |
if (!is_array($latest_log)) { |
| 597 |
$latest_log = botwriter_get_latest_log_by_post_id($post_id); |
| 598 |
} |
| 599 |
|
| 600 |
$prefill_prompt = is_array($latest_log) ? trim((string) ($latest_log['image_prompt'] ?? '')) : ''; |
| 601 |
if ($prefill_prompt !== '') { |
| 602 |
$prompt_source = 'log'; |
| 603 |
// Legacy migration: if prompt exists in log but not in post meta, persist it now. |
| 604 |
botwriter_save_post_image_prompt_meta($post_id, $prefill_prompt, $provider); |
| 605 |
botwriter_log('Image regeneration context: prompt recovered from log and migrated to meta', array( |
| 606 |
'post_id' => $post_id, |
| 607 |
'log_id' => intval($latest_log['id'] ?? 0), |
| 608 |
'prompt_len' => strlen($prefill_prompt), |
| 609 |
)); |
| 610 |
} |
| 611 |
} |
| 612 |
|
| 613 |
if ($prefill_prompt === '') { |
| 614 |
$title_prompt = trim((string) get_the_title($post_id)); |
| 615 |
if ($title_prompt !== '') { |
| 616 |
$prefill_prompt = $title_prompt; |
| 617 |
$prompt_source = 'post_title'; |
| 618 |
botwriter_log('Image regeneration context: fallback to post title', array( |
| 619 |
'post_id' => $post_id, |
| 620 |
'title_len' => strlen($title_prompt), |
| 621 |
)); |
| 622 |
} |
| 623 |
} |
| 624 |
|
| 625 |
$has_meta_prompt = in_array($prompt_source, array('meta_ai', 'meta_stock', 'meta_last'), true); |
| 626 |
|
| 627 |
botwriter_log('Image regeneration context resolved', array( |
| 628 |
'post_id' => $post_id, |
| 629 |
'provider' => $provider, |
| 630 |
'prompt_source' => $prompt_source, |
| 631 |
'prompt_len' => strlen($prefill_prompt), |
| 632 |
'has_meta_prompt' => $has_meta_prompt, |
| 633 |
'has_log' => is_array($latest_log), |
| 634 |
'log_id' => is_array($latest_log) ? intval($latest_log['id'] ?? 0) : 0, |
| 635 |
)); |
| 636 |
|
| 637 |
return array( |
| 638 |
'post_id' => $post_id, |
| 639 |
'prompt' => $prefill_prompt, |
| 640 |
'has_log' => is_array($latest_log), |
| 641 |
'has_meta_prompt' => $has_meta_prompt, |
| 642 |
'has_prompt' => ($prefill_prompt !== ''), |
| 643 |
'prompt_source' => $prompt_source, |
| 644 |
'provider' => $provider, |
| 645 |
'model' => (string) ($settings['model'] ?? ''), |
| 646 |
'provider_disabled' => ($provider === 'none'), |
| 647 |
); |
| 648 |
} |
| 649 |
|
| 650 |
/** |
| 651 |
* Generate an image URL using the direct backend endpoint (/images) with current settings. |
| 652 |
* |
| 653 |
* @param string $prompt Prompt text. |
| 654 |
* @return array |
| 655 |
*/ |
| 656 |
function botwriter_generate_image_with_current_settings($prompt) { |
| 657 |
$prompt = trim((string) $prompt); |
| 658 |
if ($prompt === '') { |
| 659 |
return array('success' => false, 'message' => __('Image prompt is required.', 'botwriter')); |
| 660 |
} |
| 661 |
|
| 662 |
$settings = botwriter_get_current_image_generation_settings(); |
| 663 |
$provider = (string) $settings['provider']; |
| 664 |
$model = (string) $settings['model']; |
| 665 |
|
| 666 |
if ($provider === 'none') { |
| 667 |
return array('success' => false, 'message' => __('Image provider is disabled in settings.', 'botwriter')); |
| 668 |
} |
| 669 |
|
| 670 |
$style_value = (string) ($settings['style_custom'] ?: $settings['style']); |
| 671 |
if ($style_value === 'realistic' || $style_value === 'none') { |
| 672 |
$style_value = ''; |
| 673 |
} |
| 674 |
|
| 675 |
$payload = array( |
| 676 |
'prompt' => $prompt, |
| 677 |
'domain' => esc_url_raw(get_site_url()), |
| 678 |
'api_key' => get_option('botwriter_api_key'), |
| 679 |
'site_token' => get_option('botwriter_site_token', ''), |
| 680 |
// Image regenerations are UX actions and should not consume license quota. |
| 681 |
'no_count' => true, |
| 682 |
'provider' => $provider, |
| 683 |
'model' => $model, |
| 684 |
'size' => (string) $settings['size'], |
| 685 |
'quality' => (string) $settings['quality'], |
| 686 |
'style' => $style_value, |
| 687 |
'stockphoto_preferred' => (string) $settings['stockphoto_preferred'], |
| 688 |
'stockphoto_selection' => (string) $settings['stockphoto_selection'], |
| 689 |
'stockphoto_attribution' => (string) $settings['stockphoto_attribution'], |
| 690 |
// Forward client keys (edge endpoint overlays provider keys from this payload) |
| 691 |
'openai_api_key' => botwriter_decrypt_api_key(get_option('botwriter_openai_api_key')), |
| 692 |
'google_api_key' => botwriter_decrypt_api_key(get_option('botwriter_google_api_key')), |
| 693 |
'fal_api_key' => botwriter_decrypt_api_key(get_option('botwriter_fal_api_key')), |
| 694 |
'replicate_api_key' => botwriter_decrypt_api_key(get_option('botwriter_replicate_api_key')), |
| 695 |
'stability_api_key' => botwriter_decrypt_api_key(get_option('botwriter_stability_api_key')), |
| 696 |
'cloudflare_api_key' => botwriter_decrypt_api_key(get_option('botwriter_cloudflare_api_key')), |
| 697 |
'cloudflare_account_id' => get_option('botwriter_cloudflare_account_id'), |
| 698 |
); |
| 699 |
|
| 700 |
$ssl_verify = get_option('botwriter_sslverify'); |
| 701 |
$ssl_verify = ($ssl_verify !== 'no'); |
| 702 |
|
| 703 |
$remote_url = BOTWRITER_API_URL . 'images'; |
| 704 |
$response = wp_remote_post($remote_url, array( |
| 705 |
'method' => 'POST', |
| 706 |
'headers' => array( |
| 707 |
'Content-Type' => 'application/json', |
| 708 |
), |
| 709 |
'body' => wp_json_encode($payload), |
| 710 |
'timeout' => 120, |
| 711 |
'sslverify' => $ssl_verify, |
| 712 |
)); |
| 713 |
|
| 714 |
if (is_wp_error($response)) { |
| 715 |
return array('success' => false, 'message' => $response->get_error_message()); |
| 716 |
} |
| 717 |
|
| 718 |
$status_code = wp_remote_retrieve_response_code($response); |
| 719 |
$body_raw = wp_remote_retrieve_body($response); |
| 720 |
$result = json_decode($body_raw, true); |
| 721 |
|
| 722 |
if ($status_code !== 200 || !is_array($result) || ($result['status'] ?? '') !== 'success' || empty($result['download_url'])) { |
| 723 |
$error_message = ''; |
| 724 |
if (is_array($result)) { |
| 725 |
$error_message = (string) ($result['error'] ?? $result['message'] ?? ''); |
| 726 |
} |
| 727 |
if ($error_message === '') { |
| 728 |
$error_message = __('Image generation failed on server.', 'botwriter'); |
| 729 |
} |
| 730 |
return array('success' => false, 'message' => $error_message); |
| 731 |
} |
| 732 |
|
| 733 |
$download_url = (string) $result['download_url']; |
| 734 |
$image_url = $download_url; |
| 735 |
if (strpos($download_url, 'http://') !== 0 && strpos($download_url, 'https://') !== 0) { |
| 736 |
$image_url = rtrim(BOTWRITER_API_URL, '/') . '/' . ltrim($download_url, '/'); |
| 737 |
} |
| 738 |
|
| 739 |
return array( |
| 740 |
'success' => true, |
| 741 |
'image_url' => $image_url, |
| 742 |
'provider' => $provider, |
| 743 |
'model' => $model, |
| 744 |
); |
| 745 |
} |
| 746 |
|
| 747 |
/** |
| 748 |
* AJAX: fetch context for image regeneration modal. |
| 749 |
*/ |
| 750 |
function botwriter_get_post_image_regeneration_context_ajax() { |
| 751 |
if (!current_user_can('manage_options')) { |
| 752 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 753 |
} |
| 754 |
|
| 755 |
check_ajax_referer('botwriter_regenerate_image_nonce', 'nonce'); |
| 756 |
|
| 757 |
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; |
| 758 |
if ($post_id <= 0 || !get_post($post_id)) { |
| 759 |
wp_send_json_error(array('message' => __('Invalid post.', 'botwriter'))); |
| 760 |
} |
| 761 |
|
| 762 |
if (!current_user_can('edit_post', $post_id)) { |
| 763 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 764 |
} |
| 765 |
|
| 766 |
$context = botwriter_get_image_regeneration_context($post_id); |
| 767 |
botwriter_log('AJAX context response for image regeneration', array( |
| 768 |
'post_id' => $post_id, |
| 769 |
'prompt_source' => $context['prompt_source'] ?? 'unknown', |
| 770 |
'prompt_len' => strlen((string) ($context['prompt'] ?? '')), |
| 771 |
'has_meta_prompt' => !empty($context['has_meta_prompt']), |
| 772 |
'has_log' => !empty($context['has_log']), |
| 773 |
)); |
| 774 |
wp_send_json_success($context); |
| 775 |
} |
| 776 |
add_action('wp_ajax_botwriter_get_post_image_regeneration_context', 'botwriter_get_post_image_regeneration_context_ajax'); |
| 777 |
|
| 778 |
/** |
| 779 |
* AJAX: generate preview image only (does not apply to post yet). |
| 780 |
*/ |
| 781 |
function botwriter_generate_post_image_preview_ajax() { |
| 782 |
if (!current_user_can('manage_options')) { |
| 783 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 784 |
} |
| 785 |
|
| 786 |
check_ajax_referer('botwriter_regenerate_image_nonce', 'nonce'); |
| 787 |
|
| 788 |
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; |
| 789 |
$prompt = isset($_POST['prompt']) ? sanitize_textarea_field(wp_unslash($_POST['prompt'])) : ''; |
| 790 |
|
| 791 |
if ($post_id <= 0 || !get_post($post_id)) { |
| 792 |
wp_send_json_error(array('message' => __('Invalid post.', 'botwriter'))); |
| 793 |
} |
| 794 |
|
| 795 |
if (!current_user_can('edit_post', $post_id)) { |
| 796 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 797 |
} |
| 798 |
|
| 799 |
$generated = botwriter_generate_image_with_current_settings($prompt); |
| 800 |
if (empty($generated['success'])) { |
| 801 |
wp_send_json_error(array('message' => (string) ($generated['message'] ?? __('Image generation failed.', 'botwriter')))); |
| 802 |
} |
| 803 |
|
| 804 |
wp_send_json_success(array( |
| 805 |
'post_id' => $post_id, |
| 806 |
'prompt' => trim((string) $prompt), |
| 807 |
'image_url' => (string) $generated['image_url'], |
| 808 |
'provider' => (string) $generated['provider'], |
| 809 |
'model' => (string) $generated['model'], |
| 810 |
)); |
| 811 |
} |
| 812 |
add_action('wp_ajax_botwriter_generate_post_image_preview', 'botwriter_generate_post_image_preview_ajax'); |
| 813 |
|
| 814 |
/** |
| 815 |
* Check if an attachment is used as featured image by posts other than the current one. |
| 816 |
* |
| 817 |
* @param int $attachment_id Attachment ID. |
| 818 |
* @param int $exclude_post_id Post ID to exclude. |
| 819 |
* @return bool |
| 820 |
*/ |
| 821 |
function botwriter_is_attachment_featured_elsewhere($attachment_id, $exclude_post_id = 0) { |
| 822 |
global $wpdb; |
| 823 |
|
| 824 |
$attachment_id = intval($attachment_id); |
| 825 |
$exclude_post_id = intval($exclude_post_id); |
| 826 |
|
| 827 |
if ($attachment_id <= 0) { |
| 828 |
return false; |
| 829 |
} |
| 830 |
|
| 831 |
$query = "SELECT COUNT(1) FROM {$wpdb->postmeta} WHERE meta_key = '_thumbnail_id' AND meta_value = %d"; |
| 832 |
$params = array($attachment_id); |
| 833 |
|
| 834 |
if ($exclude_post_id > 0) { |
| 835 |
$query .= " AND post_id <> %d"; |
| 836 |
$params[] = $exclude_post_id; |
| 837 |
} |
| 838 |
|
| 839 |
$count = $wpdb->get_var($wpdb->prepare($query, $params)); |
| 840 |
return intval($count) > 0; |
| 841 |
} |
| 842 |
|
| 843 |
/** |
| 844 |
* Register a lightweight image-only log entry for future prompt prefill. |
| 845 |
* |
| 846 |
* @param int $post_id Post ID. |
| 847 |
* @param string $prompt Prompt used. |
| 848 |
* @param string $image_url Generated image URL. |
| 849 |
* @param string $provider Current provider. |
| 850 |
* @param string $model Current model. |
| 851 |
* @param array|null $source_log Existing latest log for this post. |
| 852 |
* @return int|false |
| 853 |
*/ |
| 854 |
function botwriter_register_only_image_log($post_id, $prompt, $image_url, $provider, $model, $source_log = null) { |
| 855 |
$post = get_post($post_id); |
| 856 |
if (!($post instanceof WP_Post)) { |
| 857 |
return false; |
| 858 |
} |
| 859 |
|
| 860 |
$base = array( |
| 861 |
'id_task' => 0, |
| 862 |
'id_task_server' => 0, |
| 863 |
'post_status' => $post->post_status ?: 'draft', |
| 864 |
'task_name' => sprintf(__('Image regeneration for post #%d', 'botwriter'), intval($post_id)), |
| 865 |
'task_type' => 'only_image', |
| 866 |
'writer' => 'orion', |
| 867 |
'narration' => 'Descriptive', |
| 868 |
'custom_style' => '', |
| 869 |
'post_language' => substr(get_locale(), 0, 2), |
| 870 |
'post_length' => '800', |
| 871 |
'link_post_original' => get_permalink($post_id), |
| 872 |
'id_post_published' => intval($post_id), |
| 873 |
'task_status' => 'completed', |
| 874 |
'error' => '', |
| 875 |
'website_name' => '', |
| 876 |
'website_type' => 'ai', |
| 877 |
'domain_name' => esc_url_raw(get_site_url()), |
| 878 |
'post_type' => $post->post_type ?: 'post', |
| 879 |
'category_id' => '', |
| 880 |
'taxonomy_data' => '', |
| 881 |
'website_category_id' => '', |
| 882 |
'aigenerated_title' => get_the_title($post_id), |
| 883 |
'aigenerated_content' => '', |
| 884 |
'aigenerated_tags' => '', |
| 885 |
'aigenerated_image' => $image_url, |
| 886 |
'post_count' => '1', |
| 887 |
'post_order' => '', |
| 888 |
'title_prompt' => '', |
| 889 |
'content_prompt' => '', |
| 890 |
'tags_prompt' => '', |
| 891 |
'image_prompt' => $prompt, |
| 892 |
'image_generating_status' => 'completed', |
| 893 |
'author_selection' => strval($post->post_author ?: get_current_user_id()), |
| 894 |
'news_time_published' => '', |
| 895 |
'news_language' => '', |
| 896 |
'news_country' => '', |
| 897 |
'news_keyword' => '', |
| 898 |
'news_source' => '', |
| 899 |
'rss_source' => '', |
| 900 |
'ai_keywords' => '', |
| 901 |
'disable_ai_images' => 0, |
| 902 |
'template_id' => null, |
| 903 |
'intentosfase1' => 0, |
| 904 |
'last_execution_time' => current_time('mysql'), |
| 905 |
); |
| 906 |
|
| 907 |
// Reuse as much context as possible from latest known log. |
| 908 |
if (is_array($source_log) && !empty($source_log)) { |
| 909 |
$inherit_keys = array( |
| 910 |
'id_task', |
| 911 |
'post_status', |
| 912 |
'task_name', |
| 913 |
'writer', |
| 914 |
'narration', |
| 915 |
'custom_style', |
| 916 |
'post_language', |
| 917 |
'post_length', |
| 918 |
'website_name', |
| 919 |
'website_type', |
| 920 |
'domain_name', |
| 921 |
'post_type', |
| 922 |
'category_id', |
| 923 |
'taxonomy_data', |
| 924 |
'website_category_id', |
| 925 |
'title_prompt', |
| 926 |
'content_prompt', |
| 927 |
'tags_prompt', |
| 928 |
'author_selection', |
| 929 |
'ai_keywords', |
| 930 |
'template_id', |
| 931 |
); |
| 932 |
|
| 933 |
foreach ($inherit_keys as $key) { |
| 934 |
if (array_key_exists($key, $source_log) && $source_log[$key] !== null && $source_log[$key] !== '') { |
| 935 |
$base[$key] = $source_log[$key]; |
| 936 |
} |
| 937 |
} |
| 938 |
} |
| 939 |
|
| 940 |
// Ensure this log is identifiable as image-only and references current settings context. |
| 941 |
$base['task_type'] = 'only_image'; |
| 942 |
$base['task_status'] = 'completed'; |
| 943 |
$base['id_post_published'] = intval($post_id); |
| 944 |
$base['image_prompt'] = $prompt; |
| 945 |
$base['aigenerated_image'] = $image_url; |
| 946 |
$base['error'] = ''; |
| 947 |
$base['last_execution_time'] = current_time('mysql'); |
| 948 |
$base['task_name'] = sprintf( |
| 949 |
__('Only image regeneration (%1$s / %2$s) - Post #%3$d', 'botwriter'), |
| 950 |
$provider, |
| 951 |
$model, |
| 952 |
intval($post_id) |
| 953 |
); |
| 954 |
|
| 955 |
return botwriter_logs_register($base); |
| 956 |
} |
| 957 |
|
| 958 |
/** |
| 959 |
* AJAX: apply a regenerated image URL as featured image for an existing post. |
| 960 |
* If no image_url is provided, it can generate one using current settings (legacy fallback). |
| 961 |
*/ |
| 962 |
function botwriter_apply_post_regenerated_image_ajax() { |
| 963 |
if (!current_user_can('manage_options')) { |
| 964 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 965 |
} |
| 966 |
|
| 967 |
check_ajax_referer('botwriter_regenerate_image_nonce', 'nonce'); |
| 968 |
|
| 969 |
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; |
| 970 |
$prompt = isset($_POST['prompt']) ? sanitize_textarea_field(wp_unslash($_POST['prompt'])) : ''; |
| 971 |
$image_url = isset($_POST['image_url']) ? esc_url_raw(wp_unslash($_POST['image_url'])) : ''; |
| 972 |
$cleanup_policy = isset($_POST['cleanup_policy']) ? sanitize_key(wp_unslash($_POST['cleanup_policy'])) : 'keep_old'; |
| 973 |
$provider = isset($_POST['provider']) ? sanitize_key(wp_unslash($_POST['provider'])) : ''; |
| 974 |
$model = isset($_POST['model']) ? sanitize_text_field(wp_unslash($_POST['model'])) : ''; |
| 975 |
|
| 976 |
if ($post_id <= 0 || !get_post($post_id)) { |
| 977 |
wp_send_json_error(array('message' => __('Invalid post.', 'botwriter'))); |
| 978 |
} |
| 979 |
|
| 980 |
if (!current_user_can('edit_post', $post_id)) { |
| 981 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 982 |
} |
| 983 |
|
| 984 |
if (trim($prompt) === '') { |
| 985 |
wp_send_json_error(array('message' => __('Image prompt is required.', 'botwriter'))); |
| 986 |
} |
| 987 |
|
| 988 |
if (!in_array($cleanup_policy, array('keep_old', 'delete_old'), true)) { |
| 989 |
$cleanup_policy = 'keep_old'; |
| 990 |
} |
| 991 |
|
| 992 |
if ($image_url === '') { |
| 993 |
// Legacy fallback: if called without image_url, generate directly now. |
| 994 |
$generated = botwriter_generate_image_with_current_settings($prompt); |
| 995 |
if (empty($generated['success'])) { |
| 996 |
wp_send_json_error(array('message' => (string) ($generated['message'] ?? __('Image generation failed.', 'botwriter')))); |
| 997 |
} |
| 998 |
$image_url = (string) $generated['image_url']; |
| 999 |
$provider = (string) $generated['provider']; |
| 1000 |
$model = (string) $generated['model']; |
| 1001 |
} |
| 1002 |
|
| 1003 |
if (strpos($image_url, 'http://') !== 0 && strpos($image_url, 'https://') !== 0) { |
| 1004 |
wp_send_json_error(array('message' => __('Invalid image URL.', 'botwriter'))); |
| 1005 |
} |
| 1006 |
|
| 1007 |
if ($provider === '' || $model === '') { |
| 1008 |
$settings = botwriter_get_current_image_generation_settings(); |
| 1009 |
if ($provider === '') { |
| 1010 |
$provider = (string) ($settings['provider'] ?? 'dalle'); |
| 1011 |
} |
| 1012 |
if ($model === '') { |
| 1013 |
$model = (string) ($settings['model'] ?? 'gpt-image-1'); |
| 1014 |
} |
| 1015 |
} |
| 1016 |
|
| 1017 |
$old_thumbnail_id = get_post_thumbnail_id($post_id); |
| 1018 |
$post_title = get_the_title($post_id); |
| 1019 |
|
| 1020 |
botwriter_attach_image_to_post($post_id, $image_url, $post_title); |
| 1021 |
$new_thumbnail_id = get_post_thumbnail_id($post_id); |
| 1022 |
|
| 1023 |
if (empty($new_thumbnail_id)) { |
| 1024 |
wp_send_json_error(array('message' => __('Image was generated but could not be attached as featured image.', 'botwriter'))); |
| 1025 |
} |
| 1026 |
|
| 1027 |
$deleted_old = false; |
| 1028 |
$delete_note = ''; |
| 1029 |
if ($cleanup_policy === 'delete_old' && !empty($old_thumbnail_id) && intval($old_thumbnail_id) !== intval($new_thumbnail_id)) { |
| 1030 |
if (botwriter_is_attachment_featured_elsewhere(intval($old_thumbnail_id), $post_id)) { |
| 1031 |
$delete_note = __('Previous featured image was not deleted because it is used by other posts.', 'botwriter'); |
| 1032 |
} else { |
| 1033 |
$deleted_old = (bool) wp_delete_attachment(intval($old_thumbnail_id), true); |
| 1034 |
if (!$deleted_old) { |
| 1035 |
$delete_note = __('Previous featured image could not be deleted automatically.', 'botwriter'); |
| 1036 |
} |
| 1037 |
} |
| 1038 |
} |
| 1039 |
|
| 1040 |
$latest_log = botwriter_get_latest_log_by_post_id($post_id); |
| 1041 |
botwriter_log('Apply regenerated image: persisting prompt to log/meta', array( |
| 1042 |
'post_id' => $post_id, |
| 1043 |
'provider' => $provider, |
| 1044 |
'model' => $model, |
| 1045 |
'prompt_len' => strlen((string) $prompt), |
| 1046 |
'latest_log_id' => is_array($latest_log) ? intval($latest_log['id'] ?? 0) : 0, |
| 1047 |
)); |
| 1048 |
$log_id = botwriter_register_only_image_log($post_id, $prompt, $image_url, $provider, $model, $latest_log); |
| 1049 |
botwriter_save_post_image_prompt_meta($post_id, $prompt, $provider); |
| 1050 |
|
| 1051 |
$thumb_src = wp_get_attachment_image_src($new_thumbnail_id, 'medium'); |
| 1052 |
$featured_src = is_array($thumb_src) ? $thumb_src[0] : ''; |
| 1053 |
|
| 1054 |
wp_send_json_success(array( |
| 1055 |
'message' => __('Featured image regenerated successfully.', 'botwriter'), |
| 1056 |
'post_id' => $post_id, |
| 1057 |
'image_url' => $image_url, |
| 1058 |
'featured_image_src' => $featured_src, |
| 1059 |
'attachment_id' => intval($new_thumbnail_id), |
| 1060 |
'provider' => $provider, |
| 1061 |
'model' => $model, |
| 1062 |
'deleted_old' => $deleted_old, |
| 1063 |
'delete_note' => $delete_note, |
| 1064 |
'log_id' => $log_id ?: 0, |
| 1065 |
)); |
| 1066 |
} |
| 1067 |
add_action('wp_ajax_botwriter_apply_post_regenerated_image', 'botwriter_apply_post_regenerated_image_ajax'); |
| 1068 |
// Backward-compatible alias for previous one-step endpoint name. |
| 1069 |
add_action('wp_ajax_botwriter_regenerate_post_image', 'botwriter_apply_post_regenerated_image_ajax'); |
| 1070 |
|
| 1071 |
|
| 1072 |
|
| 1073 |
if (!function_exists('deactivate_plugins')) { |
| 1074 |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 1075 |
} |
| 1076 |
|
| 1077 |
|
| 1078 |
function botwriter_enqueue_styles(){ |
| 1079 |
$my_plugin_dir = plugin_dir_url(__FILE__); |
| 1080 |
$screen = get_current_screen(); |
| 1081 |
|
| 1082 |
$slug = $screen->id; |
| 1083 |
|
| 1084 |
// Welcome banner CSS - load on ALL admin pages if not dismissed |
| 1085 |
// (because admin_notices shows on all pages) |
| 1086 |
$welcome_dismissed = get_option('botwriter_welcome_dismissed', false); |
| 1087 |
if (!$welcome_dismissed) { |
| 1088 |
wp_register_style('botwriter_welcome_banner', $my_plugin_dir . 'assets/css/welcome-banner.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/welcome-banner.css')); |
| 1089 |
wp_enqueue_style('botwriter_welcome_banner'); |
| 1090 |
} |
| 1091 |
|
| 1092 |
// Only enqueue other styles for BotWriter admin screens |
| 1093 |
|
| 1094 |
if (strpos((string)$slug, 'botwriter') !== false) { |
| 1095 |
|
| 1096 |
// Register and enqueue styles with dynamic versioning for better caching |
| 1097 |
wp_register_style('botwriter_bootstrap', $my_plugin_dir . 'assets/css/bootstrap.min.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/bootstrap.min.css')); |
| 1098 |
wp_enqueue_style('botwriter_bootstrap'); |
| 1099 |
|
| 1100 |
wp_register_style('botwriter_jquery_ui', $my_plugin_dir . 'assets/css/jquery-ui.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/jquery-ui.css')); |
| 1101 |
wp_enqueue_style('botwriter_jquery_ui'); |
| 1102 |
|
| 1103 |
wp_register_style('botwriter_loader', $my_plugin_dir . 'assets/css/loader.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/loader.css')); |
| 1104 |
wp_enqueue_style('botwriter_loader'); |
| 1105 |
|
| 1106 |
wp_register_style('botwriter_style', $my_plugin_dir . 'assets/css/style.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/style.css')); |
| 1107 |
wp_enqueue_style('botwriter_style'); |
| 1108 |
|
| 1109 |
// Admin menu styles (hide duplicate submenus) |
| 1110 |
wp_register_style('botwriter_admin_menu', $my_plugin_dir . 'assets/css/admin-menu.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/admin-menu.css')); |
| 1111 |
wp_enqueue_style('botwriter_admin_menu'); |
| 1112 |
|
| 1113 |
// Settings page specific styles |
| 1114 |
if (strpos((string)$slug, 'botwriter_settings') !== false) { |
| 1115 |
wp_register_style('botwriter_settings', $my_plugin_dir . 'assets/css/settings.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/settings.css')); |
| 1116 |
wp_enqueue_style('botwriter_settings'); |
| 1117 |
} |
| 1118 |
|
| 1119 |
if ($slug === 'botwriter_page_botwriter_siterewriter_page') { |
| 1120 |
wp_register_style('botwriter_siterewriter', $my_plugin_dir . 'assets/css/siterewriter.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/siterewriter.css')); |
| 1121 |
wp_enqueue_style('botwriter_siterewriter'); |
| 1122 |
} |
| 1123 |
} |
| 1124 |
} |
| 1125 |
|
| 1126 |
add_action('admin_enqueue_scripts', 'botwriter_enqueue_styles'); |
| 1127 |
|
| 1128 |
|
| 1129 |
|
| 1130 |
|
| 1131 |
// Hook to add the admin menu |
| 1132 |
add_action('admin_menu', function() { |
| 1133 |
add_menu_page( |
| 1134 |
__('BotWriter', 'botwriter'), |
| 1135 |
__('BotWriter', 'botwriter'), |
| 1136 |
'manage_options', |
| 1137 |
'botwriter_menu', |
| 1138 |
'botwriter_admin_page', |
| 1139 |
plugin_dir_url(__FILE__) . '/assets/images/icono25.png', |
| 1140 |
90 |
| 1141 |
); |
| 1142 |
|
| 1143 |
add_submenu_page('botwriter_menu', |
| 1144 |
__('Write now', 'botwriter'), |
| 1145 |
__('Write now', 'botwriter'), |
| 1146 |
'manage_options', |
| 1147 |
'botwriter_write_now', |
| 1148 |
'botwriter_quick_post_page_handler' |
| 1149 |
); |
| 1150 |
|
| 1151 |
add_submenu_page('botwriter_menu', |
| 1152 |
__('New task', 'botwriter'), |
| 1153 |
__('New task', 'botwriter'), |
| 1154 |
'manage_options', |
| 1155 |
'botwriter_addnew_page', |
| 1156 |
'botwriter_addnew_page_handler' |
| 1157 |
); |
| 1158 |
|
| 1159 |
// Register under parent to avoid deprecations (null parent). We'll hide it from the submenu below. |
| 1160 |
add_submenu_page('botwriter_menu', |
| 1161 |
__('Super Task AI', 'botwriter'), |
| 1162 |
__('Super Task AI', 'botwriter'), |
| 1163 |
'manage_options', |
| 1164 |
'botwriter_super_page', |
| 1165 |
'botwriter_super_page_handler' |
| 1166 |
); |
| 1167 |
|
| 1168 |
add_submenu_page('botwriter_menu', |
| 1169 |
__('Tasks AI', 'botwriter'), |
| 1170 |
__('Tasks AI', 'botwriter'), |
| 1171 |
'manage_options', |
| 1172 |
'botwriter_automatic_posts', |
| 1173 |
'botwriter_automatic_posts_page' |
| 1174 |
); |
| 1175 |
|
| 1176 |
add_submenu_page('botwriter_menu', |
| 1177 |
__('Content Rewriter', 'botwriter'), |
| 1178 |
__('Content Rewriter', 'botwriter'), |
| 1179 |
'manage_options', |
| 1180 |
'botwriter_rewriter_page', |
| 1181 |
'botwriter_rewriter_page_handler' |
| 1182 |
); |
| 1183 |
|
| 1184 |
add_submenu_page('botwriter_menu', |
| 1185 |
__('Site Rewriter', 'botwriter'), |
| 1186 |
__('Site Rewriter', 'botwriter'), |
| 1187 |
'manage_options', |
| 1188 |
'botwriter_siterewriter_page', |
| 1189 |
'botwriter_siterewriter_page_handler' |
| 1190 |
); |
| 1191 |
|
| 1192 |
// for development |
| 1193 |
/* |
| 1194 |
add_submenu_page('botwriter_menu', |
| 1195 |
__('Test Call', 'botwriter'), |
| 1196 |
__('Test Call', 'botwriter'), |
| 1197 |
'manage_options', |
| 1198 |
'botwriter_prueba', |
| 1199 |
'botwriter_prueba' |
| 1200 |
); |
| 1201 |
*/ |
| 1202 |
|
| 1203 |
|
| 1204 |
// Register the edit/detail page under the parent, then hide it programmatically to avoid null parent deprecations |
| 1205 |
$hook = add_submenu_page('botwriter_menu', |
| 1206 |
__('Add New Task', 'botwriter'), |
| 1207 |
__('Add New Task', 'botwriter'), |
| 1208 |
'manage_options', |
| 1209 |
'botwriter_automatic_post_new', |
| 1210 |
'botwriter_form_page_handler' |
| 1211 |
); |
| 1212 |
|
| 1213 |
add_submenu_page('botwriter_menu', |
| 1214 |
__('Settings', 'botwriter'), |
| 1215 |
__('Settings', 'botwriter'), |
| 1216 |
'manage_options', |
| 1217 |
'botwriter_settings', |
| 1218 |
'botwriter_settings_page_handler' |
| 1219 |
); |
| 1220 |
|
| 1221 |
add_submenu_page('botwriter_menu', |
| 1222 |
__('Templates', 'botwriter'), |
| 1223 |
__('Templates', 'botwriter'), |
| 1224 |
'manage_options', |
| 1225 |
'botwriter_templates', |
| 1226 |
'botwriter_templates_page_handler' |
| 1227 |
); |
| 1228 |
|
| 1229 |
add_submenu_page('botwriter_menu', |
| 1230 |
__('Logs', 'botwriter'), |
| 1231 |
get_option('botwriter_stopformany', false) |
| 1232 |
? __('Logs', 'botwriter') . ' <span class="update-plugins count-1" style="background:#d63638;"><span class="plugin-count">!</span></span>' |
| 1233 |
: __('Logs', 'botwriter'), |
| 1234 |
'manage_options', |
| 1235 |
'botwriter_logs', |
| 1236 |
'botwriter_logs_page_handler' |
| 1237 |
); |
| 1238 |
}); |
| 1239 |
|
| 1240 |
// CSS for hiding duplicate submenu entries is now in assets/css/admin-menu.css |
| 1241 |
// and enqueued via botwriter_enqueue_styles() |
| 1242 |
|
| 1243 |
|
| 1244 |
|
| 1245 |
|
| 1246 |
|
| 1247 |
function botwriter_prueba() { |
| 1248 |
if (!current_user_can('manage_options')) { |
| 1249 |
return; |
| 1250 |
} |
| 1251 |
|
| 1252 |
?> |
| 1253 |
|
| 1254 |
<h1>Prueba...</h1> |
| 1255 |
<div> |
| 1256 |
Llamando a la funcion que ejecuta las tareas |
| 1257 |
</div> |
| 1258 |
|
| 1259 |
<?php |
| 1260 |
botwriter_scheduled_events_execute_tasks(); |
| 1261 |
|
| 1262 |
} |
| 1263 |
|
| 1264 |
|
| 1265 |
|
| 1266 |
|
| 1267 |
|
| 1268 |
|
| 1269 |
// First screen of the plugin |
| 1270 |
function botwriter_admin_page() { |
| 1271 |
if (!current_user_can('manage_options')) { |
| 1272 |
return; |
| 1273 |
} |
| 1274 |
|
| 1275 |
// Check if any API key is configured |
| 1276 |
$has_api_key = false; |
| 1277 |
$text_providers = [ |
| 1278 |
'botwriter_openai_api_key', |
| 1279 |
'botwriter_anthropic_api_key', |
| 1280 |
'botwriter_google_api_key', |
| 1281 |
'botwriter_mistral_api_key', |
| 1282 |
'botwriter_groq_api_key', |
| 1283 |
'botwriter_openrouter_api_key' |
| 1284 |
]; |
| 1285 |
foreach ($text_providers as $provider_key) { |
| 1286 |
$key_value = get_option($provider_key); |
| 1287 |
if (!empty($key_value) && function_exists('botwriter_decrypt_api_key')) { |
| 1288 |
$decrypted = botwriter_decrypt_api_key($key_value); |
| 1289 |
if (!empty($decrypted)) { |
| 1290 |
$has_api_key = true; |
| 1291 |
break; |
| 1292 |
} |
| 1293 |
} |
| 1294 |
} |
| 1295 |
|
| 1296 |
$settings_url = admin_url('admin.php?page=botwriter_settings'); |
| 1297 |
$addnew_url = admin_url('admin.php?page=botwriter_addnew_page'); |
| 1298 |
$tasks_url = admin_url('admin.php?page=botwriter_automatic_posts'); |
| 1299 |
$logs_url = admin_url('admin.php?page=botwriter_logs'); |
| 1300 |
?> |
| 1301 |
<div class="wrap"> |
| 1302 |
<div style="max-width: 900px; margin: 0 auto;"> |
| 1303 |
|
| 1304 |
<!-- Header --> |
| 1305 |
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 12px; margin-bottom: 25px; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);"> |
| 1306 |
<h1 style="margin: 0 0 10px 0; font-size: 28px; font-weight: 600;"> |
| 1307 |
<span style="margin-right: 10px;">🤖</span><?php echo esc_html__('BotWriter', 'botwriter'); ?> |
| 1308 |
</h1> |
| 1309 |
<p style="margin: 0; font-size: 16px; opacity: 0.95;"> |
| 1310 |
<?php echo esc_html__('AI-Powered Content Creation for WordPress', 'botwriter'); ?> |
| 1311 |
</p> |
| 1312 |
</div> |
| 1313 |
|
| 1314 |
<!-- Quick Start Alert --> |
| 1315 |
<?php if (!$has_api_key): ?> |
| 1316 |
<div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px 20px; border-radius: 0 8px 8px 0; margin-bottom: 25px;"> |
| 1317 |
<strong style="color: #856404;">⚡ <?php echo esc_html__('Quick Start:', 'botwriter'); ?></strong> |
| 1318 |
<span style="color: #856404;"> |
| 1319 |
<?php echo esc_html__('Configure your AI provider API key to get started.', 'botwriter'); ?> |
| 1320 |
<a href="<?php echo esc_url($settings_url); ?>" style="color: #856404; font-weight: 600;"><?php echo esc_html__('Go to Settings →', 'botwriter'); ?></a> |
| 1321 |
</span> |
| 1322 |
</div> |
| 1323 |
<?php endif; ?> |
| 1324 |
|
| 1325 |
<!-- Description --> |
| 1326 |
<div style="background: white; padding: 25px; border-radius: 10px; margin-bottom: 25px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 1327 |
<p style="font-size: 15px; line-height: 1.7; color: #444; margin: 0;"> |
| 1328 |
<?php echo esc_html__('BotWriter automates content creation using the latest AI models. Connect your preferred AI provider, configure your content sources, and let BotWriter generate SEO-optimized articles with AI-generated images—completely hands-free.', 'botwriter'); ?> |
| 1329 |
</p> |
| 1330 |
</div> |
| 1331 |
|
| 1332 |
<!-- Features Grid --> |
| 1333 |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin-bottom: 25px;"> |
| 1334 |
|
| 1335 |
<!-- Text AI Card --> |
| 1336 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 1337 |
<div style="font-size: 24px; margin-bottom: 12px;">✍️</div> |
| 1338 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('Multi-Provider Text AI', 'botwriter'); ?></h3> |
| 1339 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 1340 |
<?php echo esc_html__('Choose from OpenAI (GPT-4o), Anthropic (Claude), Google (Gemini), Mistral, Groq, or OpenRouter. Use your own API keys.', 'botwriter'); ?> |
| 1341 |
</p> |
| 1342 |
</div> |
| 1343 |
|
| 1344 |
<!-- Image AI Card --> |
| 1345 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 1346 |
<div style="font-size: 24px; margin-bottom: 12px;">🎨</div> |
| 1347 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('AI Image Generation', 'botwriter'); ?></h3> |
| 1348 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 1349 |
<?php echo esc_html__('Generate featured images with DALL-E, Stable Diffusion, Flux, Recraft, and more via Replicate, Stability AI, or Fal.ai.', 'botwriter'); ?> |
| 1350 |
</p> |
| 1351 |
</div> |
| 1352 |
|
| 1353 |
<!-- Content Sources Card --> |
| 1354 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 1355 |
<div style="font-size: 24px; margin-bottom: 12px;">📡</div> |
| 1356 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('Multiple Content Sources', 'botwriter'); ?></h3> |
| 1357 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 1358 |
<?php echo esc_html__('Import and rewrite content from any WordPress site, RSS feed, or news API. Prevent duplicates automatically.', 'botwriter'); ?> |
| 1359 |
</p> |
| 1360 |
</div> |
| 1361 |
|
| 1362 |
<!-- Automation Card --> |
| 1363 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 1364 |
<div style="font-size: 24px; margin-bottom: 12px;">⚙️</div> |
| 1365 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('Full Automation', 'botwriter'); ?></h3> |
| 1366 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 1367 |
<?php echo esc_html__('Schedule unlimited tasks, set publishing frequency, and let BotWriter work 24/7. Monitor everything from the Logs.', 'botwriter'); ?> |
| 1368 |
</p> |
| 1369 |
</div> |
| 1370 |
|
| 1371 |
</div> |
| 1372 |
|
| 1373 |
<!-- Getting Started Steps --> |
| 1374 |
<div style="background: white; padding: 25px; border-radius: 10px; margin-bottom: 25px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 1375 |
<h2 style="margin: 0 0 20px 0; font-size: 18px; color: #333;"> |
| 1376 |
🚀 <?php echo esc_html__('Getting Started', 'botwriter'); ?> |
| 1377 |
</h2> |
| 1378 |
|
| 1379 |
<div style="display: flex; flex-direction: column; gap: 15px;"> |
| 1380 |
|
| 1381 |
<div style="display: flex; align-items: flex-start; gap: 15px;"> |
| 1382 |
<div style="background: <?php echo $has_api_key ? '#28a745' : '#667eea'; ?>; color: white; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; flex-shrink: 0;"> |
| 1383 |
<?php echo $has_api_key ? '✓' : '1'; ?> |
| 1384 |
</div> |
| 1385 |
<div> |
| 1386 |
<strong style="color: #333;"><?php echo esc_html__('Configure your AI Provider', 'botwriter'); ?></strong> |
| 1387 |
<p style="margin: 5px 0 0 0; color: #666; font-size: 13px;"> |
| 1388 |
<?php echo esc_html__('Add your API key from OpenAI, Anthropic (Claude), Google (Gemini), Mistral, Groq, or OpenRouter.', 'botwriter'); ?> |
| 1389 |
<a href="<?php echo esc_url($settings_url); ?>"><?php echo esc_html__('Settings →', 'botwriter'); ?></a> |
| 1390 |
</p> |
| 1391 |
</div> |
| 1392 |
</div> |
| 1393 |
|
| 1394 |
<div style="display: flex; align-items: flex-start; gap: 15px;"> |
| 1395 |
<div style="background: #667eea; color: white; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; flex-shrink: 0;">2</div> |
| 1396 |
<div> |
| 1397 |
<strong style="color: #333;"><?php echo esc_html__('Create Your First Task', 'botwriter'); ?></strong> |
| 1398 |
<p style="margin: 5px 0 0 0; color: #666; font-size: 13px;"> |
| 1399 |
<?php echo esc_html__('Define your content source, AI prompts, categories, and publishing schedule.', 'botwriter'); ?> |
| 1400 |
<a href="<?php echo esc_url($addnew_url); ?>"><?php echo esc_html__('Add New →', 'botwriter'); ?></a> |
| 1401 |
</p> |
| 1402 |
</div> |
| 1403 |
</div> |
| 1404 |
|
| 1405 |
<div style="display: flex; align-items: flex-start; gap: 15px;"> |
| 1406 |
<div style="background: #667eea; color: white; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; flex-shrink: 0;">3</div> |
| 1407 |
<div> |
| 1408 |
<strong style="color: #333;"><?php echo esc_html__('Activate and Monitor', 'botwriter'); ?></strong> |
| 1409 |
<p style="margin: 5px 0 0 0; color: #666; font-size: 13px;"> |
| 1410 |
<?php echo esc_html__('Enable your tasks and watch BotWriter generate posts automatically. Check the Logs for status updates.', 'botwriter'); ?> |
| 1411 |
<a href="<?php echo esc_url($logs_url); ?>"><?php echo esc_html__('Logs →', 'botwriter'); ?></a> |
| 1412 |
</p> |
| 1413 |
</div> |
| 1414 |
</div> |
| 1415 |
|
| 1416 |
</div> |
| 1417 |
</div> |
| 1418 |
|
| 1419 |
<!-- Quick Links --> |
| 1420 |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px;"> |
| 1421 |
<a href="<?php echo esc_url($settings_url); ?>" class="botwriter-quick-link" style="background: white; padding: 15px; border-radius: 8px; text-align: center; text-decoration: none; color: #333; box-shadow: 0 2px 8px rgba(0,0,0,0.08); transition: transform 0.2s, box-shadow 0.2s;"> |
| 1422 |
<div style="font-size: 20px; margin-bottom: 6px;">⚙️</div> |
| 1423 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Settings', 'botwriter'); ?></div> |
| 1424 |
</a> |
| 1425 |
<a href="<?php echo esc_url($addnew_url); ?>" class="botwriter-quick-link" style="background: white; padding: 15px; border-radius: 8px; text-align: center; text-decoration: none; color: #333; box-shadow: 0 2px 8px rgba(0,0,0,0.08); transition: transform 0.2s, box-shadow 0.2s;"> |
| 1426 |
<div style="font-size: 20px; margin-bottom: 6px;">➕</div> |
| 1427 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Add New', 'botwriter'); ?></div> |
| 1428 |
</a> |
| 1429 |
<a href="<?php echo esc_url($tasks_url); ?>" class="botwriter-quick-link" style="background: white; padding: 15px; border-radius: 8px; text-align: center; text-decoration: none; color: #333; box-shadow: 0 2px 8px rgba(0,0,0,0.08); transition: transform 0.2s, box-shadow 0.2s;"> |
| 1430 |
<div style="font-size: 20px; margin-bottom: 6px;">📋</div> |
| 1431 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Tasks', 'botwriter'); ?></div> |
| 1432 |
</a> |
| 1433 |
<a href="<?php echo esc_url($logs_url); ?>" class="botwriter-quick-link" style="background: white; padding: 15px; border-radius: 8px; text-align: center; text-decoration: none; color: #333; box-shadow: 0 2px 8px rgba(0,0,0,0.08); transition: transform 0.2s, box-shadow 0.2s;"> |
| 1434 |
<div style="font-size: 20px; margin-bottom: 6px;">📊</div> |
| 1435 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Logs', 'botwriter'); ?></div> |
| 1436 |
</a> |
| 1437 |
<a href="https://wpbotwriter.com/faq-frequently-asked-questions/" target="_blank" class="botwriter-quick-link botwriter-quick-link-highlight" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 15px; border-radius: 8px; text-align: center; text-decoration: none; color: white; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); transition: transform 0.2s, box-shadow 0.2s;"> |
| 1438 |
<div style="font-size: 20px; margin-bottom: 6px;">❓</div> |
| 1439 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Help & FAQ', 'botwriter'); ?></div> |
| 1440 |
</a> |
| 1441 |
</div> |
| 1442 |
|
| 1443 |
<!-- Footer --> |
| 1444 |
<div style="text-align: center; margin-top: 30px; padding: 15px; color: #888; font-size: 12px;"> |
| 1445 |
<?php echo esc_html__('BotWriter', 'botwriter'); ?> v<?php echo esc_html(BOTWRITER_VERSION); ?> — 100% Free |
| 1446 |
<br> |
| 1447 |
<a href="https://www.wpbotwriter.com" target="_blank" style="color: #667eea; text-decoration: none;"><?php echo esc_html__('Website', 'botwriter'); ?></a> |
| 1448 |
• |
| 1449 |
<a href="https://www.wpbotwriter.com/faq.html" target="_blank" style="color: #667eea; text-decoration: none;">FAQ</a> |
| 1450 |
• |
| 1451 |
<a href="https://wordpress.org/support/plugin/botwriter/" target="_blank" style="color: #667eea; text-decoration: none;"><?php echo esc_html__('Support', 'botwriter'); ?></a> |
| 1452 |
</div> |
| 1453 |
|
| 1454 |
</div> |
| 1455 |
</div> |
| 1456 |
|
| 1457 |
<?php |
| 1458 |
} |
| 1459 |
|
| 1460 |
|
| 1461 |
|
| 1462 |
// Hook that runs on plugin activation |
| 1463 |
register_activation_hook(__FILE__, 'botwriter_plugin_activate'); |
| 1464 |
function botwriter_plugin_activate() { |
| 1465 |
// Store first install date if missing |
| 1466 |
if (get_option('botwriter_install_date') === false) { |
| 1467 |
update_option('botwriter_install_date', current_time('timestamp')); |
| 1468 |
} |
| 1469 |
botwriter_activate_apikey_and_defaults(); |
| 1470 |
botwriter_create_table(); |
| 1471 |
// Create the first supertask if it doesn't exist |
| 1472 |
/* |
| 1473 |
if (!botwriter_super1_check_task_exist()) { |
| 1474 |
botwriter_super1_create_first_task(); |
| 1475 |
} |
| 1476 |
*/ |
| 1477 |
} |
| 1478 |
|
| 1479 |
|
| 1480 |
function botwriter_activate_apikey_and_defaults() { |
| 1481 |
if (get_option('botwriter_paused_tasks') === false) { |
| 1482 |
update_option('botwriter_paused_tasks', "2"); |
| 1483 |
} |
| 1484 |
|
| 1485 |
if (get_option('botwriter_email') === false) { |
| 1486 |
update_option('botwriter_email', get_option('admin_email')); |
| 1487 |
} |
| 1488 |
|
| 1489 |
if (get_option('botwriter_cron_active') === false) { |
| 1490 |
update_option('botwriter_cron_active', '1'); |
| 1491 |
} |
| 1492 |
|
| 1493 |
if (get_option('botwriter_ai_image_size') === false) { |
| 1494 |
update_option('botwriter_ai_image_size', 'square'); |
| 1495 |
} |
| 1496 |
|
| 1497 |
if (get_option('botwriter_sslverify') === false) { |
| 1498 |
update_option('botwriter_sslverify', 'yes'); |
| 1499 |
} |
| 1500 |
|
| 1501 |
if (get_option('botwriter_openai_model') === false) { |
| 1502 |
update_option('botwriter_openai_model', 'gpt-5.4-mini'); |
| 1503 |
} |
| 1504 |
if (get_option('botwriter_ai_image_quality') === false) { |
| 1505 |
update_option('botwriter_ai_image_quality', 'medium'); |
| 1506 |
} |
| 1507 |
} |
| 1508 |
|
| 1509 |
|
| 1510 |
// Compatibility check for different WordPress versions |
| 1511 |
add_action('plugins_loaded', 'botwriter_compatibility_check'); |
| 1512 |
|
| 1513 |
function botwriter_compatibility_check() { |
| 1514 |
global $wp_version; |
| 1515 |
|
| 1516 |
if (version_compare($wp_version, '4.0', '<')) { |
| 1517 |
deactivate_plugins(plugin_basename(__FILE__)); |
| 1518 |
|
| 1519 |
wp_die(esc_html__('This plugin requires WordPress 4.0 or higher', 'botwriter')); |
| 1520 |
} |
| 1521 |
} |
| 1522 |
|
| 1523 |
|
| 1524 |
// Ensure DB schema migrations run even for sites that didn't re-activate the plugin |
| 1525 |
add_action('plugins_loaded', 'botwriter_maybe_add_task_type_col', 20); |
| 1526 |
function botwriter_maybe_add_task_type_col() { |
| 1527 |
global $wpdb; |
| 1528 |
$tasks_table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 1529 |
// Bail if table is missing |
| 1530 |
$table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tasks_table_name)); |
| 1531 |
if ($table_exists !== $tasks_table_name) { |
| 1532 |
return; |
| 1533 |
} |
| 1534 |
// Add task_type if missing |
| 1535 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'task_type')); |
| 1536 |
if (!$col) { |
| 1537 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `task_type` VARCHAR(50) NULL AFTER `website_type`"); |
| 1538 |
} |
| 1539 |
} |
| 1540 |
|
| 1541 |
|
| 1542 |
|
| 1543 |
// funciones extra |
| 1544 |
if (!class_exists('WP_List_Table')) { |
| 1545 |
require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php'); |
| 1546 |
} |
| 1547 |
|
| 1548 |
|
| 1549 |
function botwriter_create_table() { |
| 1550 |
global $wpdb; |
| 1551 |
try { |
| 1552 |
|
| 1553 |
$tasks_table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 1554 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tasks_table_name)) !== $tasks_table_name) { |
| 1555 |
$charset_collate = $wpdb->get_charset_collate(); |
| 1556 |
|
| 1557 |
$tasks_sql = "CREATE TABLE $tasks_table_name ( |
| 1558 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 1559 |
`post_status` VARCHAR(20) NOT NULL, |
| 1560 |
`task_name` VARCHAR(255) NOT NULL, |
| 1561 |
`writer` VARCHAR(255) NOT NULL, |
| 1562 |
`narration` VARCHAR(255), |
| 1563 |
`custom_style` VARCHAR(255), |
| 1564 |
`post_language` VARCHAR(255) NOT NULL, |
| 1565 |
`post_length` VARCHAR(255) NOT NULL, |
| 1566 |
`custom_post_length` VARCHAR(255) NOT NULL, |
| 1567 |
`days` VARCHAR(255) NOT NULL, |
| 1568 |
`times_per_day` INT NOT NULL, |
| 1569 |
`execution_count` INT DEFAULT 0, |
| 1570 |
`last_execution_date` DATE DEFAULT NULL, |
| 1571 |
`last_execution_time` TIMESTAMP DEFAULT 0, |
| 1572 |
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 1573 |
`status` int DEFAULT 1, |
| 1574 |
`website_name` VARCHAR(255), |
| 1575 |
`website_type` VARCHAR(255), |
| 1576 |
`task_type` VARCHAR(50) DEFAULT NULL, |
| 1577 |
`domain_name` VARCHAR(255) NOT NULL, |
| 1578 |
`post_type` VARCHAR(50) DEFAULT 'post', |
| 1579 |
`category_id` VARCHAR(255), |
| 1580 |
`taxonomy_data` TEXT, |
| 1581 |
`website_category_id` VARCHAR(255), |
| 1582 |
`website_category_name` VARCHAR(255), |
| 1583 |
`aigenerated_title` TEXT NOT NULL, |
| 1584 |
`aigenerated_content` TEXT NOT NULL, |
| 1585 |
`aigenerated_tags` TEXT NOT NULL, |
| 1586 |
`aigenerated_image` TEXT NOT NULL, |
| 1587 |
`post_count` VARCHAR(255), |
| 1588 |
`post_order` VARCHAR(255), |
| 1589 |
`title_prompt` TEXT NOT NULL, |
| 1590 |
`content_prompt` TEXT NOT NULL, |
| 1591 |
`tags_prompt` TEXT NOT NULL, |
| 1592 |
`image_prompt` TEXT NOT NULL, |
| 1593 |
`image_generating_status` VARCHAR(255), |
| 1594 |
`author_selection` VARCHAR(255), |
| 1595 |
`news_time_published` VARCHAR(255), |
| 1596 |
`news_language` VARCHAR(255), |
| 1597 |
`news_country` VARCHAR(255), |
| 1598 |
`news_keyword` VARCHAR(255), |
| 1599 |
`news_source` VARCHAR(255), |
| 1600 |
`rss_source` VARCHAR(255), |
| 1601 |
`ai_keywords` TEXT NOT NULL, |
| 1602 |
`disable_ai_images` TINYINT(1) DEFAULT 0, |
| 1603 |
`template_id` INT(11) DEFAULT NULL, |
| 1604 |
PRIMARY KEY (`id`) |
| 1605 |
) $charset_collate;"; |
| 1606 |
|
| 1607 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 1608 |
dbDelta($tasks_sql); |
| 1609 |
} else { |
| 1610 |
// Ensure new column task_type exists for legacy installs |
| 1611 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'task_type')); |
| 1612 |
if (!$col) { |
| 1613 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `task_type` VARCHAR(50) NULL AFTER `website_type`"); |
| 1614 |
} |
| 1615 |
|
| 1616 |
// Ensure new column disable_ai_images exists for legacy installs |
| 1617 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'disable_ai_images')); |
| 1618 |
if (!$col) { |
| 1619 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `disable_ai_images` TINYINT(1) DEFAULT 0"); |
| 1620 |
} |
| 1621 |
|
| 1622 |
// Ensure new column template_id exists for legacy installs |
| 1623 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'template_id')); |
| 1624 |
if (!$col) { |
| 1625 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `template_id` INT(11) DEFAULT NULL"); |
| 1626 |
} |
| 1627 |
|
| 1628 |
// Ensure new column post_type exists for legacy installs |
| 1629 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'post_type')); |
| 1630 |
if (!$col) { |
| 1631 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `post_type` VARCHAR(50) DEFAULT 'post' AFTER `domain_name`"); |
| 1632 |
} |
| 1633 |
|
| 1634 |
// Ensure new column taxonomy_data exists for legacy installs |
| 1635 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'taxonomy_data')); |
| 1636 |
if (!$col) { |
| 1637 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `taxonomy_data` TEXT AFTER `category_id`"); |
| 1638 |
} |
| 1639 |
} |
| 1640 |
|
| 1641 |
// Table botwriter_logs |
| 1642 |
$logs_table_name = $wpdb->prefix . 'botwriter_logs'; |
| 1643 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $logs_table_name)) !== $logs_table_name) { |
| 1644 |
$charset_collate = $wpdb->get_charset_collate(); |
| 1645 |
|
| 1646 |
$logs_sql = "CREATE TABLE $logs_table_name ( |
| 1647 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 1648 |
`id_task` int(11) NOT NULL, |
| 1649 |
`id_task_server` int(11) NOT NULL, |
| 1650 |
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 1651 |
`last_execution_time` TIMESTAMP DEFAULT 0, |
| 1652 |
`intentosfase1` int(11) NOT NULL DEFAULT 0, |
| 1653 |
`intentosfase2` int(11) NOT NULL DEFAULT 0, |
| 1654 |
`task_status` VARCHAR(255), |
| 1655 |
`task_type` VARCHAR(50) DEFAULT NULL, |
| 1656 |
`error` TEXT, |
| 1657 |
`link_post_original` TEXT, |
| 1658 |
`id_post_published` int(11) default 0, |
| 1659 |
`post_status` VARCHAR(20) NOT NULL, |
| 1660 |
`task_name` VARCHAR(255) NOT NULL, |
| 1661 |
`writer` VARCHAR(255) NOT NULL, |
| 1662 |
`narration` VARCHAR(255), |
| 1663 |
`custom_style` VARCHAR(255), |
| 1664 |
`post_language` VARCHAR(255) NOT NULL, |
| 1665 |
`post_length` VARCHAR(255) NOT NULL, |
| 1666 |
`custom_post_length` VARCHAR(255) NOT NULL, |
| 1667 |
`website_name` VARCHAR(255), |
| 1668 |
`website_type` VARCHAR(255), |
| 1669 |
`domain_name` VARCHAR(255) NOT NULL, |
| 1670 |
`post_type` VARCHAR(50) DEFAULT 'post', |
| 1671 |
`category_id` VARCHAR(255), |
| 1672 |
`taxonomy_data` TEXT, |
| 1673 |
`website_category_id` VARCHAR(255), |
| 1674 |
`aigenerated_title` TEXT NOT NULL, |
| 1675 |
`aigenerated_content` TEXT NOT NULL, |
| 1676 |
`aigenerated_tags` TEXT NOT NULL, |
| 1677 |
`aigenerated_image` TEXT NOT NULL, |
| 1678 |
`post_count` VARCHAR(255), |
| 1679 |
`post_order` VARCHAR(255), |
| 1680 |
`title_prompt` TEXT NOT NULL, |
| 1681 |
`content_prompt` TEXT NOT NULL, |
| 1682 |
`tags_prompt` TEXT NOT NULL, |
| 1683 |
`image_prompt` TEXT NOT NULL, |
| 1684 |
`image_generating_status` VARCHAR(255), |
| 1685 |
`author_selection` VARCHAR(255), |
| 1686 |
`news_time_published` VARCHAR(255), |
| 1687 |
`news_language` VARCHAR(255), |
| 1688 |
`news_country` VARCHAR(255), |
| 1689 |
`news_keyword` VARCHAR(255), |
| 1690 |
`news_source` VARCHAR(255), |
| 1691 |
`rss_source` VARCHAR(255), |
| 1692 |
`ai_keywords` TEXT NOT NULL, |
| 1693 |
`disable_ai_images` TINYINT(1) DEFAULT 0, |
| 1694 |
`template_id` INT(11) DEFAULT NULL, |
| 1695 |
PRIMARY KEY (`id`) |
| 1696 |
) $charset_collate;"; |
| 1697 |
|
| 1698 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 1699 |
dbDelta($logs_sql); |
| 1700 |
} else { |
| 1701 |
// Ensure new column disable_ai_images exists in logs table for legacy installs |
| 1702 |
$logs_table_name = $wpdb->prefix . 'botwriter_logs'; |
| 1703 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'disable_ai_images')); |
| 1704 |
if (!$col) { |
| 1705 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `disable_ai_images` TINYINT(1) DEFAULT 0"); |
| 1706 |
} |
| 1707 |
|
| 1708 |
// Ensure new column template_id exists in logs table for legacy installs |
| 1709 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'template_id')); |
| 1710 |
if (!$col) { |
| 1711 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `template_id` INT(11) DEFAULT NULL"); |
| 1712 |
} |
| 1713 |
|
| 1714 |
// Ensure new column task_type exists in logs table for legacy installs (for writenow exclusion) |
| 1715 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'task_type')); |
| 1716 |
if (!$col) { |
| 1717 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `task_type` VARCHAR(50) DEFAULT NULL AFTER `task_status`"); |
| 1718 |
} |
| 1719 |
|
| 1720 |
// Ensure new column post_type exists in logs table for legacy installs |
| 1721 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'post_type')); |
| 1722 |
if (!$col) { |
| 1723 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `post_type` VARCHAR(50) DEFAULT 'post' AFTER `domain_name`"); |
| 1724 |
} |
| 1725 |
|
| 1726 |
// Ensure new column taxonomy_data exists in logs table for legacy installs |
| 1727 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'taxonomy_data')); |
| 1728 |
if (!$col) { |
| 1729 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `taxonomy_data` TEXT AFTER `category_id`"); |
| 1730 |
} |
| 1731 |
} |
| 1732 |
|
| 1733 |
|
| 1734 |
|
| 1735 |
$tasks_table_name = $wpdb->prefix . 'botwriter_super'; |
| 1736 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tasks_table_name)) !== $tasks_table_name) { |
| 1737 |
$charset_collate = $wpdb->get_charset_collate(); |
| 1738 |
|
| 1739 |
$tasks_sql = "CREATE TABLE $tasks_table_name ( |
| 1740 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 1741 |
`id_task` int(11) NOT NULL, |
| 1742 |
`id_log` int(11) NOT NULL, |
| 1743 |
`title` VARCHAR(255) NOT NULL, |
| 1744 |
`content` TEXT NOT NULL, |
| 1745 |
`category_id` VARCHAR(255), |
| 1746 |
`category_name` VARCHAR(255), |
| 1747 |
`task_status` VARCHAR(255), |
| 1748 |
PRIMARY KEY (`id`) |
| 1749 |
) $charset_collate;"; |
| 1750 |
|
| 1751 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 1752 |
dbDelta($tasks_sql); |
| 1753 |
} |
| 1754 |
|
| 1755 |
// Table botwriter_templates for prompt templates |
| 1756 |
$templates_table_name = $wpdb->prefix . 'botwriter_templates'; |
| 1757 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $templates_table_name)) !== $templates_table_name) { |
| 1758 |
$charset_collate = $wpdb->get_charset_collate(); |
| 1759 |
|
| 1760 |
$templates_sql = "CREATE TABLE $templates_table_name ( |
| 1761 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 1762 |
`name` VARCHAR(255) NOT NULL, |
| 1763 |
`content` LONGTEXT NOT NULL, |
| 1764 |
`is_default` TINYINT(1) DEFAULT 0, |
| 1765 |
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 1766 |
PRIMARY KEY (`id`) |
| 1767 |
) $charset_collate;"; |
| 1768 |
|
| 1769 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 1770 |
dbDelta($templates_sql); |
| 1771 |
|
| 1772 |
// Insert all default templates |
| 1773 |
botwriter_insert_all_default_templates(); |
| 1774 |
} |
| 1775 |
|
| 1776 |
} catch (Exception $e) { |
| 1777 |
|
| 1778 |
//error_log("Error creating botwriter tables: " . $e->getMessage()); |
| 1779 |
} |
| 1780 |
} |
| 1781 |
|
| 1782 |
/** |
| 1783 |
* Insert the default prompt template (legacy function - now uses default-templates.php) |
| 1784 |
* @deprecated Use botwriter_insert_all_default_templates() instead |
| 1785 |
*/ |
| 1786 |
function botwriter_insert_default_template() { |
| 1787 |
// Now handled by botwriter_insert_all_default_templates() in default-templates.php |
| 1788 |
botwriter_insert_all_default_templates(); |
| 1789 |
} |
| 1790 |
|
| 1791 |
/** |
| 1792 |
* Get the default template content with all placeholders |
| 1793 |
* Uses the content from default-templates.php if available |
| 1794 |
*/ |
| 1795 |
function botwriter_get_default_template_content() { |
| 1796 |
// Try to get from the default templates array |
| 1797 |
if (function_exists('botwriter_get_default_template_by_name')) { |
| 1798 |
$default = botwriter_get_default_template_by_name('Default Template'); |
| 1799 |
if ($default && !empty($default['content'])) { |
| 1800 |
return $default['content']; |
| 1801 |
} |
| 1802 |
} |
| 1803 |
|
| 1804 |
// Fallback hardcoded template |
| 1805 |
$template = 'Write an article for a blog, follow these instructions: |
| 1806 |
|
| 1807 |
-The article must be HTML, with proper opening and closing H2-H4 tags for headings, and <p> for paragraphs. |
| 1808 |
-The length should be approximately {{post_length}} words. |
| 1809 |
-The article language must be: {{post_language}}. |
| 1810 |
-Narrative style: {{writer_style}} |
| 1811 |
-The topic must be related to some of the following keywords: {{prompt_or_keywords}} |
| 1812 |
|
| 1813 |
-IMPORTANT: Do not title or label the last paragraph with Conclusion, Final Thoughts, Summary, or any similar term. The last paragraph should integrate naturally into the article, without any heading or subheading. It should subtly close the article by reinforcing the main message or idea, offering a final reflection, or leaving the reader with a powerful takeaway, but without explicitly indicating it is the end.'; |
| 1814 |
|
| 1815 |
return $template; |
| 1816 |
} |
| 1817 |
|
| 1818 |
/** |
| 1819 |
* Get template by ID or default template |
| 1820 |
*/ |
| 1821 |
function botwriter_get_template($template_id = null) { |
| 1822 |
global $wpdb; |
| 1823 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 1824 |
|
| 1825 |
if ($template_id) { |
| 1826 |
$template = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $template_id), ARRAY_A); |
| 1827 |
} else { |
| 1828 |
$template = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE is_default = %d", 1), ARRAY_A); |
| 1829 |
} |
| 1830 |
|
| 1831 |
if (!$template) { |
| 1832 |
// Return hardcoded default if no template in DB |
| 1833 |
return [ |
| 1834 |
'id' => 0, |
| 1835 |
'name' => 'Default Template', |
| 1836 |
'content' => botwriter_get_default_template_content(), |
| 1837 |
'is_default' => 1 |
| 1838 |
]; |
| 1839 |
} |
| 1840 |
|
| 1841 |
return $template; |
| 1842 |
} |
| 1843 |
|
| 1844 |
/** |
| 1845 |
* Get all templates |
| 1846 |
*/ |
| 1847 |
function botwriter_get_all_templates() { |
| 1848 |
global $wpdb; |
| 1849 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 1850 |
return $wpdb->get_results("SELECT * FROM $table_name ORDER BY name DESC", ARRAY_A); |
| 1851 |
} |
| 1852 |
|
| 1853 |
/** |
| 1854 |
* Save a template (insert or update) |
| 1855 |
*/ |
| 1856 |
function botwriter_save_template($data) { |
| 1857 |
global $wpdb; |
| 1858 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 1859 |
|
| 1860 |
if (!empty($data['id'])) { |
| 1861 |
// Update |
| 1862 |
return $wpdb->update($table_name, [ |
| 1863 |
'name' => sanitize_text_field($data['name']), |
| 1864 |
'content' => wp_kses_post($data['content']) |
| 1865 |
], ['id' => intval($data['id'])]); |
| 1866 |
} else { |
| 1867 |
// Insert |
| 1868 |
return $wpdb->insert($table_name, [ |
| 1869 |
'name' => sanitize_text_field($data['name']), |
| 1870 |
'content' => wp_kses_post($data['content']), |
| 1871 |
'is_default' => 0 |
| 1872 |
]); |
| 1873 |
} |
| 1874 |
} |
| 1875 |
|
| 1876 |
/** |
| 1877 |
* Set a template as default (unset other defaults) |
| 1878 |
*/ |
| 1879 |
function botwriter_set_default_template($template_id) { |
| 1880 |
global $wpdb; |
| 1881 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 1882 |
|
| 1883 |
// Check if template exists |
| 1884 |
$template = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $template_id)); |
| 1885 |
if (!$template) { |
| 1886 |
return false; |
| 1887 |
} |
| 1888 |
|
| 1889 |
// Remove default from all templates |
| 1890 |
$wpdb->update($table_name, ['is_default' => 0], ['is_default' => 1]); |
| 1891 |
|
| 1892 |
// Set new default |
| 1893 |
return $wpdb->update($table_name, ['is_default' => 1], ['id' => intval($template_id)]); |
| 1894 |
} |
| 1895 |
|
| 1896 |
/** |
| 1897 |
* Delete a template (cannot delete default) |
| 1898 |
*/ |
| 1899 |
function botwriter_delete_template($template_id) { |
| 1900 |
global $wpdb; |
| 1901 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 1902 |
|
| 1903 |
// Prevent deleting default template |
| 1904 |
$is_default = $wpdb->get_var($wpdb->prepare("SELECT is_default FROM $table_name WHERE id = %d", $template_id)); |
| 1905 |
if ($is_default == 1) { |
| 1906 |
return false; |
| 1907 |
} |
| 1908 |
|
| 1909 |
return $wpdb->delete($table_name, ['id' => intval($template_id)]); |
| 1910 |
} |
| 1911 |
|
| 1912 |
/** |
| 1913 |
* Build prompt from template by replacing placeholders with actual values |
| 1914 |
* Uses Mustache-like syntax: {{variable}} for simple values, {{#section}}...{{/section}} for conditionals |
| 1915 |
*/ |
| 1916 |
function botwriter_build_prompt_from_template($template_content, $data) { |
| 1917 |
global $botwriter_languages; |
| 1918 |
|
| 1919 |
// Map writer styles |
| 1920 |
$writer_styles = [ |
| 1921 |
'ai_cerebro' => '', |
| 1922 |
'orion' => '', |
| 1923 |
'cloe' => 'ironic critic, sarcastic and witty', |
| 1924 |
'lucida' => 'analytical critic, precise and direct', |
| 1925 |
'max' => 'Passionate and descriptive', |
| 1926 |
'gael' => 'Reflective, introspective and poetic', |
| 1927 |
]; |
| 1928 |
|
| 1929 |
// Prepare data for template — NOTE: this is the legacy function, |
| 1930 |
// main flow uses botwriter_build_client_prompt() below. |
| 1931 |
$writer = strtolower($data['writer'] ?? ''); |
| 1932 |
$writer_style = ''; |
| 1933 |
|
| 1934 |
if ($writer === 'custom') { |
| 1935 |
$narration = strtolower($data['narration'] ?? ''); |
| 1936 |
if ($narration === 'custom') { |
| 1937 |
$writer_style = $data['custom_style'] ?? ''; |
| 1938 |
} else { |
| 1939 |
$writer_style = $narration; |
| 1940 |
} |
| 1941 |
} elseif (isset($writer_styles[$writer])) { |
| 1942 |
$writer_style = $writer_styles[$writer]; |
| 1943 |
} |
| 1944 |
|
| 1945 |
// Get language name from code |
| 1946 |
$post_language_code = $data['post_language'] ?? 'en'; |
| 1947 |
$post_language = $botwriter_languages[$post_language_code] ?? 'English'; |
| 1948 |
|
| 1949 |
// Post length |
| 1950 |
$post_length = $data['post_length'] ?? '800'; |
| 1951 |
if (!is_numeric($post_length)) { |
| 1952 |
$post_length = 800; |
| 1953 |
} |
| 1954 |
$post_length = min(intval($post_length), 4000); |
| 1955 |
|
| 1956 |
// Build replacements array |
| 1957 |
// Note: source_content, existing_titles, title_prompt, content_prompt are handled server-side |
| 1958 |
$replacements = [ |
| 1959 |
'post_length' => $post_length, |
| 1960 |
'post_language' => $post_language, |
| 1961 |
'writer_style' => $writer_style, |
| 1962 |
'prompt_or_keywords' => $data['ai_keywords'] ?? '', |
| 1963 |
]; |
| 1964 |
|
| 1965 |
$prompt = $template_content; |
| 1966 |
|
| 1967 |
// Replace variables: {{variable}} |
| 1968 |
foreach ($replacements as $key => $value) { |
| 1969 |
$prompt = str_replace('{{' . $key . '}}', $value, $prompt); |
| 1970 |
} |
| 1971 |
|
| 1972 |
// Clean up short instruction lines left empty after variable replacement |
| 1973 |
// e.g. "-Narrative style: " or "-Topic: " when the value is empty |
| 1974 |
// Never remove lines containing ENDARTICLE or other source markers |
| 1975 |
$lines = explode("\n", $prompt); |
| 1976 |
$cleaned_lines = []; |
| 1977 |
foreach ($lines as $line) { |
| 1978 |
$trimmed = trim($line); |
| 1979 |
if (preg_match('/^-[^:]+:\s*$/', $trimmed) && strpos($trimmed, 'ENDARTICLE') === false && strlen($trimmed) < 40) { |
| 1980 |
continue; |
| 1981 |
} |
| 1982 |
$cleaned_lines[] = $line; |
| 1983 |
} |
| 1984 |
$prompt = implode("\n", $cleaned_lines); |
| 1985 |
|
| 1986 |
// Clean up multiple blank lines |
| 1987 |
$prompt = preg_replace('/\n{3,}/', "\n\n", $prompt); |
| 1988 |
$prompt = trim($prompt); |
| 1989 |
|
| 1990 |
return $prompt; |
| 1991 |
} |
| 1992 |
|
| 1993 |
/** |
| 1994 |
* Build the complete prompt on the client side using the template system |
| 1995 |
* For types that require external content (wordpress, rss, news), |
| 1996 |
* the server will add the source content to the prompt |
| 1997 |
*/ |
| 1998 |
function botwriter_build_client_prompt($data) { |
| 1999 |
global $botwriter_languages; |
| 2000 |
|
| 2001 |
// Get the template - use task-specific template if set, otherwise default |
| 2002 |
$template_id = isset($data['template_id']) && !empty($data['template_id']) ? intval($data['template_id']) : null; |
| 2003 |
$template = botwriter_get_template($template_id); |
| 2004 |
$template_content = $template['content']; |
| 2005 |
|
| 2006 |
// Map writer styles |
| 2007 |
$writer_styles = [ |
| 2008 |
'ai_cerebro' => '', |
| 2009 |
'orion' => '', |
| 2010 |
'cloe' => 'ironic critic, sarcastic and witty', |
| 2011 |
'lucida' => 'analytical critic, precise and direct', |
| 2012 |
'max' => 'Passionate and descriptive', |
| 2013 |
'gael' => 'Reflective, introspective and poetic', |
| 2014 |
]; |
| 2015 |
|
| 2016 |
// Prepare writer style |
| 2017 |
$writer = strtolower($data['writer'] ?? ''); |
| 2018 |
$writer_style = ''; |
| 2019 |
|
| 2020 |
if ($writer === 'custom') { |
| 2021 |
$narration = strtolower($data['narration'] ?? ''); |
| 2022 |
if ($narration === 'custom') { |
| 2023 |
$writer_style = $data['custom_style'] ?? ''; |
| 2024 |
} else { |
| 2025 |
$writer_style = $narration; |
| 2026 |
} |
| 2027 |
} elseif (isset($writer_styles[$writer])) { |
| 2028 |
$writer_style = $writer_styles[$writer]; |
| 2029 |
} |
| 2030 |
|
| 2031 |
// Get language name from code |
| 2032 |
$post_language_code = $data['post_language'] ?? 'en'; |
| 2033 |
$post_language = $botwriter_languages[$post_language_code] ?? 'English'; |
| 2034 |
|
| 2035 |
// Post length |
| 2036 |
$post_length = $data['post_length'] ?? '800'; |
| 2037 |
if (!is_numeric($post_length)) { |
| 2038 |
$post_length = 800; |
| 2039 |
} |
| 2040 |
$post_length = min(intval($post_length), 4000); |
| 2041 |
|
| 2042 |
// Determine what content to include based on website_type |
| 2043 |
$website_type = $data['website_type'] ?? ''; |
| 2044 |
$source_title = ''; |
| 2045 |
$source_content = ''; |
| 2046 |
$ai_keywords = ''; |
| 2047 |
$existing_titles = ''; |
| 2048 |
$title_prompt = ''; |
| 2049 |
$content_prompt = ''; |
| 2050 |
|
| 2051 |
switch ($website_type) { |
| 2052 |
case 'ai': |
| 2053 |
case '': |
| 2054 |
// AI mode: use keywords and avoid existing titles |
| 2055 |
$ai_keywords = $data['ai_keywords'] ?? ''; |
| 2056 |
$existing_titles = $data['titles'] ?? ''; |
| 2057 |
break; |
| 2058 |
|
| 2059 |
case 'super2': |
| 2060 |
// Super2: use title_prompt and content_prompt from outline |
| 2061 |
$title_prompt = $data['title_prompt'] ?? ''; |
| 2062 |
$content_prompt = $data['content_prompt'] ?? ''; |
| 2063 |
break; |
| 2064 |
|
| 2065 |
case 'rss': |
| 2066 |
// RSS content is now pre-fetched on the client side |
| 2067 |
// Data is populated by botwriter_send1_data_to_server before calling this function |
| 2068 |
$source_title = $data['source_title'] ?? ''; |
| 2069 |
$source_content = $data['source_content'] ?? ''; |
| 2070 |
break; |
| 2071 |
|
| 2072 |
case 'wordpress': |
| 2073 |
// WordPress content is now pre-fetched on the client side |
| 2074 |
// Data is populated by botwriter_send1_data_to_server before calling this function |
| 2075 |
$source_title = $data['source_title'] ?? ''; |
| 2076 |
$source_content = $data['source_content'] ?? ''; |
| 2077 |
break; |
| 2078 |
|
| 2079 |
case 'news': |
| 2080 |
// News still requires server-side content fetching |
| 2081 |
// We leave source_title and source_content empty, server will fill them |
| 2082 |
break; |
| 2083 |
} |
| 2084 |
|
| 2085 |
// Build replacements array |
| 2086 |
$replacements = [ |
| 2087 |
'post_length' => $post_length, |
| 2088 |
'post_language' => $post_language, |
| 2089 |
'writer_style' => $writer_style, |
| 2090 |
'source_title' => $source_title, |
| 2091 |
'source_content' => $source_content, |
| 2092 |
'ai_keywords' => $ai_keywords, |
| 2093 |
'prompt_or_keywords' => $ai_keywords, // Alias for templates |
| 2094 |
'existing_titles' => $existing_titles, |
| 2095 |
'title_prompt' => $title_prompt, |
| 2096 |
'content_prompt' => $content_prompt, |
| 2097 |
]; |
| 2098 |
|
| 2099 |
$prompt = $template_content; |
| 2100 |
|
| 2101 |
botwriter_log('PROMPT BUILD: before source embed', [ |
| 2102 |
'website_type' => $website_type, |
| 2103 |
'source_title_empty' => empty($source_title), |
| 2104 |
'source_title' => mb_substr($source_title, 0, 100), |
| 2105 |
'source_content_len' => strlen($source_content), |
| 2106 |
'template_len' => strlen($template_content), |
| 2107 |
]); |
| 2108 |
|
| 2109 |
// For RSS/WordPress: embed source content directly (already pre-fetched on client) |
| 2110 |
if (in_array($website_type, ['rss', 'wordpress']) && !empty($source_title)) { |
| 2111 |
$prompt .= "\n\n-Based on this news article (I indicate the end with the word ENDARTICLE):\n\n" . $source_title . "\n" . $source_content . "\n\nENDARTICLE:\n"; |
| 2112 |
botwriter_log('PROMPT BUILD: ENDARTICLE block appended', [ |
| 2113 |
'prompt_len_after' => strlen($prompt), |
| 2114 |
]); |
| 2115 |
} else { |
| 2116 |
botwriter_log('PROMPT BUILD: ENDARTICLE block NOT appended', [ |
| 2117 |
'reason' => !in_array($website_type, ['rss', 'wordpress']) ? 'type not rss/wordpress' : 'source_title is empty', |
| 2118 |
]); |
| 2119 |
} |
| 2120 |
// For Super2: embed title and content instructions from the outline |
| 2121 |
if ($website_type === 'super2') { |
| 2122 |
// Rewrite instructions go BEFORE the ENDARTICLE block (rewriter / siterewriter tasks) |
| 2123 |
$rewrite_prompt = $data['rewrite_prompt'] ?? ''; |
| 2124 |
if (!empty($rewrite_prompt)) { |
| 2125 |
$prompt .= "\n-" . $rewrite_prompt; |
| 2126 |
} |
| 2127 |
if (!empty($title_prompt)) { |
| 2128 |
$prompt .= "\n-The article title must be: " . $title_prompt; |
| 2129 |
} |
| 2130 |
if (!empty($content_prompt)) { |
| 2131 |
$prompt .= "\n\n-Based on the following content (I indicate the end with the word ENDARTICLE):\n\n" . $content_prompt . "\n\nENDARTICLE\n"; |
| 2132 |
} |
| 2133 |
botwriter_log('PROMPT BUILD: super2 title/content appended', [ |
| 2134 |
'title_prompt' => mb_substr($title_prompt, 0, 100), |
| 2135 |
'content_prompt_len' => strlen($content_prompt), |
| 2136 |
'has_rewrite_prompt' => !empty($rewrite_prompt), |
| 2137 |
]); |
| 2138 |
} |
| 2139 |
// For News: server still needs to fetch content |
| 2140 |
if ($website_type === 'news') { |
| 2141 |
$prompt .= "\n\n{{SERVER_SOURCE_CONTENT}}"; |
| 2142 |
} |
| 2143 |
|
| 2144 |
// Replace variables: {{variable}} |
| 2145 |
foreach ($replacements as $key => $value) { |
| 2146 |
$prompt = str_replace('{{' . $key . '}}', $value, $prompt); |
| 2147 |
} |
| 2148 |
|
| 2149 |
$prompt_before_clean = $prompt; |
| 2150 |
botwriter_log('PROMPT BUILD: BEFORE cleanup', [ |
| 2151 |
'prompt_len' => strlen($prompt), |
| 2152 |
'contains_ENDARTICLE' => (strpos($prompt, 'ENDARTICLE') !== false), |
| 2153 |
'contains_Based_on' => (strpos($prompt, 'Based on this') !== false), |
| 2154 |
'first_300_chars' => mb_substr($prompt, 0, 300), |
| 2155 |
'last_300_chars' => mb_substr($prompt, -300), |
| 2156 |
]); |
| 2157 |
|
| 2158 |
// Clean up lines that have empty placeholders (lines ending with : or empty after replacement) |
| 2159 |
$lines = explode("\n", $prompt); |
| 2160 |
$cleaned_lines = []; |
| 2161 |
$removed_lines = []; |
| 2162 |
foreach ($lines as $line) { |
| 2163 |
$trimmed = trim($line); |
| 2164 |
// Skip short instruction lines left empty after variable replacement |
| 2165 |
// e.g. "-Narrative style: " or "-Topic: " — never remove ENDARTICLE marker |
| 2166 |
if (preg_match('/^-[^:]+:\s*$/', $trimmed) && strpos($trimmed, 'ENDARTICLE') === false && strlen($trimmed) < 40) { |
| 2167 |
$removed_lines[] = $trimmed . ' (len=' . strlen($trimmed) . ')'; |
| 2168 |
continue; |
| 2169 |
} |
| 2170 |
$cleaned_lines[] = $line; |
| 2171 |
} |
| 2172 |
$prompt = implode("\n", $cleaned_lines); |
| 2173 |
|
| 2174 |
if (!empty($removed_lines)) { |
| 2175 |
botwriter_log('PROMPT BUILD: lines REMOVED by cleanup', [ |
| 2176 |
'count' => count($removed_lines), |
| 2177 |
'lines' => $removed_lines, |
| 2178 |
]); |
| 2179 |
} |
| 2180 |
|
| 2181 |
// Clean up multiple blank lines |
| 2182 |
$prompt = preg_replace('/\n{3,}/', "\n\n", $prompt); |
| 2183 |
$prompt = trim($prompt); |
| 2184 |
|
| 2185 |
botwriter_log('PROMPT BUILD: AFTER cleanup (final)', [ |
| 2186 |
'prompt_len' => strlen($prompt), |
| 2187 |
'contains_ENDARTICLE' => (strpos($prompt, 'ENDARTICLE') !== false), |
| 2188 |
'contains_Based_on' => (strpos($prompt, 'Based on this') !== false), |
| 2189 |
'first_300_chars' => mb_substr($prompt, 0, 300), |
| 2190 |
'last_300_chars' => mb_substr($prompt, -300), |
| 2191 |
]); |
| 2192 |
|
| 2193 |
return $prompt; |
| 2194 |
} |
| 2195 |
|
| 2196 |
|
| 2197 |
|
| 2198 |
// Extending class |
| 2199 |
class botwriter_tasks_Table extends WP_List_Table |
| 2200 |
{ |
| 2201 |
// Define table columns |
| 2202 |
function get_columns() |
| 2203 |
{ |
| 2204 |
$columns = array( |
| 2205 |
'cb' => '<input type="checkbox" />', |
| 2206 |
'writer' => __('Writer', 'botwriter'), |
| 2207 |
'task_name' => __('Task Name', 'botwriter'), |
| 2208 |
'days' => __('Days', 'botwriter'), |
| 2209 |
'times_per_day' => __('Times per Day', 'botwriter'), |
| 2210 |
'type' => __('Type', 'botwriter'), |
| 2211 |
'status' => __('Status', 'botwriter') |
| 2212 |
|
| 2213 |
); |
| 2214 |
return $columns; |
| 2215 |
} |
| 2216 |
|
| 2217 |
|
| 2218 |
// define $table_data property |
| 2219 |
private $table_data; |
| 2220 |
|
| 2221 |
// Bind table with columns, data and all |
| 2222 |
function prepare_items() |
| 2223 |
{ |
| 2224 |
//data |
| 2225 |
if ( isset( $_POST['s'] ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'botwriter_nonce' ) ) { |
| 2226 |
$search_query = sanitize_text_field(wp_unslash($_POST['s'])); |
| 2227 |
$this->table_data = $this->get_table_data($search_query); |
| 2228 |
} else { |
| 2229 |
$this->table_data = $this->get_table_data(); |
| 2230 |
} |
| 2231 |
|
| 2232 |
|
| 2233 |
$columns = $this->get_columns(); |
| 2234 |
$hidden = ( is_array(get_user_meta( get_current_user_id(), 'managetoplevel_page_list_tablecolumnshidden', true)) ) ? get_user_meta( get_current_user_id(), 'managetoplevel_page_list_tablecolumnshidden', true) : array(); |
| 2235 |
$sortable = $this->get_sortable_columns(); |
| 2236 |
$primary = 'name'; |
| 2237 |
$this->_column_headers = array($columns, $hidden, $sortable, $primary); |
| 2238 |
$this->process_bulk_action(); |
| 2239 |
$this->table_data = $this->get_table_data(); |
| 2240 |
|
| 2241 |
usort($this->table_data, array($this, 'usort_reorder')); |
| 2242 |
|
| 2243 |
/* pagination */ |
| 2244 |
$per_page = $this->get_items_per_page('elements_per_page', 10); |
| 2245 |
$current_page = $this->get_pagenum(); |
| 2246 |
$total_items = count($this->table_data); |
| 2247 |
|
| 2248 |
$this->table_data = array_slice($this->table_data, (($current_page - 1) * $per_page), $per_page); |
| 2249 |
|
| 2250 |
$this->set_pagination_args(array( |
| 2251 |
'total_items' => $total_items, // total number of items |
| 2252 |
'per_page' => $per_page, // items to show on a page |
| 2253 |
'total_pages' => ceil( $total_items / $per_page ) // use ceil to round up |
| 2254 |
)); |
| 2255 |
|
| 2256 |
$this->items = $this->table_data; |
| 2257 |
} |
| 2258 |
|
| 2259 |
|
| 2260 |
|
| 2261 |
function column_task_name($item){ |
| 2262 |
$slug='botwriter_automatic_post_new'; |
| 2263 |
$page = isset($_REQUEST['page']) ? sanitize_text_field(wp_unslash($_REQUEST['page'])) : ''; |
| 2264 |
|
| 2265 |
if ($item["website_type"] == 'super2') { |
| 2266 |
$slug='botwriter_super_page'; |
| 2267 |
$url_edit= wp_nonce_url('?page=' . $slug . '&id=' . $item['id'], "botwriter_tasks_action"); |
| 2268 |
} else { |
| 2269 |
$url_edit= wp_nonce_url('?page=' . $slug . '&id=' . $item['id'], "botwriter_tasks_action"); |
| 2270 |
} |
| 2271 |
|
| 2272 |
$url_delete= wp_nonce_url('?page=' . $page . '&action=delete&id=' . $item['id'], "botwriter_tasks_action"); |
| 2273 |
|
| 2274 |
$actions = array( |
| 2275 |
'edit' => sprintf('<a href="%s">%s</a>', $url_edit, __('Edit', 'botwriter')), |
| 2276 |
'delete' => sprintf('<a href="%s">%s</a>', $url_delete, __('Delete', 'botwriter')), |
| 2277 |
); |
| 2278 |
|
| 2279 |
$id=$item['id']; |
| 2280 |
return sprintf('%s %s', |
| 2281 |
"<a class='row-title' href='?page=$slug&id=$id&_wpnonce=" . wp_create_nonce('botwriter_tasks_action') . "'>" . $item['task_name'] . "</a>", |
| 2282 |
$this->row_actions($actions) |
| 2283 |
); |
| 2284 |
} |
| 2285 |
|
| 2286 |
function column_writer($item){ |
| 2287 |
$dir_images_writers = plugin_dir_url(__FILE__) . 'assets/images/writers/'; |
| 2288 |
$writer=$item['writer']; |
| 2289 |
$writer = strtolower($writer); |
| 2290 |
|
| 2291 |
$slug='botwriter_automatic_post_new'; |
| 2292 |
$id=$item['id']; |
| 2293 |
$link="<a class='row-title' href='?page=$slug&id=$id'>"; |
| 2294 |
$img= '<img src="' . esc_url($dir_images_writers . $writer . '.jpeg') . '" alt="' . esc_attr($writer) . '" class="writer-photo">'; |
| 2295 |
return $link . $img . '</a>'; |
| 2296 |
|
| 2297 |
} |
| 2298 |
|
| 2299 |
|
| 2300 |
|
| 2301 |
function column_status($item){ |
| 2302 |
|
| 2303 |
$status = $item['status']; |
| 2304 |
$status_opuesto = $status ? 0 : 1; |
| 2305 |
$icono = $status ? 'dashicons-yes' : 'dashicons-dismiss'; |
| 2306 |
$texto_status = $status ? 'Desactivate' : 'Activate'; |
| 2307 |
|
| 2308 |
return sprintf( |
| 2309 |
'<a href="#" class="icono-status dashicons %s" data-id="%d" data-status="%d" title="%s"></a>', |
| 2310 |
$icono, |
| 2311 |
$item['id'], |
| 2312 |
$status_opuesto, |
| 2313 |
$texto_status |
| 2314 |
); |
| 2315 |
|
| 2316 |
|
| 2317 |
} |
| 2318 |
|
| 2319 |
/* |
| 2320 |
function column_category_id($item) { |
| 2321 |
$categories = get_categories(); |
| 2322 |
$category_name = ''; |
| 2323 |
foreach ($categories as $category) { |
| 2324 |
$aux_cateforias=explode(',',$item['category_id']); |
| 2325 |
if (in_array($category->term_id, $aux_cateforias)) { |
| 2326 |
$category_name .= $category->name . ', '; |
| 2327 |
} |
| 2328 |
|
| 2329 |
} |
| 2330 |
$category_name = rtrim($category_name, ', '); |
| 2331 |
return $category_name; |
| 2332 |
} |
| 2333 |
*/ |
| 2334 |
|
| 2335 |
|
| 2336 |
|
| 2337 |
|
| 2338 |
|
| 2339 |
// To show bulk action dropdown |
| 2340 |
function get_bulk_actions() |
| 2341 |
{ |
| 2342 |
$actions = array( |
| 2343 |
'delete_all' => __('Delete', 'botwriter'), |
| 2344 |
|
| 2345 |
); |
| 2346 |
return $actions; |
| 2347 |
} |
| 2348 |
|
| 2349 |
function process_bulk_action() |
| 2350 |
{ |
| 2351 |
// Verify user has permission |
| 2352 |
if (!current_user_can('manage_options')) { |
| 2353 |
return; |
| 2354 |
} |
| 2355 |
|
| 2356 |
// Verify nonce |
| 2357 |
if (!isset($_REQUEST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])), "botwriter_tasks_action")) { |
| 2358 |
return; |
| 2359 |
} |
| 2360 |
|
| 2361 |
global $wpdb; |
| 2362 |
|
| 2363 |
$table = $wpdb->prefix . 'botwriter_tasks'; |
| 2364 |
|
| 2365 |
if ('delete_all' === $this->current_action() || ('delete' === $this->current_action() && isset($_REQUEST['id']))) { |
| 2366 |
$request_id = isset($_REQUEST['id']) ? array_map('absint', (array) $_REQUEST['id']) : array(); |
| 2367 |
|
| 2368 |
if (!empty($request_id)) { |
| 2369 |
// Prepare the DELETE query with proper escaping |
| 2370 |
$placeholders = implode(',', array_fill(0, count($request_id), '%d')); |
| 2371 |
$wpdb->query($wpdb->prepare("DELETE FROM {$table} WHERE id IN({$placeholders})", $request_id)); |
| 2372 |
} |
| 2373 |
} |
| 2374 |
} |
| 2375 |
|
| 2376 |
|
| 2377 |
|
| 2378 |
// Get table data |
| 2379 |
private function get_table_data( $search = '' ) { |
| 2380 |
global $wpdb; |
| 2381 |
|
| 2382 |
$table = $wpdb->prefix."botwriter_tasks"; |
| 2383 |
|
| 2384 |
|
| 2385 |
if ( ! empty( $search ) ) { |
| 2386 |
$prepared_search = $wpdb->esc_like( $search ); |
| 2387 |
$prepared_search = '%' . $wpdb->esc_like( $search ) . '%'; |
| 2388 |
|
| 2389 |
return $wpdb->get_results( |
| 2390 |
$wpdb->prepare( |
| 2391 |
"SELECT * FROM {$table} WHERE name LIKE %s AND (task_type IS NULL OR task_type <> %s)", |
| 2392 |
$prepared_search, |
| 2393 |
'writenow' |
| 2394 |
), |
| 2395 |
ARRAY_A |
| 2396 |
); |
| 2397 |
} else { |
| 2398 |
return $wpdb->get_results( |
| 2399 |
$wpdb->prepare( |
| 2400 |
"SELECT * FROM {$table} WHERE (task_type IS NULL OR task_type <> %s)", |
| 2401 |
'writenow' |
| 2402 |
), |
| 2403 |
ARRAY_A |
| 2404 |
); |
| 2405 |
} |
| 2406 |
} |
| 2407 |
|
| 2408 |
function column_default($item, $column_name) |
| 2409 |
{ |
| 2410 |
|
| 2411 |
switch ($column_name) { |
| 2412 |
case 'id': |
| 2413 |
case 'website_type': |
| 2414 |
case 'website_name': |
| 2415 |
case 'task_name': |
| 2416 |
case 'category_id': |
| 2417 |
case 'website_category_id': |
| 2418 |
default: |
| 2419 |
return $item[$column_name]; |
| 2420 |
} |
| 2421 |
} |
| 2422 |
|
| 2423 |
// Render the combined Type column: website_type + task_type |
| 2424 |
function column_type($item) { |
| 2425 |
$parts = array(); |
| 2426 |
if (!empty($item['website_type'])) { |
| 2427 |
$parts[] = sanitize_text_field($item['website_type']); |
| 2428 |
} |
| 2429 |
if (!empty($item['task_type'])) { |
| 2430 |
$parts[] = sanitize_text_field($item['task_type']); |
| 2431 |
} |
| 2432 |
$label = !empty($parts) ? implode(' / ', $parts) : __('—', 'botwriter'); |
| 2433 |
return esc_html($label); |
| 2434 |
} |
| 2435 |
|
| 2436 |
function column_cb($item){ |
| 2437 |
return sprintf( |
| 2438 |
'<input type="checkbox" name="id[]" value="%s" />', |
| 2439 |
$item['id'] |
| 2440 |
); |
| 2441 |
} |
| 2442 |
|
| 2443 |
public function get_sortable_columns(){ |
| 2444 |
$sortable_columns = array( |
| 2445 |
'task_name' => array('task_name', false), |
| 2446 |
'days' => array('days', false), |
| 2447 |
'id' => array('id', true) |
| 2448 |
); |
| 2449 |
return $sortable_columns; |
| 2450 |
} |
| 2451 |
|
| 2452 |
// Sorting function |
| 2453 |
function usort_reorder($a, $b) |
| 2454 |
{ |
| 2455 |
// If no sort, default to task_name |
| 2456 |
$sanitized_orderby = isset($_GET['orderby']) ? sanitize_text_field(wp_unslash($_GET['orderby'])) : ''; |
| 2457 |
|
| 2458 |
$orderby = (!empty($sanitized_orderby)) ? $sanitized_orderby : 'task_name'; |
| 2459 |
|
| 2460 |
$order = isset($_GET['order']) ? sanitize_text_field(wp_unslash($_GET['order'])) : 'asc'; |
| 2461 |
|
| 2462 |
// filtrar order solo asd o desc |
| 2463 |
$order = in_array($order, array('asc', 'desc')) ? $order : 'asc'; |
| 2464 |
// filter orderby only allowed columns |
| 2465 |
$orderby = in_array($orderby, array('task_name', 'days', 'id')) ? $orderby : 'task_name'; |
| 2466 |
|
| 2467 |
|
| 2468 |
|
| 2469 |
|
| 2470 |
// Determine sort order |
| 2471 |
$result = strcmp($a[$orderby], $b[$orderby]); |
| 2472 |
|
| 2473 |
// Send final sort direction to usort |
| 2474 |
return ($order === 'asc') ? $result : -$result; |
| 2475 |
} |
| 2476 |
|
| 2477 |
} // end class botwriter_tasks_Table |
| 2478 |
|
| 2479 |
|
| 2480 |
|
| 2481 |
|
| 2482 |
|
| 2483 |
|
| 2484 |
function botwriter_validate_website($item,$is_manual = false) |
| 2485 |
{ |
| 2486 |
|
| 2487 |
$messages = array(); |
| 2488 |
|
| 2489 |
|
| 2490 |
if (empty($item['task_name'])) $messages[] = __('Task Name is required', 'botwriter'); |
| 2491 |
if (empty($item['website_type'])) $messages[] = __('Website Type is required', 'botwriter'); |
| 2492 |
|
| 2493 |
// Category/taxonomy validation: require category_id for 'post' type, or taxonomy_data for other types |
| 2494 |
$post_type = isset($item['post_type']) ? $item['post_type'] : 'post'; |
| 2495 |
if ($post_type === 'post') { |
| 2496 |
if (empty($item['category_id']) && empty($item['taxonomy_data'])) { |
| 2497 |
$messages[] = __('Category is required', 'botwriter'); |
| 2498 |
} |
| 2499 |
} |
| 2500 |
// For other post types, taxonomy selection is optional |
| 2501 |
|
| 2502 |
|
| 2503 |
if($item['website_type'] == 'wordpress'){ |
| 2504 |
if (empty($item['domain_name'])) { |
| 2505 |
$messages[] = __('Domain Name is required', 'botwriter'); |
| 2506 |
} |
| 2507 |
if( !botwriter_isValidDomain(sanitize_text_field($item['domain_name'])) ){ |
| 2508 |
$messages[] = __('Domain name should be valid.', 'botwriter'); |
| 2509 |
} |
| 2510 |
} |
| 2511 |
|
| 2512 |
if ($item['website_type'] == 'rss') { |
| 2513 |
if (empty($item['rss_source'])) { |
| 2514 |
$messages[] = __('RSS Source is required', 'botwriter'); |
| 2515 |
} |
| 2516 |
} |
| 2517 |
|
| 2518 |
if ($item['website_type'] == 'news') { |
| 2519 |
if (empty($item['news_keyword'])) { |
| 2520 |
$messages[] = __('News keyword is required', 'botwriter'); |
| 2521 |
} |
| 2522 |
} |
| 2523 |
|
| 2524 |
if (empty($messages)) return true; |
| 2525 |
return implode('<br />', $messages); |
| 2526 |
} |
| 2527 |
|
| 2528 |
|
| 2529 |
function botwriter_isValidDomain($domain) { |
| 2530 |
// WordPress wp_http_validate_url |
| 2531 |
$valid_url = wp_http_validate_url( $domain); |
| 2532 |
|
| 2533 |
if (!is_wp_error($valid_url)) { |
| 2534 |
return true; |
| 2535 |
} else { |
| 2536 |
return false; |
| 2537 |
} |
| 2538 |
} |
| 2539 |
|
| 2540 |
|
| 2541 |
function botwriter_is_site_working($site_url, $site_type) { |
| 2542 |
$response = false; |
| 2543 |
|
| 2544 |
if ($site_type === 'wordpress') { |
| 2545 |
// Check if the WordPress REST API is accessible |
| 2546 |
$api_url = rtrim($site_url, '/') . '/wp-json/wp/v2/posts'; |
| 2547 |
$headers = @get_headers($api_url); |
| 2548 |
if ($headers && strpos((string)$headers[0], '200') !== false) { |
| 2549 |
$response = true; |
| 2550 |
} |
| 2551 |
} elseif ($site_type === 'rss') { |
| 2552 |
// Check if the RSS feed is accessible |
| 2553 |
$rss = @simplexml_load_file($site_url); |
| 2554 |
if ($rss) { |
| 2555 |
$response = true; |
| 2556 |
} |
| 2557 |
} |
| 2558 |
|
| 2559 |
return $response; |
| 2560 |
} |
| 2561 |
|
| 2562 |
|
| 2563 |
//wp-cron: |
| 2564 |
|
| 2565 |
|
| 2566 |
// Add a custom schedule for cron jobs |
| 2567 |
|
| 2568 |
function botwriter_add_custom_cron_schedule($schedules) { |
| 2569 |
if (!isset($schedules['every_30'])) { |
| 2570 |
$schedules['every_30'] = array( |
| 2571 |
'interval' => 30, // 30 seconds |
| 2572 |
'display' => __('Every thirty seconds', 'botwriter') |
| 2573 |
); |
| 2574 |
} |
| 2575 |
return $schedules; |
| 2576 |
} |
| 2577 |
add_filter('cron_schedules', 'botwriter_add_custom_cron_schedule'); |
| 2578 |
|
| 2579 |
// Ensure cron is scheduled on admin load (in case activation hook didn't run) |
| 2580 |
function botwriter_ensure_cron_scheduled() { |
| 2581 |
if (get_option('botwriter_cron_active') === '0') { |
| 2582 |
return; |
| 2583 |
} |
| 2584 |
|
| 2585 |
if (!wp_next_scheduled('botwriter_scheduled_events_plugin_cron')) { |
| 2586 |
$scheduled = wp_schedule_event(time() + 30, 'every_30', 'botwriter_scheduled_events_plugin_cron'); |
| 2587 |
botwriter_log('Cron scheduled (admin init)', [ |
| 2588 |
'scheduled' => $scheduled ? 'yes' : 'no', |
| 2589 |
]); |
| 2590 |
} |
| 2591 |
} |
| 2592 |
add_action('admin_init', 'botwriter_ensure_cron_scheduled'); |
| 2593 |
|
| 2594 |
// Schedule the cron job during plugin activation |
| 2595 |
function botwriter_scheduled_events_plugin_activate() { |
| 2596 |
if (get_option('botwriter_cron_active')=="0") { |
| 2597 |
return; |
| 2598 |
} |
| 2599 |
if (!wp_next_scheduled('botwriter_scheduled_events_plugin_cron')) { |
| 2600 |
wp_schedule_event(time(), 'every_30', 'botwriter_scheduled_events_plugin_cron'); |
| 2601 |
} |
| 2602 |
} |
| 2603 |
register_activation_hook(__FILE__, 'botwriter_scheduled_events_plugin_activate'); |
| 2604 |
|
| 2605 |
// Register the cron task |
| 2606 |
add_action('botwriter_scheduled_events_plugin_cron', 'botwriter_scheduled_events_execute_tasks'); |
| 2607 |
|
| 2608 |
|
| 2609 |
|
| 2610 |
|
| 2611 |
// Clear the cron job upon plugin deactivation |
| 2612 |
function botwriter_scheduled_events_plugin_deactivate() { |
| 2613 |
wp_clear_scheduled_hook('botwriter_scheduled_events_plugin_cron'); |
| 2614 |
} |
| 2615 |
register_deactivation_hook(__FILE__, 'botwriter_scheduled_events_plugin_deactivate'); |
| 2616 |
|
| 2617 |
|
| 2618 |
|
| 2619 |
|
| 2620 |
function botwriter_scheduled_events_execute_tasks() { |
| 2621 |
global $wpdb; |
| 2622 |
$table_name_tasks = $wpdb->prefix . 'botwriter_tasks'; |
| 2623 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 2624 |
$table_name_super = $wpdb->prefix . 'botwriter_super'; |
| 2625 |
|
| 2626 |
// ── Prevent overlapping cron runs (race condition guard) ── |
| 2627 |
// Use a transient lock so two cron ticks cannot run simultaneously. |
| 2628 |
// Lock expires after 120 seconds as a safety net. |
| 2629 |
$lock_key = 'botwriter_cron_lock'; |
| 2630 |
if (get_transient($lock_key)) { |
| 2631 |
botwriter_log('CRON SKIPPED — another cron run is still in progress'); |
| 2632 |
return; |
| 2633 |
} |
| 2634 |
set_transient($lock_key, time(), 120); |
| 2635 |
|
| 2636 |
// Check if cron is active |
| 2637 |
$cron_active = get_option('botwriter_cron_active'); |
| 2638 |
botwriter_log('=== CRON START ===', [ |
| 2639 |
'cron_active_option' => $cron_active, |
| 2640 |
'timestamp' => current_time('Y-m-d H:i:s'), |
| 2641 |
]); |
| 2642 |
|
| 2643 |
if ($cron_active !== '1') { |
| 2644 |
botwriter_log('CRON DISABLED - exiting', ['cron_active' => $cron_active]); |
| 2645 |
delete_transient($lock_key); |
| 2646 |
return; |
| 2647 |
} |
| 2648 |
|
| 2649 |
// STOPFORMANY: refuse to dispatch new tasks while the flag is active |
| 2650 |
if (get_option('botwriter_stopformany', false)) { |
| 2651 |
botwriter_log('CRON BLOCKED by STOPFORMANY — too many consecutive errors on server'); |
| 2652 |
delete_transient($lock_key); |
| 2653 |
return; |
| 2654 |
} |
| 2655 |
|
| 2656 |
// Get the current day (English name) and date based on WordPress local time |
| 2657 |
// Use DateTime with wp_timezone() to respect site timezone and keep English day name |
| 2658 |
try { |
| 2659 |
$dt = new DateTime('now', wp_timezone()); |
| 2660 |
$current_day_en = $dt->format('l'); |
| 2661 |
} catch (Exception $e) { |
| 2662 |
// Fallback, still English but GMT-based |
| 2663 |
$current_day_en = gmdate('l'); |
| 2664 |
} |
| 2665 |
$current_date = current_time('Y-m-d'); |
| 2666 |
|
| 2667 |
botwriter_log('Cron dispatcher triggered', [ |
| 2668 |
'day' => $current_day_en, |
| 2669 |
'date' => $current_date, |
| 2670 |
'timezone' => wp_timezone_string(), |
| 2671 |
]); |
| 2672 |
|
| 2673 |
// PHASE 2 |
| 2674 |
botwriter_execute_events_pass2(); |
| 2675 |
|
| 2676 |
//PHASE 1 Execute each event if it meets the conditions |
| 2677 |
// Get tasks scheduled for today and status=1 |
| 2678 |
// Exclude one-off Write now tasks from cron to avoid duplicate logs |
| 2679 |
$tasks = (array) $wpdb->get_results( |
| 2680 |
$wpdb->prepare( |
| 2681 |
"SELECT * FROM {$table_name_tasks} WHERE days LIKE %s AND status = %d AND (task_type IS NULL OR task_type <> %s)", |
| 2682 |
'%' . $wpdb->esc_like($current_day_en) . '%', |
| 2683 |
1, |
| 2684 |
'writenow' |
| 2685 |
), |
| 2686 |
ARRAY_A |
| 2687 |
); |
| 2688 |
botwriter_log('Tasks evaluated for cron tick', [ |
| 2689 |
'count' => count($tasks), |
| 2690 |
'query_day' => $current_day_en, |
| 2691 |
]); |
| 2692 |
|
| 2693 |
// Log all tasks found for debugging |
| 2694 |
if (count($tasks) === 0) { |
| 2695 |
// Check total active tasks to see if it's a day mismatch |
| 2696 |
$all_active = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name_tasks} WHERE status = 1"); |
| 2697 |
botwriter_log('NO TASKS FOUND for today', [ |
| 2698 |
'current_day' => $current_day_en, |
| 2699 |
'total_active_tasks' => $all_active, |
| 2700 |
]); |
| 2701 |
} |
| 2702 |
|
| 2703 |
foreach ($tasks as $task) { |
| 2704 |
botwriter_log('Evaluating task', [ |
| 2705 |
'task_id' => $task['id'], |
| 2706 |
'task_name' => $task['task_name'], |
| 2707 |
'days' => $task['days'], |
| 2708 |
'times_per_day' => $task['times_per_day'], |
| 2709 |
'execution_count' => $task['execution_count'], |
| 2710 |
'last_execution_date' => $task['last_execution_date'], |
| 2711 |
'last_execution_time' => $task['last_execution_time'], |
| 2712 |
'website_type' => $task['website_type'], |
| 2713 |
]); |
| 2714 |
|
| 2715 |
// Skip Write now tasks defensively (in case of legacy rows) |
| 2716 |
if (!empty($task['task_type']) && $task['task_type'] === 'writenow') { |
| 2717 |
botwriter_log('Skipping writenow task', ['task_id' => $task['id']]); |
| 2718 |
continue; |
| 2719 |
} |
| 2720 |
|
| 2721 |
// Reset execution count daily |
| 2722 |
if ($task["last_execution_date"] !== $current_date) { |
| 2723 |
$wpdb->update($table_name_tasks, ['execution_count' => 0, 'last_execution_date' => $current_date], ['id' => $task["id"]]); |
| 2724 |
$task["execution_count"] = 0; |
| 2725 |
botwriter_log('Reset execution count for new day', ['task_id' => $task['id']]); |
| 2726 |
} |
| 2727 |
|
| 2728 |
// Check if the task is a supertask and if it exists |
| 2729 |
$super_exists = true; |
| 2730 |
if ($task["website_type"] == 'super2') { //supertask |
| 2731 |
$super = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name_super WHERE id_task = %d AND (task_status IS NULL OR task_status = '')", $task["id"]), ARRAY_A); |
| 2732 |
if (!$super) { |
| 2733 |
$super_exists = false; |
| 2734 |
botwriter_log('Super2 task skipped, no pending outline', [ |
| 2735 |
'task_id' => $task['id'], |
| 2736 |
]); |
| 2737 |
} |
| 2738 |
} |
| 2739 |
|
| 2740 |
// Check if the task can still be executed based on its daily limit |
| 2741 |
if ($task["execution_count"] < $task["times_per_day"] && $super_exists) { |
| 2742 |
$last_execution_time = $task["last_execution_time"]; |
| 2743 |
$now = current_time('timestamp'); |
| 2744 |
$diff = $now - strtotime($last_execution_time); |
| 2745 |
$pause = get_option('botwriter_paused_tasks'); |
| 2746 |
$pause = is_numeric($pause) ? intval($pause) : 2; |
| 2747 |
|
| 2748 |
botwriter_log('Checking pause time', [ |
| 2749 |
'task_id' => $task['id'], |
| 2750 |
'last_execution_time' => $last_execution_time, |
| 2751 |
'now' => current_time('Y-m-d H:i:s'), |
| 2752 |
'diff_seconds' => $diff, |
| 2753 |
'pause_minutes' => $pause, |
| 2754 |
'required_seconds' => 60 * $pause, |
| 2755 |
'can_execute' => ($diff > 60 * $pause) ? 'YES' : 'NO', |
| 2756 |
]); |
| 2757 |
|
| 2758 |
if ($diff > 60 * $pause) { // |
| 2759 |
|
| 2760 |
$event = $task; |
| 2761 |
$event["task_status"] = "pending"; |
| 2762 |
$event["id_task"] = $task["id"]; |
| 2763 |
$event["intentosfase1"] = 0; |
| 2764 |
$id_log = botwriter_logs_register($event); // create log in db |
| 2765 |
$event["id"] = $id_log; |
| 2766 |
if ($task["website_type"] == 'super2') { // supertask |
| 2767 |
botwriter_log('Preparing super2 dispatch', [ |
| 2768 |
'task_id' => $task['id'], |
| 2769 |
'log_id' => $id_log, |
| 2770 |
]); |
| 2771 |
$prepared_event = botwriter_super_prepare_event($event); |
| 2772 |
if ($prepared_event === false) { |
| 2773 |
botwriter_log('Super2 preparation returned false', [ |
| 2774 |
'task_id' => $task['id'], |
| 2775 |
'log_id' => $id_log, |
| 2776 |
]); |
| 2777 |
continue; |
| 2778 |
} |
| 2779 |
$event = $prepared_event; |
| 2780 |
$id_log = botwriter_logs_register($event, $id_log); // actualizamos log con los datos de super2 |
| 2781 |
} |
| 2782 |
botwriter_log('Queueing phase 1 send', [ |
| 2783 |
'task_id' => $task['id'], |
| 2784 |
'log_id' => $id_log, |
| 2785 |
'website_type' => $task['website_type'], |
| 2786 |
]); |
| 2787 |
// Update execution count BEFORE the HTTP call to prevent |
| 2788 |
// overlapping cron ticks from creating duplicate logs. |
| 2789 |
$current_time = current_time('Y-m-d H:i:s'); |
| 2790 |
$wpdb->update($table_name_tasks, ['execution_count' => $task["execution_count"] + 1, 'last_execution_time' => $current_time], ['id' => $task["id"]]); |
| 2791 |
botwriter_send1_data_to_server((array) $event); |
| 2792 |
} else { |
| 2793 |
botwriter_log('Task paused - waiting for pause interval', [ |
| 2794 |
'task_id' => $task['id'], |
| 2795 |
'diff_seconds' => $diff, |
| 2796 |
'required_seconds' => 60 * $pause, |
| 2797 |
'remaining_seconds' => (60 * $pause) - $diff, |
| 2798 |
]); |
| 2799 |
} |
| 2800 |
} else { |
| 2801 |
if ($super_exists) { |
| 2802 |
botwriter_log('Task skipped due to daily limit reached', [ |
| 2803 |
'task_id' => $task['id'], |
| 2804 |
'execution_count' => $task['execution_count'], |
| 2805 |
'times_per_day' => $task['times_per_day'], |
| 2806 |
]); |
| 2807 |
} |
| 2808 |
} |
| 2809 |
} // end tasks |
| 2810 |
|
| 2811 |
botwriter_log('=== CRON END ===', ['timestamp' => current_time('Y-m-d H:i:s')]); |
| 2812 |
|
| 2813 |
// ── Release cron lock ── |
| 2814 |
delete_transient('botwriter_cron_lock'); |
| 2815 |
} |
| 2816 |
|
| 2817 |
function botwriter_execute_events_pass2(){ |
| 2818 |
|
| 2819 |
// check if the task is still in queue or finished |
| 2820 |
global $wpdb; |
| 2821 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 2822 |
// INQUEUE |
| 2823 |
$events2 = (array) $wpdb->get_results($wpdb->prepare("SELECT * FROM {$table_name_logs} WHERE task_status=%s", 'inqueue')); |
| 2824 |
botwriter_log('Phase 2 queue check', ['inqueue_count' => count($events2)]); |
| 2825 |
foreach ($events2 as $event) { |
| 2826 |
$event = (array) $event; |
| 2827 |
|
| 2828 |
// ── Atomically mark as 'polling' to prevent overlapping cron ticks ── |
| 2829 |
// Only update if the status is still 'inqueue'; if another cron tick |
| 2830 |
// already changed it, affected_rows will be 0 and we skip this log. |
| 2831 |
$affected = $wpdb->query($wpdb->prepare( |
| 2832 |
"UPDATE {$table_name_logs} SET task_status = 'polling' WHERE id = %d AND task_status = 'inqueue'", |
| 2833 |
$event['id'] |
| 2834 |
)); |
| 2835 |
if ($affected === 0) { |
| 2836 |
botwriter_log('Phase 2 skipped — already being polled', ['log_id' => $event['id']]); |
| 2837 |
continue; |
| 2838 |
} |
| 2839 |
|
| 2840 |
// Execute the event (send2 will set final status: completed/error/inqueue) |
| 2841 |
$result = botwriter_send2_data_to_server( (array) $event); |
| 2842 |
|
| 2843 |
// If send2 did NOT update the log status (returned false without changing it), |
| 2844 |
// restore to 'inqueue' so the next tick can retry. |
| 2845 |
if ($result === false) { |
| 2846 |
$current_status = $wpdb->get_var($wpdb->prepare( |
| 2847 |
"SELECT task_status FROM {$table_name_logs} WHERE id = %d", |
| 2848 |
$event['id'] |
| 2849 |
)); |
| 2850 |
if ($current_status === 'polling') { |
| 2851 |
$wpdb->update($table_name_logs, ['task_status' => 'inqueue'], ['id' => $event['id']]); |
| 2852 |
} |
| 2853 |
} |
| 2854 |
} // end INQUEUE |
| 2855 |
|
| 2856 |
|
| 2857 |
//IN ERROR, depending on the attempt, it is resent later or marked as finished |
| 2858 |
// Exclude 'writenow' tasks from automatic retries - they should only be retried manually |
| 2859 |
$events1 = (array) $wpdb->get_results($wpdb->prepare("SELECT * FROM {$table_name_logs} WHERE task_status=%s AND intentosfase1 < %d AND (task_type IS NULL OR task_type <> 'writenow')", 'error', 8)); |
| 2860 |
botwriter_log('Phase 1 retries fetched', ['error_count' => count($events1)]); |
| 2861 |
$intento_tiempo = array(0=>0,1=>0,2=>5,3=>10,4=>30,5=>60,6=>120,7=>240,8=>480); // minutos |
| 2862 |
foreach ($events1 as $event) { |
| 2863 |
$event = (array) $event; |
| 2864 |
// Execute the event if the time has passed |
| 2865 |
$intentosfase1 = $event["intentosfase1"]; |
| 2866 |
$tiempo = $intento_tiempo[$intentosfase1+1]; |
| 2867 |
$created_at = strtotime($event["created_at"]); |
| 2868 |
$now = current_time('timestamp'); |
| 2869 |
|
| 2870 |
$diff = $now - $created_at; |
| 2871 |
if ($diff > $tiempo * 60) { |
| 2872 |
botwriter_log('Retrying phase 1 request', [ |
| 2873 |
'log_id' => $event['id'], |
| 2874 |
'task_id' => $event['id_task'], |
| 2875 |
'attempt' => $intentosfase1 + 1, |
| 2876 |
]); |
| 2877 |
botwriter_send1_data_to_server( (array) $event); |
| 2878 |
} |
| 2879 |
|
| 2880 |
} // END LOGS IN ERROR |
| 2881 |
|
| 2882 |
|
| 2883 |
} |
| 2884 |
|
| 2885 |
|
| 2886 |
function botwriter_generate_post($data){ |
| 2887 |
// Determine post type (default to 'post' for backward compatibility) |
| 2888 |
$post_type = isset($data['post_type']) && !empty($data['post_type']) ? $data['post_type'] : 'post'; |
| 2889 |
|
| 2890 |
// Build post data array |
| 2891 |
$post_data = array( |
| 2892 |
'post_title' => $data['aigenerated_title'], |
| 2893 |
'post_content' => $data['aigenerated_content'], |
| 2894 |
'post_status' => $data['post_status'], |
| 2895 |
'post_author' => $data['author_selection'], |
| 2896 |
'post_type' => $post_type, |
| 2897 |
); |
| 2898 |
|
| 2899 |
// For 'post' type with category_id (backward compatibility) |
| 2900 |
if ($post_type === 'post' && !empty($data['category_id'])) { |
| 2901 |
$post_data['post_category'] = array_map('intval', explode(',', $data['category_id'])); |
| 2902 |
} |
| 2903 |
|
| 2904 |
// Create the post |
| 2905 |
$post_id = wp_insert_post($post_data); |
| 2906 |
|
| 2907 |
if ($post_id === 0) { |
| 2908 |
//error_log('Error creating post'); |
| 2909 |
return false; |
| 2910 |
} |
| 2911 |
|
| 2912 |
// Assign taxonomy terms from taxonomy_data (if present) |
| 2913 |
if (!empty($data['taxonomy_data'])) { |
| 2914 |
$taxonomy_data = json_decode($data['taxonomy_data'], true); |
| 2915 |
if (is_array($taxonomy_data)) { |
| 2916 |
foreach ($taxonomy_data as $taxonomy_name => $term_ids) { |
| 2917 |
if (!empty($term_ids) && taxonomy_exists($taxonomy_name)) { |
| 2918 |
$term_ids = array_map('intval', (array)$term_ids); |
| 2919 |
wp_set_object_terms($post_id, $term_ids, $taxonomy_name); |
| 2920 |
} |
| 2921 |
} |
| 2922 |
} |
| 2923 |
} |
| 2924 |
|
| 2925 |
// Add tags to the post (unless disabled in settings) |
| 2926 |
$tags_disabled = get_option('botwriter_tags_disabled', '0'); |
| 2927 |
if ($tags_disabled !== '1' && !empty($data['aigenerated_tags'])) { |
| 2928 |
$tags = explode(',', $data['aigenerated_tags']); |
| 2929 |
wp_set_post_tags($post_id, $tags); |
| 2930 |
} |
| 2931 |
|
| 2932 |
// SEO Slug Translation: translate post slug, tag slugs, and get image slug |
| 2933 |
$translated_image_slug = ''; |
| 2934 |
if (function_exists('botwriter_apply_translated_slugs')) { |
| 2935 |
$translated_image_slug = botwriter_apply_translated_slugs( |
| 2936 |
$post_id, |
| 2937 |
$data['aigenerated_title'], |
| 2938 |
$data['aigenerated_tags'] ?? '' |
| 2939 |
); |
| 2940 |
} |
| 2941 |
|
| 2942 |
// Add image to the post only if image URL is provided and images are not disabled for this task |
| 2943 |
$task_disable_images = isset($data['disable_ai_images']) ? intval($data['disable_ai_images']) : 0; |
| 2944 |
if (!empty($data['aigenerated_image']) && $task_disable_images !== 1) { |
| 2945 |
// Pass attribution data for stock photos |
| 2946 |
$image_attribution = isset($data['image_attribution']) ? $data['image_attribution'] : null; |
| 2947 |
botwriter_attach_image_to_post($post_id, $data['aigenerated_image'], $data['aigenerated_title'], $translated_image_slug, $image_attribution); |
| 2948 |
|
| 2949 |
// Handle stock photo attribution in post content (footer mode) |
| 2950 |
if (!empty($image_attribution) && is_array($image_attribution)) { |
| 2951 |
$attribution_mode = get_option('botwriter_stockphoto_attribution', 'caption'); |
| 2952 |
if ($attribution_mode === 'content_footer') { |
| 2953 |
$author = sanitize_text_field($image_attribution['author'] ?? ''); |
| 2954 |
$source = sanitize_text_field($image_attribution['source'] ?? ''); |
| 2955 |
$source_url = esc_url($image_attribution['source_url'] ?? ''); |
| 2956 |
$author_url = esc_url($image_attribution['author_url'] ?? ''); |
| 2957 |
|
| 2958 |
if ($author || $source) { |
| 2959 |
$credit_parts = array(); |
| 2960 |
if ($author) { |
| 2961 |
$credit_parts[] = $author_url |
| 2962 |
? sprintf('<a href="%s" rel="nofollow noopener" target="_blank">%s</a>', $author_url, esc_html($author)) |
| 2963 |
: esc_html($author); |
| 2964 |
} |
| 2965 |
if ($source) { |
| 2966 |
$credit_parts[] = $source_url |
| 2967 |
? sprintf('<a href="%s" rel="nofollow noopener" target="_blank">%s</a>', $source_url, esc_html($source)) |
| 2968 |
: esc_html($source); |
| 2969 |
} |
| 2970 |
$credit_html = '<p class="botwriter-image-attribution"><small>' |
| 2971 |
. sprintf( |
| 2972 |
/* translators: %s: attribution credit (author / source) */ |
| 2973 |
esc_html__('Photo by %s', 'botwriter'), |
| 2974 |
implode(' / ', $credit_parts) |
| 2975 |
) |
| 2976 |
. '</small></p>'; |
| 2977 |
|
| 2978 |
wp_update_post(array( |
| 2979 |
'ID' => $post_id, |
| 2980 |
'post_content' => get_post_field('post_content', $post_id) . "\n" . $credit_html, |
| 2981 |
)); |
| 2982 |
} |
| 2983 |
} |
| 2984 |
} |
| 2985 |
} else { |
| 2986 |
$skip_reason = ''; |
| 2987 |
if (empty($data['aigenerated_image'])) { |
| 2988 |
$skip_reason = 'No image URL provided'; |
| 2989 |
} elseif ($task_disable_images === 1) { |
| 2990 |
$skip_reason = 'AI images disabled for this task'; |
| 2991 |
} |
| 2992 |
|
| 2993 |
botwriter_log('Image attachment skipped during post creation', [ |
| 2994 |
'post_id' => $post_id, |
| 2995 |
'post_title' => $data['aigenerated_title'], |
| 2996 |
'image_url' => $data['aigenerated_image'] ?? 'not provided', |
| 2997 |
'reason' => $skip_reason |
| 2998 |
]); |
| 2999 |
} |
| 3000 |
|
| 3001 |
// Generate SEO meta description using AI (unless disabled) |
| 3002 |
if ( function_exists( 'botwriter_generate_seo_meta' ) && get_option( 'botwriter_meta_disabled', '0' ) !== '1' ) { |
| 3003 |
$post_language = $data['post_language'] ?? ''; |
| 3004 |
$meta_description = botwriter_generate_seo_meta( |
| 3005 |
$data['aigenerated_title'], |
| 3006 |
$data['aigenerated_content'], |
| 3007 |
$post_language |
| 3008 |
); |
| 3009 |
if ( $meta_description ) { |
| 3010 |
botwriter_apply_seo_meta( $post_id, $meta_description ); |
| 3011 |
} |
| 3012 |
} |
| 3013 |
|
| 3014 |
// Persist image prompt in post meta so regeneration never depends on logs. |
| 3015 |
$saved_image_prompt = botwriter_resolve_image_prompt_from_post_data($data); |
| 3016 |
botwriter_log('Post creation: image prompt resolution before meta save', array( |
| 3017 |
'post_id' => $post_id, |
| 3018 |
'resolved_prompt_len' => strlen((string) $saved_image_prompt), |
| 3019 |
'incoming_image_prompt_len' => strlen(trim((string) ($data['image_prompt'] ?? ''))), |
| 3020 |
'incoming_image_provider' => isset($data['image_provider']) ? sanitize_key((string) $data['image_provider']) : '', |
| 3021 |
'has_image_attribution' => !empty($data['image_attribution']), |
| 3022 |
)); |
| 3023 |
if ($saved_image_prompt !== '') { |
| 3024 |
$saved_provider = isset($data['image_provider']) ? sanitize_key((string) $data['image_provider']) : ''; |
| 3025 |
|
| 3026 |
if ($saved_provider === '' && !empty($data['image_attribution'])) { |
| 3027 |
$saved_provider = 'stockphoto'; |
| 3028 |
} |
| 3029 |
|
| 3030 |
if ($saved_provider === '') { |
| 3031 |
$settings = botwriter_get_current_image_generation_settings(); |
| 3032 |
$saved_provider = (string) ($settings['provider'] ?? ''); |
| 3033 |
} |
| 3034 |
|
| 3035 |
botwriter_log('Post creation: saving image prompt meta', array( |
| 3036 |
'post_id' => $post_id, |
| 3037 |
'provider' => $saved_provider, |
| 3038 |
'prompt_len' => strlen((string) $saved_image_prompt), |
| 3039 |
)); |
| 3040 |
|
| 3041 |
botwriter_save_post_image_prompt_meta($post_id, $saved_image_prompt, $saved_provider); |
| 3042 |
} |
| 3043 |
|
| 3044 |
return $post_id; |
| 3045 |
} |
| 3046 |
|
| 3047 |
|
| 3048 |
|
| 3049 |
|
| 3050 |
// Function to send data to the server pass1 |
| 3051 |
function botwriter_send1_data_to_server($data) { |
| 3052 |
|
| 3053 |
Global $botwriter_version; |
| 3054 |
$remote_url = BOTWRITER_API_URL . 'redis_api_cola.php'; |
| 3055 |
|
| 3056 |
// Use constant to avoid get_plugin_data() and early translation loading |
| 3057 |
$botwriter_version = BOTWRITER_VERSION; |
| 3058 |
|
| 3059 |
// settings |
| 3060 |
$data['version'] = $botwriter_version; |
| 3061 |
$data['api_key'] = get_option('botwriter_api_key'); // la api_key del programa |
| 3062 |
$data["user_domainname"] = esc_url(get_site_url()); |
| 3063 |
$data['site_token'] = get_option('botwriter_site_token', ''); |
| 3064 |
|
| 3065 |
$data["ai_image_size"]=get_option('botwriter_ai_image_size'); |
| 3066 |
$data["ai_image_quality"]=get_option('botwriter_ai_image_quality'); |
| 3067 |
$data["ai_image_style"]=get_option('botwriter_ai_image_style', 'realistic'); |
| 3068 |
$data["ai_image_style_custom"]=get_option('botwriter_ai_image_style_custom', ''); |
| 3069 |
|
| 3070 |
// Use task-specific setting for disable_ai_images (already in $data from task/log) |
| 3071 |
// If not present, default to 0 (images enabled) |
| 3072 |
if (!isset($data["disable_ai_images"])) { |
| 3073 |
$data["disable_ai_images"] = 0; |
| 3074 |
} |
| 3075 |
|
| 3076 |
// If image generation fails, publish the post without image instead of erroring |
| 3077 |
$data['image_error_continue'] = get_option('botwriter_image_error_continue', '0'); |
| 3078 |
|
| 3079 |
// Provider selections |
| 3080 |
$data['text_provider'] = get_option('botwriter_text_provider', 'openai'); |
| 3081 |
$data['image_provider'] = get_option('botwriter_image_provider', 'dalle'); |
| 3082 |
|
| 3083 |
// Stock photo settings (sent always, used only when image_provider=stockphoto) |
| 3084 |
$data['stockphoto_preferred'] = get_option('botwriter_stockphoto_preferred', 'pixabay'); |
| 3085 |
$data['stockphoto_selection'] = get_option('botwriter_stockphoto_selection', 'random_top10'); |
| 3086 |
$data['stockphoto_attribution'] = get_option('botwriter_stockphoto_attribution', 'caption'); |
| 3087 |
|
| 3088 |
// Get current text model based on provider |
| 3089 |
$text_provider = $data['text_provider']; |
| 3090 |
$text_model_defaults = [ |
| 3091 |
'openai' => 'gpt-5.4-mini', |
| 3092 |
'anthropic' => 'claude-sonnet-4-6', |
| 3093 |
'google' => 'gemini-2.5-flash', |
| 3094 |
'mistral' => 'mistral-large-latest', |
| 3095 |
'groq' => 'llama-3.3-70b-versatile', |
| 3096 |
'openrouter' => 'anthropic/claude-sonnet-4.6', |
| 3097 |
]; |
| 3098 |
$data['text_model'] = get_option("botwriter_{$text_provider}_model", $text_model_defaults[$text_provider] ?? 'gpt-5.4-mini'); |
| 3099 |
|
| 3100 |
// Get current image model based on provider |
| 3101 |
$image_provider = $data['image_provider']; |
| 3102 |
$image_model_defaults = [ |
| 3103 |
'dalle' => 'gpt-image-1', |
| 3104 |
'gemini' => 'gemini-2.5-flash-image', |
| 3105 |
'fal' => 'fal-ai/flux-pro/v1.1', |
| 3106 |
'replicate' => 'black-forest-labs/flux-1.1-pro', |
| 3107 |
'stability' => 'sd3.5-large-turbo', |
| 3108 |
'cloudflare' => 'flux-1-schnell', |
| 3109 |
]; |
| 3110 |
// Gemini uses a different option name (gemini_image_model instead of gemini_model to avoid conflict with text) |
| 3111 |
$image_model_option = ($image_provider === 'gemini') ? 'botwriter_gemini_image_model' : "botwriter_{$image_provider}_model"; |
| 3112 |
$data['image_model'] = get_option($image_model_option, $image_model_defaults[$image_provider] ?? 'gpt-image-1'); |
| 3113 |
|
| 3114 |
// Also send the specific field name the server expects |
| 3115 |
if ($image_provider === 'gemini') { |
| 3116 |
$data['gemini_image_model'] = $data['image_model']; |
| 3117 |
} |
| 3118 |
|
| 3119 |
// Send all API keys (decrypted) - server will use the ones needed |
| 3120 |
$data['openai_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_openai_api_key')); |
| 3121 |
$data['anthropic_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_anthropic_api_key')); |
| 3122 |
$data['google_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_google_api_key')); |
| 3123 |
$data['mistral_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_mistral_api_key')); |
| 3124 |
$data['groq_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_groq_api_key')); |
| 3125 |
$data['openrouter_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_openrouter_api_key')); |
| 3126 |
$data['fal_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_fal_api_key')); |
| 3127 |
$data['replicate_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_replicate_api_key')); |
| 3128 |
$data['stability_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_stability_api_key')); |
| 3129 |
$data['cloudflare_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_cloudflare_api_key')); |
| 3130 |
$data['cloudflare_account_id'] = get_option('botwriter_cloudflare_account_id'); |
| 3131 |
|
| 3132 |
// Legacy field (kept for backward compatibility) |
| 3133 |
$data["openai_model"]=get_option('botwriter_openai_model', 'gpt-4o-mini'); |
| 3134 |
|
| 3135 |
$current_time = gmdate('Y-m-d H:i:s', current_time('timestamp')); |
| 3136 |
$data["last_execution_time"]=$current_time; |
| 3137 |
|
| 3138 |
$last_execution_time = $data["last_execution_time"]; |
| 3139 |
|
| 3140 |
|
| 3141 |
$category_ids=array_map('intval', explode(',', $data['category_id'])); |
| 3142 |
$titles = botwriter_get_logs_titles($data['id_task']); |
| 3143 |
if ($titles === false) { |
| 3144 |
$data['titles'] = ''; |
| 3145 |
} else { |
| 3146 |
$data['titles'] = implode(' | ', $titles); |
| 3147 |
} |
| 3148 |
|
| 3149 |
|
| 3150 |
// add the links of the posts where it has been copied |
| 3151 |
$links = botwriter_get_logs_links($data['id_task']); |
| 3152 |
if ($links === false) { |
| 3153 |
$data['links'] = ''; |
| 3154 |
} else { |
| 3155 |
$data['links'] = implode(',', $links); |
| 3156 |
} |
| 3157 |
|
| 3158 |
// post_lenght |
| 3159 |
if ($data['post_length'] === 'custom') { |
| 3160 |
$data['post_length'] = $data['custom_post_length']; |
| 3161 |
} |
| 3162 |
|
| 3163 |
// ===================================================== |
| 3164 |
// CLIENT-SIDE RSS FETCH (pre-fetch before prompt build) |
| 3165 |
// ===================================================== |
| 3166 |
$website_type = $data['website_type'] ?? ''; |
| 3167 |
if ($website_type === 'rss') { |
| 3168 |
$rss_result = botwriter_fetch_rss_content( |
| 3169 |
$data['rss_source'] ?? '', |
| 3170 |
$data['links'] ?? '' |
| 3171 |
); |
| 3172 |
|
| 3173 |
if ( ! $rss_result['success'] ) { |
| 3174 |
botwriter_log('Client RSS fetch failed', [ |
| 3175 |
'log_id' => $data['id'] ?? null, |
| 3176 |
'task_id' => $data['id_task'] ?? null, |
| 3177 |
'error' => $rss_result['error'] ?? 'Unknown error', |
| 3178 |
]); |
| 3179 |
$data['task_status'] = 'error'; |
| 3180 |
$data['error'] = $rss_result['error'] ?? 'RSS fetch failed'; |
| 3181 |
botwriter_logs_register($data, $data['id']); |
| 3182 |
return false; |
| 3183 |
} |
| 3184 |
|
| 3185 |
// Populate data with the pre-fetched article so the prompt builder can use it |
| 3186 |
$data['source_title'] = $rss_result['source_title']; |
| 3187 |
$data['source_content'] = $rss_result['source_content']; |
| 3188 |
$data['link_post_original'] = $rss_result['link_original']; |
| 3189 |
$data['source_prefetched'] = '1'; |
| 3190 |
|
| 3191 |
botwriter_log('Client RSS article ready', [ |
| 3192 |
'log_id' => $data['id'] ?? null, |
| 3193 |
'task_id' => $data['id_task'] ?? null, |
| 3194 |
'article_link' => $rss_result['link_original'], |
| 3195 |
]); |
| 3196 |
} |
| 3197 |
// ===================================================== |
| 3198 |
|
| 3199 |
// ===================================================== |
| 3200 |
// CLIENT-SIDE WORDPRESS FETCH (pre-fetch before prompt build) |
| 3201 |
// ===================================================== |
| 3202 |
if ($website_type === 'wordpress') { |
| 3203 |
$wp_result = botwriter_fetch_wordpress_content( |
| 3204 |
$data['domain_name'] ?? '', |
| 3205 |
$data['website_category_id'] ?? '', |
| 3206 |
$data['links'] ?? '' |
| 3207 |
); |
| 3208 |
|
| 3209 |
if ( ! $wp_result['success'] ) { |
| 3210 |
botwriter_log('Client WordPress fetch failed', [ |
| 3211 |
'log_id' => $data['id'] ?? null, |
| 3212 |
'task_id' => $data['id_task'] ?? null, |
| 3213 |
'error' => $wp_result['error'] ?? 'Unknown error', |
| 3214 |
]); |
| 3215 |
$data['task_status'] = 'error'; |
| 3216 |
$data['error'] = $wp_result['error'] ?? 'WordPress fetch failed'; |
| 3217 |
botwriter_logs_register($data, $data['id']); |
| 3218 |
return false; |
| 3219 |
} |
| 3220 |
|
| 3221 |
$data['source_title'] = $wp_result['source_title']; |
| 3222 |
$data['source_content'] = $wp_result['source_content']; |
| 3223 |
$data['link_post_original'] = $wp_result['link_original']; |
| 3224 |
$data['source_prefetched'] = '1'; |
| 3225 |
|
| 3226 |
botwriter_log('Client WordPress article ready', [ |
| 3227 |
'log_id' => $data['id'] ?? null, |
| 3228 |
'task_id' => $data['id_task'] ?? null, |
| 3229 |
'article_link' => $wp_result['link_original'], |
| 3230 |
]); |
| 3231 |
} |
| 3232 |
// ===================================================== |
| 3233 |
|
| 3234 |
// Build prompt from template (except for super1 which is handled by server) |
| 3235 |
if ($website_type !== 'super1') { |
| 3236 |
$data['client_prompt'] = botwriter_build_client_prompt($data); |
| 3237 |
} |
| 3238 |
|
| 3239 |
botwriter_log('SEND1: after prompt build', [ |
| 3240 |
'log_id' => $data['id'] ?? null, |
| 3241 |
'website_type' => $website_type, |
| 3242 |
'has_client_prompt' => !empty($data['client_prompt']), |
| 3243 |
'client_prompt_len' => strlen($data['client_prompt'] ?? ''), |
| 3244 |
'source_prefetched' => $data['source_prefetched'] ?? '0', |
| 3245 |
'has_source_title' => !empty($data['source_title']), |
| 3246 |
'has_source_content' => !empty($data['source_content']), |
| 3247 |
'link_post_original' => $data['link_post_original'] ?? '', |
| 3248 |
]); |
| 3249 |
|
| 3250 |
botwriter_log('Dispatching phase 1 request', [ |
| 3251 |
'log_id' => $data['id'] ?? null, |
| 3252 |
'task_id' => $data['id_task'] ?? null, |
| 3253 |
'website_type' => $data['website_type'] ?? null, |
| 3254 |
'status' => $data['task_status'] ?? null, |
| 3255 |
'attempt' => $data['intentosfase1'] ?? null, |
| 3256 |
'text_provider' => $data['text_provider'] ?? null, |
| 3257 |
'text_model' => $data['text_model'] ?? null, |
| 3258 |
'image_provider' => $data['image_provider'] ?? null, |
| 3259 |
'image_model' => $data['image_model'] ?? null, |
| 3260 |
]); |
| 3261 |
|
| 3262 |
$data["error"]= ""; |
| 3263 |
|
| 3264 |
$ssl_verify = get_option('botwriter_sslverify'); |
| 3265 |
|
| 3266 |
|
| 3267 |
if ($ssl_verify === 'no') { |
| 3268 |
$ssl_verify = false; |
| 3269 |
} else { |
| 3270 |
$ssl_verify = true; |
| 3271 |
} |
| 3272 |
|
| 3273 |
|
| 3274 |
|
| 3275 |
$response = wp_remote_post($remote_url, array( |
| 3276 |
'method' => 'POST', |
| 3277 |
'body' => $data, |
| 3278 |
'timeout' => 45, |
| 3279 |
'headers' => array(), |
| 3280 |
'sslverify' => $ssl_verify, |
| 3281 |
)); |
| 3282 |
|
| 3283 |
$data["intentosfase1"]++; |
| 3284 |
botwriter_logs_register($data, $data["id"]); |
| 3285 |
|
| 3286 |
$last_execution_time=$data["last_execution_time"]; |
| 3287 |
|
| 3288 |
|
| 3289 |
|
| 3290 |
if (is_wp_error($response)) { |
| 3291 |
$error_message = $response->get_error_message(); |
| 3292 |
botwriter_log('Phase 1 request error', [ |
| 3293 |
'log_id' => $data['id'] ?? null, |
| 3294 |
'task_id' => $data['id_task'] ?? null, |
| 3295 |
'website_type' => $data['website_type'] ?? null, |
| 3296 |
'error' => $error_message, |
| 3297 |
]); |
| 3298 |
$data["task_status"]="error"; |
| 3299 |
botwriter_logs_register($data, $data["id"]); |
| 3300 |
return false; |
| 3301 |
|
| 3302 |
} else { |
| 3303 |
|
| 3304 |
if ($response['response']['code'] === 200) { |
| 3305 |
|
| 3306 |
$body = wp_remote_retrieve_body($response); |
| 3307 |
$result = json_decode($body, true); |
| 3308 |
|
| 3309 |
//error_log("Data received: " . print_r($body, true)); |
| 3310 |
|
| 3311 |
// STOPFORMANY: server reports too many consecutive errors |
| 3312 |
if (isset($result['error']) && strpos($result['error'], 'STOPFORMANY') === 0) { |
| 3313 |
update_option('botwriter_stopformany', true); |
| 3314 |
$data['task_status'] = 'error'; |
| 3315 |
$data['error'] = $result['error']; |
| 3316 |
$data['intentosfase1'] = 8; // stop retries |
| 3317 |
botwriter_logs_register($data, $data['id']); |
| 3318 |
botwriter_log('STOPFORMANY activated from phase 1', [ |
| 3319 |
'log_id' => $data['id'] ?? null, |
| 3320 |
'task_id' => $data['id_task'] ?? null, |
| 3321 |
'error' => $result['error'], |
| 3322 |
]); |
| 3323 |
return false; |
| 3324 |
} |
| 3325 |
|
| 3326 |
if (isset($result['id_task_server']) && $result['id_task_server'] !== 0) { // ok |
| 3327 |
$data["id_task_server"]=$result['id_task_server']; |
| 3328 |
$data["task_status"]='inqueue'; |
| 3329 |
|
| 3330 |
// Capture site_token from server response (auto-provisioning) |
| 3331 |
if (!empty($result['site_token'])) { |
| 3332 |
update_option('botwriter_site_token', sanitize_text_field($result['site_token'])); |
| 3333 |
} |
| 3334 |
|
| 3335 |
botwriter_logs_register($data, $data["id"]); |
| 3336 |
botwriter_log('Phase 1 request accepted', [ |
| 3337 |
'log_id' => $data['id'] ?? null, |
| 3338 |
'task_id' => $data['id_task'] ?? null, |
| 3339 |
'id_task_server' => $result['id_task_server'], |
| 3340 |
]); |
| 3341 |
return $result['id_task_server']; |
| 3342 |
} else { // error — id_task_server is 0 or missing |
| 3343 |
$data["task_status"]="error"; |
| 3344 |
// Use full error_message (may include reset link) when available |
| 3345 |
$data["error"] = !empty($result['error_message']) ? $result['error_message'] : ($result['error'] ?? ''); |
| 3346 |
|
| 3347 |
// Generic terminal flag — server says stop retrying |
| 3348 |
if (!empty($result['terminal'])) { |
| 3349 |
$data['intentosfase1'] = 8; |
| 3350 |
} |
| 3351 |
// Show server-provided admin notice |
| 3352 |
if (!empty($result['error_message']) && (int)($result['error_level'] ?? 0) === 1) { |
| 3353 |
botwriter_announcements_add( |
| 3354 |
__('Service notice', 'botwriter'), |
| 3355 |
wp_kses_post($result['error_message']) |
| 3356 |
); |
| 3357 |
} |
| 3358 |
|
| 3359 |
// Handle known server errors (same logic as Phase 2) |
| 3360 |
if ($data["error"] == "Maximum monthly posts limit reached") { |
| 3361 |
botwriter_announcements_add("Maximum monthly posts limit reached", "You have reached the maximum monthly posts limit. Please upgrade your plan to continue using the plugin. <a href='https://wpbotwriter.com' target='_blank'>Go to upgrade</a>"); |
| 3362 |
$data["intentosfase1"] = 8; |
| 3363 |
} |
| 3364 |
if ($data["error"] == "Payment date exceeded") { |
| 3365 |
botwriter_announcements_add("Payment date exceeded", "Your subscription payment date has exceeded. Please renew your subscription to continue using the plugin. <a href='https://wpbotwriter.com' target='_blank'>Go to renew</a>"); |
| 3366 |
$data["intentosfase1"] = 8; |
| 3367 |
} |
| 3368 |
if ($data["error"] == "API Key error") { |
| 3369 |
botwriter_announcements_add("API Key error", "Your API Key is invalid. Please check your API Key in the plugin settings. <a href='admin.php?page=botwriter_settings'>Go to Settings</a>"); |
| 3370 |
$data["intentosfase1"] = 8; |
| 3371 |
} |
| 3372 |
|
| 3373 |
botwriter_logs_register($data, $data["id"]); |
| 3374 |
botwriter_log('Phase 1 request rejected', [ |
| 3375 |
'log_id' => $data['id'] ?? null, |
| 3376 |
'task_id' => $data['id_task'] ?? null, |
| 3377 |
'error' => $data['error'], |
| 3378 |
'response_length' => isset($body) ? strlen($body) : null, |
| 3379 |
]); |
| 3380 |
return false; |
| 3381 |
} |
| 3382 |
} else { // error |
| 3383 |
$data["task_status"]="error"; |
| 3384 |
botwriter_logs_register($data, $data["id"]); |
| 3385 |
botwriter_log('Phase 1 non-200 response', [ |
| 3386 |
'log_id' => $data['id'] ?? null, |
| 3387 |
'task_id' => $data['id_task'] ?? null, |
| 3388 |
'status_code' => $response['response']['code'], |
| 3389 |
]); |
| 3390 |
return false; |
| 3391 |
} |
| 3392 |
} |
| 3393 |
|
| 3394 |
|
| 3395 |
} |
| 3396 |
|
| 3397 |
// Function to send data to the server pass2 |
| 3398 |
function botwriter_send2_data_to_server($data) { |
| 3399 |
Global $wpdb; |
| 3400 |
|
| 3401 |
$data['api_key'] = get_option('botwriter_api_key'); |
| 3402 |
$data["user_domainname"] = esc_url(get_site_url()); |
| 3403 |
$data['site_token'] = get_option('botwriter_site_token', ''); |
| 3404 |
|
| 3405 |
$remote_url = BOTWRITER_API_URL . 'redis_api_finish.php'; |
| 3406 |
|
| 3407 |
$ssl_verify = get_option('botwriter_sslverify'); |
| 3408 |
if ($ssl_verify === 'no') { |
| 3409 |
$ssl_verify = false; |
| 3410 |
} else { |
| 3411 |
$ssl_verify = true; |
| 3412 |
} |
| 3413 |
|
| 3414 |
botwriter_log('Dispatching phase 2 request', [ |
| 3415 |
'log_id' => $data['id'] ?? null, |
| 3416 |
'task_id' => $data['id_task'] ?? null, |
| 3417 |
'website_type' => $data['website_type'] ?? null, |
| 3418 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 3419 |
]); |
| 3420 |
|
| 3421 |
$response = wp_remote_post($remote_url, array( |
| 3422 |
'method' => 'POST', |
| 3423 |
'body' => $data, |
| 3424 |
'timeout' => 45, |
| 3425 |
'headers' => array(), |
| 3426 |
'sslverify' => $ssl_verify |
| 3427 |
)); |
| 3428 |
|
| 3429 |
|
| 3430 |
if (is_wp_error($response)) { |
| 3431 |
$error_message = $response->get_error_message(); |
| 3432 |
botwriter_log('Phase 2 request error', [ |
| 3433 |
'log_id' => $data['id'] ?? null, |
| 3434 |
'task_id' => $data['id_task'] ?? null, |
| 3435 |
'website_type' => $data['website_type'] ?? null, |
| 3436 |
'error' => $error_message, |
| 3437 |
]); |
| 3438 |
$data["error"]= "Error sending data " . $error_message; |
| 3439 |
return false; |
| 3440 |
} else { |
| 3441 |
|
| 3442 |
if ($response['response']['code'] === 200) { |
| 3443 |
|
| 3444 |
$body = wp_remote_retrieve_body($response); |
| 3445 |
$result = json_decode($body, true); |
| 3446 |
|
| 3447 |
//echo 'Data recive: <pre>' . print_r($result, true) . '</pre>'; |
| 3448 |
|
| 3449 |
// results errors |
| 3450 |
if (isset($result["task_status"]) && $result["task_status"] == "error") { |
| 3451 |
$data["task_status"]="error"; |
| 3452 |
// Use full error_message (may include reset link) when available |
| 3453 |
$data["error"] = !empty($result['error_message']) ? $result['error_message'] : ($result['error'] ?? ''); |
| 3454 |
botwriter_log('Phase 2 reported error', [ |
| 3455 |
'log_id' => $data['id'] ?? null, |
| 3456 |
'task_id' => $data['id_task'] ?? null, |
| 3457 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 3458 |
'error' => $data['error'], |
| 3459 |
]); |
| 3460 |
|
| 3461 |
// Generic terminal flag — server says stop retrying |
| 3462 |
if (!empty($result['terminal'])) { |
| 3463 |
$data['intentosfase1'] = 8; |
| 3464 |
} |
| 3465 |
// Show server-provided admin notice |
| 3466 |
if (!empty($result['error_message']) && (int)($result['error_level'] ?? 0) === 1) { |
| 3467 |
botwriter_announcements_add( |
| 3468 |
__('Service notice', 'botwriter'), |
| 3469 |
wp_kses_post($result['error_message']) |
| 3470 |
); |
| 3471 |
} |
| 3472 |
|
| 3473 |
if ($data["error"]=="Maximum monthly posts limit reached") { |
| 3474 |
botwriter_announcements_add("Maximum monthly posts limit reached", "You have reached the maximum monthly posts limit. Please upgrade your plan to continue using the plugin. <a href='https://wpbotwriter.com' target='_blank'>Go to upgrade</a>"); |
| 3475 |
$data["intentosfase1"]=8; |
| 3476 |
} |
| 3477 |
if ($data["error"]=="Payment date exceeded") { |
| 3478 |
botwriter_announcements_add("Payment date exceeded", "Your subscription payment date has exceeded. Please renew your subscription to continue using the plugin. <a href='https://wpbotwriter.com' target='_blank'>Go to renew</a>"); |
| 3479 |
$data["intentosfase1"]=8; |
| 3480 |
} |
| 3481 |
if ($data["error"]=="API Key error") { |
| 3482 |
botwriter_announcements_add("API Key error", "Your API Key is invalid. Please check your API Key in the plugin settings. <a href='admin.php?page=botwriter_settings'>Go to Settings</a>"); |
| 3483 |
$data["intentosfase1"]=8; |
| 3484 |
} |
| 3485 |
// STOPFORMANY: server reports too many consecutive errors |
| 3486 |
if (isset($result['error']) && strpos($result['error'], 'STOPFORMANY') === 0) { |
| 3487 |
update_option('botwriter_stopformany', true); |
| 3488 |
$data['intentosfase1'] = 8; |
| 3489 |
botwriter_log('STOPFORMANY activated from phase 2', [ |
| 3490 |
'log_id' => $data['id'] ?? null, |
| 3491 |
'task_id' => $data['id_task'] ?? null, |
| 3492 |
'error' => $result['error'], |
| 3493 |
]); |
| 3494 |
} |
| 3495 |
|
| 3496 |
botwriter_logs_register($data, $data["id"]); |
| 3497 |
return false; |
| 3498 |
} |
| 3499 |
|
| 3500 |
// result completed |
| 3501 |
botwriter_log('Phase 2 response payload', [ |
| 3502 |
'log_id' => $data['id'] ?? null, |
| 3503 |
'task_id' => $data['id_task'] ?? null, |
| 3504 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 3505 |
'task_status' => $result['task_status'] ?? null, |
| 3506 |
]); |
| 3507 |
if (isset($result["task_status"]) && $result["task_status"] == "completed") { |
| 3508 |
// Preserve image_attribution from server response (not stored in DB) |
| 3509 |
$image_attribution = isset($result['image_attribution']) ? $result['image_attribution'] : null; |
| 3510 |
|
| 3511 |
botwriter_logs_register($result, $data["id"]); |
| 3512 |
botwriter_log('Phase 2 completed event', [ |
| 3513 |
'log_id' => $data['id'] ?? null, |
| 3514 |
'task_id' => $data['id_task'] ?? null, |
| 3515 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 3516 |
]); |
| 3517 |
|
| 3518 |
$result=botwriter_logs_get($data["id"]); // merge the result with the log |
| 3519 |
|
| 3520 |
// Re-attach image_attribution (not persisted in logs table) |
| 3521 |
if ($image_attribution) { |
| 3522 |
$result['image_attribution'] = $image_attribution; |
| 3523 |
} |
| 3524 |
|
| 3525 |
// ── Guard: prevent duplicate post creation ── |
| 3526 |
// If this log already has a published post, skip generation. |
| 3527 |
if (!empty($result['id_post_published']) && intval($result['id_post_published']) > 0) { |
| 3528 |
botwriter_log('Phase 2 skipped — post already published', [ |
| 3529 |
'log_id' => $data['id'], |
| 3530 |
'post_id' => $result['id_post_published'], |
| 3531 |
]); |
| 3532 |
return $result; |
| 3533 |
} |
| 3534 |
|
| 3535 |
if ($result["website_type"] == "super1") { |
| 3536 |
botwriter_super1_log_to_bd($result,$data["id"]); |
| 3537 |
|
| 3538 |
} else { |
| 3539 |
$post_id=botwriter_generate_post($result); |
| 3540 |
$result["id_post_published"]=$post_id; |
| 3541 |
} |
| 3542 |
|
| 3543 |
botwriter_logs_register($result, $data["id"]); |
| 3544 |
if ($result["website_type"] == "super2") { |
| 3545 |
//update tabla super poner el task_Status en completed |
| 3546 |
$wpdb->update($wpdb->prefix . 'botwriter_super', ['task_status' => 'completed'], ['id_log' => $data["id"]]); |
| 3547 |
} |
| 3548 |
|
| 3549 |
|
| 3550 |
return $result; |
| 3551 |
} |
| 3552 |
|
| 3553 |
// other results, inqueue, pending, etc |
| 3554 |
$now=current_time('timestamp'); |
| 3555 |
$last_execution_time = strtotime($data["last_execution_time"]); |
| 3556 |
$diff = $now - $last_execution_time; |
| 3557 |
if ($diff > 60 * 5) { // 5 minutes |
| 3558 |
$data["task_status"]="error"; |
| 3559 |
$data["error"]="Error in server"; |
| 3560 |
botwriter_logs_register($data, $data["id"]); |
| 3561 |
botwriter_log('Phase 2 timeout detected', [ |
| 3562 |
'log_id' => $data['id'] ?? null, |
| 3563 |
'task_id' => $data['id_task'] ?? null, |
| 3564 |
]); |
| 3565 |
} |
| 3566 |
return false; |
| 3567 |
|
| 3568 |
|
| 3569 |
|
| 3570 |
|
| 3571 |
|
| 3572 |
|
| 3573 |
} else { // error |
| 3574 |
// update log |
| 3575 |
$data["task_status"]="error"; |
| 3576 |
botwriter_logs_register($data, $data["id"]); |
| 3577 |
botwriter_log('Phase 2 non-200 response', [ |
| 3578 |
'log_id' => $data['id'] ?? null, |
| 3579 |
'task_id' => $data['id_task'] ?? null, |
| 3580 |
'status_code' => $response['response']['code'], |
| 3581 |
]); |
| 3582 |
return false; |
| 3583 |
} |
| 3584 |
} |
| 3585 |
|
| 3586 |
|
| 3587 |
|
| 3588 |
} |
| 3589 |
|
| 3590 |
|
| 3591 |
|
| 3592 |
|
| 3593 |
|
| 3594 |
function botwriter_cambiar_status_ajax() { |
| 3595 |
// Verify user has permission |
| 3596 |
if (!current_user_can('manage_options')) { |
| 3597 |
wp_send_json_error(['message' => 'Permission denied']); |
| 3598 |
} |
| 3599 |
|
| 3600 |
check_ajax_referer('botwriter_cambiar_status_nonce', 'nonce'); |
| 3601 |
|
| 3602 |
$id = isset($_POST['id']) ? intval($_POST['id']) : 0; |
| 3603 |
$nuevo_status = isset($_POST['status']) ? intval($_POST['status']) : 0; |
| 3604 |
|
| 3605 |
|
| 3606 |
|
| 3607 |
if ($id > 0) { |
| 3608 |
global $wpdb; |
| 3609 |
$table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 3610 |
|
| 3611 |
$result = $wpdb->update( |
| 3612 |
$table_name, |
| 3613 |
['status' => $nuevo_status], |
| 3614 |
['id' => $id], |
| 3615 |
['%d'], |
| 3616 |
['%d'] |
| 3617 |
); |
| 3618 |
|
| 3619 |
if ($result !== false) { |
| 3620 |
wp_send_json_success(['message' => 'Estado actualizado correctamente']); |
| 3621 |
} else { |
| 3622 |
wp_send_json_error(['message' => 'Error al actualizar el estado']); |
| 3623 |
} |
| 3624 |
} else { |
| 3625 |
wp_send_json_error(['message' => 'ID inválido']); |
| 3626 |
} |
| 3627 |
|
| 3628 |
wp_die(); |
| 3629 |
} |
| 3630 |
add_action('wp_ajax_botwriter_cambiar_status', 'botwriter_cambiar_status_ajax'); |
| 3631 |
|
| 3632 |
/** |
| 3633 |
* AJAX handler to check/preview an RSS feed from the admin UI. |
| 3634 |
* Reads the feed client-side using botwriter_fetch_rss_content(). |
| 3635 |
*/ |
| 3636 |
function botwriter_check_rss_ajax() { |
| 3637 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 3638 |
wp_send_json_error( [ 'error' => 'Permission denied' ] ); |
| 3639 |
} |
| 3640 |
|
| 3641 |
check_ajax_referer( 'botwriter_check_rss_nonce', 'nonce' ); |
| 3642 |
|
| 3643 |
$url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : ''; |
| 3644 |
if ( empty( $url ) ) { |
| 3645 |
wp_send_json_error( [ 'error' => 'RSS URL is required.' ] ); |
| 3646 |
} |
| 3647 |
|
| 3648 |
$result = botwriter_fetch_rss_content( $url ); |
| 3649 |
if ( $result['success'] ) { |
| 3650 |
wp_send_json_success( [ |
| 3651 |
'title' => $result['source_title'], |
| 3652 |
'description' => $result['source_content'], |
| 3653 |
'link' => $result['link_original'], |
| 3654 |
] ); |
| 3655 |
} else { |
| 3656 |
wp_send_json_error( [ 'error' => $result['error'] ] ); |
| 3657 |
} |
| 3658 |
} |
| 3659 |
add_action( 'wp_ajax_botwriter_check_rss', 'botwriter_check_rss_ajax' ); |
| 3660 |
|
| 3661 |
/** |
| 3662 |
* AJAX handler to fetch categories from a remote WordPress site. |
| 3663 |
* Reads categories client-side using botwriter_fetch_wordpress_categories(). |
| 3664 |
*/ |
| 3665 |
function botwriter_get_wordpress_categories_ajax() { |
| 3666 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 3667 |
wp_send_json_error( [ 'error' => 'Permission denied' ] ); |
| 3668 |
} |
| 3669 |
|
| 3670 |
check_ajax_referer( 'botwriter_wp_categories_nonce', 'nonce' ); |
| 3671 |
|
| 3672 |
$domain = isset( $_POST['website_domainname'] ) ? esc_url_raw( wp_unslash( $_POST['website_domainname'] ) ) : ''; |
| 3673 |
if ( empty( $domain ) ) { |
| 3674 |
wp_send_json_error( [ 'error' => 'WordPress domain is required.' ] ); |
| 3675 |
} |
| 3676 |
|
| 3677 |
$result = botwriter_fetch_wordpress_categories( $domain ); |
| 3678 |
if ( $result['success'] ) { |
| 3679 |
wp_send_json_success( $result['categories'] ); |
| 3680 |
} else { |
| 3681 |
wp_send_json_error( [ 'error' => $result['error'] ] ); |
| 3682 |
} |
| 3683 |
} |
| 3684 |
add_action( 'wp_ajax_botwriter_get_wp_categories', 'botwriter_get_wordpress_categories_ajax' ); |
| 3685 |
|
| 3686 |
// AJAX handler for deleting a log entry |
| 3687 |
add_action('wp_ajax_botwriter_delete_log', 'botwriter_delete_log_ajax'); |
| 3688 |
function botwriter_delete_log_ajax() { |
| 3689 |
// Verify user has permission |
| 3690 |
if (!current_user_can('manage_options')) { |
| 3691 |
wp_send_json_error(['message' => 'Permission denied']); |
| 3692 |
} |
| 3693 |
|
| 3694 |
// Verify nonce |
| 3695 |
check_ajax_referer('botwriter_logs_delete_nonce', 'nonce'); |
| 3696 |
|
| 3697 |
$log_id = isset($_POST['log_id']) ? intval($_POST['log_id']) : 0; |
| 3698 |
|
| 3699 |
if ($log_id > 0) { |
| 3700 |
global $wpdb; |
| 3701 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 3702 |
|
| 3703 |
$result = $wpdb->delete($table_name, array('id' => $log_id), array('%d')); |
| 3704 |
|
| 3705 |
if ($result !== false) { |
| 3706 |
wp_send_json_success(['message' => __('Log deleted successfully', 'botwriter')]); |
| 3707 |
} else { |
| 3708 |
wp_send_json_error(['message' => __('Error deleting log', 'botwriter')]); |
| 3709 |
} |
| 3710 |
} else { |
| 3711 |
wp_send_json_error(['message' => __('Invalid log ID', 'botwriter')]); |
| 3712 |
} |
| 3713 |
|
| 3714 |
wp_die(); |
| 3715 |
} |
| 3716 |
|
| 3717 |
// AJAX handler for bulk deleting log entries |
| 3718 |
add_action('wp_ajax_botwriter_bulk_delete_logs', 'botwriter_bulk_delete_logs_ajax'); |
| 3719 |
function botwriter_bulk_delete_logs_ajax() { |
| 3720 |
if (!current_user_can('manage_options')) { |
| 3721 |
wp_send_json_error(['message' => 'Permission denied']); |
| 3722 |
} |
| 3723 |
|
| 3724 |
check_ajax_referer('botwriter_logs_delete_nonce', 'nonce'); |
| 3725 |
|
| 3726 |
$log_ids = isset($_POST['log_ids']) ? array_map('intval', $_POST['log_ids']) : array(); |
| 3727 |
$log_ids = array_filter($log_ids, function($id) { return $id > 0; }); |
| 3728 |
|
| 3729 |
if (empty($log_ids)) { |
| 3730 |
wp_send_json_error(['message' => __('No logs selected', 'botwriter')]); |
| 3731 |
} |
| 3732 |
|
| 3733 |
global $wpdb; |
| 3734 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 3735 |
$placeholders = implode(',', array_fill(0, count($log_ids), '%d')); |
| 3736 |
$deleted = $wpdb->query($wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $log_ids)); |
| 3737 |
|
| 3738 |
if ($deleted !== false) { |
| 3739 |
wp_send_json_success(['message' => sprintf(__('%d log(s) deleted successfully', 'botwriter'), $deleted)]); |
| 3740 |
} else { |
| 3741 |
wp_send_json_error(['message' => __('Error deleting logs', 'botwriter')]); |
| 3742 |
} |
| 3743 |
|
| 3744 |
wp_die(); |
| 3745 |
} |
| 3746 |
|
| 3747 |
// AJAX handler for getting taxonomies and terms for a post type |
| 3748 |
add_action('wp_ajax_botwriter_get_taxonomies', 'botwriter_get_taxonomies_ajax'); |
| 3749 |
function botwriter_get_taxonomies_ajax() { |
| 3750 |
// Verify user has permission |
| 3751 |
if (!current_user_can('manage_options')) { |
| 3752 |
wp_send_json_error(['message' => 'Permission denied']); |
| 3753 |
} |
| 3754 |
|
| 3755 |
// Verify nonce |
| 3756 |
check_ajax_referer('botwriter_taxonomies_nonce', 'nonce'); |
| 3757 |
|
| 3758 |
$post_type = isset($_POST['post_type']) ? sanitize_text_field(wp_unslash($_POST['post_type'])) : 'post'; |
| 3759 |
|
| 3760 |
// Get taxonomies for this post type |
| 3761 |
$taxonomies = get_object_taxonomies($post_type, 'objects'); |
| 3762 |
|
| 3763 |
$result = array(); |
| 3764 |
// Skip tag taxonomies since AI generates tags automatically |
| 3765 |
$skip_taxonomies = array('post_tag', 'product_tag'); |
| 3766 |
|
| 3767 |
foreach ($taxonomies as $taxonomy) { |
| 3768 |
// Skip non-public taxonomies |
| 3769 |
if (!$taxonomy->public) { |
| 3770 |
continue; |
| 3771 |
} |
| 3772 |
|
| 3773 |
// Skip tag taxonomies (AI generates tags) |
| 3774 |
if (in_array($taxonomy->name, $skip_taxonomies, true)) { |
| 3775 |
continue; |
| 3776 |
} |
| 3777 |
|
| 3778 |
// Get terms for this taxonomy |
| 3779 |
$terms = get_terms(array( |
| 3780 |
'taxonomy' => $taxonomy->name, |
| 3781 |
'hide_empty' => false, |
| 3782 |
'orderby' => 'name', |
| 3783 |
'order' => 'ASC', |
| 3784 |
)); |
| 3785 |
|
| 3786 |
$terms_data = array(); |
| 3787 |
if (!is_wp_error($terms)) { |
| 3788 |
foreach ($terms as $term) { |
| 3789 |
$terms_data[] = array( |
| 3790 |
'id' => $term->term_id, |
| 3791 |
'name' => $term->name, |
| 3792 |
'slug' => $term->slug, |
| 3793 |
'parent' => $term->parent, |
| 3794 |
); |
| 3795 |
} |
| 3796 |
} |
| 3797 |
|
| 3798 |
$result[] = array( |
| 3799 |
'name' => $taxonomy->name, |
| 3800 |
'label' => $taxonomy->label, |
| 3801 |
'hierarchical' => $taxonomy->hierarchical, |
| 3802 |
'terms' => $terms_data, |
| 3803 |
); |
| 3804 |
} |
| 3805 |
|
| 3806 |
wp_send_json_success($result); |
| 3807 |
} |
| 3808 |
|
| 3809 |
|
| 3810 |
function botwriter_attach_image_to_post($post_id, $image_url, $post_title, $translated_image_slug = '', $image_attribution = null) { |
| 3811 |
if (!$image_url || !$post_id || empty(trim($image_url))) { |
| 3812 |
botwriter_log('Image attachment skipped', [ |
| 3813 |
'post_id' => $post_id, |
| 3814 |
'image_url' => $image_url ?: 'empty', |
| 3815 |
'reason' => 'Invalid post ID or empty image URL' |
| 3816 |
]); |
| 3817 |
return 'Invalid post ID or image URL'; |
| 3818 |
} |
| 3819 |
|
| 3820 |
if ($image_url && $post_id) { |
| 3821 |
//$image_data = file_get_contents($image_url); |
| 3822 |
$image_data = wp_remote_retrieve_body(wp_remote_get($image_url)); |
| 3823 |
$upload_dir = wp_upload_dir(); |
| 3824 |
// Use translated image slug if available, otherwise fall back to title |
| 3825 |
if (!empty($translated_image_slug)) { |
| 3826 |
$base_name = sanitize_file_name($translated_image_slug); |
| 3827 |
} else { |
| 3828 |
$base_name = sanitize_file_name(remove_accents($post_title)); |
| 3829 |
} |
| 3830 |
if (empty($base_name)) { |
| 3831 |
$base_name = 'botwriter-post-' . $post_id; |
| 3832 |
} |
| 3833 |
/** |
| 3834 |
* Filter the filename used for BotWriter generated images. |
| 3835 |
* |
| 3836 |
* @param string $base_name Base filename without extension. |
| 3837 |
* @param int $post_id Post ID. |
| 3838 |
* @param string $post_title Post title. |
| 3839 |
*/ |
| 3840 |
$base_name = apply_filters('botwriter_image_filename', $base_name, $post_id, $post_title); |
| 3841 |
$filename = $base_name . '.jpg'; |
| 3842 |
$filename = wp_unique_filename($upload_dir['path'], $filename); |
| 3843 |
|
| 3844 |
if (wp_mkdir_p($upload_dir['path'])) { |
| 3845 |
$file = $upload_dir['path'] . '/' . $filename; |
| 3846 |
} else { |
| 3847 |
$file = $upload_dir['basedir'] . '/' . $filename; |
| 3848 |
} |
| 3849 |
|
| 3850 |
global $wp_filesystem; |
| 3851 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 3852 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 3853 |
} |
| 3854 |
WP_Filesystem(); |
| 3855 |
if ( $wp_filesystem->put_contents( $file, $image_data, FS_CHMOD_FILE ) ) { |
| 3856 |
//error_log('Imagen guardada exitosamente en: ' . $file); |
| 3857 |
} else { |
| 3858 |
//error_log('Error al guardar la imagen en: ' . $file); |
| 3859 |
} |
| 3860 |
|
| 3861 |
$wp_filetype = wp_check_filetype($filename, null); |
| 3862 |
$attachment = [ |
| 3863 |
'post_mime_type' => $wp_filetype['type'], |
| 3864 |
'post_title' => sanitize_file_name($filename), |
| 3865 |
'post_content' => '', |
| 3866 |
'post_status' => 'inherit', |
| 3867 |
]; |
| 3868 |
|
| 3869 |
$attach_id = wp_insert_attachment($attachment, $file, $post_id); |
| 3870 |
require_once(ABSPATH . 'wp-admin/includes/image.php'); |
| 3871 |
|
| 3872 |
// Apply post-processing if enabled |
| 3873 |
$postprocess_enabled = get_option('botwriter_image_postprocess_enabled', '0'); |
| 3874 |
if ($postprocess_enabled === '1') { |
| 3875 |
$processed_file = botwriter_process_image($file); |
| 3876 |
if ($processed_file && $processed_file !== $file) { |
| 3877 |
// Update file reference if format changed |
| 3878 |
$file = $processed_file; |
| 3879 |
// Update attachment with new file info |
| 3880 |
$wp_filetype = wp_check_filetype(basename($file), null); |
| 3881 |
wp_update_post([ |
| 3882 |
'ID' => $attach_id, |
| 3883 |
'post_mime_type' => $wp_filetype['type'], |
| 3884 |
]); |
| 3885 |
update_attached_file($attach_id, $file); |
| 3886 |
} |
| 3887 |
} |
| 3888 |
|
| 3889 |
$attach_data = wp_generate_attachment_metadata($attach_id, $file); |
| 3890 |
wp_update_attachment_metadata($attach_id, $attach_data); |
| 3891 |
set_post_thumbnail($post_id, $attach_id); |
| 3892 |
|
| 3893 |
// Apply stock photo attribution as image caption and alt text |
| 3894 |
if (!empty($image_attribution) && is_array($image_attribution)) { |
| 3895 |
$attribution_mode = get_option('botwriter_stockphoto_attribution', 'caption'); |
| 3896 |
if ($attribution_mode !== 'disabled') { |
| 3897 |
$author = sanitize_text_field($image_attribution['author'] ?? ''); |
| 3898 |
$source = sanitize_text_field($image_attribution['source'] ?? ''); |
| 3899 |
|
| 3900 |
// Build caption: "Photo by Author on Source" |
| 3901 |
$caption = ''; |
| 3902 |
if ($author && $source) { |
| 3903 |
$caption = sprintf( |
| 3904 |
/* translators: 1: photographer name, 2: image source name */ |
| 3905 |
esc_html__('Photo by %1$s on %2$s', 'botwriter'), |
| 3906 |
$author, |
| 3907 |
$source |
| 3908 |
); |
| 3909 |
} elseif ($source) { |
| 3910 |
$caption = sprintf(esc_html__('Photo from %s', 'botwriter'), $source); |
| 3911 |
} |
| 3912 |
|
| 3913 |
if ($caption && $attribution_mode === 'caption') { |
| 3914 |
wp_update_post(array( |
| 3915 |
'ID' => $attach_id, |
| 3916 |
'post_excerpt' => $caption, |
| 3917 |
)); |
| 3918 |
} |
| 3919 |
|
| 3920 |
// Always set a descriptive alt text from the post title |
| 3921 |
update_post_meta($attach_id, '_wp_attachment_image_alt', sanitize_text_field($post_title)); |
| 3922 |
|
| 3923 |
// Store full attribution data as post meta for later use |
| 3924 |
update_post_meta($attach_id, '_botwriter_image_attribution', $image_attribution); |
| 3925 |
} |
| 3926 |
} |
| 3927 |
|
| 3928 |
|
| 3929 |
} |
| 3930 |
} |
| 3931 |
|
| 3932 |
/** |
| 3933 |
* Process image for optimization (resize, compress, convert format) |
| 3934 |
* Uses WordPress wp_get_image_editor for maximum hosting compatibility. |
| 3935 |
* |
| 3936 |
* @param string $file_path Full path to the image file. |
| 3937 |
* @return string|false New file path if processed, original path if no changes, false on error. |
| 3938 |
*/ |
| 3939 |
function botwriter_process_image($file_path) { |
| 3940 |
if (!file_exists($file_path)) { |
| 3941 |
botwriter_log('Image post-processing: File not found', ['path' => $file_path]); |
| 3942 |
return false; |
| 3943 |
} |
| 3944 |
|
| 3945 |
// Get settings |
| 3946 |
$output_format = get_option('botwriter_image_output_format', 'webp'); |
| 3947 |
$max_width = intval(get_option('botwriter_image_max_width', 1200)); |
| 3948 |
$compression = intval(get_option('botwriter_image_compression', 85)); |
| 3949 |
$max_filesize = intval(get_option('botwriter_image_max_filesize', 120)) * 1024; // Convert KB to bytes |
| 3950 |
|
| 3951 |
// Log settings at start |
| 3952 |
$original_size = filesize($file_path); |
| 3953 |
botwriter_log('Image post-processing: Starting', [ |
| 3954 |
'file' => basename($file_path), |
| 3955 |
'original_size_kb' => round($original_size / 1024, 1), |
| 3956 |
'settings' => [ |
| 3957 |
'output_format' => $output_format, |
| 3958 |
'max_width' => $max_width, |
| 3959 |
'compression' => $compression, |
| 3960 |
'max_filesize_kb' => $max_filesize / 1024 |
| 3961 |
] |
| 3962 |
]); |
| 3963 |
|
| 3964 |
// Get WordPress image editor |
| 3965 |
$editor = wp_get_image_editor($file_path); |
| 3966 |
if (is_wp_error($editor)) { |
| 3967 |
botwriter_log('Image post-processing: Failed to load editor', [ |
| 3968 |
'path' => $file_path, |
| 3969 |
'error' => $editor->get_error_message() |
| 3970 |
]); |
| 3971 |
return $file_path; // Return original on error |
| 3972 |
} |
| 3973 |
|
| 3974 |
// Log which editor is being used |
| 3975 |
botwriter_log('Image post-processing: Editor loaded', [ |
| 3976 |
'editor_class' => get_class($editor) |
| 3977 |
]); |
| 3978 |
|
| 3979 |
$size = $editor->get_size(); |
| 3980 |
$modified = false; |
| 3981 |
|
| 3982 |
botwriter_log('Image post-processing: Original dimensions', [ |
| 3983 |
'width' => $size['width'], |
| 3984 |
'height' => $size['height'] |
| 3985 |
]); |
| 3986 |
|
| 3987 |
// Resize if wider than max width |
| 3988 |
if ($max_width > 0 && $size['width'] > $max_width) { |
| 3989 |
$new_height = intval($size['height'] * ($max_width / $size['width'])); |
| 3990 |
$result = $editor->resize($max_width, $new_height, false); |
| 3991 |
if (!is_wp_error($result)) { |
| 3992 |
$modified = true; |
| 3993 |
botwriter_log('Image post-processing: Resized', [ |
| 3994 |
'from' => $size['width'] . 'x' . $size['height'], |
| 3995 |
'to' => $max_width . 'x' . $new_height |
| 3996 |
]); |
| 3997 |
} |
| 3998 |
} |
| 3999 |
|
| 4000 |
// Set quality |
| 4001 |
$editor->set_quality($compression); |
| 4002 |
|
| 4003 |
// Determine output file path and mime type |
| 4004 |
$path_info = pathinfo($file_path); |
| 4005 |
$new_extension = $path_info['extension']; |
| 4006 |
$mime_type = null; |
| 4007 |
|
| 4008 |
if ($output_format !== 'original') { |
| 4009 |
switch ($output_format) { |
| 4010 |
case 'webp': |
| 4011 |
// Check if WebP is supported via GD or Imagick |
| 4012 |
$webp_supported = function_exists('imagewebp'); |
| 4013 |
if (!$webp_supported && extension_loaded('imagick') && class_exists('Imagick')) { |
| 4014 |
// Check Imagick WebP support dynamically to avoid static analysis errors |
| 4015 |
$imagick_formats = call_user_func(['Imagick', 'queryFormats'], 'WEBP'); |
| 4016 |
$webp_supported = !empty($imagick_formats); |
| 4017 |
} |
| 4018 |
if ($webp_supported) { |
| 4019 |
$new_extension = 'webp'; |
| 4020 |
$mime_type = 'image/webp'; |
| 4021 |
} else { |
| 4022 |
// Fallback to JPEG if WebP not supported |
| 4023 |
$new_extension = 'jpg'; |
| 4024 |
$mime_type = 'image/jpeg'; |
| 4025 |
botwriter_log('Image post-processing: WebP not supported, falling back to JPEG'); |
| 4026 |
} |
| 4027 |
break; |
| 4028 |
case 'jpeg': |
| 4029 |
$new_extension = 'jpg'; |
| 4030 |
$mime_type = 'image/jpeg'; |
| 4031 |
break; |
| 4032 |
case 'png': |
| 4033 |
$new_extension = 'png'; |
| 4034 |
$mime_type = 'image/png'; |
| 4035 |
break; |
| 4036 |
} |
| 4037 |
} |
| 4038 |
|
| 4039 |
// Build new file path |
| 4040 |
$new_file_path = $path_info['dirname'] . '/' . $path_info['filename'] . '.' . $new_extension; |
| 4041 |
|
| 4042 |
botwriter_log('Image post-processing: Format decision', [ |
| 4043 |
'original_extension' => $path_info['extension'], |
| 4044 |
'new_extension' => $new_extension, |
| 4045 |
'mime_type' => $mime_type, |
| 4046 |
'new_file_path' => basename($new_file_path) |
| 4047 |
]); |
| 4048 |
|
| 4049 |
// Save the image |
| 4050 |
$save_args = []; |
| 4051 |
if ($mime_type) { |
| 4052 |
$save_args['mime_type'] = $mime_type; |
| 4053 |
} |
| 4054 |
|
| 4055 |
$saved = $editor->save($new_file_path, $mime_type); |
| 4056 |
|
| 4057 |
if (is_wp_error($saved)) { |
| 4058 |
botwriter_log('Image post-processing: Failed to save', [ |
| 4059 |
'path' => $new_file_path, |
| 4060 |
'error' => $saved->get_error_message() |
| 4061 |
]); |
| 4062 |
return $file_path; // Return original on error |
| 4063 |
} |
| 4064 |
|
| 4065 |
$new_file_path = $saved['path']; |
| 4066 |
|
| 4067 |
botwriter_log('Image post-processing: Initial save complete', [ |
| 4068 |
'saved_file' => basename($new_file_path), |
| 4069 |
'size_kb' => round(filesize($new_file_path) / 1024, 1) |
| 4070 |
]); |
| 4071 |
|
| 4072 |
// If max filesize is set and file is too large, reduce quality iteratively |
| 4073 |
if ($max_filesize > 0) { |
| 4074 |
$current_size = filesize($new_file_path); |
| 4075 |
$quality = $compression; |
| 4076 |
$attempts = 0; |
| 4077 |
$max_attempts = 5; |
| 4078 |
|
| 4079 |
botwriter_log('Image post-processing: Checking filesize target', [ |
| 4080 |
'current_kb' => round($current_size / 1024, 1), |
| 4081 |
'target_kb' => $max_filesize / 1024, |
| 4082 |
'needs_compression' => $current_size > $max_filesize |
| 4083 |
]); |
| 4084 |
|
| 4085 |
while ($current_size > $max_filesize && $quality > 40 && $attempts < $max_attempts) { |
| 4086 |
$quality -= 10; |
| 4087 |
$attempts++; |
| 4088 |
|
| 4089 |
botwriter_log('Image post-processing: Compression attempt', [ |
| 4090 |
'attempt' => $attempts, |
| 4091 |
'quality' => $quality |
| 4092 |
]); |
| 4093 |
|
| 4094 |
// Reload and resave with lower quality |
| 4095 |
$editor = wp_get_image_editor($new_file_path); |
| 4096 |
if (!is_wp_error($editor)) { |
| 4097 |
$editor->set_quality($quality); |
| 4098 |
$saved = $editor->save($new_file_path, $mime_type); |
| 4099 |
if (!is_wp_error($saved)) { |
| 4100 |
$current_size = filesize($saved['path']); |
| 4101 |
$new_file_path = $saved['path']; |
| 4102 |
} |
| 4103 |
} |
| 4104 |
} |
| 4105 |
|
| 4106 |
if ($attempts > 0) { |
| 4107 |
botwriter_log('Image post-processing: Compressed for filesize target', [ |
| 4108 |
'target_kb' => $max_filesize / 1024, |
| 4109 |
'final_kb' => round($current_size / 1024, 1), |
| 4110 |
'final_quality' => $quality, |
| 4111 |
'attempts' => $attempts |
| 4112 |
]); |
| 4113 |
} |
| 4114 |
} |
| 4115 |
|
| 4116 |
// Delete original file if format changed |
| 4117 |
if ($new_file_path !== $file_path && file_exists($file_path) && file_exists($new_file_path)) { |
| 4118 |
wp_delete_file($file_path); |
| 4119 |
botwriter_log('Image post-processing: Converted format', [ |
| 4120 |
'from' => $path_info['extension'], |
| 4121 |
'to' => $new_extension, |
| 4122 |
'new_size_kb' => round(filesize($new_file_path) / 1024, 1) |
| 4123 |
]); |
| 4124 |
} |
| 4125 |
|
| 4126 |
// Final summary log |
| 4127 |
$final_size = filesize($new_file_path); |
| 4128 |
botwriter_log('Image post-processing: Complete', [ |
| 4129 |
'original_file' => basename($file_path), |
| 4130 |
'final_file' => basename($new_file_path), |
| 4131 |
'original_size_kb' => round($original_size / 1024, 1), |
| 4132 |
'final_size_kb' => round($final_size / 1024, 1), |
| 4133 |
'size_reduction_percent' => round((1 - ($final_size / $original_size)) * 100, 1) |
| 4134 |
]); |
| 4135 |
|
| 4136 |
return $new_file_path; |
| 4137 |
} |
| 4138 |
|
| 4139 |
|
| 4140 |
|
| 4141 |
add_action('plugins_loaded', 'botwriter_check_update'); |
| 4142 |
function botwriter_check_update() { |
| 4143 |
// Ensure install date exists for legacy installs |
| 4144 |
if (get_option('botwriter_install_date') === false) { |
| 4145 |
update_option('botwriter_install_date', current_time('timestamp')); |
| 4146 |
} |
| 4147 |
// Use constant instead of get_plugin_data() to obtain current plugin version |
| 4148 |
$plugin_version = BOTWRITER_VERSION; |
| 4149 |
|
| 4150 |
// Get the previously installed version |
| 4151 |
$version_instalada = get_option('botwriter_version'); |
| 4152 |
|
| 4153 |
if ($version_instalada != $plugin_version) { |
| 4154 |
botwriter_create_table(); |
| 4155 |
|
| 4156 |
// Insert default templates if none exist (for updates from older versions) |
| 4157 |
botwriter_insert_all_default_templates(); |
| 4158 |
|
| 4159 |
// Migration: reset 'custom' provider to defaults (removed in this version) |
| 4160 |
if (get_option('botwriter_text_provider') === 'custom') { |
| 4161 |
update_option('botwriter_text_provider', 'openai'); |
| 4162 |
} |
| 4163 |
if (get_option('botwriter_image_provider') === 'custom') { |
| 4164 |
update_option('botwriter_image_provider', 'dalle'); |
| 4165 |
} |
| 4166 |
// Clean up custom provider options |
| 4167 |
delete_option('botwriter_custom_text_url'); |
| 4168 |
delete_option('botwriter_custom_text_api_key'); |
| 4169 |
delete_option('botwriter_custom_text_model'); |
| 4170 |
delete_option('botwriter_custom_text_timeout'); |
| 4171 |
delete_option('botwriter_custom_image_url'); |
| 4172 |
delete_option('botwriter_custom_image_type'); |
| 4173 |
delete_option('botwriter_custom_image_model'); |
| 4174 |
delete_option('botwriter_custom_image_timeout'); |
| 4175 |
|
| 4176 |
// Drop direct mode table if it exists |
| 4177 |
global $wpdb; |
| 4178 |
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}botwriter_direct_tasks"); |
| 4179 |
|
| 4180 |
update_option('botwriter_version', $plugin_version); // Update version in database |
| 4181 |
} |
| 4182 |
} |
| 4183 |
|
| 4184 |
|
| 4185 |
|
| 4186 |
// new super functions |
| 4187 |
add_action('wp_ajax_botwriter_actualizar_articulo', 'botwriter_actualizar_articulo_callback'); |
| 4188 |
|
| 4189 |
function botwriter_actualizar_articulo_callback() { |
| 4190 |
// Verify user has permission |
| 4191 |
if (!current_user_can('manage_options')) { |
| 4192 |
wp_send_json_error('Permission denied'); |
| 4193 |
} |
| 4194 |
|
| 4195 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 4196 |
global $wpdb; |
| 4197 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 4198 |
|
| 4199 |
$id = isset($_POST['id']) ? intval($_POST['id']) : 0; |
| 4200 |
$title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : ''; |
| 4201 |
$content = isset($_POST['content']) ? sanitize_textarea_field(wp_unslash($_POST['content'])) : ''; |
| 4202 |
|
| 4203 |
$result = $wpdb->update( |
| 4204 |
$table_name, |
| 4205 |
array('title' => $title, 'content' => $content), |
| 4206 |
array('id' => $id) |
| 4207 |
); |
| 4208 |
|
| 4209 |
if ($result !== false) { |
| 4210 |
wp_send_json_success('Artículo actualizado correctamente.'); |
| 4211 |
} else { |
| 4212 |
wp_send_json_error('Error al actualizar el artículo.'); |
| 4213 |
} |
| 4214 |
|
| 4215 |
wp_die(); |
| 4216 |
} |
| 4217 |
|
| 4218 |
add_action('wp_ajax_botwriter_eliminar_articulo', 'botwriter_eliminar_articulo_callback'); |
| 4219 |
|
| 4220 |
function botwriter_eliminar_articulo_callback() { |
| 4221 |
// Verify user has permission |
| 4222 |
if (!current_user_can('manage_options')) { |
| 4223 |
wp_send_json_error('Permission denied'); |
| 4224 |
} |
| 4225 |
|
| 4226 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 4227 |
global $wpdb; |
| 4228 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 4229 |
|
| 4230 |
$id = isset($_POST['id']) ? intval($_POST['id']) : 0; |
| 4231 |
|
| 4232 |
$result = $wpdb->delete($table_name, array('id' => $id)); |
| 4233 |
// consultar cuantos registros con id_task=0 |
| 4234 |
$result = $wpdb->get_results("SELECT * FROM $table_name WHERE id_task='0'"); |
| 4235 |
$num_rows = count($result); |
| 4236 |
if ($num_rows == 0) { |
| 4237 |
// borramos en la tabla logs la tarea super1 si no hay articulos |
| 4238 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 4239 |
$wpdb->delete($table_name_logs, array('website_type' => 'super1')); |
| 4240 |
} |
| 4241 |
|
| 4242 |
|
| 4243 |
|
| 4244 |
if ($result !== false) { |
| 4245 |
wp_send_json_success('Artículo eliminado correctamente.'); |
| 4246 |
} else { |
| 4247 |
wp_send_json_error('Error al eliminar el artículo.'); |
| 4248 |
} |
| 4249 |
|
| 4250 |
wp_die(); |
| 4251 |
} |
| 4252 |
|
| 4253 |
add_action('wp_ajax_botwriter_check_super1', 'botwriter_check_super1_callback'); |
| 4254 |
|
| 4255 |
function botwriter_check_super1_callback() { |
| 4256 |
// Verify user has permission |
| 4257 |
if (!current_user_can('manage_options')) { |
| 4258 |
wp_send_json_error('Permission denied'); |
| 4259 |
} |
| 4260 |
|
| 4261 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 4262 |
$estado_super1=botwriter_super1_check_task_finish(); |
| 4263 |
if ($estado_super1=="completed") { |
| 4264 |
$response = botwriter_super1_view_articles_html(); |
| 4265 |
wp_send_json_success($response); |
| 4266 |
return; |
| 4267 |
} |
| 4268 |
|
| 4269 |
if ($estado_super1=="error") { |
| 4270 |
// borramos la tarea super1 |
| 4271 |
global $wpdb; |
| 4272 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 4273 |
$wpdb->delete($table_name, array('id_task' => 0)); |
| 4274 |
wp_send_json_error("error"); |
| 4275 |
return; |
| 4276 |
} |
| 4277 |
wp_send_json_error('inqueue'); |
| 4278 |
|
| 4279 |
|
| 4280 |
} |
| 4281 |
|
| 4282 |
|
| 4283 |
add_action('wp_ajax_botwriter_create_super1', 'botwriter_create_super1_callback'); |
| 4284 |
|
| 4285 |
function botwriter_create_super1_callback() { |
| 4286 |
// Verify user has permission |
| 4287 |
if (!current_user_can('manage_options')) { |
| 4288 |
wp_send_json_error('Permission denied'); |
| 4289 |
} |
| 4290 |
|
| 4291 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 4292 |
if (!isset($_POST['prompt']) || !isset($_POST['numarticles'])) { |
| 4293 |
botwriter_log('Super1 creation request missing parameters'); |
| 4294 |
wp_send_json_error('Missing required parameters.'); |
| 4295 |
wp_die(); |
| 4296 |
} |
| 4297 |
|
| 4298 |
$prompt = sanitize_text_field(wp_unslash($_POST['prompt'])); |
| 4299 |
$numarticles = intval(wp_unslash($_POST['numarticles'])); |
| 4300 |
if ($prompt=="Custom") { |
| 4301 |
$category_id = isset($_POST['category_id']) ? sanitize_text_field(wp_unslash($_POST['category_id'])) : '0'; |
| 4302 |
} |
| 4303 |
|
| 4304 |
botwriter_log('Super1 creation request received', [ |
| 4305 |
'prompt' => $prompt, |
| 4306 |
'num_articles' => $numarticles, |
| 4307 |
'category_id' => isset($category_id) ? $category_id : null, |
| 4308 |
]); |
| 4309 |
|
| 4310 |
$title_prompt = $prompt; |
| 4311 |
|
| 4312 |
if ($prompt=="Custom") { |
| 4313 |
$content_prompt = sanitize_text_field(wp_unslash($_POST['custom_prompt'])); |
| 4314 |
} else { |
| 4315 |
$info_blog = botwriter_get_info_blog(); |
| 4316 |
$json_info_blog = json_encode($info_blog, JSON_PRETTY_PRINT); |
| 4317 |
$content_prompt = $json_info_blog; |
| 4318 |
} |
| 4319 |
$task_name = "Super1 Task " . current_time('Y-m-d H:i:s'); |
| 4320 |
$log_id = botwriter_super1_create_task($task_name, $title_prompt,$content_prompt, $numarticles, $category_id); |
| 4321 |
|
| 4322 |
botwriter_log('Super1 task queued', [ |
| 4323 |
'log_id' => $log_id, |
| 4324 |
'title_prompt' => $title_prompt, |
| 4325 |
'num_articles' => $numarticles, |
| 4326 |
]); |
| 4327 |
|
| 4328 |
wp_send_json_success("Task created successfully: " . $title_prompt . " " . $content_prompt . " " . $numarticles . " " . $category_id); |
| 4329 |
|
| 4330 |
|
| 4331 |
} |
| 4332 |
|
| 4333 |
|
| 4334 |
add_action('wp_ajax_botwriter_eliminar_super1', 'botwriter_eliminar_super1_y_logs0'); |
| 4335 |
|
| 4336 |
function botwriter_eliminar_super1_y_logs0() { |
| 4337 |
// Verify user has permission |
| 4338 |
if (!current_user_can('manage_options')) { |
| 4339 |
wp_send_json_error('Permission denied'); |
| 4340 |
} |
| 4341 |
|
| 4342 |
check_ajax_referer('botwriter_super_nonce'); |
| 4343 |
global $wpdb; |
| 4344 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 4345 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 4346 |
|
| 4347 |
$result = $wpdb->delete($table_name, array('id_task' => 0)); |
| 4348 |
$result = $wpdb->delete($table_name_logs, array('website_type' => 'super1')); |
| 4349 |
|
| 4350 |
if ($result !== false) { |
| 4351 |
wp_send_json_success('Super1 task deleted successfully.'); |
| 4352 |
} else { |
| 4353 |
wp_send_json_error('Error'); |
| 4354 |
} |
| 4355 |
wp_die(); |
| 4356 |
} |
| 4357 |
|
| 4358 |
add_action('wp_ajax_botwriter_create_super1_manual', 'botwriter_create_super1_manual_callback'); |
| 4359 |
|
| 4360 |
function botwriter_create_super1_manual_callback() { |
| 4361 |
// Verify user has permission |
| 4362 |
if (!current_user_can('manage_options')) { |
| 4363 |
wp_send_json_error('Permission denied'); |
| 4364 |
} |
| 4365 |
|
| 4366 |
check_ajax_referer('botwriter_super_nonce'); |
| 4367 |
|
| 4368 |
if (!isset($_POST['manual_titles']) || empty(trim(wp_unslash($_POST['manual_titles'])))) { |
| 4369 |
wp_send_json_error('No titles provided.'); |
| 4370 |
wp_die(); |
| 4371 |
} |
| 4372 |
|
| 4373 |
$raw_titles = sanitize_textarea_field(wp_unslash($_POST['manual_titles'])); |
| 4374 |
$global_prompt = isset($_POST['global_prompt']) ? sanitize_textarea_field(wp_unslash($_POST['global_prompt'])) : ''; |
| 4375 |
$category_id = isset($_POST['category_id']) ? intval(wp_unslash($_POST['category_id'])) : 0; |
| 4376 |
|
| 4377 |
// Split titles by newline and filter empty lines |
| 4378 |
$titles = array_filter(array_map('trim', explode("\n", $raw_titles)), function($t) { |
| 4379 |
return !empty($t); |
| 4380 |
}); |
| 4381 |
|
| 4382 |
if (empty($titles)) { |
| 4383 |
wp_send_json_error('No valid titles found.'); |
| 4384 |
wp_die(); |
| 4385 |
} |
| 4386 |
|
| 4387 |
// Limit to 100 titles max |
| 4388 |
if (count($titles) > 100) { |
| 4389 |
$titles = array_slice($titles, 0, 100); |
| 4390 |
} |
| 4391 |
|
| 4392 |
botwriter_log('Manual Super1 creation request', [ |
| 4393 |
'num_titles' => count($titles), |
| 4394 |
'category_id' => $category_id, |
| 4395 |
'has_global_prompt' => !empty($global_prompt), |
| 4396 |
]); |
| 4397 |
|
| 4398 |
global $wpdb; |
| 4399 |
|
| 4400 |
// 1. Create a completed super1 log entry (bypassing AI generation) |
| 4401 |
$log_data = array( |
| 4402 |
'task_name' => 'Manual Super1 Task ' . current_time('Y-m-d H:i:s'), |
| 4403 |
'task_status' => 'completed', |
| 4404 |
'website_type' => 'super1', |
| 4405 |
'title_prompt' => 'Manual', |
| 4406 |
'content_prompt' => $global_prompt, |
| 4407 |
'post_count' => count($titles), |
| 4408 |
'category_id' => $category_id, |
| 4409 |
); |
| 4410 |
$log_id = botwriter_logs_register($log_data); |
| 4411 |
|
| 4412 |
if (!$log_id) { |
| 4413 |
wp_send_json_error('Error creating log entry.'); |
| 4414 |
wp_die(); |
| 4415 |
} |
| 4416 |
|
| 4417 |
// 2. Insert each title into wp_botwriter_super |
| 4418 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 4419 |
$category_name = ''; |
| 4420 |
if ($category_id > 0) { |
| 4421 |
$category_name = get_cat_name($category_id); |
| 4422 |
} |
| 4423 |
|
| 4424 |
foreach ($titles as $title) { |
| 4425 |
$data = array( |
| 4426 |
'title' => sanitize_text_field($title), |
| 4427 |
'content' => $global_prompt, |
| 4428 |
'category_name' => $category_name, |
| 4429 |
'category_id' => $category_id, |
| 4430 |
'id_log' => $log_id, |
| 4431 |
'id_task' => 0, // draft, will be assigned on save |
| 4432 |
'task_status' => '', |
| 4433 |
); |
| 4434 |
$wpdb->insert($table_name, $data); |
| 4435 |
} |
| 4436 |
|
| 4437 |
botwriter_log('Manual Super1 titles inserted', [ |
| 4438 |
'log_id' => $log_id, |
| 4439 |
'count' => count($titles), |
| 4440 |
]); |
| 4441 |
|
| 4442 |
// 3. Return the articles HTML for immediate review |
| 4443 |
$html = botwriter_super1_view_articles_html(0); |
| 4444 |
wp_send_json_success($html); |
| 4445 |
} |
| 4446 |
|
| 4447 |
|
| 4448 |
// ======================================== |
| 4449 |
// CONTENT REWRITER AJAX HANDLERS |
| 4450 |
// ======================================== |
| 4451 |
add_action('wp_ajax_botwriter_rewriter_fetch', 'botwriter_rewriter_fetch_ajax'); |
| 4452 |
add_action('wp_ajax_botwriter_rewriter_create_task', 'botwriter_rewriter_create_task_ajax'); |
| 4453 |
|
| 4454 |
/** |
| 4455 |
* AJAX: Fetch and extract content from URLs |
| 4456 |
*/ |
| 4457 |
function botwriter_rewriter_fetch_ajax() { |
| 4458 |
check_ajax_referer('botwriter_rewriter_nonce'); |
| 4459 |
|
| 4460 |
if (!current_user_can('manage_options')) { |
| 4461 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 4462 |
} |
| 4463 |
|
| 4464 |
$urls = isset($_POST['urls']) ? array_map('esc_url_raw', $_POST['urls']) : array(); |
| 4465 |
|
| 4466 |
if (empty($urls)) { |
| 4467 |
wp_send_json_error(__('No URLs provided.', 'botwriter')); |
| 4468 |
} |
| 4469 |
|
| 4470 |
if (count($urls) > 20) { |
| 4471 |
wp_send_json_error(__('Maximum 20 URLs allowed.', 'botwriter')); |
| 4472 |
} |
| 4473 |
|
| 4474 |
$articles = array(); |
| 4475 |
$errors = array(); |
| 4476 |
|
| 4477 |
foreach ($urls as $url) { |
| 4478 |
if (empty($url)) { |
| 4479 |
continue; |
| 4480 |
} |
| 4481 |
|
| 4482 |
$result = botwriter_rewriter_extract_content($url); |
| 4483 |
|
| 4484 |
if (is_wp_error($result)) { |
| 4485 |
$errors[] = array( |
| 4486 |
'url' => $url, |
| 4487 |
'error' => $result->get_error_message(), |
| 4488 |
); |
| 4489 |
} else { |
| 4490 |
$articles[] = $result; |
| 4491 |
} |
| 4492 |
} |
| 4493 |
|
| 4494 |
wp_send_json_success(array( |
| 4495 |
'articles' => $articles, |
| 4496 |
'errors' => $errors, |
| 4497 |
)); |
| 4498 |
} |
| 4499 |
|
| 4500 |
/** |
| 4501 |
* AJAX: Create a Super Task with the articles to rewrite. |
| 4502 |
* |
| 4503 |
* Inserts the task as an active super2 task and stores each article |
| 4504 |
* in the botwriter_super table. The cron loop picks them up via |
| 4505 |
* botwriter_super_prepare_event() just like normal Super Tasks. |
| 4506 |
*/ |
| 4507 |
function botwriter_rewriter_create_task_ajax() { |
| 4508 |
check_ajax_referer('botwriter_rewriter_nonce'); |
| 4509 |
|
| 4510 |
if (!current_user_can('manage_options')) { |
| 4511 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 4512 |
} |
| 4513 |
|
| 4514 |
// wp_unslash only — sanitize_text_field corrupts JSON (strips tags/newlines) |
| 4515 |
$articles_json = isset($_POST['articles']) ? wp_unslash($_POST['articles']) : ''; |
| 4516 |
$rewrite_prompt = isset($_POST['rewrite_prompt']) ? sanitize_textarea_field(wp_unslash($_POST['rewrite_prompt'])) : ''; |
| 4517 |
$category_id = isset($_POST['category_id']) ? intval($_POST['category_id']) : 0; |
| 4518 |
|
| 4519 |
// Task properties from Step 3 form |
| 4520 |
$post_status = isset($_POST['post_status']) ? sanitize_text_field(wp_unslash($_POST['post_status'])) : 'draft'; |
| 4521 |
$post_language = isset($_POST['post_language']) ? sanitize_text_field(wp_unslash($_POST['post_language'])) : substr(get_locale(), 0, 2); |
| 4522 |
$author_selection = isset($_POST['author_selection']) ? sanitize_text_field(wp_unslash($_POST['author_selection'])) : strval(get_current_user_id()); |
| 4523 |
$post_length = isset($_POST['post_length']) ? sanitize_text_field(wp_unslash($_POST['post_length'])) : '800'; |
| 4524 |
$custom_post_length = isset($_POST['custom_post_length']) ? sanitize_text_field(wp_unslash($_POST['custom_post_length'])) : ''; |
| 4525 |
$template_id = isset($_POST['template_id']) && !empty($_POST['template_id']) ? intval($_POST['template_id']) : null; |
| 4526 |
$disable_ai_images = isset($_POST['disable_ai_images']) ? intval($_POST['disable_ai_images']) : 0; |
| 4527 |
$days = isset($_POST['days']) ? sanitize_text_field(wp_unslash($_POST['days'])) : 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'; |
| 4528 |
$times_per_day = isset($_POST['times_per_day']) ? intval($_POST['times_per_day']) : 1; |
| 4529 |
$task_name_custom = isset($_POST['task_name']) ? sanitize_text_field(wp_unslash($_POST['task_name'])) : ''; |
| 4530 |
|
| 4531 |
// Validate post_status |
| 4532 |
if (!in_array($post_status, array('draft', 'publish'), true)) { |
| 4533 |
$post_status = 'draft'; |
| 4534 |
} |
| 4535 |
|
| 4536 |
$articles = json_decode($articles_json, true); |
| 4537 |
if (!is_array($articles) || empty($articles)) { |
| 4538 |
wp_send_json_error(__('No articles provided.', 'botwriter')); |
| 4539 |
} |
| 4540 |
|
| 4541 |
global $wpdb; |
| 4542 |
$tasks_table = $wpdb->prefix . 'botwriter_tasks'; |
| 4543 |
$super_table = $wpdb->prefix . 'botwriter_super'; |
| 4544 |
|
| 4545 |
// Build default rewrite prompt if user didn't provide one |
| 4546 |
if (empty($rewrite_prompt)) { |
| 4547 |
$rewrite_prompt = 'Rewrite this article completely in your own words while preserving all key information. Make it original, engaging, and well-structured.'; |
| 4548 |
} |
| 4549 |
|
| 4550 |
// Count valid articles (filter empties before inserting) |
| 4551 |
$valid_articles = array(); |
| 4552 |
foreach ($articles as $article) { |
| 4553 |
$title = sanitize_text_field($article['title'] ?? ''); |
| 4554 |
$content = wp_kses_post($article['content'] ?? ''); |
| 4555 |
if (!empty($title) || !empty($content)) { |
| 4556 |
$valid_articles[] = array('title' => $title, 'content' => $content); |
| 4557 |
} |
| 4558 |
} |
| 4559 |
|
| 4560 |
if (empty($valid_articles)) { |
| 4561 |
wp_send_json_error(__('All articles are empty.', 'botwriter')); |
| 4562 |
} |
| 4563 |
|
| 4564 |
$task_name = !empty($task_name_custom) ? $task_name_custom : 'Rewriter: ' . count($valid_articles) . ' articles - ' . wp_date('M j, Y H:i'); |
| 4565 |
|
| 4566 |
// Use the days and times_per_day from the form |
| 4567 |
$wpdb->insert($tasks_table, array( |
| 4568 |
'task_name' => $task_name, |
| 4569 |
'post_status' => $post_status, |
| 4570 |
'writer' => 'ai_cerebro', |
| 4571 |
'narration' => 'Descriptive', |
| 4572 |
'custom_style' => '', |
| 4573 |
'post_language' => $post_language, |
| 4574 |
'post_length' => $post_length, |
| 4575 |
'custom_post_length' => $custom_post_length, |
| 4576 |
'days' => $days, |
| 4577 |
'times_per_day' => $times_per_day > 0 ? $times_per_day : 1, |
| 4578 |
'status' => 1, |
| 4579 |
'website_type' => 'super2', |
| 4580 |
'task_type' => 'rewriter', |
| 4581 |
'domain_name' => get_site_url(), |
| 4582 |
'category_id' => $category_id > 0 ? strval($category_id) : '', |
| 4583 |
'title_prompt' => '', |
| 4584 |
'content_prompt' => $rewrite_prompt, |
| 4585 |
'tags_prompt' => '', |
| 4586 |
'image_prompt' => '', |
| 4587 |
'aigenerated_title' => '', |
| 4588 |
'aigenerated_content' => '', |
| 4589 |
'aigenerated_tags' => '', |
| 4590 |
'aigenerated_image' => '', |
| 4591 |
'ai_keywords' => '', |
| 4592 |
'author_selection' => $author_selection, |
| 4593 |
'disable_ai_images' => $disable_ai_images, |
| 4594 |
'template_id' => $template_id, |
| 4595 |
)); |
| 4596 |
|
| 4597 |
$task_id = $wpdb->insert_id; |
| 4598 |
|
| 4599 |
if (!$task_id) { |
| 4600 |
wp_send_json_error(__('Failed to create task.', 'botwriter')); |
| 4601 |
} |
| 4602 |
|
| 4603 |
// Insert each article into the super table. |
| 4604 |
// Only the article content goes here — rewrite instructions stay in the task's content_prompt. |
| 4605 |
// super_prepare_event() preserves the rewrite_prompt, and |
| 4606 |
// botwriter_build_client_prompt() outputs it BEFORE the ENDARTICLE-wrapped content. |
| 4607 |
$inserted = 0; |
| 4608 |
foreach ($valid_articles as $art) { |
| 4609 |
$wpdb->insert($super_table, array( |
| 4610 |
'id_task' => $task_id, |
| 4611 |
'id_log' => 0, |
| 4612 |
'title' => $art['title'], |
| 4613 |
'content' => $art['content'], |
| 4614 |
)); |
| 4615 |
// task_status left NULL — super_prepare_event picks rows with NULL/empty task_status |
| 4616 |
$inserted++; |
| 4617 |
} |
| 4618 |
|
| 4619 |
botwriter_log('Content Rewriter: Task created', [ |
| 4620 |
'task_id' => $task_id, |
| 4621 |
'article_count' => $inserted, |
| 4622 |
]); |
| 4623 |
|
| 4624 |
wp_send_json_success(array( |
| 4625 |
'task_id' => $task_id, |
| 4626 |
'count' => $inserted, |
| 4627 |
'edit_url' => wp_nonce_url(admin_url('admin.php?page=botwriter_super_page&id=' . $task_id), 'botwriter_tasks_action'), |
| 4628 |
)); |
| 4629 |
} |
| 4630 |
|
| 4631 |
// ======================================== |
| 4632 |
// SITE REWRITER AJAX HANDLERS |
| 4633 |
// ======================================== |
| 4634 |
add_action('wp_ajax_botwriter_siterewriter_crawl', 'botwriter_siterewriter_crawl_ajax'); |
| 4635 |
add_action('wp_ajax_botwriter_siterewriter_fetch', 'botwriter_siterewriter_fetch_ajax'); |
| 4636 |
add_action('wp_ajax_botwriter_siterewriter_create_task', 'botwriter_siterewriter_create_task_ajax'); |
| 4637 |
|
| 4638 |
/** |
| 4639 |
* AJAX: Crawl a single page — returns title + internal links. |
| 4640 |
* The JS manages the BFS queue for live/progressive UI updates. |
| 4641 |
*/ |
| 4642 |
function botwriter_siterewriter_crawl_ajax() { |
| 4643 |
check_ajax_referer('botwriter_siterewriter_nonce'); |
| 4644 |
|
| 4645 |
if (!current_user_can('manage_options')) { |
| 4646 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 4647 |
} |
| 4648 |
|
| 4649 |
$url = isset($_POST['url']) ? esc_url_raw(wp_unslash($_POST['url'])) : ''; |
| 4650 |
$base_domain = isset($_POST['base_domain']) ? sanitize_text_field(wp_unslash($_POST['base_domain'])) : ''; |
| 4651 |
|
| 4652 |
if (empty($url) || empty($base_domain)) { |
| 4653 |
wp_send_json_error(__('Missing URL or domain.', 'botwriter')); |
| 4654 |
} |
| 4655 |
|
| 4656 |
$result = botwriter_siterewriter_crawl_page($url, $base_domain); |
| 4657 |
|
| 4658 |
if (is_wp_error($result)) { |
| 4659 |
wp_send_json_error($result->get_error_message()); |
| 4660 |
} |
| 4661 |
|
| 4662 |
wp_send_json_success($result); |
| 4663 |
} |
| 4664 |
|
| 4665 |
/** |
| 4666 |
* AJAX: Fetch and extract content from selected URLs. |
| 4667 |
* Reuses botwriter_rewriter_extract_content() for full content extraction. |
| 4668 |
*/ |
| 4669 |
function botwriter_siterewriter_fetch_ajax() { |
| 4670 |
check_ajax_referer('botwriter_siterewriter_nonce'); |
| 4671 |
|
| 4672 |
if (!current_user_can('manage_options')) { |
| 4673 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 4674 |
} |
| 4675 |
|
| 4676 |
@set_time_limit(0); |
| 4677 |
|
| 4678 |
$urls = isset($_POST['urls']) ? array_map('esc_url_raw', $_POST['urls']) : array(); |
| 4679 |
|
| 4680 |
if (empty($urls)) { |
| 4681 |
wp_send_json_error(__('No URLs provided.', 'botwriter')); |
| 4682 |
} |
| 4683 |
|
| 4684 |
$articles = array(); |
| 4685 |
$errors = array(); |
| 4686 |
|
| 4687 |
foreach ($urls as $url) { |
| 4688 |
if (empty($url)) continue; |
| 4689 |
|
| 4690 |
$result = botwriter_rewriter_extract_content($url); |
| 4691 |
|
| 4692 |
if (is_wp_error($result)) { |
| 4693 |
$errors[] = array( |
| 4694 |
'url' => $url, |
| 4695 |
'error' => $result->get_error_message(), |
| 4696 |
); |
| 4697 |
} else { |
| 4698 |
$articles[] = $result; |
| 4699 |
} |
| 4700 |
} |
| 4701 |
|
| 4702 |
wp_send_json_success(array( |
| 4703 |
'articles' => $articles, |
| 4704 |
'errors' => $errors, |
| 4705 |
)); |
| 4706 |
} |
| 4707 |
|
| 4708 |
/** |
| 4709 |
* AJAX: Create a Super Task with the articles to rewrite. |
| 4710 |
* Identical flow to Content Rewriter but with task_type = 'siterewriter'. |
| 4711 |
*/ |
| 4712 |
function botwriter_siterewriter_create_task_ajax() { |
| 4713 |
check_ajax_referer('botwriter_siterewriter_nonce'); |
| 4714 |
|
| 4715 |
if (!current_user_can('manage_options')) { |
| 4716 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 4717 |
} |
| 4718 |
|
| 4719 |
$articles_json = isset($_POST['articles']) ? wp_unslash($_POST['articles']) : ''; |
| 4720 |
$rewrite_prompt = isset($_POST['rewrite_prompt']) ? sanitize_textarea_field(wp_unslash($_POST['rewrite_prompt'])) : ''; |
| 4721 |
$category_id = isset($_POST['category_id']) ? intval($_POST['category_id']) : 0; |
| 4722 |
|
| 4723 |
$post_status = isset($_POST['post_status']) ? sanitize_text_field(wp_unslash($_POST['post_status'])) : 'draft'; |
| 4724 |
$post_language = isset($_POST['post_language']) ? sanitize_text_field(wp_unslash($_POST['post_language'])) : substr(get_locale(), 0, 2); |
| 4725 |
$author_selection = isset($_POST['author_selection']) ? sanitize_text_field(wp_unslash($_POST['author_selection'])) : strval(get_current_user_id()); |
| 4726 |
$post_length = isset($_POST['post_length']) ? sanitize_text_field(wp_unslash($_POST['post_length'])) : '800'; |
| 4727 |
$custom_post_length = isset($_POST['custom_post_length'])? sanitize_text_field(wp_unslash($_POST['custom_post_length'])): ''; |
| 4728 |
$template_id = isset($_POST['template_id']) && !empty($_POST['template_id']) ? intval($_POST['template_id']) : null; |
| 4729 |
$disable_ai_images = isset($_POST['disable_ai_images']) ? intval($_POST['disable_ai_images']) : 0; |
| 4730 |
$days = isset($_POST['days']) ? sanitize_text_field(wp_unslash($_POST['days'])) : 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'; |
| 4731 |
$times_per_day = isset($_POST['times_per_day']) ? intval($_POST['times_per_day']) : 1; |
| 4732 |
$task_name_custom = isset($_POST['task_name']) ? sanitize_text_field(wp_unslash($_POST['task_name'])) : ''; |
| 4733 |
|
| 4734 |
if (!in_array($post_status, array('draft', 'publish'), true)) { |
| 4735 |
$post_status = 'draft'; |
| 4736 |
} |
| 4737 |
|
| 4738 |
$articles = json_decode($articles_json, true); |
| 4739 |
if (!is_array($articles) || empty($articles)) { |
| 4740 |
wp_send_json_error(__('No articles provided.', 'botwriter')); |
| 4741 |
} |
| 4742 |
|
| 4743 |
global $wpdb; |
| 4744 |
$tasks_table = $wpdb->prefix . 'botwriter_tasks'; |
| 4745 |
$super_table = $wpdb->prefix . 'botwriter_super'; |
| 4746 |
|
| 4747 |
if (empty($rewrite_prompt)) { |
| 4748 |
$rewrite_prompt = 'Rewrite this article completely in your own words while preserving all key information. Make it original, engaging, and well-structured.'; |
| 4749 |
} |
| 4750 |
|
| 4751 |
$valid_articles = array(); |
| 4752 |
foreach ($articles as $article) { |
| 4753 |
$title = sanitize_text_field($article['title'] ?? ''); |
| 4754 |
$content = wp_kses_post($article['content'] ?? ''); |
| 4755 |
if (!empty($title) || !empty($content)) { |
| 4756 |
$valid_articles[] = array('title' => $title, 'content' => $content); |
| 4757 |
} |
| 4758 |
} |
| 4759 |
|
| 4760 |
if (empty($valid_articles)) { |
| 4761 |
wp_send_json_error(__('All articles are empty.', 'botwriter')); |
| 4762 |
} |
| 4763 |
|
| 4764 |
$task_name = !empty($task_name_custom) |
| 4765 |
? $task_name_custom |
| 4766 |
: 'Site Rewriter: ' . count($valid_articles) . ' articles - ' . wp_date('M j, Y H:i'); |
| 4767 |
|
| 4768 |
$wpdb->insert($tasks_table, array( |
| 4769 |
'task_name' => $task_name, |
| 4770 |
'post_status' => $post_status, |
| 4771 |
'writer' => 'ai_cerebro', |
| 4772 |
'narration' => 'Descriptive', |
| 4773 |
'custom_style' => '', |
| 4774 |
'post_language' => $post_language, |
| 4775 |
'post_length' => $post_length, |
| 4776 |
'custom_post_length' => $custom_post_length, |
| 4777 |
'days' => $days, |
| 4778 |
'times_per_day' => $times_per_day > 0 ? $times_per_day : 1, |
| 4779 |
'status' => 1, |
| 4780 |
'website_type' => 'super2', |
| 4781 |
'task_type' => 'siterewriter', |
| 4782 |
'domain_name' => get_site_url(), |
| 4783 |
'category_id' => $category_id > 0 ? strval($category_id) : '', |
| 4784 |
'title_prompt' => '', |
| 4785 |
'content_prompt' => $rewrite_prompt, |
| 4786 |
'tags_prompt' => '', |
| 4787 |
'image_prompt' => '', |
| 4788 |
'aigenerated_title' => '', |
| 4789 |
'aigenerated_content' => '', |
| 4790 |
'aigenerated_tags' => '', |
| 4791 |
'aigenerated_image' => '', |
| 4792 |
'ai_keywords' => '', |
| 4793 |
'author_selection' => $author_selection, |
| 4794 |
'disable_ai_images' => $disable_ai_images, |
| 4795 |
'template_id' => $template_id, |
| 4796 |
)); |
| 4797 |
|
| 4798 |
$task_id = $wpdb->insert_id; |
| 4799 |
|
| 4800 |
if (!$task_id) { |
| 4801 |
wp_send_json_error(__('Failed to create task.', 'botwriter')); |
| 4802 |
} |
| 4803 |
|
| 4804 |
// Insert each article into the super table. |
| 4805 |
// Only the article content goes here — rewrite instructions stay in the task's content_prompt. |
| 4806 |
// super_prepare_event() preserves the rewrite_prompt, and |
| 4807 |
// botwriter_build_client_prompt() outputs it BEFORE the ENDARTICLE-wrapped content. |
| 4808 |
$inserted = 0; |
| 4809 |
foreach ($valid_articles as $art) { |
| 4810 |
$wpdb->insert($super_table, array( |
| 4811 |
'id_task' => $task_id, |
| 4812 |
'id_log' => 0, |
| 4813 |
'title' => $art['title'], |
| 4814 |
'content' => $art['content'], |
| 4815 |
)); |
| 4816 |
$inserted++; |
| 4817 |
} |
| 4818 |
|
| 4819 |
botwriter_log('Site Rewriter: Task created', [ |
| 4820 |
'task_id' => $task_id, |
| 4821 |
'article_count' => $inserted, |
| 4822 |
]); |
| 4823 |
|
| 4824 |
wp_send_json_success(array( |
| 4825 |
'task_id' => $task_id, |
| 4826 |
'count' => $inserted, |
| 4827 |
'edit_url' => wp_nonce_url(admin_url('admin.php?page=botwriter_super_page&id=' . $task_id), 'botwriter_tasks_action'), |
| 4828 |
)); |
| 4829 |
} |
| 4830 |
|
| 4831 |
?> |
| 4832 |
|