| 1 |
<?php |
| 2 |
/* |
| 3 |
Plugin Name: BotWriter – AI Writer & SEO 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.3.7 |
| 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.3.7'); |
| 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 |
if (!function_exists('botwriter_is_seo_module_enabled')) { |
| 45 |
function botwriter_is_seo_module_enabled() { |
| 46 |
return get_option('botwriter_seo_module_enabled', '1') === '1'; |
| 47 |
} |
| 48 |
} |
| 49 |
|
| 50 |
|
| 51 |
/** |
| 52 |
* Returns absolute filesystem path to the plugin debug log file |
| 53 |
* (wp-content/uploads/botwriter-debug.log) or false if uploads dir |
| 54 |
* is not writable. |
| 55 |
*/ |
| 56 |
if (!function_exists('botwriter_debug_log_path')) { |
| 57 |
function botwriter_debug_log_path() { |
| 58 |
$uploads = wp_upload_dir(); |
| 59 |
if (!empty($uploads['error'])) { |
| 60 |
return false; |
| 61 |
} |
| 62 |
$base = isset($uploads['basedir']) ? $uploads['basedir'] : ''; |
| 63 |
if (!$base) { |
| 64 |
return false; |
| 65 |
} |
| 66 |
return trailingslashit($base) . 'botwriter-debug.log'; |
| 67 |
} |
| 68 |
} |
| 69 |
|
| 70 |
/** |
| 71 |
* Whether the UI-toggle for debug logging to file is enabled. |
| 72 |
*/ |
| 73 |
if (!function_exists('botwriter_debug_log_enabled')) { |
| 74 |
function botwriter_debug_log_enabled() { |
| 75 |
return get_option('botwriter_debug_logging', '0') === '1'; |
| 76 |
} |
| 77 |
} |
| 78 |
|
| 79 |
|
| 80 |
if (!function_exists('botwriter_log')) { |
| 81 |
function botwriter_log($message, array $context = []) { |
| 82 |
$botwriter_debug = defined('BOTWRITER_DEBUG') && BOTWRITER_DEBUG === true; |
| 83 |
$wp_debug_log = defined('WP_DEBUG') && WP_DEBUG === true && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG; |
| 84 |
$file_log = botwriter_debug_log_enabled(); |
| 85 |
if (!$botwriter_debug && !$wp_debug_log && !$file_log) { |
| 86 |
return; |
| 87 |
} |
| 88 |
|
| 89 |
if (!is_string($message)) { |
| 90 |
$encoded = wp_json_encode($message); |
| 91 |
if ($encoded !== false) { |
| 92 |
$message = $encoded; |
| 93 |
} else { |
| 94 |
// Fallback to safe string/serialization without using print_r |
| 95 |
if (is_scalar($message)) { |
| 96 |
$message = (string) $message; |
| 97 |
} else { |
| 98 |
$message = maybe_serialize($message); |
| 99 |
} |
| 100 |
} |
| 101 |
} |
| 102 |
|
| 103 |
if (!empty($context)) { |
| 104 |
$encoded_context = wp_json_encode($context); |
| 105 |
if ($encoded_context !== false) { |
| 106 |
$message .= ' ' . $encoded_context; |
| 107 |
} |
| 108 |
} |
| 109 |
|
| 110 |
$line = '[BotWriter] ' . $message; |
| 111 |
|
| 112 |
if ($botwriter_debug || $wp_debug_log) { |
| 113 |
error_log($line); |
| 114 |
} |
| 115 |
|
| 116 |
if ($file_log) { |
| 117 |
$path = botwriter_debug_log_path(); |
| 118 |
if ($path) { |
| 119 |
// Cap file size at 5 MB by truncating-then-rotating in place. |
| 120 |
if (file_exists($path) && filesize($path) > 5 * 1024 * 1024) { |
| 121 |
@file_put_contents($path, ''); |
| 122 |
} |
| 123 |
$stamp = gmdate('Y-m-d H:i:s'); |
| 124 |
@file_put_contents( |
| 125 |
$path, |
| 126 |
'[' . $stamp . ' UTC] ' . $line . "\n", |
| 127 |
FILE_APPEND | LOCK_EX |
| 128 |
); |
| 129 |
} |
| 130 |
} |
| 131 |
} |
| 132 |
} |
| 133 |
|
| 134 |
|
| 135 |
/** |
| 136 |
* Add plugin action links (Settings link next to Deactivate) |
| 137 |
*/ |
| 138 |
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'botwriter_plugin_action_links'); |
| 139 |
function botwriter_plugin_action_links($links) { |
| 140 |
$settings_link = '<a href="' . admin_url('admin.php?page=botwriter_settings') . '">' . __('Settings', 'botwriter') . '</a>'; |
| 141 |
array_unshift($links, $settings_link); |
| 142 |
return $links; |
| 143 |
} |
| 144 |
|
| 145 |
/** |
| 146 |
* Add plugin row meta links (Website, FAQ, Support below plugin description) |
| 147 |
*/ |
| 148 |
add_filter('plugin_row_meta', 'botwriter_plugin_row_meta', 10, 2); |
| 149 |
function botwriter_plugin_row_meta($links, $file) { |
| 150 |
if (plugin_basename(__FILE__) === $file) { |
| 151 |
$row_meta = array( |
| 152 |
'website' => '<a href="https://www.wpbotwriter.com" target="_blank" rel="noopener">' . __('Website', 'botwriter') . '</a>', |
| 153 |
'faq' => '<a href="https://wpbotwriter.com/faq.html" target="_blank" rel="noopener">' . __('FAQ', 'botwriter') . '</a>', |
| 154 |
'support' => '<a href="https://wordpress.org/support/plugin/botwriter/" target="_blank" rel="noopener">' . __('Support', 'botwriter') . '</a>', |
| 155 |
); |
| 156 |
return array_merge($links, $row_meta); |
| 157 |
} |
| 158 |
return $links; |
| 159 |
} |
| 160 |
|
| 161 |
|
| 162 |
require plugin_dir_path( __FILE__ ) . 'includes/posts.php'; |
| 163 |
require plugin_dir_path( __FILE__ ) . 'includes/functions.php'; |
| 164 |
require plugin_dir_path( __FILE__ ) . 'includes/settings.php'; |
| 165 |
require plugin_dir_path( __FILE__ ) . 'includes/logs.php'; |
| 166 |
require plugin_dir_path( __FILE__ ) . 'includes/dedup.php'; |
| 167 |
require plugin_dir_path( __FILE__ ) . 'includes/announcements.php'; |
| 168 |
require plugin_dir_path( __FILE__ ) . 'includes/super.php'; |
| 169 |
require plugin_dir_path( __FILE__ ) . 'includes/addnew.php'; |
| 170 |
require plugin_dir_path( __FILE__ ) . 'includes/quickpost.php'; |
| 171 |
require plugin_dir_path( __FILE__ ) . 'includes/rewriter.php'; |
| 172 |
require plugin_dir_path( __FILE__ ) . 'includes/siterewriter.php'; |
| 173 |
require plugin_dir_path( __FILE__ ) . 'includes/templates.php'; |
| 174 |
require plugin_dir_path( __FILE__ ) . 'includes/default-templates.php'; |
| 175 |
if (botwriter_is_seo_module_enabled()) { |
| 176 |
require plugin_dir_path( __FILE__ ) . 'includes/seo/seo.php'; |
| 177 |
} |
| 178 |
|
| 179 |
// WooCommerce AI Content Optimizer (loads only when WooCommerce is active) |
| 180 |
require plugin_dir_path( __FILE__ ) . 'includes/woocommerce-ai/class-bw-woo-ai.php'; |
| 181 |
add_action( 'plugins_loaded', function () { |
| 182 |
$bw_woo_ai = new BotWriter_Woo_AI(); |
| 183 |
$bw_woo_ai->init(); |
| 184 |
} ); |
| 185 |
|
| 186 |
|
| 187 |
// Enqueque JS Files |
| 188 |
function botwriter_enqueue_scripts() { |
| 189 |
$my_plugin_dir = plugin_dir_url(__FILE__); |
| 190 |
$screen = get_current_screen(); |
| 191 |
$slug = $screen->id; |
| 192 |
|
| 193 |
|
| 194 |
|
| 195 |
wp_register_script( 'bootstrapjs',$my_plugin_dir.'/assets/js/bootstrap.min.js' , array('jquery'), false, true ); |
| 196 |
wp_enqueue_script( 'bootstrapjs' ); |
| 197 |
|
| 198 |
|
| 199 |
wp_register_script( 'botwriter_bootstrap_bundle',$my_plugin_dir.'/assets/js/bootstrap.bundle.min.js' , array('jquery'), false, true ); |
| 200 |
wp_enqueue_script( 'botwriter_bootstrap_bundle' ); |
| 201 |
|
| 202 |
|
| 203 |
wp_register_script( 'botwriter_botwriter',$my_plugin_dir.'/assets/js/botwriter.js' , array('jquery'), false, true ); |
| 204 |
wp_enqueue_script( 'botwriter_botwriter' ); |
| 205 |
wp_localize_script('botwriter_botwriter', 'botwriter_ajax', array( |
| 206 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 207 |
'nonce' => wp_create_nonce('botwriter_super_nonce'), |
| 208 |
'rss_nonce' => wp_create_nonce('botwriter_check_rss_nonce'), |
| 209 |
'wp_categories_nonce' => wp_create_nonce('botwriter_wp_categories_nonce'), |
| 210 |
)); |
| 211 |
|
| 212 |
wp_enqueue_script('botwriter-admin-ajax-status', $my_plugin_dir.'/assets/js/admin-ajax-status.js', ['jquery'], null, true); |
| 213 |
wp_localize_script('botwriter-admin-ajax-status', 'botwriter_ajax_object', [ |
| 214 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 215 |
'nonce' => wp_create_nonce('botwriter_cambiar_status_nonce') |
| 216 |
]); |
| 217 |
|
| 218 |
|
| 219 |
wp_enqueue_script('botwriter-dismiss-script', $my_plugin_dir . '/assets/js/botwriter_dismiss.js', array('jquery'), null, true); |
| 220 |
wp_localize_script('botwriter-dismiss-script','botwriterData', |
| 221 |
array( |
| 222 |
'nonce' => wp_create_nonce('botwriter_dismiss_nonce'), |
| 223 |
'ajaxurl' => admin_url('admin-ajax.php') |
| 224 |
) |
| 225 |
); |
| 226 |
|
| 227 |
|
| 228 |
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') { |
| 229 |
wp_register_script('botwriter_automatic_posts', $my_plugin_dir . 'assets/js/posts.js', array('jquery'), false, true); |
| 230 |
wp_enqueue_script('botwriter_automatic_posts'); |
| 231 |
wp_localize_script('botwriter_automatic_posts', 'botwriter_posts_ajax', array( |
| 232 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 233 |
'taxonomies_nonce' => wp_create_nonce('botwriter_taxonomies_nonce'), |
| 234 |
)); |
| 235 |
} |
| 236 |
|
| 237 |
|
| 238 |
if ($slug==="botwriter_page_botwriter_logs") { |
| 239 |
wp_register_script('botwriter_logs', $my_plugin_dir . 'assets/js/logs.js', array('jquery'), false, true); |
| 240 |
wp_enqueue_script('botwriter_logs'); |
| 241 |
wp_localize_script('botwriter_logs', 'botwriter_logs_vars', array( |
| 242 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 243 |
'nonce' => wp_create_nonce('botwriter_logs_delete_nonce'), |
| 244 |
'confirm_delete' => __('Are you sure you want to delete this log entry? This action cannot be undone.', 'botwriter'), |
| 245 |
'confirm_bulk_delete' => __('Are you sure you want to delete the selected log entries? This action cannot be undone.', 'botwriter'), |
| 246 |
'error_delete' => __('Error deleting log. Please try again.', 'botwriter'), |
| 247 |
)); |
| 248 |
|
| 249 |
// Reuse the featured image regeneration modal inside BotWriter logs. |
| 250 |
wp_register_script('botwriter_post_image_regeneration', $my_plugin_dir . 'assets/js/post-image-regeneration.js', array('jquery'), BOTWRITER_VERSION, true); |
| 251 |
wp_enqueue_script('botwriter_post_image_regeneration'); |
| 252 |
wp_localize_script('botwriter_post_image_regeneration', 'botwriter_post_image_regeneration', array( |
| 253 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 254 |
'nonce' => wp_create_nonce('botwriter_regenerate_image_nonce'), |
| 255 |
'i18n' => array( |
| 256 |
'link_text' => __('Regenerate', 'botwriter'), |
| 257 |
'modal_title' => __('BotWriter Image Regeneration', 'botwriter'), |
| 258 |
'modal_subtitle' => __('Generate and preview a featured image before applying it.', 'botwriter'), |
| 259 |
'provider' => __('Current provider', 'botwriter'), |
| 260 |
'model' => __('Current model', 'botwriter'), |
| 261 |
'prompt_label' => __('Image Prompt', 'botwriter'), |
| 262 |
'cleanup_label' => __('Previous featured image', 'botwriter'), |
| 263 |
'cleanup_keep' => __('Keep in media library', 'botwriter'), |
| 264 |
'cleanup_delete' => __('Delete permanently (if not used elsewhere)', 'botwriter'), |
| 265 |
'current_image' => __('Current featured image', 'botwriter'), |
| 266 |
'no_current_image' => __('This post has no featured image yet.', 'botwriter'), |
| 267 |
'btn_regenerate' => __('Regenerate', 'botwriter'), |
| 268 |
'btn_accept' => __('Accept', 'botwriter'), |
| 269 |
'btn_close' => __('Close', 'botwriter'), |
| 270 |
'loading_context' => __('Loading data...', 'botwriter'), |
| 271 |
'generating' => __('Generating image preview...', 'botwriter'), |
| 272 |
'applying' => __('Applying featured image...', 'botwriter'), |
| 273 |
'missing_log' => __('No saved image prompt was found for this post. Please write your prompt manually.', 'botwriter'), |
| 274 |
'provider_none' => __('Image provider is currently set to "none" in settings. Select an image provider first.', 'botwriter'), |
| 275 |
'invalid_post' => __('No valid published post is linked to this log entry.', 'botwriter'), |
| 276 |
'empty_prompt' => __('Please enter an image prompt before regenerating.', 'botwriter'), |
| 277 |
'working' => __('Regenerating image...', 'botwriter'), |
| 278 |
'generic_error' => __('Could not regenerate the image. Please try again.', 'botwriter'), |
| 279 |
), |
| 280 |
)); |
| 281 |
} |
| 282 |
|
| 283 |
|
| 284 |
|
| 285 |
if ($slug === 'botwriter_page_botwriter_super_page') { |
| 286 |
wp_register_script('botwriter_super', $my_plugin_dir . 'assets/js/super.js', array('jquery'), false, true); |
| 287 |
wp_enqueue_script('botwriter_super'); |
| 288 |
wp_localize_script('botwriter_super', 'botwriter_super_ajax', array( |
| 289 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 290 |
'nonce' => wp_create_nonce('botwriter_super_nonce') |
| 291 |
)); |
| 292 |
} |
| 293 |
|
| 294 |
if ($slug === 'botwriter_page_botwriter_rewriter_page') { |
| 295 |
wp_register_script('botwriter_rewriter', $my_plugin_dir . 'assets/js/rewriter.js', array('jquery'), false, true); |
| 296 |
wp_enqueue_script('botwriter_rewriter'); |
| 297 |
wp_localize_script('botwriter_rewriter', 'botwriter_rewriter_ajax', array( |
| 298 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 299 |
'nonce' => wp_create_nonce('botwriter_rewriter_nonce'), |
| 300 |
'logs_url' => admin_url('admin.php?page=botwriter_logs'), |
| 301 |
)); |
| 302 |
} |
| 303 |
|
| 304 |
if ($slug === 'botwriter_page_botwriter_siterewriter_page') { |
| 305 |
wp_register_script('botwriter_siterewriter', $my_plugin_dir . 'assets/js/siterewriter.js', array('jquery'), false, true); |
| 306 |
wp_enqueue_script('botwriter_siterewriter'); |
| 307 |
wp_localize_script('botwriter_siterewriter', 'botwriter_siterewriter_ajax', array( |
| 308 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 309 |
'nonce' => wp_create_nonce('botwriter_siterewriter_nonce'), |
| 310 |
'logs_url' => admin_url('admin.php?page=botwriter_logs'), |
| 311 |
)); |
| 312 |
} |
| 313 |
|
| 314 |
if ($slug === 'botwriter_page_botwriter_settings') { |
| 315 |
wp_register_script('botwriter_settings', $my_plugin_dir . 'assets/js/botwriter-settings.js', array('jquery'), false, true); |
| 316 |
wp_enqueue_script('botwriter_settings'); |
| 317 |
wp_localize_script('botwriter_settings', 'botwriter_settings', array( |
| 318 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 319 |
'nonce' => wp_create_nonce('botwriter_settings_nonce'), |
| 320 |
'i18n' => array( |
| 321 |
'saving' => __('Saving...', 'botwriter'), |
| 322 |
'saved' => __('Saved', 'botwriter'), |
| 323 |
'error' => __('Error', 'botwriter'), |
| 324 |
'connection_error' => __('Connection error', 'botwriter'), |
| 325 |
'connection_failed' => __('Connection failed', 'botwriter'), |
| 326 |
'hide' => __('Hide', 'botwriter'), |
| 327 |
'show' => __('Show', 'botwriter'), |
| 328 |
'active' => __('Active', 'botwriter'), |
| 329 |
'enter_api_key' => __('Please enter an API key first.', 'botwriter'), |
| 330 |
'enter_api_url' => __('Please enter an API URL', 'botwriter'), |
| 331 |
'testing' => __('Testing...', 'botwriter'), |
| 332 |
'models_found' => __('Models found:', 'botwriter'), |
| 333 |
'configure_openai_key' => __('Configure OpenAI API key in Text AI tab first.', 'botwriter'), |
| 334 |
'confirm_reset_models' => __('Are you sure you want to reset all model lists to factory defaults?', 'botwriter'), |
| 335 |
) |
| 336 |
)); |
| 337 |
|
| 338 |
wp_register_script('botwriter_debug_log', $my_plugin_dir . 'assets/js/debug-log.js', array('jquery', 'botwriter_settings'), BOTWRITER_VERSION, true); |
| 339 |
wp_enqueue_script('botwriter_debug_log'); |
| 340 |
wp_localize_script('botwriter_debug_log', 'botwriter_debug_log', array( |
| 341 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 342 |
'nonce' => wp_create_nonce('botwriter_settings_nonce'), |
| 343 |
'i18n' => array( |
| 344 |
'loading' => __('Loading...', 'botwriter'), |
| 345 |
'size' => __('Size:', 'botwriter'), |
| 346 |
'showing_last' => __('showing last 512 KB', 'botwriter'), |
| 347 |
'logging_off' => __('logging is OFF', 'botwriter'), |
| 348 |
'error' => __('Error.', 'botwriter'), |
| 349 |
'request_failed' => __('Request failed.', 'botwriter'), |
| 350 |
'confirm_clear' => __('Clear the debug log file?', 'botwriter'), |
| 351 |
'clearing' => __('Clearing...', 'botwriter'), |
| 352 |
'cleared' => __('Log cleared.', 'botwriter'), |
| 353 |
), |
| 354 |
)); |
| 355 |
} |
| 356 |
|
| 357 |
if ($slug === 'botwriter_page_botwriter_write_now') { |
| 358 |
wp_register_script('botwriter_quickpost', $my_plugin_dir . 'assets/js/quickpost.js', array('jquery'), false, true); |
| 359 |
wp_enqueue_script('botwriter_quickpost'); |
| 360 |
wp_localize_script('botwriter_quickpost', 'botwriter_quickpost_ajax', array( |
| 361 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 362 |
'nonce' => wp_create_nonce('botwriter_quickpost_nonce') |
| 363 |
)); |
| 364 |
} |
| 365 |
|
| 366 |
// Regenerate featured image modal (post editor) |
| 367 |
if ($screen && $screen->base === 'post' && current_user_can('manage_options')) { |
| 368 |
wp_register_script('botwriter_post_image_regeneration', $my_plugin_dir . 'assets/js/post-image-regeneration.js', array('jquery'), BOTWRITER_VERSION, true); |
| 369 |
wp_enqueue_script('botwriter_post_image_regeneration'); |
| 370 |
wp_localize_script('botwriter_post_image_regeneration', 'botwriter_post_image_regeneration', array( |
| 371 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 372 |
'nonce' => wp_create_nonce('botwriter_regenerate_image_nonce'), |
| 373 |
'i18n' => array( |
| 374 |
'link_text' => __('Regenerate', 'botwriter'), |
| 375 |
'modal_title' => __('BotWriter Image Regeneration', 'botwriter'), |
| 376 |
'modal_subtitle' => __('Generate and preview a featured image before applying it.', 'botwriter'), |
| 377 |
'provider' => __('Current provider', 'botwriter'), |
| 378 |
'model' => __('Current model', 'botwriter'), |
| 379 |
'prompt_label' => __('Image Prompt', 'botwriter'), |
| 380 |
'cleanup_label' => __('Previous featured image', 'botwriter'), |
| 381 |
'cleanup_keep' => __('Keep in media library', 'botwriter'), |
| 382 |
'cleanup_delete' => __('Delete permanently (if not used elsewhere)', 'botwriter'), |
| 383 |
'current_image' => __('Current featured image', 'botwriter'), |
| 384 |
'no_current_image' => __('This post has no featured image yet.', 'botwriter'), |
| 385 |
'btn_regenerate' => __('Regenerate', 'botwriter'), |
| 386 |
'btn_accept' => __('Accept', 'botwriter'), |
| 387 |
'btn_close' => __('Close', 'botwriter'), |
| 388 |
'loading_context'=> __('Loading data...', 'botwriter'), |
| 389 |
'generating' => __('Generating image preview...', 'botwriter'), |
| 390 |
'applying' => __('Applying featured image...', 'botwriter'), |
| 391 |
'missing_log' => __('No saved image prompt was found for this post. Please write your prompt manually.', 'botwriter'), |
| 392 |
'provider_none' => __('Image provider is currently set to "none" in settings. Select an image provider first.', 'botwriter'), |
| 393 |
'invalid_post' => __('No valid published post is linked to this log entry.', 'botwriter'), |
| 394 |
'empty_prompt' => __('Please enter an image prompt before regenerating.', 'botwriter'), |
| 395 |
'working' => __('Regenerating image...', 'botwriter'), |
| 396 |
'generic_error'=> __('Could not regenerate the image. Please try again.', 'botwriter'), |
| 397 |
), |
| 398 |
)); |
| 399 |
} |
| 400 |
|
| 401 |
// Floating AI assistant widget (post editor) |
| 402 |
if ($screen && $screen->base === 'post' && (string) $screen->post_type === 'post' && current_user_can('edit_posts') && get_option('botwriter_editor_assistant_enabled', '1') === '1') { |
| 403 |
wp_register_script('botwriter_editor_assistant', $my_plugin_dir . 'assets/js/editor-ai-assistant.js', array('jquery'), BOTWRITER_VERSION, true); |
| 404 |
wp_enqueue_script('botwriter_editor_assistant'); |
| 405 |
wp_localize_script('botwriter_editor_assistant', 'botwriter_editor_ai', array( |
| 406 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 407 |
'nonce' => wp_create_nonce('botwriter_editor_assistant_nonce'), |
| 408 |
'robot_image' => $my_plugin_dir . 'assets/images/robot.png', |
| 409 |
'robot_face_image' => $my_plugin_dir . 'assets/images/robot_face.png', |
| 410 |
'settings' => array( |
| 411 |
'skip_heading_links' => '1', |
| 412 |
'seo_module_enabled' => botwriter_is_seo_module_enabled() ? '1' : '0', |
| 413 |
), |
| 414 |
'i18n' => array( |
| 415 |
'widget_title' => __('BotWriter Copilot', 'botwriter'), |
| 416 |
'tab_prompt' => __('Prompt', 'botwriter'), |
| 417 |
'tab_seo' => __('SEO', 'botwriter'), |
| 418 |
'intro' => __('Select what to update', 'botwriter'), |
| 419 |
'seo_intro' => __('Review the current post SEO checks.', 'botwriter'), |
| 420 |
'seo_subtab_analysis' => __('SEO analysis', 'botwriter'), |
| 421 |
'seo_subtab_readability' => __('Readability', 'botwriter'), |
| 422 |
'seo_loading' => __('Loading SEO report...', 'botwriter'), |
| 423 |
'seo_missing_post' => __('Save the post first to view SEO reports.', 'botwriter'), |
| 424 |
'seo_error' => __('Could not load SEO report.', 'botwriter'), |
| 425 |
'seo_empty' => __('No SEO checks available.', 'botwriter'), |
| 426 |
'target_text' => __('Text', 'botwriter'), |
| 427 |
'target_title' => __('Title', 'botwriter'), |
| 428 |
'target_tags' => __('Tags', 'botwriter'), |
| 429 |
'target_excerpt' => __('Excerpt', 'botwriter'), |
| 430 |
'target_seo_meta' => __('SEO Meta', 'botwriter'), |
| 431 |
'target_internal_links' => __('Internal Links', 'botwriter'), |
| 432 |
'suggestions_title' => __('Suggestions', 'botwriter'), |
| 433 |
'prompt_placeholder' => __('Describe exactly what you want to improve...', 'botwriter'), |
| 434 |
'links_prompt_placeholder' => __('What kind of internal links do you want (educational, conversion, cluster, etc.)?', 'botwriter'), |
| 435 |
'keyphrases_label' => __('Keyphrases (up to 5, comma-separated)', 'botwriter'), |
| 436 |
'keyphrases_placeholder' => __('e.g. internal linking, seo writing, topic clusters', 'botwriter'), |
| 437 |
'links_title' => __('Suggested internal links', 'botwriter'), |
| 438 |
'insert_link' => __('Insert', 'botwriter'), |
| 439 |
'inserted_link' => __('Inserted', 'botwriter'), |
| 440 |
'open_link' => __('Open', 'botwriter'), |
| 441 |
'links_ready' => __('Suggestions are ready. Insert the links you want, then Keep or Undo.', 'botwriter'), |
| 442 |
'links_mode_ai' => __('AI mode: suggestions ranked by semantic relevance and anchor fit.', 'botwriter'), |
| 443 |
'links_mode_noai' => __('Deterministic mode: suggestions ranked using taxonomy and keyword overlap (no AI call).', 'botwriter'), |
| 444 |
'links_empty' => __('No relevant internal links were found yet.', 'botwriter'), |
| 445 |
'link_inserted' => __('Internal link inserted. Review and choose Keep or Undo.', 'botwriter'), |
| 446 |
'link_already_exists' => __('This URL is already linked in the content.', 'botwriter'), |
| 447 |
'same_response_notice' => __('AI returned the same text. No changes were applied.', 'botwriter'), |
| 448 |
'sending' => __('Thinking', 'botwriter'), |
| 449 |
'keep' => __('Keep', 'botwriter'), |
| 450 |
'undo' => __('Undo', 'botwriter'), |
| 451 |
'confirm_label' => __('Apply this AI change?', 'botwriter'), |
| 452 |
'missing_prompt' => __('Write a prompt or choose a suggestion first.', 'botwriter'), |
| 453 |
'generic_error' => __('Could not generate a response. Please try again.', 'botwriter'), |
| 454 |
'empty_response' => __('The assistant returned an empty response.', 'botwriter'), |
| 455 |
'updated_notice' => __('Updated. Review and choose Keep or Undo.', 'botwriter'), |
| 456 |
'reverted_notice' => __('Change reverted.', 'botwriter'), |
| 457 |
'kept_notice' => __('Change kept. Save or update the post when ready.', 'botwriter'), |
| 458 |
), |
| 459 |
)); |
| 460 |
} |
| 461 |
|
| 462 |
|
| 463 |
|
| 464 |
} |
| 465 |
add_action('admin_enqueue_scripts','botwriter_enqueue_scripts'); |
| 466 |
|
| 467 |
/** |
| 468 |
* Retrieve the latest BotWriter log associated with a published post. |
| 469 |
* |
| 470 |
* @param int $post_id Post ID. |
| 471 |
* @return array|null |
| 472 |
*/ |
| 473 |
function botwriter_get_latest_log_by_post_id($post_id) { |
| 474 |
global $wpdb; |
| 475 |
|
| 476 |
$post_id = intval($post_id); |
| 477 |
if ($post_id <= 0) { |
| 478 |
return null; |
| 479 |
} |
| 480 |
|
| 481 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 482 |
$log = $wpdb->get_row( |
| 483 |
$wpdb->prepare( |
| 484 |
"SELECT * FROM {$table_name} WHERE id_post_published = %d ORDER BY id DESC LIMIT 1", |
| 485 |
$post_id |
| 486 |
), |
| 487 |
ARRAY_A |
| 488 |
); |
| 489 |
|
| 490 |
botwriter_log('Image prompt lookup: latest log by post', array( |
| 491 |
'post_id' => $post_id, |
| 492 |
'found' => is_array($log), |
| 493 |
'log_id' => is_array($log) ? intval($log['id'] ?? 0) : 0, |
| 494 |
'id_post_published' => is_array($log) ? intval($log['id_post_published'] ?? 0) : 0, |
| 495 |
'image_prompt_len' => is_array($log) ? strlen(trim((string) ($log['image_prompt'] ?? ''))) : 0, |
| 496 |
)); |
| 497 |
|
| 498 |
return is_array($log) ? $log : null; |
| 499 |
} |
| 500 |
|
| 501 |
/** |
| 502 |
* Retrieve the latest BotWriter log for a post that has a non-empty image_prompt. |
| 503 |
* |
| 504 |
* @param int $post_id Post ID. |
| 505 |
* @return array|null |
| 506 |
*/ |
| 507 |
function botwriter_get_latest_log_with_image_prompt_by_post_id($post_id) { |
| 508 |
global $wpdb; |
| 509 |
|
| 510 |
$post_id = intval($post_id); |
| 511 |
if ($post_id <= 0) { |
| 512 |
return null; |
| 513 |
} |
| 514 |
|
| 515 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 516 |
$log = $wpdb->get_row( |
| 517 |
$wpdb->prepare( |
| 518 |
"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", |
| 519 |
$post_id |
| 520 |
), |
| 521 |
ARRAY_A |
| 522 |
); |
| 523 |
|
| 524 |
botwriter_log('Image prompt lookup: latest log WITH prompt by post', array( |
| 525 |
'post_id' => $post_id, |
| 526 |
'found' => is_array($log), |
| 527 |
'log_id' => is_array($log) ? intval($log['id'] ?? 0) : 0, |
| 528 |
'id_post_published' => is_array($log) ? intval($log['id_post_published'] ?? 0) : 0, |
| 529 |
'image_prompt_len' => is_array($log) ? strlen(trim((string) ($log['image_prompt'] ?? ''))) : 0, |
| 530 |
)); |
| 531 |
|
| 532 |
return is_array($log) ? $log : null; |
| 533 |
} |
| 534 |
|
| 535 |
/** |
| 536 |
* Resolve current image model from provider settings. |
| 537 |
* |
| 538 |
* @param string $provider Provider slug. |
| 539 |
* @return string |
| 540 |
*/ |
| 541 |
function botwriter_get_current_image_model_by_provider($provider) { |
| 542 |
$provider = sanitize_key((string) $provider); |
| 543 |
|
| 544 |
if ($provider === 'stockphoto') { |
| 545 |
$preferred = sanitize_key((string) get_option('botwriter_stockphoto_preferred', 'random')); |
| 546 |
$allowed_preferred = array('pixabay', 'pexels', 'unsplash', 'openverse', 'random'); |
| 547 |
if (!in_array($preferred, $allowed_preferred, true)) { |
| 548 |
$preferred = 'random'; |
| 549 |
} |
| 550 |
|
| 551 |
return $preferred; |
| 552 |
} |
| 553 |
if ($provider === 'none') { |
| 554 |
return 'none'; |
| 555 |
} |
| 556 |
|
| 557 |
$default_model = function_exists('botwriter_get_provider_default_image_model') |
| 558 |
? (string) botwriter_get_provider_default_image_model($provider) |
| 559 |
: ''; |
| 560 |
if ($default_model === '') { |
| 561 |
$fallback_defaults = array( |
| 562 |
'dalle' => 'gpt-image-1', |
| 563 |
'gemini' => 'gemini-2.5-flash-image', |
| 564 |
'fal' => 'fal-ai/flux-pro/v1.1', |
| 565 |
'replicate' => 'black-forest-labs/flux-1.1-pro', |
| 566 |
'stability' => 'sd3.5-large-turbo', |
| 567 |
'cloudflare' => 'flux-1-schnell', |
| 568 |
); |
| 569 |
$default_model = (string) ($fallback_defaults[$provider] ?? 'gpt-image-1'); |
| 570 |
} |
| 571 |
|
| 572 |
$option_name = function_exists('botwriter_get_image_model_option_name') |
| 573 |
? botwriter_get_image_model_option_name($provider) |
| 574 |
: ($provider === 'gemini' ? 'botwriter_gemini_image_model' : "botwriter_{$provider}_model"); |
| 575 |
|
| 576 |
$model = (string) get_option($option_name, $default_model); |
| 577 |
|
| 578 |
if (function_exists('botwriter_normalize_image_model')) { |
| 579 |
$normalized_model = (string) botwriter_normalize_image_model($provider, $model); |
| 580 |
|
| 581 |
// Persist normalized aliases (for example legacy Gemini 2.0 IDs) |
| 582 |
// so the settings UI reflects the real value used in dispatch. |
| 583 |
if ($normalized_model !== '' && $normalized_model !== $model) { |
| 584 |
update_option($option_name, $normalized_model); |
| 585 |
botwriter_log('Image model option auto-normalized', array( |
| 586 |
'provider' => $provider, |
| 587 |
'option_name' => $option_name, |
| 588 |
'raw_model' => $model, |
| 589 |
'normalized_model' => $normalized_model, |
| 590 |
)); |
| 591 |
} |
| 592 |
|
| 593 |
return $normalized_model; |
| 594 |
} |
| 595 |
|
| 596 |
return $model; |
| 597 |
} |
| 598 |
|
| 599 |
/** |
| 600 |
* Normalize image size values to the canonical semantic set. |
| 601 |
* |
| 602 |
* Legacy values such as square_hd or landscape_16_9 may still exist in |
| 603 |
* older installations/options. The direct /images endpoint expects only |
| 604 |
* landscape|square|portrait. |
| 605 |
* |
| 606 |
* @param string $size Raw size value. |
| 607 |
* @return string |
| 608 |
*/ |
| 609 |
function botwriter_normalize_ai_image_size($size) { |
| 610 |
$normalized = strtolower(trim((string) $size)); |
| 611 |
|
| 612 |
if ($normalized === '' || $normalized === 'square' || $normalized === 'square_hd' |
| 613 |
|| $normalized === '1:1' || $normalized === '1024x1024') { |
| 614 |
return 'square'; |
| 615 |
} |
| 616 |
|
| 617 |
if ($normalized === 'landscape' || $normalized === 'landscape_4_3' || $normalized === 'landscape_16_9' |
| 618 |
|| $normalized === '4:3' || $normalized === '16:9' || $normalized === '1536x1024' |
| 619 |
|| $normalized === '1792x1024') { |
| 620 |
return 'landscape'; |
| 621 |
} |
| 622 |
|
| 623 |
if ($normalized === 'portrait' || $normalized === 'portrait_4_3' || $normalized === 'portrait_16_9' |
| 624 |
|| $normalized === '3:4' || $normalized === '9:16' || $normalized === '1024x1536' |
| 625 |
|| $normalized === '1024x1792') { |
| 626 |
return 'portrait'; |
| 627 |
} |
| 628 |
|
| 629 |
return 'square'; |
| 630 |
} |
| 631 |
|
| 632 |
/** |
| 633 |
* Return current image settings used for regeneration. |
| 634 |
* Uses active plugin settings at execution time. |
| 635 |
* |
| 636 |
* @return array |
| 637 |
*/ |
| 638 |
function botwriter_get_current_image_generation_settings() { |
| 639 |
$provider = (string) get_option('botwriter_image_provider', 'stockphoto'); |
| 640 |
$model = botwriter_get_current_image_model_by_provider($provider); |
| 641 |
$size = botwriter_normalize_ai_image_size((string) get_option('botwriter_ai_image_size', 'square')); |
| 642 |
$stockphoto_preferred = sanitize_key((string) get_option('botwriter_stockphoto_preferred', 'random')); |
| 643 |
$allowed_preferred = array('pixabay', 'pexels', 'unsplash', 'openverse', 'random'); |
| 644 |
if (!in_array($stockphoto_preferred, $allowed_preferred, true)) { |
| 645 |
$stockphoto_preferred = 'random'; |
| 646 |
} |
| 647 |
|
| 648 |
return array( |
| 649 |
'provider' => $provider, |
| 650 |
'model' => $model, |
| 651 |
'size' => $size, |
| 652 |
'quality' => (string) get_option('botwriter_ai_image_quality', 'medium'), |
| 653 |
'style' => (string) get_option('botwriter_ai_image_style', 'realistic'), |
| 654 |
'style_custom' => (string) get_option('botwriter_ai_image_style_custom', ''), |
| 655 |
'stockphoto_preferred' => $stockphoto_preferred, |
| 656 |
'stockphoto_selection' => (string) get_option('botwriter_stockphoto_selection', 'random_top10'), |
| 657 |
'stockphoto_attribution' => (string) get_option('botwriter_stockphoto_attribution', 'caption'), |
| 658 |
); |
| 659 |
} |
| 660 |
|
| 661 |
/** |
| 662 |
* Return post meta keys used to persist image prompts. |
| 663 |
* |
| 664 |
* @return array |
| 665 |
*/ |
| 666 |
function botwriter_get_image_prompt_meta_keys() { |
| 667 |
return array( |
| 668 |
'ai' => 'botwriter_image_prompt', |
| 669 |
'stock' => 'botwriter_stockphoto_prompt', |
| 670 |
'last' => 'botwriter_image_prompt_last', |
| 671 |
'provider' => 'botwriter_image_prompt_last_provider', |
| 672 |
); |
| 673 |
} |
| 674 |
|
| 675 |
/** |
| 676 |
* Persist image prompt metadata on the post itself. |
| 677 |
* |
| 678 |
* @param int $post_id Post ID. |
| 679 |
* @param string $prompt Prompt text. |
| 680 |
* @param string $provider Provider used for generation. |
| 681 |
* @return bool |
| 682 |
*/ |
| 683 |
function botwriter_save_post_image_prompt_meta($post_id, $prompt, $provider = '') { |
| 684 |
$post_id = intval($post_id); |
| 685 |
if ($post_id <= 0) { |
| 686 |
botwriter_log('Image prompt meta save skipped: invalid post_id', array('post_id' => $post_id)); |
| 687 |
return false; |
| 688 |
} |
| 689 |
|
| 690 |
$prompt = trim(sanitize_textarea_field((string) $prompt)); |
| 691 |
if ($prompt === '') { |
| 692 |
botwriter_log('Image prompt meta save skipped: empty prompt', array( |
| 693 |
'post_id' => $post_id, |
| 694 |
'provider' => $provider, |
| 695 |
)); |
| 696 |
return false; |
| 697 |
} |
| 698 |
|
| 699 |
$provider = sanitize_key((string) $provider); |
| 700 |
$keys = botwriter_get_image_prompt_meta_keys(); |
| 701 |
|
| 702 |
update_post_meta($post_id, $keys['last'], $prompt); |
| 703 |
|
| 704 |
if ($provider === 'stockphoto') { |
| 705 |
update_post_meta($post_id, $keys['stock'], $prompt); |
| 706 |
} else { |
| 707 |
update_post_meta($post_id, $keys['ai'], $prompt); |
| 708 |
} |
| 709 |
|
| 710 |
if ($provider !== '') { |
| 711 |
update_post_meta($post_id, $keys['provider'], $provider); |
| 712 |
} |
| 713 |
|
| 714 |
botwriter_log('Image prompt meta saved', array( |
| 715 |
'post_id' => $post_id, |
| 716 |
'provider' => $provider, |
| 717 |
'prompt_len' => strlen($prompt), |
| 718 |
'saved_ai_meta' => ($provider !== 'stockphoto'), |
| 719 |
'saved_stock_meta' => ($provider === 'stockphoto'), |
| 720 |
'meta_key_last' => $keys['last'], |
| 721 |
)); |
| 722 |
|
| 723 |
return true; |
| 724 |
} |
| 725 |
|
| 726 |
/** |
| 727 |
* Resolve the best prompt saved on post meta for current provider context. |
| 728 |
* |
| 729 |
* @param int $post_id Post ID. |
| 730 |
* @param string $provider Current provider. |
| 731 |
* @return array |
| 732 |
*/ |
| 733 |
function botwriter_get_post_image_prompt_from_meta($post_id, $provider = '') { |
| 734 |
$post_id = intval($post_id); |
| 735 |
$provider = sanitize_key((string) $provider); |
| 736 |
$keys = botwriter_get_image_prompt_meta_keys(); |
| 737 |
|
| 738 |
$ai_prompt = trim((string) get_post_meta($post_id, $keys['ai'], true)); |
| 739 |
$stock_prompt = trim((string) get_post_meta($post_id, $keys['stock'], true)); |
| 740 |
$last_prompt = trim((string) get_post_meta($post_id, $keys['last'], true)); |
| 741 |
|
| 742 |
botwriter_log('Image prompt lookup: post meta snapshot', array( |
| 743 |
'post_id' => $post_id, |
| 744 |
'provider' => $provider, |
| 745 |
'ai_len' => strlen($ai_prompt), |
| 746 |
'stock_len' => strlen($stock_prompt), |
| 747 |
'last_len' => strlen($last_prompt), |
| 748 |
'meta_ai_key' => $keys['ai'], |
| 749 |
'meta_stock_key' => $keys['stock'], |
| 750 |
'meta_last_key' => $keys['last'], |
| 751 |
)); |
| 752 |
|
| 753 |
if ($provider === 'stockphoto') { |
| 754 |
if ($stock_prompt !== '') { |
| 755 |
botwriter_log('Image prompt lookup: selected meta_stock', array('post_id' => $post_id, 'len' => strlen($stock_prompt))); |
| 756 |
return array('prompt' => $stock_prompt, 'source' => 'meta_stock'); |
| 757 |
} |
| 758 |
if ($ai_prompt !== '') { |
| 759 |
botwriter_log('Image prompt lookup: selected meta_ai for stock provider', array('post_id' => $post_id, 'len' => strlen($ai_prompt))); |
| 760 |
return array('prompt' => $ai_prompt, 'source' => 'meta_ai'); |
| 761 |
} |
| 762 |
} else { |
| 763 |
if ($ai_prompt !== '') { |
| 764 |
botwriter_log('Image prompt lookup: selected meta_ai', array('post_id' => $post_id, 'len' => strlen($ai_prompt))); |
| 765 |
return array('prompt' => $ai_prompt, 'source' => 'meta_ai'); |
| 766 |
} |
| 767 |
} |
| 768 |
|
| 769 |
if ($last_prompt !== '') { |
| 770 |
botwriter_log('Image prompt lookup: selected meta_last', array('post_id' => $post_id, 'len' => strlen($last_prompt))); |
| 771 |
return array('prompt' => $last_prompt, 'source' => 'meta_last'); |
| 772 |
} |
| 773 |
|
| 774 |
if ($stock_prompt !== '') { |
| 775 |
botwriter_log('Image prompt lookup: selected stock fallback', array('post_id' => $post_id, 'len' => strlen($stock_prompt))); |
| 776 |
return array('prompt' => $stock_prompt, 'source' => 'meta_stock'); |
| 777 |
} |
| 778 |
|
| 779 |
botwriter_log('Image prompt lookup: no prompt found in post meta', array('post_id' => $post_id)); |
| 780 |
|
| 781 |
return array('prompt' => '', 'source' => 'none'); |
| 782 |
} |
| 783 |
|
| 784 |
/** |
| 785 |
* Resolve the best available image prompt from generated post data. |
| 786 |
* |
| 787 |
* @param array $data Data used to generate/publish the post. |
| 788 |
* @return string |
| 789 |
*/ |
| 790 |
function botwriter_resolve_image_prompt_from_post_data($data) { |
| 791 |
if (!is_array($data)) { |
| 792 |
botwriter_log('Image prompt resolve from post data: invalid payload'); |
| 793 |
return ''; |
| 794 |
} |
| 795 |
|
| 796 |
$explicit_prompt = isset($data['image_prompt']) ? trim(sanitize_textarea_field((string) $data['image_prompt'])) : ''; |
| 797 |
if ($explicit_prompt !== '') { |
| 798 |
botwriter_log('Image prompt resolve from post data: using explicit image_prompt', array( |
| 799 |
'len' => strlen($explicit_prompt), |
| 800 |
'has_image_provider' => isset($data['image_provider']), |
| 801 |
)); |
| 802 |
return $explicit_prompt; |
| 803 |
} |
| 804 |
|
| 805 |
// Edge/legacy fallback: when image_prompt is not returned, the title is the usual fallback basis. |
| 806 |
$title_based_prompt = isset($data['aigenerated_title']) ? trim(sanitize_text_field((string) $data['aigenerated_title'])) : ''; |
| 807 |
if ($title_based_prompt !== '') { |
| 808 |
botwriter_log('Image prompt resolve from post data: fallback to generated title', array( |
| 809 |
'title_len' => strlen($title_based_prompt), |
| 810 |
'has_image_prompt_key' => array_key_exists('image_prompt', $data), |
| 811 |
)); |
| 812 |
return $title_based_prompt; |
| 813 |
} |
| 814 |
|
| 815 |
botwriter_log('Image prompt resolve from post data: fallback to hardcoded default'); |
| 816 |
return 'Blog post illustration'; |
| 817 |
} |
| 818 |
|
| 819 |
/** |
| 820 |
* Build UI context for image regeneration modal. |
| 821 |
* |
| 822 |
* @param int $post_id Post ID. |
| 823 |
* @return array |
| 824 |
*/ |
| 825 |
function botwriter_get_image_regeneration_context($post_id) { |
| 826 |
$post_id = intval($post_id); |
| 827 |
$settings = botwriter_get_current_image_generation_settings(); |
| 828 |
$provider = (string) ($settings['provider'] ?? ''); |
| 829 |
|
| 830 |
$prompt_context = botwriter_get_post_image_prompt_from_meta($post_id, $provider); |
| 831 |
$prefill_prompt = (string) ($prompt_context['prompt'] ?? ''); |
| 832 |
$prompt_source = (string) ($prompt_context['source'] ?? 'none'); |
| 833 |
|
| 834 |
botwriter_log('Image regeneration context: after meta lookup', array( |
| 835 |
'post_id' => $post_id, |
| 836 |
'provider' => $provider, |
| 837 |
'prompt_source' => $prompt_source, |
| 838 |
'prompt_len' => strlen($prefill_prompt), |
| 839 |
)); |
| 840 |
|
| 841 |
$latest_log = null; |
| 842 |
if ($prefill_prompt === '') { |
| 843 |
// Prefer the newest log that actually contains an image prompt. |
| 844 |
$latest_log = botwriter_get_latest_log_with_image_prompt_by_post_id($post_id); |
| 845 |
|
| 846 |
// Backward-compat fallback: if no prompt-carrying log exists, inspect latest log anyway. |
| 847 |
if (!is_array($latest_log)) { |
| 848 |
$latest_log = botwriter_get_latest_log_by_post_id($post_id); |
| 849 |
} |
| 850 |
|
| 851 |
$prefill_prompt = is_array($latest_log) ? trim((string) ($latest_log['image_prompt'] ?? '')) : ''; |
| 852 |
if ($prefill_prompt !== '') { |
| 853 |
$prompt_source = 'log'; |
| 854 |
// Legacy migration: if prompt exists in log but not in post meta, persist it now. |
| 855 |
botwriter_save_post_image_prompt_meta($post_id, $prefill_prompt, $provider); |
| 856 |
botwriter_log('Image regeneration context: prompt recovered from log and migrated to meta', array( |
| 857 |
'post_id' => $post_id, |
| 858 |
'log_id' => intval($latest_log['id'] ?? 0), |
| 859 |
'prompt_len' => strlen($prefill_prompt), |
| 860 |
)); |
| 861 |
} |
| 862 |
} |
| 863 |
|
| 864 |
if ($prefill_prompt === '') { |
| 865 |
$title_prompt = trim((string) get_the_title($post_id)); |
| 866 |
if ($title_prompt !== '') { |
| 867 |
$prefill_prompt = $title_prompt; |
| 868 |
$prompt_source = 'post_title'; |
| 869 |
botwriter_log('Image regeneration context: fallback to post title', array( |
| 870 |
'post_id' => $post_id, |
| 871 |
'title_len' => strlen($title_prompt), |
| 872 |
)); |
| 873 |
} |
| 874 |
} |
| 875 |
|
| 876 |
$has_meta_prompt = in_array($prompt_source, array('meta_ai', 'meta_stock', 'meta_last'), true); |
| 877 |
|
| 878 |
$current_image_src = ''; |
| 879 |
$current_attachment_id = intval(get_post_thumbnail_id($post_id)); |
| 880 |
if ($current_attachment_id > 0) { |
| 881 |
$current_image = wp_get_attachment_image_src($current_attachment_id, 'medium_large'); |
| 882 |
if (is_array($current_image) && !empty($current_image[0])) { |
| 883 |
$current_image_src = esc_url_raw($current_image[0]); |
| 884 |
} |
| 885 |
} |
| 886 |
|
| 887 |
botwriter_log('Image regeneration context resolved', array( |
| 888 |
'post_id' => $post_id, |
| 889 |
'provider' => $provider, |
| 890 |
'prompt_source' => $prompt_source, |
| 891 |
'prompt_len' => strlen($prefill_prompt), |
| 892 |
'has_meta_prompt' => $has_meta_prompt, |
| 893 |
'has_current_image' => ($current_image_src !== ''), |
| 894 |
'has_log' => is_array($latest_log), |
| 895 |
'log_id' => is_array($latest_log) ? intval($latest_log['id'] ?? 0) : 0, |
| 896 |
)); |
| 897 |
|
| 898 |
return array( |
| 899 |
'post_id' => $post_id, |
| 900 |
'prompt' => $prefill_prompt, |
| 901 |
'has_log' => is_array($latest_log), |
| 902 |
'has_meta_prompt' => $has_meta_prompt, |
| 903 |
'has_prompt' => ($prefill_prompt !== ''), |
| 904 |
'prompt_source' => $prompt_source, |
| 905 |
'provider' => $provider, |
| 906 |
'model' => (string) ($settings['model'] ?? ''), |
| 907 |
'provider_disabled' => ($provider === 'none'), |
| 908 |
'current_attachment_id' => $current_attachment_id, |
| 909 |
'current_image_src' => $current_image_src, |
| 910 |
); |
| 911 |
} |
| 912 |
|
| 913 |
/** |
| 914 |
* Generate an image URL using the direct backend endpoint (/images) with current settings. |
| 915 |
* |
| 916 |
* @param string $prompt Prompt text. |
| 917 |
* @return array |
| 918 |
*/ |
| 919 |
function botwriter_generate_image_with_current_settings($prompt) { |
| 920 |
$prompt = trim((string) $prompt); |
| 921 |
if ($prompt === '') { |
| 922 |
return array('success' => false, 'message' => __('Image prompt is required.', 'botwriter')); |
| 923 |
} |
| 924 |
|
| 925 |
$settings = botwriter_get_current_image_generation_settings(); |
| 926 |
$provider = (string) $settings['provider']; |
| 927 |
$model = (string) $settings['model']; |
| 928 |
|
| 929 |
if ($provider === 'none') { |
| 930 |
return array('success' => false, 'message' => __('Image provider is disabled in settings.', 'botwriter')); |
| 931 |
} |
| 932 |
|
| 933 |
$style_value = (string) ($settings['style_custom'] ?: $settings['style']); |
| 934 |
if ($style_value === 'realistic' || $style_value === 'none') { |
| 935 |
$style_value = ''; |
| 936 |
} |
| 937 |
|
| 938 |
$payload = array( |
| 939 |
'prompt' => $prompt, |
| 940 |
'domain' => esc_url_raw(get_site_url()), |
| 941 |
'api_key' => get_option('botwriter_api_key'), |
| 942 |
'site_token' => get_option('botwriter_site_token', ''), |
| 943 |
// Image regenerations are UX actions and should not consume license quota. |
| 944 |
'no_count' => true, |
| 945 |
'provider' => $provider, |
| 946 |
'model' => $model, |
| 947 |
'size' => (string) $settings['size'], |
| 948 |
'quality' => (string) $settings['quality'], |
| 949 |
'style' => $style_value, |
| 950 |
'stockphoto_preferred' => (string) $settings['stockphoto_preferred'], |
| 951 |
'stockphoto_selection' => (string) $settings['stockphoto_selection'], |
| 952 |
'stockphoto_attribution' => (string) $settings['stockphoto_attribution'], |
| 953 |
// Forward client keys (edge endpoint overlays provider keys from this payload) |
| 954 |
'openai_api_key' => botwriter_decrypt_api_key(get_option('botwriter_openai_api_key')), |
| 955 |
'google_api_key' => botwriter_decrypt_api_key(get_option('botwriter_google_api_key')), |
| 956 |
'fal_api_key' => botwriter_decrypt_api_key(get_option('botwriter_fal_api_key')), |
| 957 |
'replicate_api_key' => botwriter_decrypt_api_key(get_option('botwriter_replicate_api_key')), |
| 958 |
'stability_api_key' => botwriter_decrypt_api_key(get_option('botwriter_stability_api_key')), |
| 959 |
'cloudflare_api_key' => botwriter_decrypt_api_key(get_option('botwriter_cloudflare_api_key')), |
| 960 |
'cloudflare_account_id' => get_option('botwriter_cloudflare_account_id'), |
| 961 |
); |
| 962 |
|
| 963 |
$ssl_verify = get_option('botwriter_sslverify'); |
| 964 |
$ssl_verify = ($ssl_verify !== 'no'); |
| 965 |
|
| 966 |
$remote_url = BOTWRITER_API_URL . 'images'; |
| 967 |
$response = wp_remote_post($remote_url, array( |
| 968 |
'method' => 'POST', |
| 969 |
'headers' => array( |
| 970 |
'Content-Type' => 'application/json', |
| 971 |
), |
| 972 |
'body' => wp_json_encode($payload), |
| 973 |
'timeout' => 120, |
| 974 |
'sslverify' => $ssl_verify, |
| 975 |
)); |
| 976 |
|
| 977 |
if (is_wp_error($response)) { |
| 978 |
return array('success' => false, 'message' => $response->get_error_message()); |
| 979 |
} |
| 980 |
|
| 981 |
$status_code = wp_remote_retrieve_response_code($response); |
| 982 |
$body_raw = wp_remote_retrieve_body($response); |
| 983 |
$result = json_decode($body_raw, true); |
| 984 |
|
| 985 |
if ($status_code !== 200 || !is_array($result) || ($result['status'] ?? '') !== 'success' || empty($result['download_url'])) { |
| 986 |
$error_message = ''; |
| 987 |
if (is_array($result)) { |
| 988 |
$error_message = (string) ($result['error'] ?? $result['message'] ?? ''); |
| 989 |
} |
| 990 |
if ($error_message === '') { |
| 991 |
$error_message = __('Image generation failed on server.', 'botwriter'); |
| 992 |
} |
| 993 |
return array('success' => false, 'message' => $error_message); |
| 994 |
} |
| 995 |
|
| 996 |
$download_url = (string) $result['download_url']; |
| 997 |
$image_url = $download_url; |
| 998 |
if (strpos($download_url, 'http://') !== 0 && strpos($download_url, 'https://') !== 0) { |
| 999 |
$image_url = rtrim(BOTWRITER_API_URL, '/') . '/' . ltrim($download_url, '/'); |
| 1000 |
} |
| 1001 |
|
| 1002 |
return array( |
| 1003 |
'success' => true, |
| 1004 |
'image_url' => $image_url, |
| 1005 |
'provider' => $provider, |
| 1006 |
'model' => $model, |
| 1007 |
); |
| 1008 |
} |
| 1009 |
|
| 1010 |
/** |
| 1011 |
* AJAX: fetch context for image regeneration modal. |
| 1012 |
*/ |
| 1013 |
function botwriter_get_post_image_regeneration_context_ajax() { |
| 1014 |
if (!current_user_can('manage_options')) { |
| 1015 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 1016 |
} |
| 1017 |
|
| 1018 |
check_ajax_referer('botwriter_regenerate_image_nonce', 'nonce'); |
| 1019 |
|
| 1020 |
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; |
| 1021 |
if ($post_id <= 0 || !get_post($post_id)) { |
| 1022 |
wp_send_json_error(array('message' => __('Invalid post.', 'botwriter'))); |
| 1023 |
} |
| 1024 |
|
| 1025 |
if (!current_user_can('edit_post', $post_id)) { |
| 1026 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 1027 |
} |
| 1028 |
|
| 1029 |
$context = botwriter_get_image_regeneration_context($post_id); |
| 1030 |
botwriter_log('AJAX context response for image regeneration', array( |
| 1031 |
'post_id' => $post_id, |
| 1032 |
'prompt_source' => $context['prompt_source'] ?? 'unknown', |
| 1033 |
'prompt_len' => strlen((string) ($context['prompt'] ?? '')), |
| 1034 |
'has_meta_prompt' => !empty($context['has_meta_prompt']), |
| 1035 |
'has_log' => !empty($context['has_log']), |
| 1036 |
)); |
| 1037 |
wp_send_json_success($context); |
| 1038 |
} |
| 1039 |
add_action('wp_ajax_botwriter_get_post_image_regeneration_context', 'botwriter_get_post_image_regeneration_context_ajax'); |
| 1040 |
|
| 1041 |
/** |
| 1042 |
* AJAX: generate preview image only (does not apply to post yet). |
| 1043 |
*/ |
| 1044 |
function botwriter_generate_post_image_preview_ajax() { |
| 1045 |
if (!current_user_can('manage_options')) { |
| 1046 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 1047 |
} |
| 1048 |
|
| 1049 |
check_ajax_referer('botwriter_regenerate_image_nonce', 'nonce'); |
| 1050 |
|
| 1051 |
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; |
| 1052 |
$prompt = isset($_POST['prompt']) ? sanitize_textarea_field(wp_unslash($_POST['prompt'])) : ''; |
| 1053 |
|
| 1054 |
if ($post_id <= 0 || !get_post($post_id)) { |
| 1055 |
wp_send_json_error(array('message' => __('Invalid post.', 'botwriter'))); |
| 1056 |
} |
| 1057 |
|
| 1058 |
if (!current_user_can('edit_post', $post_id)) { |
| 1059 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 1060 |
} |
| 1061 |
|
| 1062 |
$generated = botwriter_generate_image_with_current_settings($prompt); |
| 1063 |
if (empty($generated['success'])) { |
| 1064 |
wp_send_json_error(array('message' => (string) ($generated['message'] ?? __('Image generation failed.', 'botwriter')))); |
| 1065 |
} |
| 1066 |
|
| 1067 |
wp_send_json_success(array( |
| 1068 |
'post_id' => $post_id, |
| 1069 |
'prompt' => trim((string) $prompt), |
| 1070 |
'image_url' => (string) $generated['image_url'], |
| 1071 |
'provider' => (string) $generated['provider'], |
| 1072 |
'model' => (string) $generated['model'], |
| 1073 |
)); |
| 1074 |
} |
| 1075 |
add_action('wp_ajax_botwriter_generate_post_image_preview', 'botwriter_generate_post_image_preview_ajax'); |
| 1076 |
|
| 1077 |
/** |
| 1078 |
* Check if an attachment is used as featured image by posts other than the current one. |
| 1079 |
* |
| 1080 |
* @param int $attachment_id Attachment ID. |
| 1081 |
* @param int $exclude_post_id Post ID to exclude. |
| 1082 |
* @return bool |
| 1083 |
*/ |
| 1084 |
function botwriter_is_attachment_featured_elsewhere($attachment_id, $exclude_post_id = 0) { |
| 1085 |
global $wpdb; |
| 1086 |
|
| 1087 |
$attachment_id = intval($attachment_id); |
| 1088 |
$exclude_post_id = intval($exclude_post_id); |
| 1089 |
|
| 1090 |
if ($attachment_id <= 0) { |
| 1091 |
return false; |
| 1092 |
} |
| 1093 |
|
| 1094 |
$query = "SELECT COUNT(1) FROM {$wpdb->postmeta} WHERE meta_key = '_thumbnail_id' AND meta_value = %d"; |
| 1095 |
$params = array($attachment_id); |
| 1096 |
|
| 1097 |
if ($exclude_post_id > 0) { |
| 1098 |
$query .= " AND post_id <> %d"; |
| 1099 |
$params[] = $exclude_post_id; |
| 1100 |
} |
| 1101 |
|
| 1102 |
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Query is built dynamically and prepared with placeholders for the only user-supplied values. |
| 1103 |
$count = $wpdb->get_var($wpdb->prepare($query, $params)); |
| 1104 |
return intval($count) > 0; |
| 1105 |
} |
| 1106 |
|
| 1107 |
/** |
| 1108 |
* Register a lightweight image-only log entry for future prompt prefill. |
| 1109 |
* |
| 1110 |
* @param int $post_id Post ID. |
| 1111 |
* @param string $prompt Prompt used. |
| 1112 |
* @param string $image_url Generated image URL. |
| 1113 |
* @param string $provider Current provider. |
| 1114 |
* @param string $model Current model. |
| 1115 |
* @param array|null $source_log Existing latest log for this post. |
| 1116 |
* @return int|false |
| 1117 |
*/ |
| 1118 |
function botwriter_register_only_image_log($post_id, $prompt, $image_url, $provider, $model, $source_log = null) { |
| 1119 |
$post = get_post($post_id); |
| 1120 |
if (!($post instanceof WP_Post)) { |
| 1121 |
return false; |
| 1122 |
} |
| 1123 |
|
| 1124 |
$base = array( |
| 1125 |
'id_task' => 0, |
| 1126 |
'id_task_server' => 0, |
| 1127 |
'post_status' => $post->post_status ?: 'draft', |
| 1128 |
'task_name' => sprintf( |
| 1129 |
/* translators: %d: post ID. */ |
| 1130 |
__('Image regeneration for post #%d', 'botwriter'), |
| 1131 |
intval($post_id) |
| 1132 |
), |
| 1133 |
'task_type' => 'only_image', |
| 1134 |
'writer' => 'orion', |
| 1135 |
'narration' => 'Descriptive', |
| 1136 |
'custom_style' => '', |
| 1137 |
'post_language' => substr(get_locale(), 0, 2), |
| 1138 |
'post_length' => '800', |
| 1139 |
'link_post_original' => get_permalink($post_id), |
| 1140 |
'id_post_published' => intval($post_id), |
| 1141 |
'task_status' => 'completed', |
| 1142 |
'error' => '', |
| 1143 |
'website_name' => '', |
| 1144 |
'website_type' => 'ai', |
| 1145 |
'domain_name' => esc_url_raw(get_site_url()), |
| 1146 |
'post_type' => $post->post_type ?: 'post', |
| 1147 |
'category_id' => '', |
| 1148 |
'taxonomy_data' => '', |
| 1149 |
'website_category_id' => '', |
| 1150 |
'aigenerated_title' => get_the_title($post_id), |
| 1151 |
'aigenerated_content' => '', |
| 1152 |
'aigenerated_tags' => '', |
| 1153 |
'aigenerated_image' => $image_url, |
| 1154 |
'post_count' => '1', |
| 1155 |
'post_order' => '', |
| 1156 |
'title_prompt' => '', |
| 1157 |
'content_prompt' => '', |
| 1158 |
'tags_prompt' => '', |
| 1159 |
'image_prompt' => $prompt, |
| 1160 |
'image_generating_status' => 'completed', |
| 1161 |
'author_selection' => strval($post->post_author ?: get_current_user_id()), |
| 1162 |
'news_time_published' => '', |
| 1163 |
'news_language' => '', |
| 1164 |
'news_country' => '', |
| 1165 |
'news_keyword' => '', |
| 1166 |
'news_source' => '', |
| 1167 |
'rss_source' => '', |
| 1168 |
'ai_keywords' => '', |
| 1169 |
'disable_ai_images' => 0, |
| 1170 |
'template_id' => null, |
| 1171 |
'intentosfase1' => 0, |
| 1172 |
'last_execution_time' => current_time('mysql'), |
| 1173 |
); |
| 1174 |
|
| 1175 |
// Reuse as much context as possible from latest known log. |
| 1176 |
if (is_array($source_log) && !empty($source_log)) { |
| 1177 |
$inherit_keys = array( |
| 1178 |
'id_task', |
| 1179 |
'post_status', |
| 1180 |
'task_name', |
| 1181 |
'writer', |
| 1182 |
'narration', |
| 1183 |
'custom_style', |
| 1184 |
'post_language', |
| 1185 |
'post_length', |
| 1186 |
'website_name', |
| 1187 |
'website_type', |
| 1188 |
'domain_name', |
| 1189 |
'post_type', |
| 1190 |
'category_id', |
| 1191 |
'taxonomy_data', |
| 1192 |
'website_category_id', |
| 1193 |
'title_prompt', |
| 1194 |
'content_prompt', |
| 1195 |
'tags_prompt', |
| 1196 |
'author_selection', |
| 1197 |
'ai_keywords', |
| 1198 |
'template_id', |
| 1199 |
); |
| 1200 |
|
| 1201 |
foreach ($inherit_keys as $key) { |
| 1202 |
if (array_key_exists($key, $source_log) && $source_log[$key] !== null && $source_log[$key] !== '') { |
| 1203 |
$base[$key] = $source_log[$key]; |
| 1204 |
} |
| 1205 |
} |
| 1206 |
} |
| 1207 |
|
| 1208 |
// Ensure this log is identifiable as image-only and references current settings context. |
| 1209 |
$base['task_type'] = 'only_image'; |
| 1210 |
$base['task_status'] = 'completed'; |
| 1211 |
$base['id_post_published'] = intval($post_id); |
| 1212 |
$base['image_prompt'] = $prompt; |
| 1213 |
$base['aigenerated_image'] = $image_url; |
| 1214 |
$base['error'] = ''; |
| 1215 |
$base['last_execution_time'] = current_time('mysql'); |
| 1216 |
$base['task_name'] = sprintf( |
| 1217 |
/* translators: 1: image provider name, 2: image model name, 3: post ID. */ |
| 1218 |
__('Only image regeneration (%1$s / %2$s) - Post #%3$d', 'botwriter'), |
| 1219 |
$provider, |
| 1220 |
$model, |
| 1221 |
intval($post_id) |
| 1222 |
); |
| 1223 |
|
| 1224 |
return botwriter_logs_register($base); |
| 1225 |
} |
| 1226 |
|
| 1227 |
/** |
| 1228 |
* AJAX: apply a regenerated image URL as featured image for an existing post. |
| 1229 |
* If no image_url is provided, it can generate one using current settings (legacy fallback). |
| 1230 |
*/ |
| 1231 |
function botwriter_apply_post_regenerated_image_ajax() { |
| 1232 |
if (!current_user_can('manage_options')) { |
| 1233 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 1234 |
} |
| 1235 |
|
| 1236 |
check_ajax_referer('botwriter_regenerate_image_nonce', 'nonce'); |
| 1237 |
|
| 1238 |
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; |
| 1239 |
$prompt = isset($_POST['prompt']) ? sanitize_textarea_field(wp_unslash($_POST['prompt'])) : ''; |
| 1240 |
$image_url = isset($_POST['image_url']) ? esc_url_raw(wp_unslash($_POST['image_url'])) : ''; |
| 1241 |
$cleanup_policy = isset($_POST['cleanup_policy']) ? sanitize_key(wp_unslash($_POST['cleanup_policy'])) : 'keep_old'; |
| 1242 |
$provider = isset($_POST['provider']) ? sanitize_key(wp_unslash($_POST['provider'])) : ''; |
| 1243 |
$model = isset($_POST['model']) ? sanitize_text_field(wp_unslash($_POST['model'])) : ''; |
| 1244 |
|
| 1245 |
if ($post_id <= 0 || !get_post($post_id)) { |
| 1246 |
wp_send_json_error(array('message' => __('Invalid post.', 'botwriter'))); |
| 1247 |
} |
| 1248 |
|
| 1249 |
if (!current_user_can('edit_post', $post_id)) { |
| 1250 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 1251 |
} |
| 1252 |
|
| 1253 |
if (trim($prompt) === '') { |
| 1254 |
wp_send_json_error(array('message' => __('Image prompt is required.', 'botwriter'))); |
| 1255 |
} |
| 1256 |
|
| 1257 |
if (!in_array($cleanup_policy, array('keep_old', 'delete_old'), true)) { |
| 1258 |
$cleanup_policy = 'keep_old'; |
| 1259 |
} |
| 1260 |
|
| 1261 |
if ($image_url === '') { |
| 1262 |
// Legacy fallback: if called without image_url, generate directly now. |
| 1263 |
$generated = botwriter_generate_image_with_current_settings($prompt); |
| 1264 |
if (empty($generated['success'])) { |
| 1265 |
wp_send_json_error(array('message' => (string) ($generated['message'] ?? __('Image generation failed.', 'botwriter')))); |
| 1266 |
} |
| 1267 |
$image_url = (string) $generated['image_url']; |
| 1268 |
$provider = (string) $generated['provider']; |
| 1269 |
$model = (string) $generated['model']; |
| 1270 |
} |
| 1271 |
|
| 1272 |
if (strpos($image_url, 'http://') !== 0 && strpos($image_url, 'https://') !== 0) { |
| 1273 |
wp_send_json_error(array('message' => __('Invalid image URL.', 'botwriter'))); |
| 1274 |
} |
| 1275 |
|
| 1276 |
if ($provider === '' || $model === '') { |
| 1277 |
$settings = botwriter_get_current_image_generation_settings(); |
| 1278 |
if ($provider === '') { |
| 1279 |
$provider = (string) ($settings['provider'] ?? 'dalle'); |
| 1280 |
} |
| 1281 |
if ($model === '') { |
| 1282 |
$model = (string) ($settings['model'] ?? 'gpt-image-1'); |
| 1283 |
} |
| 1284 |
} |
| 1285 |
|
| 1286 |
$old_thumbnail_id = get_post_thumbnail_id($post_id); |
| 1287 |
$post_title = get_the_title($post_id); |
| 1288 |
|
| 1289 |
botwriter_attach_image_to_post($post_id, $image_url, $post_title); |
| 1290 |
$new_thumbnail_id = get_post_thumbnail_id($post_id); |
| 1291 |
|
| 1292 |
if (empty($new_thumbnail_id)) { |
| 1293 |
wp_send_json_error(array('message' => __('Image was generated but could not be attached as featured image.', 'botwriter'))); |
| 1294 |
} |
| 1295 |
|
| 1296 |
$deleted_old = false; |
| 1297 |
$delete_note = ''; |
| 1298 |
if ($cleanup_policy === 'delete_old' && !empty($old_thumbnail_id) && intval($old_thumbnail_id) !== intval($new_thumbnail_id)) { |
| 1299 |
if (botwriter_is_attachment_featured_elsewhere(intval($old_thumbnail_id), $post_id)) { |
| 1300 |
$delete_note = __('Previous featured image was not deleted because it is used by other posts.', 'botwriter'); |
| 1301 |
} else { |
| 1302 |
$deleted_old = (bool) wp_delete_attachment(intval($old_thumbnail_id), true); |
| 1303 |
if (!$deleted_old) { |
| 1304 |
$delete_note = __('Previous featured image could not be deleted automatically.', 'botwriter'); |
| 1305 |
} |
| 1306 |
} |
| 1307 |
} |
| 1308 |
|
| 1309 |
$latest_log = botwriter_get_latest_log_by_post_id($post_id); |
| 1310 |
botwriter_log('Apply regenerated image: persisting prompt to log/meta', array( |
| 1311 |
'post_id' => $post_id, |
| 1312 |
'provider' => $provider, |
| 1313 |
'model' => $model, |
| 1314 |
'prompt_len' => strlen((string) $prompt), |
| 1315 |
'latest_log_id' => is_array($latest_log) ? intval($latest_log['id'] ?? 0) : 0, |
| 1316 |
)); |
| 1317 |
$log_id = botwriter_register_only_image_log($post_id, $prompt, $image_url, $provider, $model, $latest_log); |
| 1318 |
botwriter_save_post_image_prompt_meta($post_id, $prompt, $provider); |
| 1319 |
|
| 1320 |
$thumb_src = wp_get_attachment_image_src($new_thumbnail_id, 'medium'); |
| 1321 |
$featured_src = is_array($thumb_src) ? $thumb_src[0] : ''; |
| 1322 |
|
| 1323 |
wp_send_json_success(array( |
| 1324 |
'message' => __('Featured image regenerated successfully.', 'botwriter'), |
| 1325 |
'post_id' => $post_id, |
| 1326 |
'image_url' => $image_url, |
| 1327 |
'featured_image_src' => $featured_src, |
| 1328 |
'attachment_id' => intval($new_thumbnail_id), |
| 1329 |
'provider' => $provider, |
| 1330 |
'model' => $model, |
| 1331 |
'deleted_old' => $deleted_old, |
| 1332 |
'delete_note' => $delete_note, |
| 1333 |
'log_id' => $log_id ?: 0, |
| 1334 |
)); |
| 1335 |
} |
| 1336 |
add_action('wp_ajax_botwriter_apply_post_regenerated_image', 'botwriter_apply_post_regenerated_image_ajax'); |
| 1337 |
// Backward-compatible alias for previous one-step endpoint name. |
| 1338 |
add_action('wp_ajax_botwriter_regenerate_post_image', 'botwriter_apply_post_regenerated_image_ajax'); |
| 1339 |
|
| 1340 |
/** |
| 1341 |
* Build an instruction prompt for the post editor assistant. |
| 1342 |
* |
| 1343 |
* @param string $target Selected field target. |
| 1344 |
* @param string $user_prompt User instruction. |
| 1345 |
* @param array $context Current post context. |
| 1346 |
* @return string |
| 1347 |
*/ |
| 1348 |
function botwriter_build_editor_assistant_prompt($target, $user_prompt, $context) { |
| 1349 |
$rules = array( |
| 1350 |
'text' => 'Return only the improved post body as valid HTML. Do not include title, tags, excerpt, SEO meta, or explanations.', |
| 1351 |
'title' => 'Return only one improved post title as plain text. No quotes, bullets, or commentary.', |
| 1352 |
'tags' => 'Return only a comma-separated list of tags. No hashtags, no numbering, and no extra text.', |
| 1353 |
'excerpt' => 'Return only one short excerpt (max 160 characters) as plain text.', |
| 1354 |
'seo_meta' => 'Return only one SEO meta description (max 160 characters) as plain text.', |
| 1355 |
); |
| 1356 |
|
| 1357 |
$context_payload = array( |
| 1358 |
'title' => (string) ($context['title'] ?? ''), |
| 1359 |
'content' => (string) ($context['content'] ?? ''), |
| 1360 |
'tags' => (string) ($context['tags'] ?? ''), |
| 1361 |
'excerpt' => (string) ($context['excerpt'] ?? ''), |
| 1362 |
'seo_meta' => (string) ($context['seo_meta'] ?? ''), |
| 1363 |
); |
| 1364 |
|
| 1365 |
$context_json = wp_json_encode($context_payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); |
| 1366 |
if (!is_string($context_json) || $context_json === '') { |
| 1367 |
$context_json = '{}'; |
| 1368 |
} |
| 1369 |
|
| 1370 |
$target_rule = isset($rules[$target]) ? $rules[$target] : $rules['text']; |
| 1371 |
|
| 1372 |
return "You are BotWriter inline editor assistant for WordPress.\n" |
| 1373 |
. "The user is editing a post right now.\n" |
| 1374 |
. "Selected field: {$target}\n" |
| 1375 |
. "Output format rule: {$target_rule}\n" |
| 1376 |
. "Keep the same language as the source content unless the user asks otherwise.\n" |
| 1377 |
. "Never use markdown code fences.\n\n" |
| 1378 |
. "User instruction:\n{$user_prompt}\n\n" |
| 1379 |
. "Current post context JSON:\n{$context_json}"; |
| 1380 |
} |
| 1381 |
|
| 1382 |
/** |
| 1383 |
* Call the Cloudflare Worker for editor assistant generation. |
| 1384 |
* |
| 1385 |
* Uses a dedicated /editor endpoint and falls back to /woo only when the |
| 1386 |
* new endpoint is not available yet. |
| 1387 |
* |
| 1388 |
* @param string $provider Provider key (openai, anthropic, google, etc.). |
| 1389 |
* @param string $api_key Provider API key. |
| 1390 |
* @param string $model Model name. |
| 1391 |
* @param string $prompt Prompt text. |
| 1392 |
* @param int $max_tokens Max output tokens. |
| 1393 |
* @param float $temperature Temperature. |
| 1394 |
* @return string|WP_Error |
| 1395 |
*/ |
| 1396 |
function botwriter_call_editor_worker($provider, $api_key, $model, $prompt, $max_tokens = 2048, $temperature = 0.35) { |
| 1397 |
$ssl_verify = get_option('botwriter_sslverify', 'yes') === 'yes'; |
| 1398 |
|
| 1399 |
$provider_map = array( |
| 1400 |
'google' => 'gemini', |
| 1401 |
); |
| 1402 |
$worker_provider = isset($provider_map[$provider]) ? $provider_map[$provider] : $provider; |
| 1403 |
|
| 1404 |
$key_field_map = array( |
| 1405 |
'openai' => 'openai_api_key', |
| 1406 |
'anthropic' => 'anthropic_api_key', |
| 1407 |
'google' => 'google_api_key', |
| 1408 |
'mistral' => 'mistral_api_key', |
| 1409 |
'groq' => 'groq_api_key', |
| 1410 |
'openrouter' => 'openrouter_api_key', |
| 1411 |
); |
| 1412 |
|
| 1413 |
$domain = preg_replace('#^https?://#', '', home_url()); |
| 1414 |
$domain = rtrim((string) $domain, '/'); |
| 1415 |
|
| 1416 |
$payload = array( |
| 1417 |
'prompt' => $prompt, |
| 1418 |
'domain' => $domain, |
| 1419 |
'provider' => $worker_provider, |
| 1420 |
'model' => $model, |
| 1421 |
'max_tokens' => intval($max_tokens), |
| 1422 |
'temperature' => floatval($temperature), |
| 1423 |
'site_token' => get_option('botwriter_site_token', ''), |
| 1424 |
// Keep editor assistant out of quota checks for now. |
| 1425 |
'no_count' => true, |
| 1426 |
'assistant' => 'post_editor', |
| 1427 |
); |
| 1428 |
|
| 1429 |
if (!empty($api_key) && isset($key_field_map[$provider])) { |
| 1430 |
$payload[$key_field_map[$provider]] = $api_key; |
| 1431 |
} |
| 1432 |
|
| 1433 |
$base_url = rtrim(BOTWRITER_API_URL, '/'); |
| 1434 |
$endpoints = array( |
| 1435 |
$base_url . '/editor', |
| 1436 |
$base_url . '/woo', |
| 1437 |
); |
| 1438 |
|
| 1439 |
$endpoint_total = count($endpoints); |
| 1440 |
foreach ($endpoints as $index => $remote_url) { |
| 1441 |
$response = wp_remote_post($remote_url, array( |
| 1442 |
'timeout' => 90, |
| 1443 |
'sslverify' => $ssl_verify, |
| 1444 |
'headers' => array('Content-Type' => 'application/json'), |
| 1445 |
'body' => wp_json_encode($payload), |
| 1446 |
)); |
| 1447 |
|
| 1448 |
if (is_wp_error($response)) { |
| 1449 |
if ($index === $endpoint_total - 1) { |
| 1450 |
return new WP_Error('editor_worker_network', $response->get_error_message(), array( |
| 1451 |
'provider' => (string) $provider, |
| 1452 |
'worker_provider' => (string) $worker_provider, |
| 1453 |
'model' => (string) $model, |
| 1454 |
'endpoint' => (string) $remote_url, |
| 1455 |
'transport_code' => (string) $response->get_error_code(), |
| 1456 |
)); |
| 1457 |
} |
| 1458 |
continue; |
| 1459 |
} |
| 1460 |
|
| 1461 |
$http_code = wp_remote_retrieve_response_code($response); |
| 1462 |
$body = wp_remote_retrieve_body($response); |
| 1463 |
$data = json_decode($body, true); |
| 1464 |
|
| 1465 |
// If the new route is not deployed yet, retry once with /woo. |
| 1466 |
if ($http_code === 404 && $index === 0) { |
| 1467 |
continue; |
| 1468 |
} |
| 1469 |
|
| 1470 |
if (!empty($data['site_token'])) { |
| 1471 |
update_option('botwriter_site_token', sanitize_text_field((string) $data['site_token'])); |
| 1472 |
} |
| 1473 |
|
| 1474 |
if (!empty($data['warning']) && function_exists('botwriter_announcements_add')) { |
| 1475 |
botwriter_announcements_add( |
| 1476 |
__('Service notice', 'botwriter'), |
| 1477 |
(string) $data['warning'] |
| 1478 |
); |
| 1479 |
} |
| 1480 |
|
| 1481 |
if ($http_code !== 200 || (isset($data['status']) && $data['status'] === 'error')) { |
| 1482 |
$error_message = ''; |
| 1483 |
if (is_array($data)) { |
| 1484 |
$error_message = (string) ($data['error'] ?? $data['message'] ?? ''); |
| 1485 |
} |
| 1486 |
if ($error_message === '') { |
| 1487 |
$error_message = "HTTP {$http_code}"; |
| 1488 |
} |
| 1489 |
return new WP_Error('editor_worker_error', $error_message, array( |
| 1490 |
'provider' => (string) $provider, |
| 1491 |
'worker_provider' => (string) $worker_provider, |
| 1492 |
'model' => (string) $model, |
| 1493 |
'endpoint' => (string) $remote_url, |
| 1494 |
'http_code' => (int) $http_code, |
| 1495 |
'worker_error_code' => is_array($data) ? (string) ($data['error_code'] ?? '') : '', |
| 1496 |
)); |
| 1497 |
} |
| 1498 |
|
| 1499 |
$content = is_array($data) ? (string) ($data['content'] ?? '') : ''; |
| 1500 |
if ($content === '') { |
| 1501 |
return new WP_Error('editor_worker_empty', __('AI returned an empty response.', 'botwriter')); |
| 1502 |
} |
| 1503 |
|
| 1504 |
return $content; |
| 1505 |
} |
| 1506 |
|
| 1507 |
return new WP_Error('editor_worker_unavailable', __('Editor assistant service is currently unavailable.', 'botwriter')); |
| 1508 |
} |
| 1509 |
|
| 1510 |
// SEO module relocated to includes/seo/ — see botwriter_seo_register_admin_menu and botwriter_seo_auto_internal_links_postprocess. |
| 1511 |
|
| 1512 |
/** |
| 1513 |
* Strip wrapping quotes added by model responses. |
| 1514 |
* |
| 1515 |
* @param string $text Input text. |
| 1516 |
* @return string |
| 1517 |
*/ |
| 1518 |
function botwriter_editor_strip_wrapping_quotes($text) { |
| 1519 |
$text = trim((string) $text); |
| 1520 |
|
| 1521 |
for ($i = 0; $i < 3; $i++) { |
| 1522 |
$next = preg_replace("/^[\"'`\\x{201C}\\x{201D}\\x{00AB}\\x{00BB}\\x{2018}\\x{2019}]+|[\"'`\\x{201C}\\x{201D}\\x{00AB}\\x{00BB}\\x{2018}\\x{2019}]+$/u", '', $text); |
| 1523 |
$next = trim((string) $next); |
| 1524 |
if ($next === $text) { |
| 1525 |
break; |
| 1526 |
} |
| 1527 |
$text = $next; |
| 1528 |
} |
| 1529 |
|
| 1530 |
return $text; |
| 1531 |
} |
| 1532 |
|
| 1533 |
/** |
| 1534 |
* AJAX: generate post editor assistant output for selected field. |
| 1535 |
*/ |
| 1536 |
function botwriter_editor_assistant_generate_ajax() { |
| 1537 |
if (!current_user_can('edit_posts')) { |
| 1538 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 1539 |
} |
| 1540 |
|
| 1541 |
check_ajax_referer('botwriter_editor_assistant_nonce', 'nonce'); |
| 1542 |
|
| 1543 |
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; |
| 1544 |
if ($post_id > 0 && !current_user_can('edit_post', $post_id)) { |
| 1545 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 1546 |
} |
| 1547 |
|
| 1548 |
$allowed_targets = array('text', 'title', 'tags', 'excerpt', 'seo_meta', 'internal_links'); |
| 1549 |
$target = isset($_POST['target']) ? sanitize_key(wp_unslash($_POST['target'])) : 'text'; |
| 1550 |
if (!in_array($target, $allowed_targets, true)) { |
| 1551 |
$target = 'text'; |
| 1552 |
} |
| 1553 |
|
| 1554 |
$user_prompt = isset($_POST['prompt']) ? sanitize_textarea_field(wp_unslash($_POST['prompt'])) : ''; |
| 1555 |
if ($user_prompt === '') { |
| 1556 |
wp_send_json_error(array('message' => __('Prompt is required.', 'botwriter'))); |
| 1557 |
} |
| 1558 |
|
| 1559 |
$context = array( |
| 1560 |
'title' => isset($_POST['context_title']) ? sanitize_text_field(wp_unslash($_POST['context_title'])) : '', |
| 1561 |
'content' => isset($_POST['context_content']) ? wp_kses_post(wp_unslash($_POST['context_content'])) : '', |
| 1562 |
'tags' => isset($_POST['context_tags']) ? sanitize_text_field(wp_unslash($_POST['context_tags'])) : '', |
| 1563 |
'excerpt' => isset($_POST['context_excerpt']) ? sanitize_textarea_field(wp_unslash($_POST['context_excerpt'])) : '', |
| 1564 |
'seo_meta' => isset($_POST['context_seo_meta']) ? sanitize_textarea_field(wp_unslash($_POST['context_seo_meta'])) : '', |
| 1565 |
); |
| 1566 |
|
| 1567 |
$context_limits = array( |
| 1568 |
'title' => 300, |
| 1569 |
'content' => 30000, |
| 1570 |
'tags' => 1000, |
| 1571 |
'excerpt' => 500, |
| 1572 |
'seo_meta' => 500, |
| 1573 |
); |
| 1574 |
foreach ($context_limits as $field => $max_length) { |
| 1575 |
$value = (string) ($context[$field] ?? ''); |
| 1576 |
if (function_exists('mb_strlen') && function_exists('mb_substr')) { |
| 1577 |
if (mb_strlen($value) > $max_length) { |
| 1578 |
$context[$field] = mb_substr($value, 0, $max_length); |
| 1579 |
} |
| 1580 |
} elseif (strlen($value) > $max_length) { |
| 1581 |
$context[$field] = substr($value, 0, $max_length); |
| 1582 |
} |
| 1583 |
} |
| 1584 |
|
| 1585 |
$keyphrases_raw = isset($_POST['context_keyphrases']) ? sanitize_text_field(wp_unslash($_POST['context_keyphrases'])) : ''; |
| 1586 |
$keyphrases = botwriter_editor_parse_keyphrases($keyphrases_raw); |
| 1587 |
|
| 1588 |
$provider = sanitize_key((string) get_option('botwriter_text_provider', 'openai')); |
| 1589 |
$model = function_exists('botwriter_get_current_text_model') |
| 1590 |
? (string) botwriter_get_current_text_model() |
| 1591 |
: (string) get_option('botwriter_openai_model', 'gpt-5.4-mini'); |
| 1592 |
$api_key = function_exists('botwriter_get_provider_api_key') |
| 1593 |
? (string) botwriter_get_provider_api_key($provider) |
| 1594 |
: ''; |
| 1595 |
|
| 1596 |
// SEO settings tab controls automatic publish post-processing, not the editor widget. |
| 1597 |
$internal_links_ai_enabled = true; |
| 1598 |
$internal_links_noai_enabled = true; |
| 1599 |
|
| 1600 |
if ($target === 'internal_links') { |
| 1601 |
$candidates = botwriter_editor_get_internal_link_candidates($post_id, $context, 26); |
| 1602 |
if (empty($candidates)) { |
| 1603 |
wp_send_json_success(array( |
| 1604 |
'target' => $target, |
| 1605 |
'suggestions' => array(), |
| 1606 |
'provider' => $provider, |
| 1607 |
'model' => $model, |
| 1608 |
'candidate_count' => 0, |
| 1609 |
'strategy' => 'no_candidates', |
| 1610 |
)); |
| 1611 |
} |
| 1612 |
|
| 1613 |
$use_ai_strategy = $internal_links_ai_enabled && $api_key !== ''; |
| 1614 |
if (!$use_ai_strategy) { |
| 1615 |
if (!$internal_links_noai_enabled) { |
| 1616 |
if ($api_key === '') { |
| 1617 |
wp_send_json_error(array('message' => __('Please configure the API key for your selected text provider, or enable deterministic internal-link mode in SEO settings.', 'botwriter'))); |
| 1618 |
} |
| 1619 |
|
| 1620 |
wp_send_json_error(array('message' => __('Internal-link generation is disabled. Enable AI mode or deterministic mode in SEO settings.', 'botwriter'))); |
| 1621 |
} |
| 1622 |
|
| 1623 |
$suggestions = botwriter_editor_build_internal_links_noai_suggestions($candidates, $context, $keyphrases, 8); |
| 1624 |
wp_send_json_success(array( |
| 1625 |
'target' => $target, |
| 1626 |
'suggestions' => $suggestions, |
| 1627 |
'provider' => $provider, |
| 1628 |
'model' => $model, |
| 1629 |
'candidate_count' => count($candidates), |
| 1630 |
'keyphrases' => $keyphrases, |
| 1631 |
'strategy' => 'no_ai', |
| 1632 |
)); |
| 1633 |
} |
| 1634 |
|
| 1635 |
$links_prompt = botwriter_build_editor_internal_links_prompt($user_prompt, $context, $keyphrases, $candidates); |
| 1636 |
$generated_links = botwriter_call_editor_worker($provider, $api_key, $model, $links_prompt, 2400, 0.2); |
| 1637 |
|
| 1638 |
if (is_wp_error($generated_links)) { |
| 1639 |
$error_message = $generated_links->get_error_message(); |
| 1640 |
if ($error_message === '') { |
| 1641 |
$error_message = __('Could not generate internal link suggestions.', 'botwriter'); |
| 1642 |
} |
| 1643 |
wp_send_json_error(array('message' => $error_message)); |
| 1644 |
} |
| 1645 |
|
| 1646 |
$suggestions = botwriter_parse_editor_internal_links_response((string) $generated_links, $candidates); |
| 1647 |
|
| 1648 |
wp_send_json_success(array( |
| 1649 |
'target' => $target, |
| 1650 |
'suggestions' => $suggestions, |
| 1651 |
'provider' => $provider, |
| 1652 |
'model' => $model, |
| 1653 |
'candidate_count' => count($candidates), |
| 1654 |
'keyphrases' => $keyphrases, |
| 1655 |
'strategy' => 'ai', |
| 1656 |
)); |
| 1657 |
} |
| 1658 |
|
| 1659 |
if ($api_key === '') { |
| 1660 |
wp_send_json_error(array('message' => __('Please configure the API key for your selected text provider in BotWriter settings.', 'botwriter'))); |
| 1661 |
} |
| 1662 |
|
| 1663 |
$max_tokens = ($target === 'text') ? 4096 : 700; |
| 1664 |
$assistant_prompt = botwriter_build_editor_assistant_prompt($target, $user_prompt, $context); |
| 1665 |
$generated = botwriter_call_editor_worker($provider, $api_key, $model, $assistant_prompt, $max_tokens, 0.35); |
| 1666 |
|
| 1667 |
if (is_wp_error($generated)) { |
| 1668 |
$error_message = $generated->get_error_message(); |
| 1669 |
if ($error_message === '') { |
| 1670 |
$error_message = __('Could not generate a response.', 'botwriter'); |
| 1671 |
} |
| 1672 |
wp_send_json_error(array('message' => $error_message)); |
| 1673 |
} |
| 1674 |
|
| 1675 |
$content = trim((string) $generated); |
| 1676 |
$content = preg_replace('/^```(?:[a-zA-Z0-9_-]+)?\s*/', '', $content); |
| 1677 |
$content = preg_replace('/\s*```$/', '', $content); |
| 1678 |
$content = trim((string) $content); |
| 1679 |
|
| 1680 |
if ($content === '') { |
| 1681 |
wp_send_json_error(array('message' => __('AI returned an empty response.', 'botwriter'))); |
| 1682 |
} |
| 1683 |
|
| 1684 |
if ($target === 'title') { |
| 1685 |
$content = sanitize_text_field($content); |
| 1686 |
$content = botwriter_editor_strip_wrapping_quotes($content); |
| 1687 |
} elseif ($target === 'tags') { |
| 1688 |
$parts = preg_split('/[\r\n,]+/', $content); |
| 1689 |
$parts = is_array($parts) ? $parts : array(); |
| 1690 |
$tags = array(); |
| 1691 |
foreach ($parts as $part) { |
| 1692 |
$tag = trim(sanitize_text_field((string) $part)); |
| 1693 |
if ($tag !== '') { |
| 1694 |
$tags[] = $tag; |
| 1695 |
} |
| 1696 |
} |
| 1697 |
$tags = array_values(array_unique($tags)); |
| 1698 |
$content = implode(', ', $tags); |
| 1699 |
} elseif ($target === 'excerpt' || $target === 'seo_meta') { |
| 1700 |
$content = botwriter_editor_strip_wrapping_quotes($content); |
| 1701 |
if (function_exists('botwriter_sanitize_meta_description')) { |
| 1702 |
$content = botwriter_sanitize_meta_description($content); |
| 1703 |
} else { |
| 1704 |
$content = sanitize_textarea_field($content); |
| 1705 |
} |
| 1706 |
} else { |
| 1707 |
$content = botwriter_editor_strip_wrapping_quotes($content); |
| 1708 |
$content = str_replace(array("\\r\\n", "\\n", "\\r"), "\n", $content); |
| 1709 |
$content = preg_replace('/(\r?\n){3,}/', "\n\n", $content); |
| 1710 |
$content = wp_kses_post($content); |
| 1711 |
} |
| 1712 |
|
| 1713 |
wp_send_json_success(array( |
| 1714 |
'target' => $target, |
| 1715 |
'content' => $content, |
| 1716 |
'provider' => $provider, |
| 1717 |
'model' => $model, |
| 1718 |
)); |
| 1719 |
} |
| 1720 |
add_action('wp_ajax_botwriter_editor_ai_generate', 'botwriter_editor_assistant_generate_ajax'); |
| 1721 |
|
| 1722 |
/** |
| 1723 |
* Render SEO checks for editor widget tab. |
| 1724 |
* |
| 1725 |
* @param array $seo_report SEO report array. |
| 1726 |
* @return string |
| 1727 |
*/ |
| 1728 |
function botwriter_editor_render_seo_checks_html($seo_report) { |
| 1729 |
$seo_report = is_array($seo_report) ? $seo_report : array(); |
| 1730 |
$seo_score = (int) ($seo_report['score'] ?? 0); |
| 1731 |
$seo_grade = (string) ($seo_report['grade'] ?? 'n/a'); |
| 1732 |
$grade_label = function_exists('botwriter_seo_grade_label') |
| 1733 |
? botwriter_seo_grade_label($seo_grade) |
| 1734 |
: ucfirst($seo_grade); |
| 1735 |
|
| 1736 |
$seo_counts = array('good' => 0, 'warn' => 0, 'bad' => 0); |
| 1737 |
foreach ((array) ($seo_report['checks'] ?? array()) as $check) { |
| 1738 |
$status = function_exists('botwriter_seo_check_status') |
| 1739 |
? botwriter_seo_check_status($check) |
| 1740 |
: (!empty($check['passed']) ? 'good' : 'bad'); |
| 1741 |
$seo_counts[$status] = ($seo_counts[$status] ?? 0) + 1; |
| 1742 |
} |
| 1743 |
|
| 1744 |
ob_start(); |
| 1745 |
?> |
| 1746 |
<div class="bw-editor-ai-seo-score-box bw-grade-<?php echo esc_attr($seo_grade); ?>"> |
| 1747 |
<div class="bw-editor-ai-seo-score-main"><?php echo (int) $seo_score; ?></div> |
| 1748 |
<div class="bw-editor-ai-seo-score-label"><?php echo esc_html($grade_label); ?></div> |
| 1749 |
</div> |
| 1750 |
<div class="bw-summary-row"> |
| 1751 |
<span class="bw-pill good"><span class="dashicons dashicons-yes-alt"></span> <?php echo (int) ($seo_counts['good'] ?? 0); ?> <?php esc_html_e('passed', 'botwriter'); ?></span> |
| 1752 |
<span class="bw-pill warn"><span class="dashicons dashicons-warning"></span> <?php echo (int) ($seo_counts['warn'] ?? 0); ?> <?php esc_html_e('to improve', 'botwriter'); ?></span> |
| 1753 |
<span class="bw-pill bad"><span class="dashicons dashicons-dismiss"></span> <?php echo (int) ($seo_counts['bad'] ?? 0); ?> <?php esc_html_e('issues', 'botwriter'); ?></span> |
| 1754 |
</div> |
| 1755 |
<ul class="bw-report-checks"> |
| 1756 |
<?php foreach ((array) ($seo_report['checks'] ?? array()) as $check) : |
| 1757 |
$status = function_exists('botwriter_seo_check_status') |
| 1758 |
? botwriter_seo_check_status($check) |
| 1759 |
: (!empty($check['passed']) ? 'good' : 'bad'); |
| 1760 |
$icon = function_exists('botwriter_seo_status_icon') |
| 1761 |
? botwriter_seo_status_icon($status) |
| 1762 |
: ($status === 'good' ? 'dashicons-yes-alt' : ($status === 'warn' ? 'dashicons-warning' : 'dashicons-dismiss')); |
| 1763 |
?> |
| 1764 |
<li class="bw-check bw-status-<?php echo esc_attr($status); ?>"> |
| 1765 |
<span class="dashicons <?php echo esc_attr($icon); ?> bw-check-icon"></span> |
| 1766 |
<div class="bw-check-body"> |
| 1767 |
<div class="bw-check-label"><?php echo esc_html((string) ($check['label'] ?? '')); ?></div> |
| 1768 |
<?php if (!empty($check['hint'])) : ?> |
| 1769 |
<div class="bw-check-hint"><?php echo esc_html((string) $check['hint']); ?></div> |
| 1770 |
<?php endif; ?> |
| 1771 |
</div> |
| 1772 |
<?php if ((int) ($check['weight'] ?? 0) > 0) : ?> |
| 1773 |
<span class="bw-weight" title="<?php esc_attr_e('Weight', 'botwriter'); ?>"><?php echo (int) ($check['weight'] ?? 0); ?></span> |
| 1774 |
<?php endif; ?> |
| 1775 |
</li> |
| 1776 |
<?php endforeach; ?> |
| 1777 |
</ul> |
| 1778 |
<?php |
| 1779 |
return (string) ob_get_clean(); |
| 1780 |
} |
| 1781 |
|
| 1782 |
/** |
| 1783 |
* Render readability checks for editor widget tab. |
| 1784 |
* |
| 1785 |
* @param array $readability_report Readability report array. |
| 1786 |
* @return string |
| 1787 |
*/ |
| 1788 |
function botwriter_editor_render_readability_checks_html($readability_report) { |
| 1789 |
$readability_report = is_array($readability_report) ? $readability_report : array(); |
| 1790 |
$read_score = (int) ($readability_report['score'] ?? 0); |
| 1791 |
$read_grade = (string) ($readability_report['grade'] ?? 'n/a'); |
| 1792 |
$grade_label = function_exists('botwriter_seo_grade_label') |
| 1793 |
? botwriter_seo_grade_label($read_grade) |
| 1794 |
: ucfirst($read_grade); |
| 1795 |
|
| 1796 |
$read_counts = array('good' => 0, 'warn' => 0, 'bad' => 0); |
| 1797 |
foreach ((array) ($readability_report['checks'] ?? array()) as $check) { |
| 1798 |
$status = (string) ($check['status'] ?? 'bad'); |
| 1799 |
$read_counts[$status] = ($read_counts[$status] ?? 0) + 1; |
| 1800 |
} |
| 1801 |
|
| 1802 |
ob_start(); |
| 1803 |
?> |
| 1804 |
<div class="bw-editor-ai-seo-score-box bw-grade-<?php echo esc_attr($read_grade); ?>"> |
| 1805 |
<div class="bw-editor-ai-seo-score-main"><?php echo (int) $read_score; ?></div> |
| 1806 |
<div class="bw-editor-ai-seo-score-label"><?php echo esc_html($grade_label); ?></div> |
| 1807 |
</div> |
| 1808 |
<div class="bw-summary-row"> |
| 1809 |
<span class="bw-pill good"><span class="dashicons dashicons-yes-alt"></span> <?php echo (int) ($read_counts['good'] ?? 0); ?> <?php esc_html_e('great', 'botwriter'); ?></span> |
| 1810 |
<span class="bw-pill warn"><span class="dashicons dashicons-warning"></span> <?php echo (int) ($read_counts['warn'] ?? 0); ?> <?php esc_html_e('ok', 'botwriter'); ?></span> |
| 1811 |
<span class="bw-pill bad"><span class="dashicons dashicons-dismiss"></span> <?php echo (int) ($read_counts['bad'] ?? 0); ?> <?php esc_html_e('hard', 'botwriter'); ?></span> |
| 1812 |
</div> |
| 1813 |
<ul class="bw-report-checks"> |
| 1814 |
<?php foreach ((array) ($readability_report['checks'] ?? array()) as $check) : |
| 1815 |
$status = (string) ($check['status'] ?? 'bad'); |
| 1816 |
$icon = function_exists('botwriter_seo_status_icon') |
| 1817 |
? botwriter_seo_status_icon($status) |
| 1818 |
: ($status === 'good' ? 'dashicons-yes-alt' : ($status === 'warn' ? 'dashicons-warning' : 'dashicons-dismiss')); |
| 1819 |
?> |
| 1820 |
<li class="bw-check bw-status-<?php echo esc_attr($status); ?>"> |
| 1821 |
<span class="dashicons <?php echo esc_attr($icon); ?> bw-check-icon"></span> |
| 1822 |
<div class="bw-check-body"> |
| 1823 |
<div class="bw-check-label"> |
| 1824 |
<?php echo esc_html((string) ($check['label'] ?? '')); ?> |
| 1825 |
<?php if (!empty($check['value'])) : ?> |
| 1826 |
<span class="bw-tag"><?php echo esc_html((string) $check['value']); ?></span> |
| 1827 |
<?php endif; ?> |
| 1828 |
</div> |
| 1829 |
<?php if (!empty($check['hint'])) : ?> |
| 1830 |
<div class="bw-check-hint"><?php echo esc_html((string) $check['hint']); ?></div> |
| 1831 |
<?php endif; ?> |
| 1832 |
</div> |
| 1833 |
<span class="bw-weight" title="<?php esc_attr_e('Weight', 'botwriter'); ?>"><?php echo (int) ($check['weight'] ?? 0); ?></span> |
| 1834 |
</li> |
| 1835 |
<?php endforeach; ?> |
| 1836 |
</ul> |
| 1837 |
<?php |
| 1838 |
return (string) ob_get_clean(); |
| 1839 |
} |
| 1840 |
|
| 1841 |
/** |
| 1842 |
* AJAX: return SEO and readability report sections for editor widget. |
| 1843 |
*/ |
| 1844 |
function botwriter_editor_assistant_get_seo_report_ajax() { |
| 1845 |
if (!current_user_can('edit_posts')) { |
| 1846 |
wp_send_json_error(array('message' => __('Permission denied.', 'botwriter'))); |
| 1847 |
} |
| 1848 |
|
| 1849 |
check_ajax_referer('botwriter_editor_assistant_nonce', 'nonce'); |
| 1850 |
|
| 1851 |
$post_id = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0; |
| 1852 |
if ($post_id <= 0) { |
| 1853 |
wp_send_json_error(array('message' => __('Invalid post.', 'botwriter'))); |
| 1854 |
} |
| 1855 |
|
| 1856 |
if (!current_user_can('edit_post', $post_id)) { |
| 1857 |
wp_send_json_error(array('message' => __('You cannot edit this post.', 'botwriter'))); |
| 1858 |
} |
| 1859 |
|
| 1860 |
if (!function_exists('botwriter_seo_compute_score') || !function_exists('botwriter_seo_compute_readability')) { |
| 1861 |
wp_send_json_error(array('message' => __('SEO module is not available.', 'botwriter'))); |
| 1862 |
} |
| 1863 |
|
| 1864 |
$seo_report = botwriter_seo_compute_score($post_id); |
| 1865 |
$readability_report = botwriter_seo_compute_readability($post_id); |
| 1866 |
|
| 1867 |
wp_send_json_success(array( |
| 1868 |
'seo_html' => botwriter_editor_render_seo_checks_html($seo_report), |
| 1869 |
'readability_html' => botwriter_editor_render_readability_checks_html($readability_report), |
| 1870 |
)); |
| 1871 |
} |
| 1872 |
add_action('wp_ajax_botwriter_editor_ai_get_seo_report', 'botwriter_editor_assistant_get_seo_report_ajax'); |
| 1873 |
|
| 1874 |
|
| 1875 |
|
| 1876 |
if (!function_exists('deactivate_plugins')) { |
| 1877 |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 1878 |
} |
| 1879 |
|
| 1880 |
|
| 1881 |
function botwriter_enqueue_styles(){ |
| 1882 |
$my_plugin_dir = plugin_dir_url(__FILE__); |
| 1883 |
$screen = get_current_screen(); |
| 1884 |
|
| 1885 |
$slug = $screen->id; |
| 1886 |
|
| 1887 |
// Keep submenu cleanup styles available across the whole admin so folded flyouts stay filtered too. |
| 1888 |
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')); |
| 1889 |
wp_enqueue_style('botwriter_admin_menu'); |
| 1890 |
|
| 1891 |
// Welcome banner CSS - load on ALL admin pages if not dismissed |
| 1892 |
// (because admin_notices shows on all pages) |
| 1893 |
$welcome_dismissed = get_option('botwriter_welcome_dismissed', false); |
| 1894 |
if (!$welcome_dismissed) { |
| 1895 |
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')); |
| 1896 |
wp_enqueue_style('botwriter_welcome_banner'); |
| 1897 |
} |
| 1898 |
|
| 1899 |
if ($screen && $screen->base === 'post' && (string) $screen->post_type === 'post' && current_user_can('edit_posts') && get_option('botwriter_editor_assistant_enabled', '1') === '1') { |
| 1900 |
wp_register_style('botwriter_editor_assistant', $my_plugin_dir . 'assets/css/editor-ai-assistant.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/editor-ai-assistant.css')); |
| 1901 |
wp_enqueue_style('botwriter_editor_assistant'); |
| 1902 |
} |
| 1903 |
|
| 1904 |
// Only enqueue other styles for BotWriter admin screens |
| 1905 |
|
| 1906 |
if (strpos((string)$slug, 'botwriter') !== false) { |
| 1907 |
|
| 1908 |
// Register and enqueue styles with dynamic versioning for better caching |
| 1909 |
wp_register_style('botwriter_bootstrap', $my_plugin_dir . 'assets/css/bootstrap.min.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/bootstrap.min.css')); |
| 1910 |
wp_enqueue_style('botwriter_bootstrap'); |
| 1911 |
|
| 1912 |
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')); |
| 1913 |
wp_enqueue_style('botwriter_jquery_ui'); |
| 1914 |
|
| 1915 |
wp_register_style('botwriter_loader', $my_plugin_dir . 'assets/css/loader.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/loader.css')); |
| 1916 |
wp_enqueue_style('botwriter_loader'); |
| 1917 |
|
| 1918 |
wp_register_style('botwriter_style', $my_plugin_dir . 'assets/css/style.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/style.css')); |
| 1919 |
wp_enqueue_style('botwriter_style'); |
| 1920 |
|
| 1921 |
// Settings page specific styles |
| 1922 |
if (strpos((string)$slug, 'botwriter_settings') !== false) { |
| 1923 |
wp_register_style('botwriter_settings', $my_plugin_dir . 'assets/css/settings.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/settings.css')); |
| 1924 |
wp_enqueue_style('botwriter_settings'); |
| 1925 |
} |
| 1926 |
|
| 1927 |
if ($slug === 'botwriter_page_botwriter_siterewriter_page') { |
| 1928 |
wp_register_style('botwriter_siterewriter', $my_plugin_dir . 'assets/css/siterewriter.css', array(), filemtime(plugin_dir_path(__FILE__) . 'assets/css/siterewriter.css')); |
| 1929 |
wp_enqueue_style('botwriter_siterewriter'); |
| 1930 |
} |
| 1931 |
} |
| 1932 |
} |
| 1933 |
|
| 1934 |
add_action('admin_enqueue_scripts', 'botwriter_enqueue_styles'); |
| 1935 |
|
| 1936 |
|
| 1937 |
|
| 1938 |
|
| 1939 |
// Hook to add the admin menu |
| 1940 |
add_action('admin_menu', function() { |
| 1941 |
add_menu_page( |
| 1942 |
__('BotWriter', 'botwriter'), |
| 1943 |
__('BotWriter', 'botwriter'), |
| 1944 |
'manage_options', |
| 1945 |
'botwriter_menu', |
| 1946 |
'botwriter_admin_page', |
| 1947 |
plugin_dir_url(__FILE__) . '/assets/images/icono25.png', |
| 1948 |
90 |
| 1949 |
); |
| 1950 |
|
| 1951 |
add_submenu_page('botwriter_menu', |
| 1952 |
__('Write now', 'botwriter'), |
| 1953 |
__('Write now', 'botwriter'), |
| 1954 |
'manage_options', |
| 1955 |
'botwriter_write_now', |
| 1956 |
'botwriter_quick_post_page_handler' |
| 1957 |
); |
| 1958 |
|
| 1959 |
add_submenu_page('botwriter_menu', |
| 1960 |
__('New task', 'botwriter'), |
| 1961 |
__('New task', 'botwriter'), |
| 1962 |
'manage_options', |
| 1963 |
'botwriter_addnew_page', |
| 1964 |
'botwriter_addnew_page_handler' |
| 1965 |
); |
| 1966 |
|
| 1967 |
// Register under parent to avoid deprecations (null parent). We'll hide it from the submenu below. |
| 1968 |
add_submenu_page('botwriter_menu', |
| 1969 |
__('Super Task AI', 'botwriter'), |
| 1970 |
__('Super Task AI', 'botwriter'), |
| 1971 |
'manage_options', |
| 1972 |
'botwriter_super_page', |
| 1973 |
'botwriter_super_page_handler' |
| 1974 |
); |
| 1975 |
|
| 1976 |
add_submenu_page('botwriter_menu', |
| 1977 |
__('Tasks AI', 'botwriter'), |
| 1978 |
__('Tasks AI', 'botwriter'), |
| 1979 |
'manage_options', |
| 1980 |
'botwriter_automatic_posts', |
| 1981 |
'botwriter_automatic_posts_page' |
| 1982 |
); |
| 1983 |
|
| 1984 |
add_submenu_page('botwriter_menu', |
| 1985 |
__('Content Rewriter', 'botwriter'), |
| 1986 |
__('Content Rewriter', 'botwriter'), |
| 1987 |
'manage_options', |
| 1988 |
'botwriter_rewriter_page', |
| 1989 |
'botwriter_rewriter_page_handler' |
| 1990 |
); |
| 1991 |
|
| 1992 |
add_submenu_page('botwriter_menu', |
| 1993 |
__('Site Rewriter', 'botwriter'), |
| 1994 |
__('Site Rewriter', 'botwriter'), |
| 1995 |
'manage_options', |
| 1996 |
'botwriter_siterewriter_page', |
| 1997 |
'botwriter_siterewriter_page_handler' |
| 1998 |
); |
| 1999 |
|
| 2000 |
// for development |
| 2001 |
/* |
| 2002 |
add_submenu_page('botwriter_menu', |
| 2003 |
__('Test Call', 'botwriter'), |
| 2004 |
__('Test Call', 'botwriter'), |
| 2005 |
'manage_options', |
| 2006 |
'botwriter_prueba', |
| 2007 |
'botwriter_prueba' |
| 2008 |
); |
| 2009 |
*/ |
| 2010 |
|
| 2011 |
|
| 2012 |
// Register the edit/detail page under the parent, then hide it programmatically to avoid null parent deprecations |
| 2013 |
$hook = add_submenu_page('botwriter_menu', |
| 2014 |
__('Add New Task', 'botwriter'), |
| 2015 |
__('Add New Task', 'botwriter'), |
| 2016 |
'manage_options', |
| 2017 |
'botwriter_automatic_post_new', |
| 2018 |
'botwriter_form_page_handler' |
| 2019 |
); |
| 2020 |
|
| 2021 |
add_submenu_page('botwriter_menu', |
| 2022 |
__('Settings', 'botwriter'), |
| 2023 |
__('Settings', 'botwriter'), |
| 2024 |
'manage_options', |
| 2025 |
'botwriter_settings', |
| 2026 |
'botwriter_settings_page_handler' |
| 2027 |
); |
| 2028 |
|
| 2029 |
add_submenu_page('botwriter_menu', |
| 2030 |
__('Templates', 'botwriter'), |
| 2031 |
__('Templates', 'botwriter'), |
| 2032 |
'manage_options', |
| 2033 |
'botwriter_templates', |
| 2034 |
'botwriter_templates_page_handler' |
| 2035 |
); |
| 2036 |
|
| 2037 |
add_submenu_page('botwriter_menu', |
| 2038 |
__('Logs', 'botwriter'), |
| 2039 |
get_option('botwriter_stopformany', false) |
| 2040 |
? __('Logs', 'botwriter') . ' <span class="update-plugins count-1" style="background:#d63638;"><span class="plugin-count">!</span></span>' |
| 2041 |
: __('Logs', 'botwriter'), |
| 2042 |
'manage_options', |
| 2043 |
'botwriter_logs', |
| 2044 |
'botwriter_logs_page_handler' |
| 2045 |
); |
| 2046 |
}); |
| 2047 |
|
| 2048 |
add_filter('submenu_file', function($submenu_file) { |
| 2049 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only routing parameter used to highlight hidden submenu pages. |
| 2050 |
$page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; |
| 2051 |
if (in_array($page, array('botwriter_rewriter_page', 'botwriter_siterewriter_page'), true)) { |
| 2052 |
return 'botwriter_addnew_page'; |
| 2053 |
} |
| 2054 |
return $submenu_file; |
| 2055 |
}); |
| 2056 |
|
| 2057 |
// CSS for hiding duplicate submenu entries is now in assets/css/admin-menu.css |
| 2058 |
// and enqueued via botwriter_enqueue_styles() |
| 2059 |
|
| 2060 |
|
| 2061 |
|
| 2062 |
|
| 2063 |
|
| 2064 |
function botwriter_prueba() { |
| 2065 |
if (!current_user_can('manage_options')) { |
| 2066 |
return; |
| 2067 |
} |
| 2068 |
|
| 2069 |
?> |
| 2070 |
|
| 2071 |
<h1>Prueba...</h1> |
| 2072 |
<div> |
| 2073 |
Llamando a la funcion que ejecuta las tareas |
| 2074 |
</div> |
| 2075 |
|
| 2076 |
<?php |
| 2077 |
botwriter_scheduled_events_execute_tasks(); |
| 2078 |
|
| 2079 |
} |
| 2080 |
|
| 2081 |
|
| 2082 |
|
| 2083 |
|
| 2084 |
|
| 2085 |
|
| 2086 |
// First screen of the plugin |
| 2087 |
function botwriter_admin_page() { |
| 2088 |
if (!current_user_can('manage_options')) { |
| 2089 |
return; |
| 2090 |
} |
| 2091 |
|
| 2092 |
// Check if any API key is configured |
| 2093 |
$has_api_key = false; |
| 2094 |
$text_providers = [ |
| 2095 |
'botwriter_openai_api_key', |
| 2096 |
'botwriter_anthropic_api_key', |
| 2097 |
'botwriter_google_api_key', |
| 2098 |
'botwriter_mistral_api_key', |
| 2099 |
'botwriter_groq_api_key', |
| 2100 |
'botwriter_openrouter_api_key' |
| 2101 |
]; |
| 2102 |
foreach ($text_providers as $provider_key) { |
| 2103 |
$key_value = get_option($provider_key); |
| 2104 |
if (!empty($key_value) && function_exists('botwriter_decrypt_api_key')) { |
| 2105 |
$decrypted = botwriter_decrypt_api_key($key_value); |
| 2106 |
if (!empty($decrypted)) { |
| 2107 |
$has_api_key = true; |
| 2108 |
break; |
| 2109 |
} |
| 2110 |
} |
| 2111 |
} |
| 2112 |
|
| 2113 |
$settings_url = admin_url('admin.php?page=botwriter_settings'); |
| 2114 |
$addnew_url = admin_url('admin.php?page=botwriter_addnew_page'); |
| 2115 |
$tasks_url = admin_url('admin.php?page=botwriter_automatic_posts'); |
| 2116 |
$logs_url = admin_url('admin.php?page=botwriter_logs'); |
| 2117 |
?> |
| 2118 |
<div class="wrap"> |
| 2119 |
<div style="max-width: 900px; margin: 0 auto;"> |
| 2120 |
|
| 2121 |
<!-- Header --> |
| 2122 |
<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);"> |
| 2123 |
<h1 style="margin: 0 0 10px 0; font-size: 28px; font-weight: 600;"> |
| 2124 |
<span class="dashicons dashicons-superhero" style="margin-right: 10px; font-size: 28px; width: 28px; height: 28px;"></span><?php echo esc_html__('BotWriter', 'botwriter'); ?> |
| 2125 |
</h1> |
| 2126 |
<p style="margin: 0; font-size: 16px; opacity: 0.95;"> |
| 2127 |
<?php echo esc_html__('AI-Powered Content Creation for WordPress', 'botwriter'); ?> |
| 2128 |
</p> |
| 2129 |
</div> |
| 2130 |
|
| 2131 |
<!-- Quick Start Alert --> |
| 2132 |
<?php if (!$has_api_key): ?> |
| 2133 |
<div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px 20px; border-radius: 0 8px 8px 0; margin-bottom: 25px;"> |
| 2134 |
<strong style="color: #856404;"><span class="dashicons dashicons-lightbulb" style="font-size: 18px; width: 18px; height: 18px; vertical-align: text-bottom;"></span> <?php echo esc_html__('Quick Start:', 'botwriter'); ?></strong> |
| 2135 |
<span style="color: #856404;"> |
| 2136 |
<?php echo esc_html__('Configure your AI provider API key to get started.', 'botwriter'); ?> |
| 2137 |
<a href="<?php echo esc_url($settings_url); ?>" style="color: #856404; font-weight: 600;"><?php echo esc_html__('Go to Settings', 'botwriter'); ?> →</a> |
| 2138 |
</span> |
| 2139 |
</div> |
| 2140 |
<?php endif; ?> |
| 2141 |
|
| 2142 |
<!-- Description --> |
| 2143 |
<div style="background: white; padding: 25px; border-radius: 10px; margin-bottom: 25px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 2144 |
<p style="font-size: 15px; line-height: 1.7; color: #444; margin: 0;"> |
| 2145 |
<?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'); ?> |
| 2146 |
</p> |
| 2147 |
</div> |
| 2148 |
|
| 2149 |
<!-- Features Grid --> |
| 2150 |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin-bottom: 25px;"> |
| 2151 |
|
| 2152 |
<!-- Text AI Card --> |
| 2153 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 2154 |
<div style="margin-bottom: 12px;"><span class="dashicons dashicons-edit" style="font-size: 24px; width: 24px; height: 24px;"></span></div> |
| 2155 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('Multi-Provider Text AI', 'botwriter'); ?></h3> |
| 2156 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 2157 |
<?php echo esc_html__('Choose from OpenAI (GPT-4o), Anthropic (Claude), Google (Gemini), Mistral, Groq, or OpenRouter. Use your own API keys.', 'botwriter'); ?> |
| 2158 |
</p> |
| 2159 |
</div> |
| 2160 |
|
| 2161 |
<!-- Image AI Card --> |
| 2162 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 2163 |
<div style="margin-bottom: 12px;"><span class="dashicons dashicons-format-image" style="font-size: 24px; width: 24px; height: 24px;"></span></div> |
| 2164 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('AI Image Generation', 'botwriter'); ?></h3> |
| 2165 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 2166 |
<?php echo esc_html__('Generate featured images with DALL-E, Stable Diffusion, Flux, Recraft, and more via Replicate, Stability AI, or Fal.ai.', 'botwriter'); ?> |
| 2167 |
</p> |
| 2168 |
</div> |
| 2169 |
|
| 2170 |
<!-- Content Sources Card --> |
| 2171 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 2172 |
<div style="margin-bottom: 12px;"><span class="dashicons dashicons-rss" style="font-size: 24px; width: 24px; height: 24px;"></span></div> |
| 2173 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('Multiple Content Sources', 'botwriter'); ?></h3> |
| 2174 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 2175 |
<?php echo esc_html__('Import and rewrite content from any WordPress site, RSS feed, or news API. Prevent duplicates automatically.', 'botwriter'); ?> |
| 2176 |
</p> |
| 2177 |
</div> |
| 2178 |
|
| 2179 |
<!-- Automation Card --> |
| 2180 |
<div style="background: white; padding: 22px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 2181 |
<div style="margin-bottom: 12px;"><span class="dashicons dashicons-admin-generic" style="font-size: 24px; width: 24px; height: 24px;"></span></div> |
| 2182 |
<h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333;"><?php echo esc_html__('Full Automation', 'botwriter'); ?></h3> |
| 2183 |
<p style="margin: 0; color: #666; font-size: 13px; line-height: 1.6;"> |
| 2184 |
<?php echo esc_html__('Schedule unlimited tasks, set publishing frequency, and let BotWriter work 24/7. Monitor everything from the Logs.', 'botwriter'); ?> |
| 2185 |
</p> |
| 2186 |
</div> |
| 2187 |
|
| 2188 |
</div> |
| 2189 |
|
| 2190 |
<!-- Getting Started Steps --> |
| 2191 |
<div style="background: white; padding: 25px; border-radius: 10px; margin-bottom: 25px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);"> |
| 2192 |
<h2 style="margin: 0 0 20px 0; font-size: 18px; color: #333;"> |
| 2193 |
<span class="dashicons dashicons-controls-play" style="font-size: 20px; width: 20px; height: 20px; vertical-align: text-bottom;"></span> <?php echo esc_html__('Getting Started', 'botwriter'); ?> |
| 2194 |
</h2> |
| 2195 |
|
| 2196 |
<div style="display: flex; flex-direction: column; gap: 15px;"> |
| 2197 |
|
| 2198 |
<div style="display: flex; align-items: flex-start; gap: 15px;"> |
| 2199 |
<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;"> |
| 2200 |
<?php echo $has_api_key ? '✓' : '1'; ?> |
| 2201 |
</div> |
| 2202 |
<div> |
| 2203 |
<strong style="color: #333;"><?php echo esc_html__('Configure your AI Provider', 'botwriter'); ?></strong> |
| 2204 |
<p style="margin: 5px 0 0 0; color: #666; font-size: 13px;"> |
| 2205 |
<?php echo esc_html__('Add your API key from OpenAI, Anthropic (Claude), Google (Gemini), Mistral, Groq, or OpenRouter.', 'botwriter'); ?> |
| 2206 |
<a href="<?php echo esc_url($settings_url); ?>"><?php echo esc_html__('Settings', 'botwriter'); ?> →</a> |
| 2207 |
</p> |
| 2208 |
</div> |
| 2209 |
</div> |
| 2210 |
|
| 2211 |
<div style="display: flex; align-items: flex-start; gap: 15px;"> |
| 2212 |
<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> |
| 2213 |
<div> |
| 2214 |
<strong style="color: #333;"><?php echo esc_html__('Create Your First Task', 'botwriter'); ?></strong> |
| 2215 |
<p style="margin: 5px 0 0 0; color: #666; font-size: 13px;"> |
| 2216 |
<?php echo esc_html__('Define your content source, AI prompts, categories, and publishing schedule.', 'botwriter'); ?> |
| 2217 |
<a href="<?php echo esc_url($addnew_url); ?>"><?php echo esc_html__('Add New', 'botwriter'); ?> →</a> |
| 2218 |
</p> |
| 2219 |
</div> |
| 2220 |
</div> |
| 2221 |
|
| 2222 |
<div style="display: flex; align-items: flex-start; gap: 15px;"> |
| 2223 |
<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> |
| 2224 |
<div> |
| 2225 |
<strong style="color: #333;"><?php echo esc_html__('Activate and Monitor', 'botwriter'); ?></strong> |
| 2226 |
<p style="margin: 5px 0 0 0; color: #666; font-size: 13px;"> |
| 2227 |
<?php echo esc_html__('Enable your tasks and watch BotWriter generate posts automatically. Check the Logs for status updates.', 'botwriter'); ?> |
| 2228 |
<a href="<?php echo esc_url($logs_url); ?>"><?php echo esc_html__('Logs', 'botwriter'); ?> →</a> |
| 2229 |
</p> |
| 2230 |
</div> |
| 2231 |
</div> |
| 2232 |
|
| 2233 |
</div> |
| 2234 |
</div> |
| 2235 |
|
| 2236 |
<!-- Quick Links --> |
| 2237 |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px;"> |
| 2238 |
<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;"> |
| 2239 |
<div style="margin-bottom: 6px;"><span class="dashicons dashicons-admin-generic" style="font-size: 20px; width: 20px; height: 20px;"></span></div> |
| 2240 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Settings', 'botwriter'); ?></div> |
| 2241 |
</a> |
| 2242 |
<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;"> |
| 2243 |
<div style="margin-bottom: 6px;"><span class="dashicons dashicons-plus-alt2" style="font-size: 20px; width: 20px; height: 20px;"></span></div> |
| 2244 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Add New', 'botwriter'); ?></div> |
| 2245 |
</a> |
| 2246 |
<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;"> |
| 2247 |
<div style="margin-bottom: 6px;"><span class="dashicons dashicons-list-view" style="font-size: 20px; width: 20px; height: 20px;"></span></div> |
| 2248 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Tasks', 'botwriter'); ?></div> |
| 2249 |
</a> |
| 2250 |
<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;"> |
| 2251 |
<div style="margin-bottom: 6px;"><span class="dashicons dashicons-chart-bar" style="font-size: 20px; width: 20px; height: 20px;"></span></div> |
| 2252 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Logs', 'botwriter'); ?></div> |
| 2253 |
</a> |
| 2254 |
<a href="https://wpbotwriter.com/faq.html" 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;"> |
| 2255 |
<div style="margin-bottom: 6px;"><span class="dashicons dashicons-editor-help" style="font-size: 20px; width: 20px; height: 20px;"></span></div> |
| 2256 |
<div style="font-size: 13px; font-weight: 500;"><?php echo esc_html__('Help & FAQ', 'botwriter'); ?></div> |
| 2257 |
</a> |
| 2258 |
</div> |
| 2259 |
|
| 2260 |
<!-- Footer --> |
| 2261 |
<div style="text-align: center; margin-top: 30px; padding: 15px; color: #888; font-size: 12px;"> |
| 2262 |
<?php echo esc_html__('BotWriter', 'botwriter'); ?> v<?php echo esc_html(BOTWRITER_VERSION); ?> — 100% Free |
| 2263 |
<br> |
| 2264 |
<a href="https://www.wpbotwriter.com" target="_blank" style="color: #667eea; text-decoration: none;"><?php echo esc_html__('Website', 'botwriter'); ?></a> |
| 2265 |
• |
| 2266 |
<a href="https://wpbotwriter.com/faq.html" target="_blank" style="color: #667eea; text-decoration: none;">FAQ</a> |
| 2267 |
• |
| 2268 |
<a href="https://wordpress.org/support/plugin/botwriter/" target="_blank" style="color: #667eea; text-decoration: none;"><?php echo esc_html__('Support', 'botwriter'); ?></a> |
| 2269 |
</div> |
| 2270 |
|
| 2271 |
</div> |
| 2272 |
</div> |
| 2273 |
|
| 2274 |
<?php |
| 2275 |
} |
| 2276 |
|
| 2277 |
|
| 2278 |
|
| 2279 |
// Hook that runs on plugin activation |
| 2280 |
register_activation_hook(__FILE__, 'botwriter_plugin_activate'); |
| 2281 |
function botwriter_plugin_activate() { |
| 2282 |
// Store first install date if missing |
| 2283 |
if (get_option('botwriter_install_date') === false) { |
| 2284 |
update_option('botwriter_install_date', current_time('timestamp')); |
| 2285 |
} |
| 2286 |
botwriter_activate_apikey_and_defaults(); |
| 2287 |
botwriter_create_table(); |
| 2288 |
// Create the first supertask if it doesn't exist |
| 2289 |
/* |
| 2290 |
if (!botwriter_super1_check_task_exist()) { |
| 2291 |
botwriter_super1_create_first_task(); |
| 2292 |
} |
| 2293 |
*/ |
| 2294 |
} |
| 2295 |
|
| 2296 |
|
| 2297 |
function botwriter_activate_apikey_and_defaults() { |
| 2298 |
if (get_option('botwriter_paused_tasks') === false) { |
| 2299 |
update_option('botwriter_paused_tasks', "2"); |
| 2300 |
} |
| 2301 |
|
| 2302 |
if (get_option('botwriter_email') === false) { |
| 2303 |
update_option('botwriter_email', get_option('admin_email')); |
| 2304 |
} |
| 2305 |
|
| 2306 |
if (get_option('botwriter_cron_active') === false) { |
| 2307 |
update_option('botwriter_cron_active', '1'); |
| 2308 |
} |
| 2309 |
|
| 2310 |
if (get_option('botwriter_image_provider') === false) { |
| 2311 |
update_option('botwriter_image_provider', 'stockphoto'); |
| 2312 |
} |
| 2313 |
|
| 2314 |
if (get_option('botwriter_stockphoto_preferred') === false) { |
| 2315 |
update_option('botwriter_stockphoto_preferred', 'random'); |
| 2316 |
} |
| 2317 |
|
| 2318 |
if (get_option('botwriter_stockphoto_selection') === false) { |
| 2319 |
update_option('botwriter_stockphoto_selection', 'random_top10'); |
| 2320 |
} |
| 2321 |
|
| 2322 |
if (get_option('botwriter_stockphoto_attribution') === false) { |
| 2323 |
update_option('botwriter_stockphoto_attribution', 'caption'); |
| 2324 |
} |
| 2325 |
|
| 2326 |
if (get_option('botwriter_ai_image_size') === false) { |
| 2327 |
update_option('botwriter_ai_image_size', 'square'); |
| 2328 |
} |
| 2329 |
|
| 2330 |
if (get_option('botwriter_sslverify') === false) { |
| 2331 |
update_option('botwriter_sslverify', 'yes'); |
| 2332 |
} |
| 2333 |
|
| 2334 |
if (get_option('botwriter_openai_model') === false) { |
| 2335 |
update_option('botwriter_openai_model', 'gpt-5.4-mini'); |
| 2336 |
} |
| 2337 |
if (get_option('botwriter_ai_image_quality') === false) { |
| 2338 |
update_option('botwriter_ai_image_quality', 'medium'); |
| 2339 |
} |
| 2340 |
|
| 2341 |
if (get_option('botwriter_seo_featured_image_alt_enabled') === false) { |
| 2342 |
update_option('botwriter_seo_featured_image_alt_enabled', '1'); |
| 2343 |
} |
| 2344 |
|
| 2345 |
if (get_option('botwriter_seo_publish_focus_keyword_enabled') === false) { |
| 2346 |
update_option('botwriter_seo_publish_focus_keyword_enabled', '0'); |
| 2347 |
} |
| 2348 |
|
| 2349 |
if (get_option('botwriter_seo_publish_faq_enabled') === false) { |
| 2350 |
update_option('botwriter_seo_publish_faq_enabled', '0'); |
| 2351 |
} |
| 2352 |
|
| 2353 |
if (get_option('botwriter_seo_publish_faq_mode') === false) { |
| 2354 |
update_option('botwriter_seo_publish_faq_mode', 'visible_schema'); |
| 2355 |
} |
| 2356 |
|
| 2357 |
if (get_option('botwriter_seo_social_meta_enabled') === false) { |
| 2358 |
update_option('botwriter_seo_social_meta_enabled', '0'); |
| 2359 |
} |
| 2360 |
} |
| 2361 |
|
| 2362 |
|
| 2363 |
// Compatibility check for different WordPress versions |
| 2364 |
add_action('plugins_loaded', 'botwriter_compatibility_check'); |
| 2365 |
|
| 2366 |
function botwriter_compatibility_check() { |
| 2367 |
global $wp_version; |
| 2368 |
|
| 2369 |
if (version_compare($wp_version, '4.0', '<')) { |
| 2370 |
deactivate_plugins(plugin_basename(__FILE__)); |
| 2371 |
|
| 2372 |
wp_die(esc_html__('This plugin requires WordPress 4.0 or higher', 'botwriter')); |
| 2373 |
} |
| 2374 |
} |
| 2375 |
|
| 2376 |
|
| 2377 |
// Ensure DB schema migrations run even for sites that didn't re-activate the plugin |
| 2378 |
add_action('plugins_loaded', 'botwriter_maybe_add_task_type_col', 20); |
| 2379 |
function botwriter_maybe_add_task_type_col() { |
| 2380 |
global $wpdb; |
| 2381 |
$tasks_table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 2382 |
// Bail if table is missing |
| 2383 |
$table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tasks_table_name)); |
| 2384 |
if ($table_exists !== $tasks_table_name) { |
| 2385 |
return; |
| 2386 |
} |
| 2387 |
// Add task_type if missing |
| 2388 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'task_type')); |
| 2389 |
if (!$col) { |
| 2390 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `task_type` VARCHAR(50) NULL AFTER `website_type`"); |
| 2391 |
} |
| 2392 |
} |
| 2393 |
|
| 2394 |
|
| 2395 |
|
| 2396 |
// funciones extra |
| 2397 |
if (!class_exists('WP_List_Table')) { |
| 2398 |
require_once(ABSPATH . 'wp-admin/includes/class-wp-list-table.php'); |
| 2399 |
} |
| 2400 |
|
| 2401 |
|
| 2402 |
function botwriter_create_table() { |
| 2403 |
global $wpdb; |
| 2404 |
try { |
| 2405 |
|
| 2406 |
$tasks_table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 2407 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tasks_table_name)) !== $tasks_table_name) { |
| 2408 |
$charset_collate = $wpdb->get_charset_collate(); |
| 2409 |
|
| 2410 |
$tasks_sql = "CREATE TABLE $tasks_table_name ( |
| 2411 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 2412 |
`post_status` VARCHAR(20) NOT NULL, |
| 2413 |
`task_name` VARCHAR(255) NOT NULL, |
| 2414 |
`writer` VARCHAR(255) NOT NULL, |
| 2415 |
`narration` VARCHAR(255), |
| 2416 |
`custom_style` VARCHAR(255), |
| 2417 |
`post_language` VARCHAR(255) NOT NULL, |
| 2418 |
`post_length` VARCHAR(255) NOT NULL, |
| 2419 |
`custom_post_length` VARCHAR(255) NOT NULL, |
| 2420 |
`days` VARCHAR(255) NOT NULL, |
| 2421 |
`times_per_day` INT NOT NULL, |
| 2422 |
`execution_count` INT DEFAULT 0, |
| 2423 |
`last_execution_date` DATE DEFAULT NULL, |
| 2424 |
`last_execution_time` TIMESTAMP DEFAULT 0, |
| 2425 |
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 2426 |
`status` int DEFAULT 1, |
| 2427 |
`website_name` VARCHAR(255), |
| 2428 |
`website_type` VARCHAR(255), |
| 2429 |
`task_type` VARCHAR(50) DEFAULT NULL, |
| 2430 |
`domain_name` VARCHAR(255) NOT NULL, |
| 2431 |
`post_type` VARCHAR(50) DEFAULT 'post', |
| 2432 |
`category_id` VARCHAR(255), |
| 2433 |
`taxonomy_data` TEXT, |
| 2434 |
`website_category_id` VARCHAR(255), |
| 2435 |
`website_category_name` VARCHAR(255), |
| 2436 |
`aigenerated_title` TEXT NOT NULL, |
| 2437 |
`aigenerated_content` TEXT NOT NULL, |
| 2438 |
`aigenerated_tags` TEXT NOT NULL, |
| 2439 |
`aigenerated_image` TEXT NOT NULL, |
| 2440 |
`post_count` VARCHAR(255), |
| 2441 |
`post_order` VARCHAR(255), |
| 2442 |
`title_prompt` TEXT NOT NULL, |
| 2443 |
`content_prompt` TEXT NOT NULL, |
| 2444 |
`tags_prompt` TEXT NOT NULL, |
| 2445 |
`image_prompt` TEXT NOT NULL, |
| 2446 |
`image_generating_status` VARCHAR(255), |
| 2447 |
`author_selection` VARCHAR(255), |
| 2448 |
`news_time_published` VARCHAR(255), |
| 2449 |
`news_language` VARCHAR(255), |
| 2450 |
`news_country` VARCHAR(255), |
| 2451 |
`news_keyword` VARCHAR(255), |
| 2452 |
`news_source` VARCHAR(255), |
| 2453 |
`rss_source` VARCHAR(255), |
| 2454 |
`ai_keywords` TEXT NOT NULL, |
| 2455 |
`disable_ai_images` TINYINT(1) DEFAULT 0, |
| 2456 |
`template_id` INT(11) DEFAULT NULL, |
| 2457 |
PRIMARY KEY (`id`) |
| 2458 |
) $charset_collate;"; |
| 2459 |
|
| 2460 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 2461 |
dbDelta($tasks_sql); |
| 2462 |
} else { |
| 2463 |
// Ensure new column task_type exists for legacy installs |
| 2464 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'task_type')); |
| 2465 |
if (!$col) { |
| 2466 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `task_type` VARCHAR(50) NULL AFTER `website_type`"); |
| 2467 |
} |
| 2468 |
|
| 2469 |
// Ensure new column disable_ai_images exists for legacy installs |
| 2470 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'disable_ai_images')); |
| 2471 |
if (!$col) { |
| 2472 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `disable_ai_images` TINYINT(1) DEFAULT 0"); |
| 2473 |
} |
| 2474 |
|
| 2475 |
// Ensure new column template_id exists for legacy installs |
| 2476 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'template_id')); |
| 2477 |
if (!$col) { |
| 2478 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `template_id` INT(11) DEFAULT NULL"); |
| 2479 |
} |
| 2480 |
|
| 2481 |
// Ensure new column post_type exists for legacy installs |
| 2482 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'post_type')); |
| 2483 |
if (!$col) { |
| 2484 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `post_type` VARCHAR(50) DEFAULT 'post' AFTER `domain_name`"); |
| 2485 |
} |
| 2486 |
|
| 2487 |
// Ensure new column taxonomy_data exists for legacy installs |
| 2488 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $tasks_table_name LIKE %s", 'taxonomy_data')); |
| 2489 |
if (!$col) { |
| 2490 |
$wpdb->query("ALTER TABLE $tasks_table_name ADD COLUMN `taxonomy_data` TEXT AFTER `category_id`"); |
| 2491 |
} |
| 2492 |
} |
| 2493 |
|
| 2494 |
// Table botwriter_logs |
| 2495 |
$logs_table_name = $wpdb->prefix . 'botwriter_logs'; |
| 2496 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $logs_table_name)) !== $logs_table_name) { |
| 2497 |
$charset_collate = $wpdb->get_charset_collate(); |
| 2498 |
|
| 2499 |
$logs_sql = "CREATE TABLE $logs_table_name ( |
| 2500 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 2501 |
`id_task` int(11) NOT NULL, |
| 2502 |
`id_task_server` int(11) NOT NULL, |
| 2503 |
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 2504 |
`last_execution_time` TIMESTAMP DEFAULT 0, |
| 2505 |
`intentosfase1` int(11) NOT NULL DEFAULT 0, |
| 2506 |
`intentosfase2` int(11) NOT NULL DEFAULT 0, |
| 2507 |
`task_status` VARCHAR(255), |
| 2508 |
`task_type` VARCHAR(50) DEFAULT NULL, |
| 2509 |
`error` TEXT, |
| 2510 |
`link_post_original` TEXT, |
| 2511 |
`id_post_published` int(11) default 0, |
| 2512 |
`post_status` VARCHAR(20) NOT NULL, |
| 2513 |
`task_name` VARCHAR(255) NOT NULL, |
| 2514 |
`writer` VARCHAR(255) NOT NULL, |
| 2515 |
`narration` VARCHAR(255), |
| 2516 |
`custom_style` VARCHAR(255), |
| 2517 |
`post_language` VARCHAR(255) NOT NULL, |
| 2518 |
`post_length` VARCHAR(255) NOT NULL, |
| 2519 |
`custom_post_length` VARCHAR(255) NOT NULL, |
| 2520 |
`website_name` VARCHAR(255), |
| 2521 |
`website_type` VARCHAR(255), |
| 2522 |
`domain_name` VARCHAR(255) NOT NULL, |
| 2523 |
`post_type` VARCHAR(50) DEFAULT 'post', |
| 2524 |
`category_id` VARCHAR(255), |
| 2525 |
`taxonomy_data` TEXT, |
| 2526 |
`website_category_id` VARCHAR(255), |
| 2527 |
`aigenerated_title` TEXT NOT NULL, |
| 2528 |
`aigenerated_content` TEXT NOT NULL, |
| 2529 |
`aigenerated_tags` TEXT NOT NULL, |
| 2530 |
`aigenerated_image` TEXT NOT NULL, |
| 2531 |
`post_count` VARCHAR(255), |
| 2532 |
`post_order` VARCHAR(255), |
| 2533 |
`title_prompt` TEXT NOT NULL, |
| 2534 |
`content_prompt` TEXT NOT NULL, |
| 2535 |
`tags_prompt` TEXT NOT NULL, |
| 2536 |
`image_prompt` TEXT NOT NULL, |
| 2537 |
`image_generating_status` VARCHAR(255), |
| 2538 |
`author_selection` VARCHAR(255), |
| 2539 |
`news_time_published` VARCHAR(255), |
| 2540 |
`news_language` VARCHAR(255), |
| 2541 |
`news_country` VARCHAR(255), |
| 2542 |
`news_keyword` VARCHAR(255), |
| 2543 |
`news_source` VARCHAR(255), |
| 2544 |
`rss_source` VARCHAR(255), |
| 2545 |
`ai_keywords` TEXT NOT NULL, |
| 2546 |
`disable_ai_images` TINYINT(1) DEFAULT 0, |
| 2547 |
`template_id` INT(11) DEFAULT NULL, |
| 2548 |
PRIMARY KEY (`id`) |
| 2549 |
) $charset_collate;"; |
| 2550 |
|
| 2551 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 2552 |
dbDelta($logs_sql); |
| 2553 |
} else { |
| 2554 |
// Ensure new column disable_ai_images exists in logs table for legacy installs |
| 2555 |
$logs_table_name = $wpdb->prefix . 'botwriter_logs'; |
| 2556 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'disable_ai_images')); |
| 2557 |
if (!$col) { |
| 2558 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `disable_ai_images` TINYINT(1) DEFAULT 0"); |
| 2559 |
} |
| 2560 |
|
| 2561 |
// Ensure new column template_id exists in logs table for legacy installs |
| 2562 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'template_id')); |
| 2563 |
if (!$col) { |
| 2564 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `template_id` INT(11) DEFAULT NULL"); |
| 2565 |
} |
| 2566 |
|
| 2567 |
// Ensure new column task_type exists in logs table for legacy installs (for writenow exclusion) |
| 2568 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'task_type')); |
| 2569 |
if (!$col) { |
| 2570 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `task_type` VARCHAR(50) DEFAULT NULL AFTER `task_status`"); |
| 2571 |
} |
| 2572 |
|
| 2573 |
// Ensure new column post_type exists in logs table for legacy installs |
| 2574 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'post_type')); |
| 2575 |
if (!$col) { |
| 2576 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `post_type` VARCHAR(50) DEFAULT 'post' AFTER `domain_name`"); |
| 2577 |
} |
| 2578 |
|
| 2579 |
// Ensure new column taxonomy_data exists in logs table for legacy installs |
| 2580 |
$col = $wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM $logs_table_name LIKE %s", 'taxonomy_data')); |
| 2581 |
if (!$col) { |
| 2582 |
$wpdb->query("ALTER TABLE $logs_table_name ADD COLUMN `taxonomy_data` TEXT AFTER `category_id`"); |
| 2583 |
} |
| 2584 |
} |
| 2585 |
|
| 2586 |
|
| 2587 |
|
| 2588 |
$tasks_table_name = $wpdb->prefix . 'botwriter_super'; |
| 2589 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tasks_table_name)) !== $tasks_table_name) { |
| 2590 |
$charset_collate = $wpdb->get_charset_collate(); |
| 2591 |
|
| 2592 |
$tasks_sql = "CREATE TABLE $tasks_table_name ( |
| 2593 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 2594 |
`id_task` int(11) NOT NULL, |
| 2595 |
`id_log` int(11) NOT NULL, |
| 2596 |
`title` VARCHAR(255) NOT NULL, |
| 2597 |
`content` TEXT NOT NULL, |
| 2598 |
`category_id` VARCHAR(255), |
| 2599 |
`category_name` VARCHAR(255), |
| 2600 |
`task_status` VARCHAR(255), |
| 2601 |
PRIMARY KEY (`id`) |
| 2602 |
) $charset_collate;"; |
| 2603 |
|
| 2604 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 2605 |
dbDelta($tasks_sql); |
| 2606 |
} |
| 2607 |
|
| 2608 |
// Table botwriter_templates for prompt templates |
| 2609 |
$templates_table_name = $wpdb->prefix . 'botwriter_templates'; |
| 2610 |
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $templates_table_name)) !== $templates_table_name) { |
| 2611 |
$charset_collate = $wpdb->get_charset_collate(); |
| 2612 |
|
| 2613 |
$templates_sql = "CREATE TABLE $templates_table_name ( |
| 2614 |
`id` int(11) NOT NULL AUTO_INCREMENT, |
| 2615 |
`name` VARCHAR(255) NOT NULL, |
| 2616 |
`content` LONGTEXT NOT NULL, |
| 2617 |
`is_default` TINYINT(1) DEFAULT 0, |
| 2618 |
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 2619 |
PRIMARY KEY (`id`) |
| 2620 |
) $charset_collate;"; |
| 2621 |
|
| 2622 |
require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| 2623 |
dbDelta($templates_sql); |
| 2624 |
|
| 2625 |
// Insert all default templates |
| 2626 |
botwriter_insert_all_default_templates(); |
| 2627 |
} |
| 2628 |
|
| 2629 |
} catch (Exception $e) { |
| 2630 |
|
| 2631 |
//error_log("Error creating botwriter tables: " . $e->getMessage()); |
| 2632 |
} |
| 2633 |
} |
| 2634 |
|
| 2635 |
/** |
| 2636 |
* Insert the default prompt template (legacy function - now uses default-templates.php) |
| 2637 |
* @deprecated Use botwriter_insert_all_default_templates() instead |
| 2638 |
*/ |
| 2639 |
function botwriter_insert_default_template() { |
| 2640 |
// Now handled by botwriter_insert_all_default_templates() in default-templates.php |
| 2641 |
botwriter_insert_all_default_templates(); |
| 2642 |
} |
| 2643 |
|
| 2644 |
/** |
| 2645 |
* Get the default template content with all placeholders |
| 2646 |
* Uses the content from default-templates.php if available |
| 2647 |
*/ |
| 2648 |
function botwriter_get_default_template_content() { |
| 2649 |
// Try to get from the default templates array |
| 2650 |
if (function_exists('botwriter_get_default_template_by_name')) { |
| 2651 |
$default = botwriter_get_default_template_by_name('Default Template'); |
| 2652 |
if ($default && !empty($default['content'])) { |
| 2653 |
return $default['content']; |
| 2654 |
} |
| 2655 |
} |
| 2656 |
|
| 2657 |
// Fallback hardcoded template |
| 2658 |
$template = 'Write an article for a blog, follow these instructions: |
| 2659 |
|
| 2660 |
-The article must be HTML, with proper opening and closing H2-H4 tags for headings, and <p> for paragraphs. |
| 2661 |
-The length should be approximately {{post_length}} words. |
| 2662 |
-The article language must be: {{post_language}}. |
| 2663 |
-Narrative style: {{writer_style}} |
| 2664 |
-The topic must be related to some of the following keywords: {{prompt_or_keywords}} |
| 2665 |
|
| 2666 |
-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.'; |
| 2667 |
|
| 2668 |
return $template; |
| 2669 |
} |
| 2670 |
|
| 2671 |
/** |
| 2672 |
* Get template by ID or default template |
| 2673 |
*/ |
| 2674 |
function botwriter_get_template($template_id = null) { |
| 2675 |
global $wpdb; |
| 2676 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 2677 |
|
| 2678 |
if ($template_id) { |
| 2679 |
$template = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $template_id), ARRAY_A); |
| 2680 |
} else { |
| 2681 |
$template = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE is_default = %d", 1), ARRAY_A); |
| 2682 |
} |
| 2683 |
|
| 2684 |
if (!$template) { |
| 2685 |
// Return hardcoded default if no template in DB |
| 2686 |
return [ |
| 2687 |
'id' => 0, |
| 2688 |
'name' => 'Default Template', |
| 2689 |
'content' => botwriter_get_default_template_content(), |
| 2690 |
'is_default' => 1 |
| 2691 |
]; |
| 2692 |
} |
| 2693 |
|
| 2694 |
return $template; |
| 2695 |
} |
| 2696 |
|
| 2697 |
/** |
| 2698 |
* Get all templates |
| 2699 |
*/ |
| 2700 |
function botwriter_get_all_templates() { |
| 2701 |
global $wpdb; |
| 2702 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 2703 |
|
| 2704 |
if (function_exists('botwriter_ensure_default_templates_exist')) { |
| 2705 |
botwriter_ensure_default_templates_exist(); |
| 2706 |
} |
| 2707 |
|
| 2708 |
return $wpdb->get_results("SELECT * FROM $table_name ORDER BY name DESC", ARRAY_A); |
| 2709 |
} |
| 2710 |
|
| 2711 |
/** |
| 2712 |
* Save a template (insert or update) |
| 2713 |
*/ |
| 2714 |
function botwriter_save_template($data) { |
| 2715 |
global $wpdb; |
| 2716 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 2717 |
|
| 2718 |
if (!empty($data['id'])) { |
| 2719 |
// Update |
| 2720 |
return $wpdb->update($table_name, [ |
| 2721 |
'name' => sanitize_text_field($data['name']), |
| 2722 |
'content' => wp_kses_post($data['content']) |
| 2723 |
], ['id' => intval($data['id'])]); |
| 2724 |
} else { |
| 2725 |
// Insert |
| 2726 |
return $wpdb->insert($table_name, [ |
| 2727 |
'name' => sanitize_text_field($data['name']), |
| 2728 |
'content' => wp_kses_post($data['content']), |
| 2729 |
'is_default' => 0 |
| 2730 |
]); |
| 2731 |
} |
| 2732 |
} |
| 2733 |
|
| 2734 |
/** |
| 2735 |
* Set a template as default (unset other defaults) |
| 2736 |
*/ |
| 2737 |
function botwriter_set_default_template($template_id) { |
| 2738 |
global $wpdb; |
| 2739 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 2740 |
|
| 2741 |
// Check if template exists |
| 2742 |
$template = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $template_id)); |
| 2743 |
if (!$template) { |
| 2744 |
return false; |
| 2745 |
} |
| 2746 |
|
| 2747 |
// Remove default from all templates |
| 2748 |
$wpdb->update($table_name, ['is_default' => 0], ['is_default' => 1]); |
| 2749 |
|
| 2750 |
// Set new default |
| 2751 |
return $wpdb->update($table_name, ['is_default' => 1], ['id' => intval($template_id)]); |
| 2752 |
} |
| 2753 |
|
| 2754 |
/** |
| 2755 |
* Delete a template (cannot delete default) |
| 2756 |
*/ |
| 2757 |
function botwriter_delete_template($template_id) { |
| 2758 |
global $wpdb; |
| 2759 |
$table_name = $wpdb->prefix . 'botwriter_templates'; |
| 2760 |
|
| 2761 |
// Prevent deleting default template |
| 2762 |
$is_default = $wpdb->get_var($wpdb->prepare("SELECT is_default FROM $table_name WHERE id = %d", $template_id)); |
| 2763 |
if ($is_default == 1) { |
| 2764 |
return false; |
| 2765 |
} |
| 2766 |
|
| 2767 |
return $wpdb->delete($table_name, ['id' => intval($template_id)]); |
| 2768 |
} |
| 2769 |
|
| 2770 |
/** |
| 2771 |
* Build prompt from template by replacing placeholders with actual values |
| 2772 |
* Uses Mustache-like syntax: {{variable}} for simple values, {{#section}}...{{/section}} for conditionals |
| 2773 |
*/ |
| 2774 |
function botwriter_build_prompt_from_template($template_content, $data) { |
| 2775 |
global $botwriter_languages; |
| 2776 |
|
| 2777 |
// Map writer styles |
| 2778 |
$writer_styles = [ |
| 2779 |
'ai_cerebro' => '', |
| 2780 |
'orion' => '', |
| 2781 |
'cloe' => 'ironic critic, sarcastic and witty', |
| 2782 |
'lucida' => 'analytical critic, precise and direct', |
| 2783 |
'max' => 'Passionate and descriptive', |
| 2784 |
'gael' => 'Reflective, introspective and poetic', |
| 2785 |
]; |
| 2786 |
|
| 2787 |
// Prepare data for template — NOTE: this is the legacy function, |
| 2788 |
// main flow uses botwriter_build_client_prompt() below. |
| 2789 |
$writer = strtolower($data['writer'] ?? ''); |
| 2790 |
$writer_style = ''; |
| 2791 |
|
| 2792 |
if ($writer === 'custom') { |
| 2793 |
$narration = strtolower($data['narration'] ?? ''); |
| 2794 |
if ($narration === 'custom') { |
| 2795 |
$writer_style = $data['custom_style'] ?? ''; |
| 2796 |
} else { |
| 2797 |
$writer_style = $narration; |
| 2798 |
} |
| 2799 |
} elseif (isset($writer_styles[$writer])) { |
| 2800 |
$writer_style = $writer_styles[$writer]; |
| 2801 |
} |
| 2802 |
|
| 2803 |
// Get language name from code |
| 2804 |
$post_language_code = $data['post_language'] ?? 'en'; |
| 2805 |
$post_language = $botwriter_languages[$post_language_code] ?? 'English'; |
| 2806 |
|
| 2807 |
// Post length |
| 2808 |
$post_length = $data['post_length'] ?? '800'; |
| 2809 |
if (!is_numeric($post_length)) { |
| 2810 |
$post_length = 800; |
| 2811 |
} |
| 2812 |
$post_length = min(intval($post_length), 4000); |
| 2813 |
|
| 2814 |
// Build replacements array |
| 2815 |
// Note: source_content, existing_titles, title_prompt, content_prompt are handled server-side |
| 2816 |
$replacements = [ |
| 2817 |
'post_length' => $post_length, |
| 2818 |
'post_language' => $post_language, |
| 2819 |
'writer_style' => $writer_style, |
| 2820 |
'prompt_or_keywords' => $data['ai_keywords'] ?? '', |
| 2821 |
]; |
| 2822 |
|
| 2823 |
$prompt = $template_content; |
| 2824 |
|
| 2825 |
// Replace variables: {{variable}} |
| 2826 |
foreach ($replacements as $key => $value) { |
| 2827 |
$prompt = str_replace('{{' . $key . '}}', $value, $prompt); |
| 2828 |
} |
| 2829 |
|
| 2830 |
// Clean up short instruction lines left empty after variable replacement |
| 2831 |
// e.g. "-Narrative style: " or "-Topic: " when the value is empty |
| 2832 |
// Never remove lines containing ENDARTICLE or other source markers |
| 2833 |
$lines = explode("\n", $prompt); |
| 2834 |
$cleaned_lines = []; |
| 2835 |
foreach ($lines as $line) { |
| 2836 |
$trimmed = trim($line); |
| 2837 |
if (preg_match('/^-[^:]+:\s*$/', $trimmed) && strpos($trimmed, 'ENDARTICLE') === false && strlen($trimmed) < 40) { |
| 2838 |
continue; |
| 2839 |
} |
| 2840 |
$cleaned_lines[] = $line; |
| 2841 |
} |
| 2842 |
$prompt = implode("\n", $cleaned_lines); |
| 2843 |
|
| 2844 |
// Clean up multiple blank lines |
| 2845 |
$prompt = preg_replace('/\n{3,}/', "\n\n", $prompt); |
| 2846 |
$prompt = trim($prompt); |
| 2847 |
|
| 2848 |
return $prompt; |
| 2849 |
} |
| 2850 |
|
| 2851 |
/** |
| 2852 |
* Build the complete prompt on the client side using the template system |
| 2853 |
* For types that require external content (wordpress, rss, news), |
| 2854 |
* the server will add the source content to the prompt |
| 2855 |
*/ |
| 2856 |
function botwriter_build_client_prompt($data) { |
| 2857 |
global $botwriter_languages; |
| 2858 |
|
| 2859 |
// Get the template - use task-specific template if set, otherwise default |
| 2860 |
$template_id = isset($data['template_id']) && !empty($data['template_id']) ? intval($data['template_id']) : null; |
| 2861 |
$template = botwriter_get_template($template_id); |
| 2862 |
$template_content = $template['content']; |
| 2863 |
|
| 2864 |
// Map writer styles |
| 2865 |
$writer_styles = [ |
| 2866 |
'ai_cerebro' => '', |
| 2867 |
'orion' => '', |
| 2868 |
'cloe' => 'ironic critic, sarcastic and witty', |
| 2869 |
'lucida' => 'analytical critic, precise and direct', |
| 2870 |
'max' => 'Passionate and descriptive', |
| 2871 |
'gael' => 'Reflective, introspective and poetic', |
| 2872 |
]; |
| 2873 |
|
| 2874 |
// Prepare writer style |
| 2875 |
$writer = strtolower($data['writer'] ?? ''); |
| 2876 |
$writer_style = ''; |
| 2877 |
|
| 2878 |
if ($writer === 'custom') { |
| 2879 |
$narration = strtolower($data['narration'] ?? ''); |
| 2880 |
if ($narration === 'custom') { |
| 2881 |
$writer_style = $data['custom_style'] ?? ''; |
| 2882 |
} else { |
| 2883 |
$writer_style = $narration; |
| 2884 |
} |
| 2885 |
} elseif (isset($writer_styles[$writer])) { |
| 2886 |
$writer_style = $writer_styles[$writer]; |
| 2887 |
} |
| 2888 |
|
| 2889 |
// Get language name from code |
| 2890 |
$post_language_code = $data['post_language'] ?? 'en'; |
| 2891 |
$post_language = $botwriter_languages[$post_language_code] ?? 'English'; |
| 2892 |
|
| 2893 |
// Post length |
| 2894 |
$post_length = $data['post_length'] ?? '800'; |
| 2895 |
if (!is_numeric($post_length)) { |
| 2896 |
$post_length = 800; |
| 2897 |
} |
| 2898 |
$post_length = min(intval($post_length), 4000); |
| 2899 |
|
| 2900 |
// Determine what content to include based on website_type |
| 2901 |
$website_type = $data['website_type'] ?? ''; |
| 2902 |
$source_title = ''; |
| 2903 |
$source_content = ''; |
| 2904 |
$ai_keywords = ''; |
| 2905 |
$existing_titles = ''; |
| 2906 |
$title_prompt = ''; |
| 2907 |
$content_prompt = ''; |
| 2908 |
|
| 2909 |
switch ($website_type) { |
| 2910 |
case 'ai': |
| 2911 |
case '': |
| 2912 |
// AI mode: use keywords and avoid existing titles |
| 2913 |
$ai_keywords = $data['ai_keywords'] ?? ''; |
| 2914 |
$existing_titles = $data['titles'] ?? ''; |
| 2915 |
break; |
| 2916 |
|
| 2917 |
case 'super2': |
| 2918 |
// Super2: use title_prompt and content_prompt from outline |
| 2919 |
$title_prompt = $data['title_prompt'] ?? ''; |
| 2920 |
$content_prompt = $data['content_prompt'] ?? ''; |
| 2921 |
break; |
| 2922 |
|
| 2923 |
case 'rss': |
| 2924 |
// RSS content is now pre-fetched on the client side |
| 2925 |
// Data is populated by botwriter_send1_data_to_server before calling this function |
| 2926 |
$source_title = $data['source_title'] ?? ''; |
| 2927 |
$source_content = $data['source_content'] ?? ''; |
| 2928 |
break; |
| 2929 |
|
| 2930 |
case 'wordpress': |
| 2931 |
// WordPress content is now pre-fetched on the client side |
| 2932 |
// Data is populated by botwriter_send1_data_to_server before calling this function |
| 2933 |
$source_title = $data['source_title'] ?? ''; |
| 2934 |
$source_content = $data['source_content'] ?? ''; |
| 2935 |
break; |
| 2936 |
|
| 2937 |
case 'news': |
| 2938 |
// News still requires server-side content fetching |
| 2939 |
// We leave source_title and source_content empty, server will fill them |
| 2940 |
break; |
| 2941 |
} |
| 2942 |
|
| 2943 |
// Build replacements array |
| 2944 |
$replacements = [ |
| 2945 |
'post_length' => $post_length, |
| 2946 |
'post_language' => $post_language, |
| 2947 |
'writer_style' => $writer_style, |
| 2948 |
'source_title' => $source_title, |
| 2949 |
'source_content' => $source_content, |
| 2950 |
'ai_keywords' => $ai_keywords, |
| 2951 |
'prompt_or_keywords' => $ai_keywords, // Alias for templates |
| 2952 |
'existing_titles' => $existing_titles, |
| 2953 |
'title_prompt' => $title_prompt, |
| 2954 |
'content_prompt' => $content_prompt, |
| 2955 |
]; |
| 2956 |
|
| 2957 |
$prompt = $template_content; |
| 2958 |
|
| 2959 |
botwriter_log('PROMPT BUILD: before source embed', [ |
| 2960 |
'website_type' => $website_type, |
| 2961 |
'source_title_empty' => empty($source_title), |
| 2962 |
'source_title' => mb_substr($source_title, 0, 100), |
| 2963 |
'source_content_len' => strlen($source_content), |
| 2964 |
'template_len' => strlen($template_content), |
| 2965 |
]); |
| 2966 |
|
| 2967 |
// For RSS/WordPress: embed source content directly (already pre-fetched on client) |
| 2968 |
if (in_array($website_type, ['rss', 'wordpress']) && !empty($source_title)) { |
| 2969 |
$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"; |
| 2970 |
botwriter_log('PROMPT BUILD: ENDARTICLE block appended', [ |
| 2971 |
'prompt_len_after' => strlen($prompt), |
| 2972 |
]); |
| 2973 |
} else { |
| 2974 |
botwriter_log('PROMPT BUILD: ENDARTICLE block NOT appended', [ |
| 2975 |
'reason' => !in_array($website_type, ['rss', 'wordpress']) ? 'type not rss/wordpress' : 'source_title is empty', |
| 2976 |
]); |
| 2977 |
} |
| 2978 |
// For Super2: embed title and content instructions from the outline |
| 2979 |
if ($website_type === 'super2') { |
| 2980 |
// Rewrite instructions go BEFORE the ENDARTICLE block (rewriter / siterewriter tasks) |
| 2981 |
$rewrite_prompt = $data['rewrite_prompt'] ?? ''; |
| 2982 |
if (!empty($rewrite_prompt)) { |
| 2983 |
$prompt .= "\n-" . $rewrite_prompt; |
| 2984 |
} |
| 2985 |
if (!empty($title_prompt)) { |
| 2986 |
$prompt .= "\n-The article title must be: " . $title_prompt; |
| 2987 |
} |
| 2988 |
if (!empty($content_prompt)) { |
| 2989 |
$prompt .= "\n\n-Based on the following content (I indicate the end with the word ENDARTICLE):\n\n" . $content_prompt . "\n\nENDARTICLE\n"; |
| 2990 |
} |
| 2991 |
botwriter_log('PROMPT BUILD: super2 title/content appended', [ |
| 2992 |
'title_prompt' => mb_substr($title_prompt, 0, 100), |
| 2993 |
'content_prompt_len' => strlen($content_prompt), |
| 2994 |
'has_rewrite_prompt' => !empty($rewrite_prompt), |
| 2995 |
]); |
| 2996 |
} |
| 2997 |
// For News: server still needs to fetch content |
| 2998 |
if ($website_type === 'news') { |
| 2999 |
$prompt .= "\n\n{{SERVER_SOURCE_CONTENT}}"; |
| 3000 |
} |
| 3001 |
|
| 3002 |
// Replace variables: {{variable}} |
| 3003 |
foreach ($replacements as $key => $value) { |
| 3004 |
$prompt = str_replace('{{' . $key . '}}', $value, $prompt); |
| 3005 |
} |
| 3006 |
|
| 3007 |
$prompt_before_clean = $prompt; |
| 3008 |
botwriter_log('PROMPT BUILD: BEFORE cleanup', [ |
| 3009 |
'prompt_len' => strlen($prompt), |
| 3010 |
'contains_ENDARTICLE' => (strpos($prompt, 'ENDARTICLE') !== false), |
| 3011 |
'contains_Based_on' => (strpos($prompt, 'Based on this') !== false), |
| 3012 |
'first_300_chars' => mb_substr($prompt, 0, 300), |
| 3013 |
'last_300_chars' => mb_substr($prompt, -300), |
| 3014 |
]); |
| 3015 |
|
| 3016 |
// Clean up lines that have empty placeholders (lines ending with : or empty after replacement) |
| 3017 |
$lines = explode("\n", $prompt); |
| 3018 |
$cleaned_lines = []; |
| 3019 |
$removed_lines = []; |
| 3020 |
foreach ($lines as $line) { |
| 3021 |
$trimmed = trim($line); |
| 3022 |
// Skip short instruction lines left empty after variable replacement |
| 3023 |
// e.g. "-Narrative style: " or "-Topic: " — never remove ENDARTICLE marker |
| 3024 |
if (preg_match('/^-[^:]+:\s*$/', $trimmed) && strpos($trimmed, 'ENDARTICLE') === false && strlen($trimmed) < 40) { |
| 3025 |
$removed_lines[] = $trimmed . ' (len=' . strlen($trimmed) . ')'; |
| 3026 |
continue; |
| 3027 |
} |
| 3028 |
$cleaned_lines[] = $line; |
| 3029 |
} |
| 3030 |
$prompt = implode("\n", $cleaned_lines); |
| 3031 |
|
| 3032 |
if (!empty($removed_lines)) { |
| 3033 |
botwriter_log('PROMPT BUILD: lines REMOVED by cleanup', [ |
| 3034 |
'count' => count($removed_lines), |
| 3035 |
'lines' => $removed_lines, |
| 3036 |
]); |
| 3037 |
} |
| 3038 |
|
| 3039 |
// Clean up multiple blank lines |
| 3040 |
$prompt = preg_replace('/\n{3,}/', "\n\n", $prompt); |
| 3041 |
$prompt = trim($prompt); |
| 3042 |
|
| 3043 |
botwriter_log('PROMPT BUILD: AFTER cleanup (final)', [ |
| 3044 |
'prompt_len' => strlen($prompt), |
| 3045 |
'contains_ENDARTICLE' => (strpos($prompt, 'ENDARTICLE') !== false), |
| 3046 |
'contains_Based_on' => (strpos($prompt, 'Based on this') !== false), |
| 3047 |
'first_300_chars' => mb_substr($prompt, 0, 300), |
| 3048 |
'last_300_chars' => mb_substr($prompt, -300), |
| 3049 |
]); |
| 3050 |
|
| 3051 |
return $prompt; |
| 3052 |
} |
| 3053 |
|
| 3054 |
|
| 3055 |
|
| 3056 |
// Extending class |
| 3057 |
class botwriter_tasks_Table extends WP_List_Table |
| 3058 |
{ |
| 3059 |
// Define table columns |
| 3060 |
function get_columns() |
| 3061 |
{ |
| 3062 |
$columns = array( |
| 3063 |
'cb' => '<input type="checkbox" />', |
| 3064 |
'writer' => __('Writer', 'botwriter'), |
| 3065 |
'task_name' => __('Task Name', 'botwriter'), |
| 3066 |
'days' => __('Days', 'botwriter'), |
| 3067 |
'times_per_day' => __('Times per Day', 'botwriter'), |
| 3068 |
'type' => __('Type', 'botwriter'), |
| 3069 |
'status' => __('Status', 'botwriter') |
| 3070 |
|
| 3071 |
); |
| 3072 |
return $columns; |
| 3073 |
} |
| 3074 |
|
| 3075 |
|
| 3076 |
// define $table_data property |
| 3077 |
private $table_data; |
| 3078 |
|
| 3079 |
// Bind table with columns, data and all |
| 3080 |
function prepare_items() |
| 3081 |
{ |
| 3082 |
//data |
| 3083 |
if ( isset( $_POST['s'] ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'botwriter_nonce' ) ) { |
| 3084 |
$search_query = sanitize_text_field(wp_unslash($_POST['s'])); |
| 3085 |
$this->table_data = $this->get_table_data($search_query); |
| 3086 |
} else { |
| 3087 |
$this->table_data = $this->get_table_data(); |
| 3088 |
} |
| 3089 |
|
| 3090 |
|
| 3091 |
$columns = $this->get_columns(); |
| 3092 |
$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(); |
| 3093 |
$sortable = $this->get_sortable_columns(); |
| 3094 |
$primary = 'name'; |
| 3095 |
$this->_column_headers = array($columns, $hidden, $sortable, $primary); |
| 3096 |
$this->process_bulk_action(); |
| 3097 |
$this->table_data = $this->get_table_data(); |
| 3098 |
|
| 3099 |
usort($this->table_data, array($this, 'usort_reorder')); |
| 3100 |
|
| 3101 |
/* pagination */ |
| 3102 |
$per_page = $this->get_items_per_page('elements_per_page', 10); |
| 3103 |
$current_page = $this->get_pagenum(); |
| 3104 |
$total_items = count($this->table_data); |
| 3105 |
|
| 3106 |
$this->table_data = array_slice($this->table_data, (($current_page - 1) * $per_page), $per_page); |
| 3107 |
|
| 3108 |
$this->set_pagination_args(array( |
| 3109 |
'total_items' => $total_items, // total number of items |
| 3110 |
'per_page' => $per_page, // items to show on a page |
| 3111 |
'total_pages' => ceil( $total_items / $per_page ) // use ceil to round up |
| 3112 |
)); |
| 3113 |
|
| 3114 |
$this->items = $this->table_data; |
| 3115 |
} |
| 3116 |
|
| 3117 |
|
| 3118 |
|
| 3119 |
function column_task_name($item){ |
| 3120 |
$slug='botwriter_automatic_post_new'; |
| 3121 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only page slug used to build edit links. |
| 3122 |
$page = isset($_REQUEST['page']) ? sanitize_text_field(wp_unslash($_REQUEST['page'])) : ''; |
| 3123 |
|
| 3124 |
if ($item["website_type"] == 'super2') { |
| 3125 |
$slug='botwriter_super_page'; |
| 3126 |
$url_edit= wp_nonce_url('?page=' . $slug . '&id=' . $item['id'], "botwriter_tasks_action"); |
| 3127 |
} else { |
| 3128 |
$url_edit= wp_nonce_url('?page=' . $slug . '&id=' . $item['id'], "botwriter_tasks_action"); |
| 3129 |
} |
| 3130 |
|
| 3131 |
$url_delete= wp_nonce_url('?page=' . $page . '&action=delete&id=' . $item['id'], "botwriter_tasks_action"); |
| 3132 |
|
| 3133 |
$actions = array( |
| 3134 |
'edit' => sprintf('<a href="%s">%s</a>', $url_edit, __('Edit', 'botwriter')), |
| 3135 |
'delete' => sprintf('<a href="%s">%s</a>', $url_delete, __('Delete', 'botwriter')), |
| 3136 |
); |
| 3137 |
|
| 3138 |
$id=$item['id']; |
| 3139 |
return sprintf('%s %s', |
| 3140 |
"<a class='row-title' href='?page=$slug&id=$id&_wpnonce=" . wp_create_nonce('botwriter_tasks_action') . "'>" . $item['task_name'] . "</a>", |
| 3141 |
$this->row_actions($actions) |
| 3142 |
); |
| 3143 |
} |
| 3144 |
|
| 3145 |
function column_writer($item){ |
| 3146 |
$dir_images_writers = plugin_dir_url(__FILE__) . 'assets/images/writers/'; |
| 3147 |
$writer=$item['writer']; |
| 3148 |
$writer = strtolower($writer); |
| 3149 |
|
| 3150 |
$slug='botwriter_automatic_post_new'; |
| 3151 |
$id=$item['id']; |
| 3152 |
$link="<a class='row-title' href='?page=$slug&id=$id'>"; |
| 3153 |
$img= '<img src="' . esc_url($dir_images_writers . $writer . '.jpeg') . '" alt="' . esc_attr($writer) . '" class="writer-photo">'; |
| 3154 |
return $link . $img . '</a>'; |
| 3155 |
|
| 3156 |
} |
| 3157 |
|
| 3158 |
|
| 3159 |
|
| 3160 |
function column_status($item){ |
| 3161 |
|
| 3162 |
$status = $item['status']; |
| 3163 |
$status_opuesto = $status ? 0 : 1; |
| 3164 |
$icono = $status ? 'dashicons-yes' : 'dashicons-dismiss'; |
| 3165 |
$texto_status = $status ? 'Desactivate' : 'Activate'; |
| 3166 |
|
| 3167 |
return sprintf( |
| 3168 |
'<a href="#" class="icono-status dashicons %s" data-id="%d" data-status="%d" title="%s"></a>', |
| 3169 |
$icono, |
| 3170 |
$item['id'], |
| 3171 |
$status_opuesto, |
| 3172 |
$texto_status |
| 3173 |
); |
| 3174 |
|
| 3175 |
|
| 3176 |
} |
| 3177 |
|
| 3178 |
/* |
| 3179 |
function column_category_id($item) { |
| 3180 |
$categories = get_categories(); |
| 3181 |
$category_name = ''; |
| 3182 |
foreach ($categories as $category) { |
| 3183 |
$aux_cateforias=explode(',',$item['category_id']); |
| 3184 |
if (in_array($category->term_id, $aux_cateforias)) { |
| 3185 |
$category_name .= $category->name . ', '; |
| 3186 |
} |
| 3187 |
|
| 3188 |
} |
| 3189 |
$category_name = rtrim($category_name, ', '); |
| 3190 |
return $category_name; |
| 3191 |
} |
| 3192 |
*/ |
| 3193 |
|
| 3194 |
|
| 3195 |
|
| 3196 |
|
| 3197 |
|
| 3198 |
// To show bulk action dropdown |
| 3199 |
function get_bulk_actions() |
| 3200 |
{ |
| 3201 |
$actions = array( |
| 3202 |
'delete_all' => __('Delete', 'botwriter'), |
| 3203 |
|
| 3204 |
); |
| 3205 |
return $actions; |
| 3206 |
} |
| 3207 |
|
| 3208 |
function process_bulk_action() |
| 3209 |
{ |
| 3210 |
// Verify user has permission |
| 3211 |
if (!current_user_can('manage_options')) { |
| 3212 |
return; |
| 3213 |
} |
| 3214 |
|
| 3215 |
// Verify nonce |
| 3216 |
if (!isset($_REQUEST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])), "botwriter_tasks_action")) { |
| 3217 |
return; |
| 3218 |
} |
| 3219 |
|
| 3220 |
global $wpdb; |
| 3221 |
|
| 3222 |
$table = esc_sql($wpdb->prefix . 'botwriter_tasks'); |
| 3223 |
|
| 3224 |
if ('delete_all' === $this->current_action() || ('delete' === $this->current_action() && isset($_REQUEST['id']))) { |
| 3225 |
$request_id = isset($_REQUEST['id']) ? array_map('absint', (array) wp_unslash($_REQUEST['id'])) : array(); |
| 3226 |
|
| 3227 |
if (!empty($request_id)) { |
| 3228 |
// Prepare the DELETE query with proper escaping |
| 3229 |
$placeholders = implode(',', array_fill(0, count($request_id), '%d')); |
| 3230 |
$query = $wpdb->prepare("DELETE FROM {$table} WHERE id IN({$placeholders})", $request_id); |
| 3231 |
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is generated internally and placeholders are prepared from sanitized IDs. |
| 3232 |
$wpdb->query($query); |
| 3233 |
} |
| 3234 |
} |
| 3235 |
} |
| 3236 |
|
| 3237 |
|
| 3238 |
|
| 3239 |
// Get table data |
| 3240 |
private function get_table_data( $search = '' ) { |
| 3241 |
global $wpdb; |
| 3242 |
|
| 3243 |
$table = esc_sql($wpdb->prefix . 'botwriter_tasks'); |
| 3244 |
|
| 3245 |
|
| 3246 |
if ( ! empty( $search ) ) { |
| 3247 |
$prepared_search = $wpdb->esc_like( $search ); |
| 3248 |
$prepared_search = '%' . $wpdb->esc_like( $search ) . '%'; |
| 3249 |
|
| 3250 |
$query = $wpdb->prepare( |
| 3251 |
"SELECT * FROM {$table} WHERE name LIKE %s AND (task_type IS NULL OR task_type <> %s)", |
| 3252 |
$prepared_search, |
| 3253 |
'writenow' |
| 3254 |
); |
| 3255 |
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is generated internally and escaped for identifier use. |
| 3256 |
return $wpdb->get_results($query, ARRAY_A); |
| 3257 |
} else { |
| 3258 |
$query = $wpdb->prepare( |
| 3259 |
"SELECT * FROM {$table} WHERE (task_type IS NULL OR task_type <> %s)", |
| 3260 |
'writenow' |
| 3261 |
); |
| 3262 |
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is generated internally and escaped for identifier use. |
| 3263 |
return $wpdb->get_results($query, ARRAY_A); |
| 3264 |
} |
| 3265 |
} |
| 3266 |
|
| 3267 |
function column_default($item, $column_name) |
| 3268 |
{ |
| 3269 |
|
| 3270 |
switch ($column_name) { |
| 3271 |
case 'id': |
| 3272 |
case 'website_type': |
| 3273 |
case 'website_name': |
| 3274 |
case 'task_name': |
| 3275 |
case 'category_id': |
| 3276 |
case 'website_category_id': |
| 3277 |
default: |
| 3278 |
return $item[$column_name]; |
| 3279 |
} |
| 3280 |
} |
| 3281 |
|
| 3282 |
// Render the combined Type column: website_type + task_type |
| 3283 |
function column_type($item) { |
| 3284 |
$parts = array(); |
| 3285 |
if (!empty($item['website_type'])) { |
| 3286 |
$parts[] = sanitize_text_field($item['website_type']); |
| 3287 |
} |
| 3288 |
if (!empty($item['task_type'])) { |
| 3289 |
$parts[] = sanitize_text_field($item['task_type']); |
| 3290 |
} |
| 3291 |
$label = !empty($parts) ? implode(' / ', $parts) : __('—', 'botwriter'); |
| 3292 |
return esc_html($label); |
| 3293 |
} |
| 3294 |
|
| 3295 |
function column_cb($item){ |
| 3296 |
return sprintf( |
| 3297 |
'<input type="checkbox" name="id[]" value="%s" />', |
| 3298 |
$item['id'] |
| 3299 |
); |
| 3300 |
} |
| 3301 |
|
| 3302 |
public function get_sortable_columns(){ |
| 3303 |
$sortable_columns = array( |
| 3304 |
'task_name' => array('task_name', false), |
| 3305 |
'days' => array('days', false), |
| 3306 |
'id' => array('id', true) |
| 3307 |
); |
| 3308 |
return $sortable_columns; |
| 3309 |
} |
| 3310 |
|
| 3311 |
// Sorting function |
| 3312 |
function usort_reorder($a, $b) |
| 3313 |
{ |
| 3314 |
// If no sort, default to task_name |
| 3315 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only sort param for list table. |
| 3316 |
$sanitized_orderby = isset($_GET['orderby']) ? sanitize_text_field(wp_unslash($_GET['orderby'])) : ''; |
| 3317 |
|
| 3318 |
$orderby = (!empty($sanitized_orderby)) ? $sanitized_orderby : 'task_name'; |
| 3319 |
|
| 3320 |
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only sort param for list table. |
| 3321 |
$order = isset($_GET['order']) ? sanitize_text_field(wp_unslash($_GET['order'])) : 'asc'; |
| 3322 |
|
| 3323 |
// filtrar order solo asd o desc |
| 3324 |
$order = in_array($order, array('asc', 'desc')) ? $order : 'asc'; |
| 3325 |
// filter orderby only allowed columns |
| 3326 |
$orderby = in_array($orderby, array('task_name', 'days', 'id')) ? $orderby : 'task_name'; |
| 3327 |
|
| 3328 |
|
| 3329 |
|
| 3330 |
|
| 3331 |
// Determine sort order |
| 3332 |
$result = strcmp($a[$orderby], $b[$orderby]); |
| 3333 |
|
| 3334 |
// Send final sort direction to usort |
| 3335 |
return ($order === 'asc') ? $result : -$result; |
| 3336 |
} |
| 3337 |
|
| 3338 |
} // end class botwriter_tasks_Table |
| 3339 |
|
| 3340 |
|
| 3341 |
|
| 3342 |
|
| 3343 |
|
| 3344 |
|
| 3345 |
function botwriter_validate_website($item,$is_manual = false) |
| 3346 |
{ |
| 3347 |
|
| 3348 |
$messages = array(); |
| 3349 |
|
| 3350 |
|
| 3351 |
if (empty($item['task_name'])) $messages[] = __('Task Name is required', 'botwriter'); |
| 3352 |
if (empty($item['website_type'])) $messages[] = __('Website Type is required', 'botwriter'); |
| 3353 |
|
| 3354 |
// Category/taxonomy validation: require category_id for 'post' type, or taxonomy_data for other types |
| 3355 |
$post_type = isset($item['post_type']) ? $item['post_type'] : 'post'; |
| 3356 |
if ($post_type === 'post') { |
| 3357 |
if (empty($item['category_id']) && empty($item['taxonomy_data'])) { |
| 3358 |
$messages[] = __('Category is required', 'botwriter'); |
| 3359 |
} |
| 3360 |
} |
| 3361 |
// For other post types, taxonomy selection is optional |
| 3362 |
|
| 3363 |
|
| 3364 |
if($item['website_type'] == 'wordpress'){ |
| 3365 |
if (empty($item['domain_name'])) { |
| 3366 |
$messages[] = __('Domain Name is required', 'botwriter'); |
| 3367 |
} |
| 3368 |
if( !botwriter_isValidDomain(sanitize_text_field($item['domain_name'])) ){ |
| 3369 |
$messages[] = __('Domain name should be valid.', 'botwriter'); |
| 3370 |
} |
| 3371 |
} |
| 3372 |
|
| 3373 |
if ($item['website_type'] == 'rss') { |
| 3374 |
if (empty($item['rss_source'])) { |
| 3375 |
$messages[] = __('RSS Source is required', 'botwriter'); |
| 3376 |
} |
| 3377 |
} |
| 3378 |
|
| 3379 |
if ($item['website_type'] == 'news') { |
| 3380 |
if (empty($item['news_keyword'])) { |
| 3381 |
$messages[] = __('News keyword is required', 'botwriter'); |
| 3382 |
} |
| 3383 |
} |
| 3384 |
|
| 3385 |
if (empty($messages)) return true; |
| 3386 |
return implode('<br />', $messages); |
| 3387 |
} |
| 3388 |
|
| 3389 |
|
| 3390 |
function botwriter_isValidDomain($domain) { |
| 3391 |
// WordPress wp_http_validate_url |
| 3392 |
$valid_url = wp_http_validate_url( $domain); |
| 3393 |
|
| 3394 |
if (!is_wp_error($valid_url)) { |
| 3395 |
return true; |
| 3396 |
} else { |
| 3397 |
return false; |
| 3398 |
} |
| 3399 |
} |
| 3400 |
|
| 3401 |
|
| 3402 |
function botwriter_is_site_working($site_url, $site_type) { |
| 3403 |
$response = false; |
| 3404 |
|
| 3405 |
if ($site_type === 'wordpress') { |
| 3406 |
// Check if the WordPress REST API is accessible |
| 3407 |
$api_url = rtrim($site_url, '/') . '/wp-json/wp/v2/posts'; |
| 3408 |
$headers = @get_headers($api_url); |
| 3409 |
if ($headers && strpos((string)$headers[0], '200') !== false) { |
| 3410 |
$response = true; |
| 3411 |
} |
| 3412 |
} elseif ($site_type === 'rss') { |
| 3413 |
// Check if the RSS feed is accessible |
| 3414 |
$rss = @simplexml_load_file($site_url); |
| 3415 |
if ($rss) { |
| 3416 |
$response = true; |
| 3417 |
} |
| 3418 |
} |
| 3419 |
|
| 3420 |
return $response; |
| 3421 |
} |
| 3422 |
|
| 3423 |
|
| 3424 |
//wp-cron: |
| 3425 |
|
| 3426 |
|
| 3427 |
// Add a custom schedule for cron jobs |
| 3428 |
|
| 3429 |
function botwriter_add_custom_cron_schedule($schedules) { |
| 3430 |
if (!isset($schedules['every_30'])) { |
| 3431 |
$schedules['every_30'] = array( |
| 3432 |
'interval' => 30, // 30 seconds |
| 3433 |
'display' => __('Every thirty seconds', 'botwriter') |
| 3434 |
); |
| 3435 |
} |
| 3436 |
return $schedules; |
| 3437 |
} |
| 3438 |
add_filter('cron_schedules', 'botwriter_add_custom_cron_schedule'); |
| 3439 |
|
| 3440 |
// Ensure cron is scheduled on admin load (in case activation hook didn't run) |
| 3441 |
function botwriter_ensure_cron_scheduled() { |
| 3442 |
if (get_option('botwriter_cron_active') === '0') { |
| 3443 |
return; |
| 3444 |
} |
| 3445 |
|
| 3446 |
if (!wp_next_scheduled('botwriter_scheduled_events_plugin_cron')) { |
| 3447 |
$scheduled = wp_schedule_event(time() + 30, 'every_30', 'botwriter_scheduled_events_plugin_cron'); |
| 3448 |
botwriter_log('Cron scheduled (admin init)', [ |
| 3449 |
'scheduled' => $scheduled ? 'yes' : 'no', |
| 3450 |
]); |
| 3451 |
} |
| 3452 |
} |
| 3453 |
add_action('admin_init', 'botwriter_ensure_cron_scheduled'); |
| 3454 |
|
| 3455 |
// Schedule the cron job during plugin activation |
| 3456 |
function botwriter_scheduled_events_plugin_activate() { |
| 3457 |
if (get_option('botwriter_cron_active')=="0") { |
| 3458 |
return; |
| 3459 |
} |
| 3460 |
if (!wp_next_scheduled('botwriter_scheduled_events_plugin_cron')) { |
| 3461 |
wp_schedule_event(time(), 'every_30', 'botwriter_scheduled_events_plugin_cron'); |
| 3462 |
} |
| 3463 |
} |
| 3464 |
register_activation_hook(__FILE__, 'botwriter_scheduled_events_plugin_activate'); |
| 3465 |
|
| 3466 |
// Register the cron task |
| 3467 |
add_action('botwriter_scheduled_events_plugin_cron', 'botwriter_scheduled_events_execute_tasks'); |
| 3468 |
|
| 3469 |
|
| 3470 |
|
| 3471 |
|
| 3472 |
// Clear the cron job upon plugin deactivation |
| 3473 |
function botwriter_scheduled_events_plugin_deactivate() { |
| 3474 |
wp_clear_scheduled_hook('botwriter_scheduled_events_plugin_cron'); |
| 3475 |
} |
| 3476 |
register_deactivation_hook(__FILE__, 'botwriter_scheduled_events_plugin_deactivate'); |
| 3477 |
|
| 3478 |
|
| 3479 |
|
| 3480 |
|
| 3481 |
function botwriter_scheduled_events_execute_tasks() { |
| 3482 |
global $wpdb; |
| 3483 |
$table_name_tasks = $wpdb->prefix . 'botwriter_tasks'; |
| 3484 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 3485 |
$table_name_super = $wpdb->prefix . 'botwriter_super'; |
| 3486 |
|
| 3487 |
// ── Prevent overlapping cron runs (race condition guard) ── |
| 3488 |
// Use a transient lock so two cron ticks cannot run simultaneously. |
| 3489 |
// Lock expires after 120 seconds as a safety net. |
| 3490 |
$lock_key = 'botwriter_cron_lock'; |
| 3491 |
if (get_transient($lock_key)) { |
| 3492 |
botwriter_log('CRON SKIPPED — another cron run is still in progress'); |
| 3493 |
return; |
| 3494 |
} |
| 3495 |
set_transient($lock_key, time(), 120); |
| 3496 |
|
| 3497 |
// Check if cron is active |
| 3498 |
$cron_active = get_option('botwriter_cron_active'); |
| 3499 |
botwriter_log('=== CRON START ===', [ |
| 3500 |
'cron_active_option' => $cron_active, |
| 3501 |
'timestamp' => current_time('Y-m-d H:i:s'), |
| 3502 |
]); |
| 3503 |
|
| 3504 |
if ($cron_active !== '1') { |
| 3505 |
botwriter_log('CRON DISABLED - exiting', ['cron_active' => $cron_active]); |
| 3506 |
delete_transient($lock_key); |
| 3507 |
return; |
| 3508 |
} |
| 3509 |
|
| 3510 |
// STOPFORMANY: refuse to dispatch new tasks while the flag is active |
| 3511 |
if (get_option('botwriter_stopformany', false)) { |
| 3512 |
botwriter_log('CRON BLOCKED by STOPFORMANY — too many consecutive errors on server'); |
| 3513 |
delete_transient($lock_key); |
| 3514 |
return; |
| 3515 |
} |
| 3516 |
|
| 3517 |
// Get the current day (English name) and date based on WordPress local time |
| 3518 |
// Use DateTime with wp_timezone() to respect site timezone and keep English day name |
| 3519 |
try { |
| 3520 |
$dt = new DateTime('now', wp_timezone()); |
| 3521 |
$current_day_en = $dt->format('l'); |
| 3522 |
} catch (Exception $e) { |
| 3523 |
// Fallback, still English but GMT-based |
| 3524 |
$current_day_en = gmdate('l'); |
| 3525 |
} |
| 3526 |
$current_date = current_time('Y-m-d'); |
| 3527 |
|
| 3528 |
botwriter_log('Cron dispatcher triggered', [ |
| 3529 |
'day' => $current_day_en, |
| 3530 |
'date' => $current_date, |
| 3531 |
'timezone' => wp_timezone_string(), |
| 3532 |
]); |
| 3533 |
|
| 3534 |
// PHASE 2 |
| 3535 |
botwriter_execute_events_pass2(); |
| 3536 |
|
| 3537 |
//PHASE 1 Execute each event if it meets the conditions |
| 3538 |
// Get tasks scheduled for today and status=1 |
| 3539 |
// Exclude one-off Write now tasks from cron to avoid duplicate logs |
| 3540 |
$tasks = (array) $wpdb->get_results( |
| 3541 |
$wpdb->prepare( |
| 3542 |
"SELECT * FROM {$table_name_tasks} WHERE days LIKE %s AND status = %d AND (task_type IS NULL OR task_type <> %s)", |
| 3543 |
'%' . $wpdb->esc_like($current_day_en) . '%', |
| 3544 |
1, |
| 3545 |
'writenow' |
| 3546 |
), |
| 3547 |
ARRAY_A |
| 3548 |
); |
| 3549 |
botwriter_log('Tasks evaluated for cron tick', [ |
| 3550 |
'count' => count($tasks), |
| 3551 |
'query_day' => $current_day_en, |
| 3552 |
]); |
| 3553 |
|
| 3554 |
// Log all tasks found for debugging |
| 3555 |
if (count($tasks) === 0) { |
| 3556 |
// Check total active tasks to see if it's a day mismatch |
| 3557 |
$all_active = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name_tasks} WHERE status = 1"); |
| 3558 |
botwriter_log('NO TASKS FOUND for today', [ |
| 3559 |
'current_day' => $current_day_en, |
| 3560 |
'total_active_tasks' => $all_active, |
| 3561 |
]); |
| 3562 |
} |
| 3563 |
|
| 3564 |
foreach ($tasks as $task) { |
| 3565 |
botwriter_log('Evaluating task', [ |
| 3566 |
'task_id' => $task['id'], |
| 3567 |
'task_name' => $task['task_name'], |
| 3568 |
'days' => $task['days'], |
| 3569 |
'times_per_day' => $task['times_per_day'], |
| 3570 |
'execution_count' => $task['execution_count'], |
| 3571 |
'last_execution_date' => $task['last_execution_date'], |
| 3572 |
'last_execution_time' => $task['last_execution_time'], |
| 3573 |
'website_type' => $task['website_type'], |
| 3574 |
]); |
| 3575 |
|
| 3576 |
// Skip Write now tasks defensively (in case of legacy rows) |
| 3577 |
if (!empty($task['task_type']) && $task['task_type'] === 'writenow') { |
| 3578 |
botwriter_log('Skipping writenow task', ['task_id' => $task['id']]); |
| 3579 |
continue; |
| 3580 |
} |
| 3581 |
|
| 3582 |
// Reset execution count daily |
| 3583 |
if ($task["last_execution_date"] !== $current_date) { |
| 3584 |
$wpdb->update($table_name_tasks, ['execution_count' => 0, 'last_execution_date' => $current_date], ['id' => $task["id"]]); |
| 3585 |
$task["execution_count"] = 0; |
| 3586 |
botwriter_log('Reset execution count for new day', ['task_id' => $task['id']]); |
| 3587 |
} |
| 3588 |
|
| 3589 |
// Check if the task is a supertask and if it exists |
| 3590 |
$super_exists = true; |
| 3591 |
if ($task["website_type"] == 'super2') { //supertask |
| 3592 |
$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); |
| 3593 |
if (!$super) { |
| 3594 |
$super_exists = false; |
| 3595 |
botwriter_log('Super2 task skipped, no pending outline', [ |
| 3596 |
'task_id' => $task['id'], |
| 3597 |
]); |
| 3598 |
} |
| 3599 |
} |
| 3600 |
|
| 3601 |
// Check if the task can still be executed based on its daily limit |
| 3602 |
if ($task["execution_count"] < $task["times_per_day"] && $super_exists) { |
| 3603 |
$last_execution_time = $task["last_execution_time"]; |
| 3604 |
$now = current_time('timestamp'); |
| 3605 |
$diff = $now - strtotime($last_execution_time); |
| 3606 |
$pause = get_option('botwriter_paused_tasks'); |
| 3607 |
$pause = is_numeric($pause) ? intval($pause) : 2; |
| 3608 |
|
| 3609 |
botwriter_log('Checking pause time', [ |
| 3610 |
'task_id' => $task['id'], |
| 3611 |
'last_execution_time' => $last_execution_time, |
| 3612 |
'now' => current_time('Y-m-d H:i:s'), |
| 3613 |
'diff_seconds' => $diff, |
| 3614 |
'pause_minutes' => $pause, |
| 3615 |
'required_seconds' => 60 * $pause, |
| 3616 |
'can_execute' => ($diff > 60 * $pause) ? 'YES' : 'NO', |
| 3617 |
]); |
| 3618 |
|
| 3619 |
if ($diff > 60 * $pause) { // |
| 3620 |
|
| 3621 |
$event = $task; |
| 3622 |
$event["task_status"] = "pending"; |
| 3623 |
$event["id_task"] = $task["id"]; |
| 3624 |
$event["intentosfase1"] = 0; |
| 3625 |
$id_log = botwriter_logs_register($event); // create log in db |
| 3626 |
$event["id"] = $id_log; |
| 3627 |
if ($task["website_type"] == 'super2') { // supertask |
| 3628 |
botwriter_log('Preparing super2 dispatch', [ |
| 3629 |
'task_id' => $task['id'], |
| 3630 |
'log_id' => $id_log, |
| 3631 |
]); |
| 3632 |
$prepared_event = botwriter_super_prepare_event($event); |
| 3633 |
if ($prepared_event === false) { |
| 3634 |
botwriter_log('Super2 preparation returned false', [ |
| 3635 |
'task_id' => $task['id'], |
| 3636 |
'log_id' => $id_log, |
| 3637 |
]); |
| 3638 |
continue; |
| 3639 |
} |
| 3640 |
$event = $prepared_event; |
| 3641 |
$id_log = botwriter_logs_register($event, $id_log); // actualizamos log con los datos de super2 |
| 3642 |
} |
| 3643 |
botwriter_log('Queueing phase 1 send', [ |
| 3644 |
'task_id' => $task['id'], |
| 3645 |
'log_id' => $id_log, |
| 3646 |
'website_type' => $task['website_type'], |
| 3647 |
]); |
| 3648 |
// Update execution count BEFORE the HTTP call to prevent |
| 3649 |
// overlapping cron ticks from creating duplicate logs. |
| 3650 |
$current_time = current_time('Y-m-d H:i:s'); |
| 3651 |
$wpdb->update($table_name_tasks, ['execution_count' => $task["execution_count"] + 1, 'last_execution_time' => $current_time], ['id' => $task["id"]]); |
| 3652 |
botwriter_send1_data_to_server((array) $event); |
| 3653 |
} else { |
| 3654 |
botwriter_log('Task paused - waiting for pause interval', [ |
| 3655 |
'task_id' => $task['id'], |
| 3656 |
'diff_seconds' => $diff, |
| 3657 |
'required_seconds' => 60 * $pause, |
| 3658 |
'remaining_seconds' => (60 * $pause) - $diff, |
| 3659 |
]); |
| 3660 |
} |
| 3661 |
} else { |
| 3662 |
if ($super_exists) { |
| 3663 |
botwriter_log('Task skipped due to daily limit reached', [ |
| 3664 |
'task_id' => $task['id'], |
| 3665 |
'execution_count' => $task['execution_count'], |
| 3666 |
'times_per_day' => $task['times_per_day'], |
| 3667 |
]); |
| 3668 |
} |
| 3669 |
} |
| 3670 |
} // end tasks |
| 3671 |
|
| 3672 |
botwriter_log('=== CRON END ===', ['timestamp' => current_time('Y-m-d H:i:s')]); |
| 3673 |
|
| 3674 |
// ── Release cron lock ── |
| 3675 |
delete_transient('botwriter_cron_lock'); |
| 3676 |
} |
| 3677 |
|
| 3678 |
function botwriter_execute_events_pass2(){ |
| 3679 |
|
| 3680 |
// check if the task is still in queue or finished |
| 3681 |
global $wpdb; |
| 3682 |
$table_name_tasks = $wpdb->prefix . 'botwriter_tasks'; |
| 3683 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 3684 |
// INQUEUE |
| 3685 |
$events2 = (array) $wpdb->get_results($wpdb->prepare("SELECT * FROM {$table_name_logs} WHERE task_status=%s", 'inqueue')); |
| 3686 |
botwriter_log('Phase 2 queue check', ['inqueue_count' => count($events2)]); |
| 3687 |
foreach ($events2 as $event) { |
| 3688 |
$event = (array) $event; |
| 3689 |
|
| 3690 |
// ── Atomically mark as 'polling' to prevent overlapping cron ticks ── |
| 3691 |
// Only update if the status is still 'inqueue'; if another cron tick |
| 3692 |
// already changed it, affected_rows will be 0 and we skip this log. |
| 3693 |
$affected = $wpdb->query($wpdb->prepare( |
| 3694 |
"UPDATE {$table_name_logs} SET task_status = 'polling' WHERE id = %d AND task_status = 'inqueue'", |
| 3695 |
$event['id'] |
| 3696 |
)); |
| 3697 |
if ($affected === 0) { |
| 3698 |
botwriter_log('Phase 2 skipped — already being polled', ['log_id' => $event['id']]); |
| 3699 |
continue; |
| 3700 |
} |
| 3701 |
|
| 3702 |
// Execute the event (send2 will set final status: completed/error/inqueue) |
| 3703 |
$result = botwriter_send2_data_to_server( (array) $event); |
| 3704 |
|
| 3705 |
// If send2 did NOT update the log status (returned false without changing it), |
| 3706 |
// restore to 'inqueue' so the next tick can retry. |
| 3707 |
if ($result === false) { |
| 3708 |
$current_status = $wpdb->get_var($wpdb->prepare( |
| 3709 |
"SELECT task_status FROM {$table_name_logs} WHERE id = %d", |
| 3710 |
$event['id'] |
| 3711 |
)); |
| 3712 |
if ($current_status === 'polling') { |
| 3713 |
$wpdb->update($table_name_logs, ['task_status' => 'inqueue'], ['id' => $event['id']]); |
| 3714 |
} |
| 3715 |
} |
| 3716 |
} // end INQUEUE |
| 3717 |
|
| 3718 |
|
| 3719 |
//IN ERROR, depending on the attempt, it is resent later or marked as finished |
| 3720 |
// Exclude 'writenow' tasks from automatic retries - they should only be retried manually |
| 3721 |
$events1 = (array) $wpdb->get_results( |
| 3722 |
$wpdb->prepare( |
| 3723 |
"SELECT l.*, t.id AS task_exists, t.status AS task_enabled |
| 3724 |
FROM {$table_name_logs} l |
| 3725 |
LEFT JOIN {$table_name_tasks} t ON t.id = l.id_task |
| 3726 |
WHERE l.task_status = %s |
| 3727 |
AND l.intentosfase1 < %d |
| 3728 |
AND (l.task_type IS NULL OR l.task_type <> 'writenow')", |
| 3729 |
'error', |
| 3730 |
8 |
| 3731 |
), |
| 3732 |
ARRAY_A |
| 3733 |
); |
| 3734 |
|
| 3735 |
$retryable_events = array(); |
| 3736 |
$closed_missing_task = 0; |
| 3737 |
$closed_disabled_task = 0; |
| 3738 |
|
| 3739 |
foreach ($events1 as $event_row) { |
| 3740 |
$event_row = (array) $event_row; |
| 3741 |
$task_exists = !empty($event_row['task_exists']); |
| 3742 |
$task_enabled = isset($event_row['task_enabled']) ? (int) $event_row['task_enabled'] : 0; |
| 3743 |
|
| 3744 |
if (!$task_exists || $task_enabled !== 1) { |
| 3745 |
$reason = !$task_exists |
| 3746 |
? 'Retry stopped: linked task was deleted' |
| 3747 |
: 'Retry stopped: linked task is disabled'; |
| 3748 |
|
| 3749 |
$prev_error = isset($event_row['error']) ? trim((string) $event_row['error']) : ''; |
| 3750 |
$new_error = $prev_error !== '' ? ($prev_error . ' | ' . $reason) : $reason; |
| 3751 |
|
| 3752 |
$wpdb->update( |
| 3753 |
$table_name_logs, |
| 3754 |
array( |
| 3755 |
'intentosfase1' => 8, |
| 3756 |
'error' => $new_error, |
| 3757 |
), |
| 3758 |
array('id' => (int) $event_row['id']) |
| 3759 |
); |
| 3760 |
|
| 3761 |
if (!$task_exists) { |
| 3762 |
$closed_missing_task++; |
| 3763 |
} else { |
| 3764 |
$closed_disabled_task++; |
| 3765 |
} |
| 3766 |
continue; |
| 3767 |
} |
| 3768 |
|
| 3769 |
$retryable_events[] = $event_row; |
| 3770 |
} |
| 3771 |
|
| 3772 |
botwriter_log('Phase 1 retries fetched', [ |
| 3773 |
'error_count' => count($retryable_events), |
| 3774 |
'closed_missing_task' => $closed_missing_task, |
| 3775 |
'closed_disabled_task' => $closed_disabled_task, |
| 3776 |
]); |
| 3777 |
|
| 3778 |
$intento_tiempo = array(0=>0,1=>0,2=>5,3=>10,4=>30,5=>60,6=>120,7=>240,8=>480); // minutos |
| 3779 |
foreach ($retryable_events as $event) { |
| 3780 |
$event = (array) $event; |
| 3781 |
// Execute the event if the time has passed |
| 3782 |
$intentosfase1 = $event["intentosfase1"]; |
| 3783 |
$tiempo = $intento_tiempo[$intentosfase1+1]; |
| 3784 |
$retry_reference = !empty($event['last_execution_time']) ? $event['last_execution_time'] : $event['created_at']; |
| 3785 |
$retry_reference_ts = strtotime($retry_reference); |
| 3786 |
if ($retry_reference_ts === false) { |
| 3787 |
$retry_reference_ts = strtotime($event['created_at']); |
| 3788 |
} |
| 3789 |
$now = current_time('timestamp'); |
| 3790 |
|
| 3791 |
$diff = $now - $retry_reference_ts; |
| 3792 |
if ($diff > $tiempo * 60) { |
| 3793 |
botwriter_log('Retrying phase 1 request', [ |
| 3794 |
'log_id' => $event['id'], |
| 3795 |
'task_id' => $event['id_task'], |
| 3796 |
'attempt' => $intentosfase1 + 1, |
| 3797 |
]); |
| 3798 |
botwriter_send1_data_to_server( (array) $event); |
| 3799 |
} |
| 3800 |
|
| 3801 |
} // END LOGS IN ERROR |
| 3802 |
|
| 3803 |
|
| 3804 |
} |
| 3805 |
|
| 3806 |
|
| 3807 |
function botwriter_generate_post($data){ |
| 3808 |
$data = botwriter_normalize_generated_post_payload($data); |
| 3809 |
|
| 3810 |
// Determine post type (default to 'post' for backward compatibility) |
| 3811 |
$post_type = isset($data['post_type']) && !empty($data['post_type']) ? $data['post_type'] : 'post'; |
| 3812 |
|
| 3813 |
// Build post data array |
| 3814 |
$post_data = array( |
| 3815 |
'post_title' => $data['aigenerated_title'], |
| 3816 |
'post_content' => $data['aigenerated_content'], |
| 3817 |
'post_status' => $data['post_status'], |
| 3818 |
'post_author' => $data['author_selection'], |
| 3819 |
'post_type' => $post_type, |
| 3820 |
); |
| 3821 |
|
| 3822 |
// For 'post' type with category_id (backward compatibility) |
| 3823 |
if ($post_type === 'post' && !empty($data['category_id'])) { |
| 3824 |
$post_data['post_category'] = array_map('intval', explode(',', $data['category_id'])); |
| 3825 |
} |
| 3826 |
|
| 3827 |
// Create the post |
| 3828 |
$post_id = wp_insert_post($post_data); |
| 3829 |
|
| 3830 |
if ($post_id === 0) { |
| 3831 |
//error_log('Error creating post'); |
| 3832 |
return false; |
| 3833 |
} |
| 3834 |
|
| 3835 |
// Assign taxonomy terms from taxonomy_data (if present) |
| 3836 |
if (!empty($data['taxonomy_data'])) { |
| 3837 |
$taxonomy_data = json_decode($data['taxonomy_data'], true); |
| 3838 |
if (is_array($taxonomy_data)) { |
| 3839 |
foreach ($taxonomy_data as $taxonomy_name => $term_ids) { |
| 3840 |
if (!empty($term_ids) && taxonomy_exists($taxonomy_name)) { |
| 3841 |
$term_ids = array_map('intval', (array)$term_ids); |
| 3842 |
wp_set_object_terms($post_id, $term_ids, $taxonomy_name); |
| 3843 |
} |
| 3844 |
} |
| 3845 |
} |
| 3846 |
} |
| 3847 |
|
| 3848 |
// Add tags to the post (unless disabled in settings) |
| 3849 |
$tags_disabled = get_option('botwriter_tags_disabled', '0'); |
| 3850 |
if ($tags_disabled !== '1' && !empty($data['aigenerated_tags'])) { |
| 3851 |
$tags = explode(',', $data['aigenerated_tags']); |
| 3852 |
wp_set_post_tags($post_id, $tags); |
| 3853 |
} |
| 3854 |
|
| 3855 |
// SEO Slug Translation: translate post slug, tag slugs, and get image slug |
| 3856 |
$translated_image_slug = ''; |
| 3857 |
if (function_exists('botwriter_apply_translated_slugs')) { |
| 3858 |
$translated_image_slug = botwriter_apply_translated_slugs( |
| 3859 |
$post_id, |
| 3860 |
$data['aigenerated_title'], |
| 3861 |
$data['aigenerated_tags'] ?? '' |
| 3862 |
); |
| 3863 |
} |
| 3864 |
|
| 3865 |
// Add image to the post only if image URL is provided and images are not disabled for this task |
| 3866 |
$task_disable_images = isset($data['disable_ai_images']) ? intval($data['disable_ai_images']) : 0; |
| 3867 |
if (!empty($data['aigenerated_image']) && $task_disable_images !== 1) { |
| 3868 |
// Pass attribution data for stock photos |
| 3869 |
$image_attribution = isset($data['image_attribution']) ? $data['image_attribution'] : null; |
| 3870 |
botwriter_attach_image_to_post($post_id, $data['aigenerated_image'], $data['aigenerated_title'], $translated_image_slug, $image_attribution); |
| 3871 |
|
| 3872 |
// Handle stock photo attribution in post content (footer mode) |
| 3873 |
if (!empty($image_attribution) && is_array($image_attribution)) { |
| 3874 |
$attribution_mode = get_option('botwriter_stockphoto_attribution', 'caption'); |
| 3875 |
if ($attribution_mode === 'content_footer') { |
| 3876 |
$author = sanitize_text_field($image_attribution['author'] ?? ''); |
| 3877 |
$source = sanitize_text_field($image_attribution['source'] ?? ''); |
| 3878 |
$source_url = esc_url($image_attribution['source_url'] ?? ''); |
| 3879 |
$author_url = esc_url($image_attribution['author_url'] ?? ''); |
| 3880 |
|
| 3881 |
if ($author || $source) { |
| 3882 |
$credit_parts = array(); |
| 3883 |
if ($author) { |
| 3884 |
$credit_parts[] = $author_url |
| 3885 |
? sprintf('<a href="%s" rel="nofollow noopener" target="_blank">%s</a>', $author_url, esc_html($author)) |
| 3886 |
: esc_html($author); |
| 3887 |
} |
| 3888 |
if ($source) { |
| 3889 |
$credit_parts[] = $source_url |
| 3890 |
? sprintf('<a href="%s" rel="nofollow noopener" target="_blank">%s</a>', $source_url, esc_html($source)) |
| 3891 |
: esc_html($source); |
| 3892 |
} |
| 3893 |
$credit_html = '<p class="botwriter-image-attribution"><small>' |
| 3894 |
. sprintf( |
| 3895 |
/* translators: %s: attribution credit (author / source) */ |
| 3896 |
esc_html__('Photo by %s', 'botwriter'), |
| 3897 |
implode(' / ', $credit_parts) |
| 3898 |
) |
| 3899 |
. '</small></p>'; |
| 3900 |
|
| 3901 |
wp_update_post(array( |
| 3902 |
'ID' => $post_id, |
| 3903 |
'post_content' => get_post_field('post_content', $post_id) . "\n" . $credit_html, |
| 3904 |
)); |
| 3905 |
} |
| 3906 |
} |
| 3907 |
} |
| 3908 |
} else { |
| 3909 |
$skip_reason = ''; |
| 3910 |
if (empty($data['aigenerated_image'])) { |
| 3911 |
$skip_reason = 'No image URL provided'; |
| 3912 |
} elseif ($task_disable_images === 1) { |
| 3913 |
$skip_reason = 'AI images disabled for this task'; |
| 3914 |
} |
| 3915 |
|
| 3916 |
botwriter_log('Image attachment skipped during post creation', [ |
| 3917 |
'post_id' => $post_id, |
| 3918 |
'post_title' => $data['aigenerated_title'], |
| 3919 |
'image_url' => $data['aigenerated_image'] ?? 'not provided', |
| 3920 |
'reason' => $skip_reason |
| 3921 |
]); |
| 3922 |
} |
| 3923 |
|
| 3924 |
// Automatic SEO post-processing: insert internal links directly on publish. |
| 3925 |
$seo_internal_links_result = array( |
| 3926 |
'updated' => false, |
| 3927 |
'inserted' => 0, |
| 3928 |
'strategy' => 'disabled', |
| 3929 |
); |
| 3930 |
if (function_exists('botwriter_seo_auto_internal_links_postprocess')) { |
| 3931 |
$seo_publish_context = array( |
| 3932 |
'title' => (string) ($data['aigenerated_title'] ?? ''), |
| 3933 |
'content' => (string) get_post_field('post_content', $post_id), |
| 3934 |
'tags' => (string) ($data['aigenerated_tags'] ?? ''), |
| 3935 |
'excerpt' => (string) get_post_field('post_excerpt', $post_id), |
| 3936 |
); |
| 3937 |
|
| 3938 |
botwriter_log('SEO publish post-processing start', array( |
| 3939 |
'post_id' => $post_id, |
| 3940 |
'title_len' => strlen((string) $seo_publish_context['title']), |
| 3941 |
'content_len' => strlen((string) $seo_publish_context['content']), |
| 3942 |
'tags_len' => strlen((string) $seo_publish_context['tags']), |
| 3943 |
'excerpt_len' => strlen((string) $seo_publish_context['excerpt']), |
| 3944 |
'content_preview' => botwriter_seo_debug_preview((string) $seo_publish_context['content'], 360), |
| 3945 |
)); |
| 3946 |
|
| 3947 |
$seo_internal_links_result = botwriter_seo_auto_internal_links_postprocess($post_id, array( |
| 3948 |
'title' => (string) $seo_publish_context['title'], |
| 3949 |
'content' => (string) $seo_publish_context['content'], |
| 3950 |
'tags' => (string) $seo_publish_context['tags'], |
| 3951 |
'excerpt' => (string) $seo_publish_context['excerpt'], |
| 3952 |
)); |
| 3953 |
} |
| 3954 |
|
| 3955 |
// Generate SEO meta description using AI (if enabled in SEO settings) |
| 3956 |
if ( function_exists( 'botwriter_generate_seo_meta' ) && function_exists( 'botwriter_is_seo_ai_meta_enabled' ) && botwriter_is_seo_ai_meta_enabled() ) { |
| 3957 |
$post_language = $data['post_language'] ?? ''; |
| 3958 |
$meta_source_content = (string) get_post_field('post_content', $post_id); |
| 3959 |
$meta_description = botwriter_generate_seo_meta( |
| 3960 |
$data['aigenerated_title'], |
| 3961 |
$meta_source_content, |
| 3962 |
$post_language |
| 3963 |
); |
| 3964 |
if ( $meta_description ) { |
| 3965 |
botwriter_apply_seo_meta( $post_id, $meta_description ); |
| 3966 |
} |
| 3967 |
} |
| 3968 |
|
| 3969 |
$seo_focus_keyword_result = array( |
| 3970 |
'enabled' => false, |
| 3971 |
'generated' => false, |
| 3972 |
'length' => 0, |
| 3973 |
); |
| 3974 |
if ( |
| 3975 |
function_exists('botwriter_generate_seo_focus_keyword') |
| 3976 |
&& function_exists('botwriter_is_seo_publish_focus_keyword_enabled') |
| 3977 |
&& botwriter_is_seo_publish_focus_keyword_enabled() |
| 3978 |
) { |
| 3979 |
$seo_focus_keyword_result['enabled'] = true; |
| 3980 |
$post_language = $data['post_language'] ?? ''; |
| 3981 |
$focus_source_content = (string) get_post_field('post_content', $post_id); |
| 3982 |
$focus_keyword = botwriter_generate_seo_focus_keyword( |
| 3983 |
(string) ($data['aigenerated_title'] ?? ''), |
| 3984 |
$focus_source_content, |
| 3985 |
$post_language |
| 3986 |
); |
| 3987 |
|
| 3988 |
if (!empty($focus_keyword) && function_exists('botwriter_apply_seo_focus_keyword')) { |
| 3989 |
botwriter_apply_seo_focus_keyword($post_id, (string) $focus_keyword); |
| 3990 |
$seo_focus_keyword_result['generated'] = true; |
| 3991 |
$seo_focus_keyword_result['length'] = function_exists('mb_strlen') |
| 3992 |
? mb_strlen((string) $focus_keyword) |
| 3993 |
: strlen((string) $focus_keyword); |
| 3994 |
} |
| 3995 |
} |
| 3996 |
|
| 3997 |
$seo_faq_result = array( |
| 3998 |
'enabled' => false, |
| 3999 |
'generated' => false, |
| 4000 |
'mode' => 'disabled', |
| 4001 |
'visible' => null, |
| 4002 |
); |
| 4003 |
if (get_option('botwriter_seo_publish_faq_enabled', '0') === '1') { |
| 4004 |
$seo_faq_result['enabled'] = true; |
| 4005 |
$faq_mode = sanitize_key((string) get_option('botwriter_seo_publish_faq_mode', 'visible_schema')); |
| 4006 |
if ($faq_mode !== 'visible_schema' && $faq_mode !== 'schema_only') { |
| 4007 |
$faq_mode = 'visible_schema'; |
| 4008 |
} |
| 4009 |
|
| 4010 |
$faq_visible = $faq_mode === 'schema_only' ? 0 : 1; |
| 4011 |
$seo_faq_result['mode'] = $faq_mode; |
| 4012 |
$seo_faq_result['visible'] = $faq_visible; |
| 4013 |
|
| 4014 |
if (function_exists('botwriter_seo_generate_faq_for_post')) { |
| 4015 |
$faq_generated = (bool) botwriter_seo_generate_faq_for_post($post_id); |
| 4016 |
$seo_faq_result['generated'] = $faq_generated; |
| 4017 |
if ($faq_generated) { |
| 4018 |
update_post_meta($post_id, '_botwriter_seo_faq_visible', $faq_visible); |
| 4019 |
if (function_exists('botwriter_seo_compute_score')) { |
| 4020 |
botwriter_seo_compute_score($post_id, true); |
| 4021 |
} |
| 4022 |
} |
| 4023 |
} else { |
| 4024 |
$seo_faq_result['mode'] = 'function_missing'; |
| 4025 |
} |
| 4026 |
} |
| 4027 |
|
| 4028 |
botwriter_log('SEO publish post-processing summary', array( |
| 4029 |
'post_id' => $post_id, |
| 4030 |
'internal_links_strategy' => (string) ($seo_internal_links_result['strategy'] ?? 'unknown'), |
| 4031 |
'internal_links_inserted' => intval($seo_internal_links_result['inserted'] ?? 0), |
| 4032 |
'internal_links_updated' => !empty($seo_internal_links_result['updated']) ? 1 : 0, |
| 4033 |
'focus_keyword_enabled' => !empty($seo_focus_keyword_result['enabled']) ? 1 : 0, |
| 4034 |
'focus_keyword_generated' => !empty($seo_focus_keyword_result['generated']) ? 1 : 0, |
| 4035 |
'focus_keyword_length' => intval($seo_focus_keyword_result['length'] ?? 0), |
| 4036 |
'faq_enabled' => !empty($seo_faq_result['enabled']) ? 1 : 0, |
| 4037 |
'faq_generated' => !empty($seo_faq_result['generated']) ? 1 : 0, |
| 4038 |
'faq_mode' => (string) ($seo_faq_result['mode'] ?? 'disabled'), |
| 4039 |
'faq_visible' => isset($seo_faq_result['visible']) ? intval($seo_faq_result['visible']) : -1, |
| 4040 |
)); |
| 4041 |
|
| 4042 |
// Persist image prompt in post meta so regeneration never depends on logs. |
| 4043 |
$saved_image_prompt = botwriter_resolve_image_prompt_from_post_data($data); |
| 4044 |
botwriter_log('Post creation: image prompt resolution before meta save', array( |
| 4045 |
'post_id' => $post_id, |
| 4046 |
'resolved_prompt_len' => strlen((string) $saved_image_prompt), |
| 4047 |
'incoming_image_prompt_len' => strlen(trim((string) ($data['image_prompt'] ?? ''))), |
| 4048 |
'incoming_image_provider' => isset($data['image_provider']) ? sanitize_key((string) $data['image_provider']) : '', |
| 4049 |
'has_image_attribution' => !empty($data['image_attribution']), |
| 4050 |
)); |
| 4051 |
if ($saved_image_prompt !== '') { |
| 4052 |
$saved_provider = isset($data['image_provider']) ? sanitize_key((string) $data['image_provider']) : ''; |
| 4053 |
|
| 4054 |
if ($saved_provider === '' && !empty($data['image_attribution'])) { |
| 4055 |
$saved_provider = 'stockphoto'; |
| 4056 |
} |
| 4057 |
|
| 4058 |
if ($saved_provider === '') { |
| 4059 |
$settings = botwriter_get_current_image_generation_settings(); |
| 4060 |
$saved_provider = (string) ($settings['provider'] ?? ''); |
| 4061 |
} |
| 4062 |
|
| 4063 |
botwriter_log('Post creation: saving image prompt meta', array( |
| 4064 |
'post_id' => $post_id, |
| 4065 |
'provider' => $saved_provider, |
| 4066 |
'prompt_len' => strlen((string) $saved_image_prompt), |
| 4067 |
)); |
| 4068 |
|
| 4069 |
botwriter_save_post_image_prompt_meta($post_id, $saved_image_prompt, $saved_provider); |
| 4070 |
} |
| 4071 |
|
| 4072 |
return $post_id; |
| 4073 |
} |
| 4074 |
|
| 4075 |
/** |
| 4076 |
* Parse a JSON-like AI payload from a raw content string. |
| 4077 |
* Handles markdown fences and typographic quotes used by some providers. |
| 4078 |
* |
| 4079 |
* @param mixed $raw_content |
| 4080 |
* @return array|null |
| 4081 |
*/ |
| 4082 |
function botwriter_parse_generated_payload_from_content($raw_content) { |
| 4083 |
if (!is_string($raw_content)) { |
| 4084 |
return null; |
| 4085 |
} |
| 4086 |
|
| 4087 |
$clean = trim($raw_content); |
| 4088 |
if ($clean === '') { |
| 4089 |
return null; |
| 4090 |
} |
| 4091 |
|
| 4092 |
// Normalize BOM + typographic quotes before parsing. |
| 4093 |
$clean = preg_replace('/^\xEF\xBB\xBF/u', '', $clean); |
| 4094 |
$clean = preg_replace('/[\x{201C}\x{201D}\x{201E}\x{201F}]/u', '"', $clean); |
| 4095 |
$clean = preg_replace('/[\x{2018}\x{2019}\x{201A}\x{201B}]/u', "'", $clean); |
| 4096 |
|
| 4097 |
// Strip markdown code fences. |
| 4098 |
if (preg_match('/^```(?:json|JSON)?\s*\n?(.*?)\n?```$/su', $clean, $matches)) { |
| 4099 |
$clean = trim($matches[1]); |
| 4100 |
} elseif (preg_match('/```(?:json|JSON)?\s*\n?(.*?)\n?```/su', $clean, $matches)) { |
| 4101 |
$clean = trim($matches[1]); |
| 4102 |
} elseif (preg_match('/^`{1,3}(?:json|JSON)?\s*\n?([\s\S]+)$/u', $clean, $matches)) { |
| 4103 |
$inner = trim($matches[1]); |
| 4104 |
$clean = preg_replace('/`{1,3}\s*$/u', '', $inner); |
| 4105 |
$clean = trim((string) $clean); |
| 4106 |
} |
| 4107 |
|
| 4108 |
// Some providers prepend a stray "json" token before the object. |
| 4109 |
$clean = preg_replace('/^json\s*(?=\{|\[)/i', '', $clean); |
| 4110 |
|
| 4111 |
// Remove decorative wrappers around the payload. |
| 4112 |
$clean = trim($clean, " \t\n\r\0\x0B`'\""); |
| 4113 |
|
| 4114 |
$parsed = json_decode($clean, true); |
| 4115 |
|
| 4116 |
if (!is_array($parsed) && preg_match('/\{[\s\S]*\}/u', $clean, $json_match)) { |
| 4117 |
$parsed = json_decode($json_match[0], true); |
| 4118 |
} |
| 4119 |
|
| 4120 |
if (!is_array($parsed) || !isset($parsed['aigenerated_content'])) { |
| 4121 |
return null; |
| 4122 |
} |
| 4123 |
|
| 4124 |
return $parsed; |
| 4125 |
} |
| 4126 |
|
| 4127 |
/** |
| 4128 |
* Ensure the post payload uses parsed fields when content accidentally contains |
| 4129 |
* wrapped JSON returned by the AI model. |
| 4130 |
* |
| 4131 |
* @param mixed $data |
| 4132 |
* @return mixed |
| 4133 |
*/ |
| 4134 |
function botwriter_normalize_generated_post_payload($data) { |
| 4135 |
if (!is_array($data)) { |
| 4136 |
return $data; |
| 4137 |
} |
| 4138 |
|
| 4139 |
$raw_content = isset($data['aigenerated_content']) ? (string) $data['aigenerated_content'] : ''; |
| 4140 |
$parsed_payload = botwriter_parse_generated_payload_from_content($raw_content); |
| 4141 |
|
| 4142 |
if (!is_array($parsed_payload)) { |
| 4143 |
return $data; |
| 4144 |
} |
| 4145 |
|
| 4146 |
$normalized = $data; |
| 4147 |
$changed = false; |
| 4148 |
|
| 4149 |
$field_map = array('aigenerated_title', 'aigenerated_content', 'aigenerated_tags', 'image_prompt', 'image_keywords'); |
| 4150 |
foreach ($field_map as $field) { |
| 4151 |
if (!array_key_exists($field, $parsed_payload)) { |
| 4152 |
continue; |
| 4153 |
} |
| 4154 |
|
| 4155 |
$new_value = $parsed_payload[$field]; |
| 4156 |
if ($field === 'aigenerated_tags' && is_array($new_value)) { |
| 4157 |
$new_value = implode(', ', $new_value); |
| 4158 |
} |
| 4159 |
$new_value = is_string($new_value) ? trim($new_value) : ''; |
| 4160 |
|
| 4161 |
if ($new_value === '') { |
| 4162 |
continue; |
| 4163 |
} |
| 4164 |
|
| 4165 |
$current_value = isset($normalized[$field]) ? (string) $normalized[$field] : ''; |
| 4166 |
if ($current_value !== $new_value) { |
| 4167 |
$normalized[$field] = $new_value; |
| 4168 |
$changed = true; |
| 4169 |
} |
| 4170 |
} |
| 4171 |
|
| 4172 |
if ($changed) { |
| 4173 |
botwriter_log('Post payload normalized from wrapped JSON response', array( |
| 4174 |
'title_len' => strlen((string) ($normalized['aigenerated_title'] ?? '')), |
| 4175 |
'content_len' => strlen((string) ($normalized['aigenerated_content'] ?? '')), |
| 4176 |
'tags_len' => strlen((string) ($normalized['aigenerated_tags'] ?? '')), |
| 4177 |
'image_prompt_len' => strlen((string) ($normalized['image_prompt'] ?? '')), |
| 4178 |
)); |
| 4179 |
} |
| 4180 |
|
| 4181 |
return $normalized; |
| 4182 |
} |
| 4183 |
|
| 4184 |
|
| 4185 |
|
| 4186 |
|
| 4187 |
/** |
| 4188 |
* Send a legacy compat request with one automatic site_token recovery retry. |
| 4189 |
* |
| 4190 |
* When the backend reports token mismatch, clear local token and retry once. |
| 4191 |
* This self-heals cloned/reinstalled sites where the stored token is stale. |
| 4192 |
* |
| 4193 |
* @param string $remote_url Endpoint URL. |
| 4194 |
* @param array $data Request body. |
| 4195 |
* @param bool $ssl_verify SSL verify flag. |
| 4196 |
* @param int $timeout Request timeout in seconds. |
| 4197 |
* @return array|WP_Error |
| 4198 |
*/ |
| 4199 |
function botwriter_post_compat_with_token_recovery($remote_url, $data, $ssl_verify, $timeout = 45) { |
| 4200 |
$request_args = array( |
| 4201 |
'method' => 'POST', |
| 4202 |
'body' => $data, |
| 4203 |
'timeout' => $timeout, |
| 4204 |
'headers' => array(), |
| 4205 |
'sslverify' => (bool) $ssl_verify, |
| 4206 |
); |
| 4207 |
|
| 4208 |
$response = wp_remote_post($remote_url, $request_args); |
| 4209 |
if (is_wp_error($response)) { |
| 4210 |
return $response; |
| 4211 |
} |
| 4212 |
|
| 4213 |
$http_code = wp_remote_retrieve_response_code($response); |
| 4214 |
if ((int) $http_code !== 200) { |
| 4215 |
return $response; |
| 4216 |
} |
| 4217 |
|
| 4218 |
$body = wp_remote_retrieve_body($response); |
| 4219 |
$result = json_decode($body, true); |
| 4220 |
if (!is_array($result)) { |
| 4221 |
return $response; |
| 4222 |
} |
| 4223 |
|
| 4224 |
$error_code = (string) ($result['error'] ?? ''); |
| 4225 |
$error_message = (string) ($result['error_message'] ?? ''); |
| 4226 |
$token_issue = in_array($error_code, array('invalid_site_token', 'token_required'), true) |
| 4227 |
|| stripos($error_message, 'site token mismatch') !== false |
| 4228 |
|| stripos($error_message, 'requires authentication') !== false; |
| 4229 |
|
| 4230 |
if (!$token_issue) { |
| 4231 |
return $response; |
| 4232 |
} |
| 4233 |
|
| 4234 |
$current_token = (string) get_option('botwriter_site_token', ''); |
| 4235 |
if ($current_token === '') { |
| 4236 |
return $response; |
| 4237 |
} |
| 4238 |
|
| 4239 |
delete_option('botwriter_site_token'); |
| 4240 |
$retry_data = $data; |
| 4241 |
$retry_data['site_token'] = ''; |
| 4242 |
|
| 4243 |
botwriter_log('Site token mismatch detected. Retrying request with empty site_token.', array( |
| 4244 |
'error' => $error_code, |
| 4245 |
'message' => $error_message, |
| 4246 |
)); |
| 4247 |
|
| 4248 |
return wp_remote_post($remote_url, array( |
| 4249 |
'method' => 'POST', |
| 4250 |
'body' => $retry_data, |
| 4251 |
'timeout' => $timeout, |
| 4252 |
'headers' => array(), |
| 4253 |
'sslverify' => (bool) $ssl_verify, |
| 4254 |
)); |
| 4255 |
} |
| 4256 |
|
| 4257 |
// Function to send data to the server pass1 |
| 4258 |
function botwriter_send1_data_to_server($data) { |
| 4259 |
|
| 4260 |
Global $botwriter_version; |
| 4261 |
$remote_url = BOTWRITER_API_URL . 'redis_api_cola.php'; |
| 4262 |
|
| 4263 |
// Use constant to avoid get_plugin_data() and early translation loading |
| 4264 |
$botwriter_version = BOTWRITER_VERSION; |
| 4265 |
|
| 4266 |
// settings |
| 4267 |
$data['version'] = $botwriter_version; |
| 4268 |
$data['api_key'] = get_option('botwriter_api_key'); // la api_key del programa |
| 4269 |
$data["user_domainname"] = esc_url(get_site_url()); |
| 4270 |
$data['site_token'] = get_option('botwriter_site_token', ''); |
| 4271 |
|
| 4272 |
$data["ai_image_size"] = botwriter_normalize_ai_image_size((string) get_option('botwriter_ai_image_size', 'square')); |
| 4273 |
$data["ai_image_quality"]=get_option('botwriter_ai_image_quality'); |
| 4274 |
$data["ai_image_style"]=get_option('botwriter_ai_image_style', 'realistic'); |
| 4275 |
$data["ai_image_style_custom"]=get_option('botwriter_ai_image_style_custom', ''); |
| 4276 |
|
| 4277 |
// Use task-specific setting for disable_ai_images (already in $data from task/log) |
| 4278 |
// If not present, default to 0 (images enabled) |
| 4279 |
if (!isset($data["disable_ai_images"])) { |
| 4280 |
$data["disable_ai_images"] = 0; |
| 4281 |
} |
| 4282 |
|
| 4283 |
// If image generation fails, publish the post without image instead of erroring |
| 4284 |
$data['image_error_continue'] = get_option('botwriter_image_error_continue', '0'); |
| 4285 |
|
| 4286 |
// Provider selections |
| 4287 |
$data['text_provider'] = get_option('botwriter_text_provider', 'openai'); |
| 4288 |
$data['image_provider'] = get_option('botwriter_image_provider', 'stockphoto'); |
| 4289 |
|
| 4290 |
// Global provider "none" always means no AI images, regardless of legacy task flags. |
| 4291 |
if ($data['image_provider'] === 'none') { |
| 4292 |
$data['disable_ai_images'] = 1; |
| 4293 |
} |
| 4294 |
|
| 4295 |
// Stock photo settings (sent always, used only when image_provider=stockphoto) |
| 4296 |
$data['stockphoto_preferred'] = botwriter_get_current_image_model_by_provider('stockphoto'); |
| 4297 |
$data['stockphoto_selection'] = get_option('botwriter_stockphoto_selection', 'random_top10'); |
| 4298 |
$data['stockphoto_attribution'] = get_option('botwriter_stockphoto_attribution', 'caption'); |
| 4299 |
|
| 4300 |
// Get current text model based on provider |
| 4301 |
$text_provider = $data['text_provider']; |
| 4302 |
$text_model_defaults = [ |
| 4303 |
'openai' => 'gpt-5.4-mini', |
| 4304 |
'anthropic' => 'claude-sonnet-4-6', |
| 4305 |
'google' => 'gemini-2.5-flash', |
| 4306 |
'mistral' => 'mistral-large-latest', |
| 4307 |
'groq' => 'llama-3.3-70b-versatile', |
| 4308 |
'openrouter' => 'anthropic/claude-sonnet-4.6', |
| 4309 |
]; |
| 4310 |
$data['text_model'] = get_option("botwriter_{$text_provider}_model", $text_model_defaults[$text_provider] ?? 'gpt-5.4-mini'); |
| 4311 |
|
| 4312 |
// Get current image model based on provider |
| 4313 |
$image_provider = $data['image_provider']; |
| 4314 |
$image_model_default = null; |
| 4315 |
$image_model_option = null; |
| 4316 |
$image_model_raw = null; |
| 4317 |
|
| 4318 |
if ($image_provider === 'stockphoto') { |
| 4319 |
$data['image_model'] = $data['stockphoto_preferred']; |
| 4320 |
} elseif ($image_provider === 'none') { |
| 4321 |
$data['image_model'] = 'none'; |
| 4322 |
} else { |
| 4323 |
$image_model_default = function_exists('botwriter_get_provider_default_image_model') |
| 4324 |
? (string) botwriter_get_provider_default_image_model($image_provider) |
| 4325 |
: ''; |
| 4326 |
if ($image_model_default === '') { |
| 4327 |
$image_model_default = 'gpt-image-1'; |
| 4328 |
} |
| 4329 |
|
| 4330 |
$image_model_option = function_exists('botwriter_get_image_model_option_name') |
| 4331 |
? botwriter_get_image_model_option_name($image_provider) |
| 4332 |
: ($image_provider === 'gemini' ? 'botwriter_gemini_image_model' : "botwriter_{$image_provider}_model"); |
| 4333 |
|
| 4334 |
$image_model_raw = (string) get_option($image_model_option, $image_model_default); |
| 4335 |
|
| 4336 |
if (function_exists('botwriter_get_current_image_model_by_provider')) { |
| 4337 |
$data['image_model'] = botwriter_get_current_image_model_by_provider($image_provider); |
| 4338 |
} else { |
| 4339 |
$data['image_model'] = $image_model_raw; |
| 4340 |
} |
| 4341 |
} |
| 4342 |
|
| 4343 |
// Also send the specific field name the server expects |
| 4344 |
if ($image_provider === 'gemini') { |
| 4345 |
$data['gemini_image_model'] = $data['image_model']; |
| 4346 |
|
| 4347 |
$gemini_catalog_models = array(); |
| 4348 |
$image_model_raw_in_catalog = null; |
| 4349 |
if (function_exists('botwriter_get_provider_image_models_flat')) { |
| 4350 |
$gemini_catalog_models = array_keys(botwriter_get_provider_image_models_flat('gemini')); |
| 4351 |
if (!empty($gemini_catalog_models)) { |
| 4352 |
$image_model_raw_in_catalog = in_array(strtolower((string) $image_model_raw), $gemini_catalog_models, true); |
| 4353 |
} |
| 4354 |
} |
| 4355 |
|
| 4356 |
botwriter_log('Image model trace before phase 1 send', array( |
| 4357 |
'log_id' => $data['id'] ?? null, |
| 4358 |
'task_id' => $data['id_task'] ?? null, |
| 4359 |
'image_provider' => $image_provider, |
| 4360 |
'image_model_option' => $image_model_option, |
| 4361 |
'image_model_default' => $image_model_default, |
| 4362 |
'image_model_raw' => $image_model_raw, |
| 4363 |
'image_model_resolved' => $data['image_model'] ?? null, |
| 4364 |
'image_model_changed' => ((string) ($image_model_raw ?? '') !== (string) ($data['image_model'] ?? '')), |
| 4365 |
'image_model_raw_in_catalog' => $image_model_raw_in_catalog, |
| 4366 |
'gemini_catalog_models' => $gemini_catalog_models, |
| 4367 |
)); |
| 4368 |
} |
| 4369 |
|
| 4370 |
// Send all API keys (decrypted) - server will use the ones needed |
| 4371 |
$data['openai_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_openai_api_key')); |
| 4372 |
$data['anthropic_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_anthropic_api_key')); |
| 4373 |
$data['google_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_google_api_key')); |
| 4374 |
$data['mistral_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_mistral_api_key')); |
| 4375 |
$data['groq_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_groq_api_key')); |
| 4376 |
$data['openrouter_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_openrouter_api_key')); |
| 4377 |
$data['fal_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_fal_api_key')); |
| 4378 |
$data['replicate_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_replicate_api_key')); |
| 4379 |
$data['stability_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_stability_api_key')); |
| 4380 |
$data['cloudflare_api_key'] = botwriter_decrypt_api_key(get_option('botwriter_cloudflare_api_key')); |
| 4381 |
$data['cloudflare_account_id'] = get_option('botwriter_cloudflare_account_id'); |
| 4382 |
|
| 4383 |
// Legacy field (kept for backward compatibility) |
| 4384 |
$data["openai_model"]=get_option('botwriter_openai_model', 'gpt-4o-mini'); |
| 4385 |
|
| 4386 |
$current_time = gmdate('Y-m-d H:i:s', current_time('timestamp')); |
| 4387 |
$data["last_execution_time"]=$current_time; |
| 4388 |
|
| 4389 |
$last_execution_time = $data["last_execution_time"]; |
| 4390 |
|
| 4391 |
|
| 4392 |
$category_ids=array_map('intval', explode(',', $data['category_id'])); |
| 4393 |
$titles = botwriter_get_logs_titles($data['id_task']); |
| 4394 |
if ($titles === false) { |
| 4395 |
$data['titles'] = ''; |
| 4396 |
} else { |
| 4397 |
$data['titles'] = implode(' | ', $titles); |
| 4398 |
} |
| 4399 |
|
| 4400 |
|
| 4401 |
// add the links of the posts where it has been copied |
| 4402 |
$links = botwriter_get_logs_links($data['id_task']); |
| 4403 |
if ($links === false) { |
| 4404 |
$data['links'] = ''; |
| 4405 |
} else { |
| 4406 |
$data['links'] = implode(',', $links); |
| 4407 |
} |
| 4408 |
|
| 4409 |
// post_lenght |
| 4410 |
if ($data['post_length'] === 'custom') { |
| 4411 |
$data['post_length'] = $data['custom_post_length']; |
| 4412 |
} |
| 4413 |
|
| 4414 |
// ===================================================== |
| 4415 |
// CLIENT-SIDE RSS FETCH (pre-fetch before prompt build) |
| 4416 |
// ===================================================== |
| 4417 |
$website_type = $data['website_type'] ?? ''; |
| 4418 |
if ($website_type === 'rss') { |
| 4419 |
$rss_result = botwriter_fetch_rss_content( |
| 4420 |
$data['rss_source'] ?? '', |
| 4421 |
$data['links'] ?? '' |
| 4422 |
); |
| 4423 |
|
| 4424 |
if ( ! $rss_result['success'] ) { |
| 4425 |
botwriter_log('Client RSS fetch failed', [ |
| 4426 |
'log_id' => $data['id'] ?? null, |
| 4427 |
'task_id' => $data['id_task'] ?? null, |
| 4428 |
'error' => $rss_result['error'] ?? 'Unknown error', |
| 4429 |
]); |
| 4430 |
$data['task_status'] = 'error'; |
| 4431 |
$data['error'] = $rss_result['error'] ?? 'RSS fetch failed'; |
| 4432 |
$data['intentosfase1'] = isset($data['intentosfase1']) ? (int) $data['intentosfase1'] + 1 : 1; |
| 4433 |
botwriter_logs_register($data, $data['id']); |
| 4434 |
return false; |
| 4435 |
} |
| 4436 |
|
| 4437 |
// Populate data with the pre-fetched article so the prompt builder can use it |
| 4438 |
$data['source_title'] = $rss_result['source_title']; |
| 4439 |
$data['source_content'] = $rss_result['source_content']; |
| 4440 |
$data['link_post_original'] = $rss_result['link_original']; |
| 4441 |
$data['source_prefetched'] = '1'; |
| 4442 |
|
| 4443 |
// Reserve the source link in the log row BEFORE the phase 1 dispatch. |
| 4444 |
// This protects against a second cron tick picking the same article |
| 4445 |
// while this dispatch is still in flight (HTTP latency, retries, etc.). |
| 4446 |
if ( ! empty( $data['id'] ) ) { |
| 4447 |
botwriter_logs_register( $data, $data['id'] ); |
| 4448 |
} |
| 4449 |
|
| 4450 |
botwriter_log('Client RSS article ready', [ |
| 4451 |
'log_id' => $data['id'] ?? null, |
| 4452 |
'task_id' => $data['id_task'] ?? null, |
| 4453 |
'article_link' => $rss_result['link_original'], |
| 4454 |
]); |
| 4455 |
} |
| 4456 |
// ===================================================== |
| 4457 |
|
| 4458 |
// ===================================================== |
| 4459 |
// CLIENT-SIDE WORDPRESS FETCH (pre-fetch before prompt build) |
| 4460 |
// ===================================================== |
| 4461 |
if ($website_type === 'wordpress') { |
| 4462 |
$wp_result = botwriter_fetch_wordpress_content( |
| 4463 |
$data['domain_name'] ?? '', |
| 4464 |
$data['website_category_id'] ?? '', |
| 4465 |
$data['links'] ?? '' |
| 4466 |
); |
| 4467 |
|
| 4468 |
if ( ! $wp_result['success'] ) { |
| 4469 |
botwriter_log('Client WordPress fetch failed', [ |
| 4470 |
'log_id' => $data['id'] ?? null, |
| 4471 |
'task_id' => $data['id_task'] ?? null, |
| 4472 |
'error' => $wp_result['error'] ?? 'Unknown error', |
| 4473 |
]); |
| 4474 |
$data['task_status'] = 'error'; |
| 4475 |
$data['error'] = $wp_result['error'] ?? 'WordPress fetch failed'; |
| 4476 |
$data['intentosfase1'] = isset($data['intentosfase1']) ? (int) $data['intentosfase1'] + 1 : 1; |
| 4477 |
botwriter_logs_register($data, $data['id']); |
| 4478 |
return false; |
| 4479 |
} |
| 4480 |
|
| 4481 |
$data['source_title'] = $wp_result['source_title']; |
| 4482 |
$data['source_content'] = $wp_result['source_content']; |
| 4483 |
$data['link_post_original'] = $wp_result['link_original']; |
| 4484 |
$data['source_prefetched'] = '1'; |
| 4485 |
|
| 4486 |
// Reserve the source link in the log row BEFORE the phase 1 dispatch |
| 4487 |
// (see RSS branch above for rationale). |
| 4488 |
if ( ! empty( $data['id'] ) ) { |
| 4489 |
botwriter_logs_register( $data, $data['id'] ); |
| 4490 |
} |
| 4491 |
|
| 4492 |
botwriter_log('Client WordPress article ready', [ |
| 4493 |
'log_id' => $data['id'] ?? null, |
| 4494 |
'task_id' => $data['id_task'] ?? null, |
| 4495 |
'article_link' => $wp_result['link_original'], |
| 4496 |
]); |
| 4497 |
} |
| 4498 |
// ===================================================== |
| 4499 |
|
| 4500 |
// Build prompt from template (except for super1 which is handled by server) |
| 4501 |
if ($website_type !== 'super1') { |
| 4502 |
$data['client_prompt'] = botwriter_build_client_prompt($data); |
| 4503 |
} |
| 4504 |
|
| 4505 |
botwriter_log('SEND1: after prompt build', [ |
| 4506 |
'log_id' => $data['id'] ?? null, |
| 4507 |
'website_type' => $website_type, |
| 4508 |
'has_client_prompt' => !empty($data['client_prompt']), |
| 4509 |
'client_prompt_len' => strlen($data['client_prompt'] ?? ''), |
| 4510 |
'source_prefetched' => $data['source_prefetched'] ?? '0', |
| 4511 |
'has_source_title' => !empty($data['source_title']), |
| 4512 |
'has_source_content' => !empty($data['source_content']), |
| 4513 |
'link_post_original' => $data['link_post_original'] ?? '', |
| 4514 |
]); |
| 4515 |
|
| 4516 |
botwriter_log('Dispatching phase 1 request', [ |
| 4517 |
'log_id' => $data['id'] ?? null, |
| 4518 |
'task_id' => $data['id_task'] ?? null, |
| 4519 |
'website_type' => $data['website_type'] ?? null, |
| 4520 |
'status' => $data['task_status'] ?? null, |
| 4521 |
'attempt' => $data['intentosfase1'] ?? null, |
| 4522 |
'text_provider' => $data['text_provider'] ?? null, |
| 4523 |
'text_model' => $data['text_model'] ?? null, |
| 4524 |
'image_provider' => $data['image_provider'] ?? null, |
| 4525 |
'image_model' => $data['image_model'] ?? null, |
| 4526 |
'gemini_image_model' => $data['gemini_image_model'] ?? null, |
| 4527 |
]); |
| 4528 |
|
| 4529 |
$data["error"]= ""; |
| 4530 |
|
| 4531 |
$ssl_verify = get_option('botwriter_sslverify'); |
| 4532 |
|
| 4533 |
|
| 4534 |
if ($ssl_verify === 'no') { |
| 4535 |
$ssl_verify = false; |
| 4536 |
} else { |
| 4537 |
$ssl_verify = true; |
| 4538 |
} |
| 4539 |
|
| 4540 |
|
| 4541 |
|
| 4542 |
$response = botwriter_post_compat_with_token_recovery($remote_url, $data, $ssl_verify, 45); |
| 4543 |
|
| 4544 |
$data["intentosfase1"]++; |
| 4545 |
botwriter_logs_register($data, $data["id"]); |
| 4546 |
|
| 4547 |
$last_execution_time=$data["last_execution_time"]; |
| 4548 |
|
| 4549 |
|
| 4550 |
|
| 4551 |
if (is_wp_error($response)) { |
| 4552 |
$error_message = $response->get_error_message(); |
| 4553 |
botwriter_log('Phase 1 request error', [ |
| 4554 |
'log_id' => $data['id'] ?? null, |
| 4555 |
'task_id' => $data['id_task'] ?? null, |
| 4556 |
'website_type' => $data['website_type'] ?? null, |
| 4557 |
'error' => $error_message, |
| 4558 |
]); |
| 4559 |
$data["task_status"]="error"; |
| 4560 |
botwriter_logs_register($data, $data["id"]); |
| 4561 |
return false; |
| 4562 |
|
| 4563 |
} else { |
| 4564 |
|
| 4565 |
if ($response['response']['code'] === 200) { |
| 4566 |
|
| 4567 |
$body = wp_remote_retrieve_body($response); |
| 4568 |
$result = json_decode($body, true); |
| 4569 |
|
| 4570 |
//error_log("Data received: " . print_r($body, true)); |
| 4571 |
|
| 4572 |
if (!empty($result['site_token'])) { |
| 4573 |
update_option('botwriter_site_token', sanitize_text_field((string) $result['site_token'])); |
| 4574 |
} |
| 4575 |
|
| 4576 |
// STOPFORMANY: server reports too many consecutive errors |
| 4577 |
if (isset($result['error']) && strpos($result['error'], 'STOPFORMANY') === 0) { |
| 4578 |
update_option('botwriter_stopformany', true); |
| 4579 |
$data['task_status'] = 'error'; |
| 4580 |
$data['error'] = $result['error']; |
| 4581 |
$data['intentosfase1'] = 8; // stop retries |
| 4582 |
botwriter_logs_register($data, $data['id']); |
| 4583 |
botwriter_log('STOPFORMANY activated from phase 1', [ |
| 4584 |
'log_id' => $data['id'] ?? null, |
| 4585 |
'task_id' => $data['id_task'] ?? null, |
| 4586 |
'error' => $result['error'], |
| 4587 |
]); |
| 4588 |
return false; |
| 4589 |
} |
| 4590 |
|
| 4591 |
if (isset($result['id_task_server']) && $result['id_task_server'] !== 0) { // ok |
| 4592 |
$data["id_task_server"]=$result['id_task_server']; |
| 4593 |
$data["task_status"]='inqueue'; |
| 4594 |
|
| 4595 |
// Capture site_token from server response (auto-provisioning) |
| 4596 |
if (!empty($result['site_token'])) { |
| 4597 |
update_option('botwriter_site_token', sanitize_text_field($result['site_token'])); |
| 4598 |
} |
| 4599 |
|
| 4600 |
botwriter_logs_register($data, $data["id"]); |
| 4601 |
botwriter_log('Phase 1 request accepted', [ |
| 4602 |
'log_id' => $data['id'] ?? null, |
| 4603 |
'task_id' => $data['id_task'] ?? null, |
| 4604 |
'id_task_server' => $result['id_task_server'], |
| 4605 |
]); |
| 4606 |
return $result['id_task_server']; |
| 4607 |
} else { // error — id_task_server is 0 or missing |
| 4608 |
$data["task_status"]="error"; |
| 4609 |
// Use full error_message (may include reset link) when available |
| 4610 |
$data["error"] = !empty($result['error_message']) ? $result['error_message'] : ($result['error'] ?? ''); |
| 4611 |
|
| 4612 |
// Generic terminal flag — server says stop retrying |
| 4613 |
if (!empty($result['terminal'])) { |
| 4614 |
$data['intentosfase1'] = 8; |
| 4615 |
} |
| 4616 |
// Show server-provided admin notice |
| 4617 |
if (!empty($result['error_message']) && (int)($result['error_level'] ?? 0) === 1) { |
| 4618 |
botwriter_announcements_add( |
| 4619 |
__('Service notice', 'botwriter'), |
| 4620 |
wp_kses_post($result['error_message']) |
| 4621 |
); |
| 4622 |
} |
| 4623 |
|
| 4624 |
// Handle known server errors (same logic as Phase 2) |
| 4625 |
if ($data["error"] == "Maximum monthly posts limit reached") { |
| 4626 |
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>"); |
| 4627 |
$data["intentosfase1"] = 8; |
| 4628 |
} |
| 4629 |
if ($data["error"] == "Payment date exceeded") { |
| 4630 |
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>"); |
| 4631 |
$data["intentosfase1"] = 8; |
| 4632 |
} |
| 4633 |
if ($data["error"] == "API Key error") { |
| 4634 |
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>"); |
| 4635 |
$data["intentosfase1"] = 8; |
| 4636 |
} |
| 4637 |
|
| 4638 |
botwriter_logs_register($data, $data["id"]); |
| 4639 |
botwriter_log('Phase 1 request rejected', [ |
| 4640 |
'log_id' => $data['id'] ?? null, |
| 4641 |
'task_id' => $data['id_task'] ?? null, |
| 4642 |
'error' => $data['error'], |
| 4643 |
'response_length' => isset($body) ? strlen($body) : null, |
| 4644 |
]); |
| 4645 |
return false; |
| 4646 |
} |
| 4647 |
} else { // error |
| 4648 |
$data["task_status"]="error"; |
| 4649 |
botwriter_logs_register($data, $data["id"]); |
| 4650 |
botwriter_log('Phase 1 non-200 response', [ |
| 4651 |
'log_id' => $data['id'] ?? null, |
| 4652 |
'task_id' => $data['id_task'] ?? null, |
| 4653 |
'status_code' => $response['response']['code'], |
| 4654 |
]); |
| 4655 |
return false; |
| 4656 |
} |
| 4657 |
} |
| 4658 |
|
| 4659 |
|
| 4660 |
} |
| 4661 |
|
| 4662 |
// Function to send data to the server pass2 |
| 4663 |
function botwriter_send2_data_to_server($data) { |
| 4664 |
Global $wpdb; |
| 4665 |
|
| 4666 |
$data['api_key'] = get_option('botwriter_api_key'); |
| 4667 |
$data["user_domainname"] = esc_url(get_site_url()); |
| 4668 |
$data['site_token'] = get_option('botwriter_site_token', ''); |
| 4669 |
|
| 4670 |
$remote_url = BOTWRITER_API_URL . 'redis_api_finish.php'; |
| 4671 |
|
| 4672 |
$ssl_verify = get_option('botwriter_sslverify'); |
| 4673 |
if ($ssl_verify === 'no') { |
| 4674 |
$ssl_verify = false; |
| 4675 |
} else { |
| 4676 |
$ssl_verify = true; |
| 4677 |
} |
| 4678 |
|
| 4679 |
botwriter_log('Dispatching phase 2 request', [ |
| 4680 |
'log_id' => $data['id'] ?? null, |
| 4681 |
'task_id' => $data['id_task'] ?? null, |
| 4682 |
'website_type' => $data['website_type'] ?? null, |
| 4683 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 4684 |
]); |
| 4685 |
|
| 4686 |
$response = botwriter_post_compat_with_token_recovery($remote_url, $data, $ssl_verify, 45); |
| 4687 |
|
| 4688 |
|
| 4689 |
if (is_wp_error($response)) { |
| 4690 |
$error_message = $response->get_error_message(); |
| 4691 |
botwriter_log('Phase 2 request error', [ |
| 4692 |
'log_id' => $data['id'] ?? null, |
| 4693 |
'task_id' => $data['id_task'] ?? null, |
| 4694 |
'website_type' => $data['website_type'] ?? null, |
| 4695 |
'error' => $error_message, |
| 4696 |
]); |
| 4697 |
$data["error"]= "Error sending data " . $error_message; |
| 4698 |
return false; |
| 4699 |
} else { |
| 4700 |
|
| 4701 |
if ($response['response']['code'] === 200) { |
| 4702 |
|
| 4703 |
$body = wp_remote_retrieve_body($response); |
| 4704 |
$result = json_decode($body, true); |
| 4705 |
|
| 4706 |
if (!empty($result['site_token'])) { |
| 4707 |
update_option('botwriter_site_token', sanitize_text_field((string) $result['site_token'])); |
| 4708 |
} |
| 4709 |
|
| 4710 |
//echo 'Data recive: <pre>' . print_r($result, true) . '</pre>'; |
| 4711 |
|
| 4712 |
// results errors |
| 4713 |
if (isset($result["task_status"]) && $result["task_status"] == "error") { |
| 4714 |
$data["task_status"]="error"; |
| 4715 |
// Use full error_message (may include reset link) when available |
| 4716 |
$data["error"] = !empty($result['error_message']) ? $result['error_message'] : ($result['error'] ?? ''); |
| 4717 |
botwriter_log('Phase 2 reported error', [ |
| 4718 |
'log_id' => $data['id'] ?? null, |
| 4719 |
'task_id' => $data['id_task'] ?? null, |
| 4720 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 4721 |
'error' => $data['error'], |
| 4722 |
]); |
| 4723 |
|
| 4724 |
// Generic terminal flag — server says stop retrying |
| 4725 |
if (!empty($result['terminal'])) { |
| 4726 |
$data['intentosfase1'] = 8; |
| 4727 |
} |
| 4728 |
// Show server-provided admin notice |
| 4729 |
if (!empty($result['error_message']) && (int)($result['error_level'] ?? 0) === 1) { |
| 4730 |
botwriter_announcements_add( |
| 4731 |
__('Service notice', 'botwriter'), |
| 4732 |
wp_kses_post($result['error_message']) |
| 4733 |
); |
| 4734 |
} |
| 4735 |
|
| 4736 |
if ($data["error"]=="Maximum monthly posts limit reached") { |
| 4737 |
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>"); |
| 4738 |
$data["intentosfase1"]=8; |
| 4739 |
} |
| 4740 |
if ($data["error"]=="Payment date exceeded") { |
| 4741 |
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>"); |
| 4742 |
$data["intentosfase1"]=8; |
| 4743 |
} |
| 4744 |
if ($data["error"]=="API Key error") { |
| 4745 |
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>"); |
| 4746 |
$data["intentosfase1"]=8; |
| 4747 |
} |
| 4748 |
// STOPFORMANY: server reports too many consecutive errors |
| 4749 |
if (isset($result['error']) && strpos($result['error'], 'STOPFORMANY') === 0) { |
| 4750 |
update_option('botwriter_stopformany', true); |
| 4751 |
$data['intentosfase1'] = 8; |
| 4752 |
botwriter_log('STOPFORMANY activated from phase 2', [ |
| 4753 |
'log_id' => $data['id'] ?? null, |
| 4754 |
'task_id' => $data['id_task'] ?? null, |
| 4755 |
'error' => $result['error'], |
| 4756 |
]); |
| 4757 |
} |
| 4758 |
|
| 4759 |
botwriter_logs_register($data, $data["id"]); |
| 4760 |
return false; |
| 4761 |
} |
| 4762 |
|
| 4763 |
// result completed |
| 4764 |
botwriter_log('Phase 2 response payload', [ |
| 4765 |
'log_id' => $data['id'] ?? null, |
| 4766 |
'task_id' => $data['id_task'] ?? null, |
| 4767 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 4768 |
'task_status' => $result['task_status'] ?? null, |
| 4769 |
]); |
| 4770 |
if (isset($result["task_status"]) && $result["task_status"] == "completed") { |
| 4771 |
// Preserve image_attribution from server response (not stored in DB) |
| 4772 |
$image_attribution = isset($result['image_attribution']) ? $result['image_attribution'] : null; |
| 4773 |
|
| 4774 |
botwriter_logs_register($result, $data["id"]); |
| 4775 |
botwriter_log('Phase 2 completed event', [ |
| 4776 |
'log_id' => $data['id'] ?? null, |
| 4777 |
'task_id' => $data['id_task'] ?? null, |
| 4778 |
'id_task_server' => $data['id_task_server'] ?? null, |
| 4779 |
]); |
| 4780 |
|
| 4781 |
$result=botwriter_logs_get($data["id"]); // merge the result with the log |
| 4782 |
|
| 4783 |
// Re-attach image_attribution (not persisted in logs table) |
| 4784 |
if ($image_attribution) { |
| 4785 |
$result['image_attribution'] = $image_attribution; |
| 4786 |
} |
| 4787 |
|
| 4788 |
// ── Guard: prevent duplicate post creation ── |
| 4789 |
// If this log already has a published post, skip generation. |
| 4790 |
if (!empty($result['id_post_published']) && intval($result['id_post_published']) > 0) { |
| 4791 |
botwriter_log('Phase 2 skipped — post already published', [ |
| 4792 |
'log_id' => $data['id'], |
| 4793 |
'post_id' => $result['id_post_published'], |
| 4794 |
]); |
| 4795 |
return $result; |
| 4796 |
} |
| 4797 |
|
| 4798 |
if ($result["website_type"] == "super1") { |
| 4799 |
botwriter_super1_log_to_bd($result,$data["id"]); |
| 4800 |
|
| 4801 |
} else { |
| 4802 |
$post_id=botwriter_generate_post($result); |
| 4803 |
$result["id_post_published"]=$post_id; |
| 4804 |
} |
| 4805 |
|
| 4806 |
botwriter_logs_register($result, $data["id"]); |
| 4807 |
if ($result["website_type"] == "super2") { |
| 4808 |
//update tabla super poner el task_Status en completed |
| 4809 |
$wpdb->update($wpdb->prefix . 'botwriter_super', ['task_status' => 'completed'], ['id_log' => $data["id"]]); |
| 4810 |
} |
| 4811 |
|
| 4812 |
|
| 4813 |
return $result; |
| 4814 |
} |
| 4815 |
|
| 4816 |
// other results, inqueue, pending, etc |
| 4817 |
$now=current_time('timestamp'); |
| 4818 |
$last_execution_time = strtotime($data["last_execution_time"]); |
| 4819 |
$diff = $now - $last_execution_time; |
| 4820 |
if ($diff > 60 * 5) { // 5 minutes |
| 4821 |
$data["task_status"]="error"; |
| 4822 |
$data["error"]="Error in server"; |
| 4823 |
botwriter_logs_register($data, $data["id"]); |
| 4824 |
botwriter_log('Phase 2 timeout detected', [ |
| 4825 |
'log_id' => $data['id'] ?? null, |
| 4826 |
'task_id' => $data['id_task'] ?? null, |
| 4827 |
]); |
| 4828 |
} |
| 4829 |
return false; |
| 4830 |
|
| 4831 |
|
| 4832 |
|
| 4833 |
|
| 4834 |
|
| 4835 |
|
| 4836 |
} else { // error |
| 4837 |
// update log |
| 4838 |
$data["task_status"]="error"; |
| 4839 |
botwriter_logs_register($data, $data["id"]); |
| 4840 |
botwriter_log('Phase 2 non-200 response', [ |
| 4841 |
'log_id' => $data['id'] ?? null, |
| 4842 |
'task_id' => $data['id_task'] ?? null, |
| 4843 |
'status_code' => $response['response']['code'], |
| 4844 |
]); |
| 4845 |
return false; |
| 4846 |
} |
| 4847 |
} |
| 4848 |
|
| 4849 |
|
| 4850 |
|
| 4851 |
} |
| 4852 |
|
| 4853 |
|
| 4854 |
|
| 4855 |
|
| 4856 |
|
| 4857 |
function botwriter_cambiar_status_ajax() { |
| 4858 |
// Verify user has permission |
| 4859 |
if (!current_user_can('manage_options')) { |
| 4860 |
wp_send_json_error(['message' => 'Permission denied']); |
| 4861 |
} |
| 4862 |
|
| 4863 |
check_ajax_referer('botwriter_cambiar_status_nonce', 'nonce'); |
| 4864 |
|
| 4865 |
$id = isset($_POST['id']) ? intval($_POST['id']) : 0; |
| 4866 |
$nuevo_status = isset($_POST['status']) ? intval($_POST['status']) : 0; |
| 4867 |
|
| 4868 |
|
| 4869 |
|
| 4870 |
if ($id > 0) { |
| 4871 |
global $wpdb; |
| 4872 |
$table_name = $wpdb->prefix . 'botwriter_tasks'; |
| 4873 |
|
| 4874 |
$result = $wpdb->update( |
| 4875 |
$table_name, |
| 4876 |
['status' => $nuevo_status], |
| 4877 |
['id' => $id], |
| 4878 |
['%d'], |
| 4879 |
['%d'] |
| 4880 |
); |
| 4881 |
|
| 4882 |
if ($result !== false) { |
| 4883 |
wp_send_json_success(['message' => 'Estado actualizado correctamente']); |
| 4884 |
} else { |
| 4885 |
wp_send_json_error(['message' => 'Error al actualizar el estado']); |
| 4886 |
} |
| 4887 |
} else { |
| 4888 |
wp_send_json_error(['message' => 'ID inválido']); |
| 4889 |
} |
| 4890 |
|
| 4891 |
wp_die(); |
| 4892 |
} |
| 4893 |
add_action('wp_ajax_botwriter_cambiar_status', 'botwriter_cambiar_status_ajax'); |
| 4894 |
|
| 4895 |
/** |
| 4896 |
* AJAX handler to check/preview an RSS feed from the admin UI. |
| 4897 |
* Reads the feed client-side using botwriter_fetch_rss_content(). |
| 4898 |
*/ |
| 4899 |
function botwriter_check_rss_ajax() { |
| 4900 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 4901 |
wp_send_json_error( [ 'error' => 'Permission denied' ] ); |
| 4902 |
} |
| 4903 |
|
| 4904 |
check_ajax_referer( 'botwriter_check_rss_nonce', 'nonce' ); |
| 4905 |
|
| 4906 |
$url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : ''; |
| 4907 |
if ( empty( $url ) ) { |
| 4908 |
wp_send_json_error( [ 'error' => 'RSS URL is required.' ] ); |
| 4909 |
} |
| 4910 |
|
| 4911 |
$result = botwriter_fetch_rss_content( $url ); |
| 4912 |
if ( $result['success'] ) { |
| 4913 |
wp_send_json_success( [ |
| 4914 |
'title' => $result['source_title'], |
| 4915 |
'description' => $result['source_content'], |
| 4916 |
'link' => $result['link_original'], |
| 4917 |
] ); |
| 4918 |
} else { |
| 4919 |
wp_send_json_error( [ 'error' => $result['error'] ] ); |
| 4920 |
} |
| 4921 |
} |
| 4922 |
add_action( 'wp_ajax_botwriter_check_rss', 'botwriter_check_rss_ajax' ); |
| 4923 |
|
| 4924 |
/** |
| 4925 |
* AJAX handler to fetch categories from a remote WordPress site. |
| 4926 |
* Reads categories client-side using botwriter_fetch_wordpress_categories(). |
| 4927 |
*/ |
| 4928 |
function botwriter_get_wordpress_categories_ajax() { |
| 4929 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 4930 |
wp_send_json_error( [ 'error' => 'Permission denied' ] ); |
| 4931 |
} |
| 4932 |
|
| 4933 |
check_ajax_referer( 'botwriter_wp_categories_nonce', 'nonce' ); |
| 4934 |
|
| 4935 |
$domain = isset( $_POST['website_domainname'] ) ? esc_url_raw( wp_unslash( $_POST['website_domainname'] ) ) : ''; |
| 4936 |
if ( empty( $domain ) ) { |
| 4937 |
wp_send_json_error( [ 'error' => 'WordPress domain is required.' ] ); |
| 4938 |
} |
| 4939 |
|
| 4940 |
$result = botwriter_fetch_wordpress_categories( $domain ); |
| 4941 |
if ( $result['success'] ) { |
| 4942 |
wp_send_json_success( $result['categories'] ); |
| 4943 |
} else { |
| 4944 |
wp_send_json_error( [ 'error' => $result['error'] ] ); |
| 4945 |
} |
| 4946 |
} |
| 4947 |
add_action( 'wp_ajax_botwriter_get_wp_categories', 'botwriter_get_wordpress_categories_ajax' ); |
| 4948 |
|
| 4949 |
// AJAX handler for deleting a log entry |
| 4950 |
add_action('wp_ajax_botwriter_delete_log', 'botwriter_delete_log_ajax'); |
| 4951 |
function botwriter_delete_log_ajax() { |
| 4952 |
// Verify user has permission |
| 4953 |
if (!current_user_can('manage_options')) { |
| 4954 |
wp_send_json_error(['message' => 'Permission denied']); |
| 4955 |
} |
| 4956 |
|
| 4957 |
// Verify nonce |
| 4958 |
check_ajax_referer('botwriter_logs_delete_nonce', 'nonce'); |
| 4959 |
|
| 4960 |
$log_id = isset($_POST['log_id']) ? intval($_POST['log_id']) : 0; |
| 4961 |
|
| 4962 |
if ($log_id > 0) { |
| 4963 |
global $wpdb; |
| 4964 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 4965 |
|
| 4966 |
$result = $wpdb->delete($table_name, array('id' => $log_id), array('%d')); |
| 4967 |
|
| 4968 |
if ($result !== false) { |
| 4969 |
wp_send_json_success(['message' => __('Log deleted successfully', 'botwriter')]); |
| 4970 |
} else { |
| 4971 |
wp_send_json_error(['message' => __('Error deleting log', 'botwriter')]); |
| 4972 |
} |
| 4973 |
} else { |
| 4974 |
wp_send_json_error(['message' => __('Invalid log ID', 'botwriter')]); |
| 4975 |
} |
| 4976 |
|
| 4977 |
wp_die(); |
| 4978 |
} |
| 4979 |
|
| 4980 |
// AJAX handler for bulk deleting log entries |
| 4981 |
add_action('wp_ajax_botwriter_bulk_delete_logs', 'botwriter_bulk_delete_logs_ajax'); |
| 4982 |
function botwriter_bulk_delete_logs_ajax() { |
| 4983 |
if (!current_user_can('manage_options')) { |
| 4984 |
wp_send_json_error(['message' => 'Permission denied']); |
| 4985 |
} |
| 4986 |
|
| 4987 |
check_ajax_referer('botwriter_logs_delete_nonce', 'nonce'); |
| 4988 |
|
| 4989 |
$log_ids = isset($_POST['log_ids']) ? array_map('intval', $_POST['log_ids']) : array(); |
| 4990 |
$log_ids = array_filter($log_ids, function($id) { return $id > 0; }); |
| 4991 |
|
| 4992 |
if (empty($log_ids)) { |
| 4993 |
wp_send_json_error(['message' => __('No logs selected', 'botwriter')]); |
| 4994 |
} |
| 4995 |
|
| 4996 |
global $wpdb; |
| 4997 |
$table_name = $wpdb->prefix . 'botwriter_logs'; |
| 4998 |
$placeholders = implode(',', array_fill(0, count($log_ids), '%d')); |
| 4999 |
$table_name = esc_sql($table_name); |
| 5000 |
$query = $wpdb->prepare("DELETE FROM {$table_name} WHERE id IN ({$placeholders})", $log_ids); |
| 5001 |
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is generated internally and placeholders are prepared from sanitized IDs. |
| 5002 |
$deleted = $wpdb->query($query); |
| 5003 |
|
| 5004 |
if ($deleted !== false) { |
| 5005 |
wp_send_json_success(['message' => sprintf( |
| 5006 |
/* translators: %d: number of deleted log entries. */ |
| 5007 |
__('%d log(s) deleted successfully', 'botwriter'), |
| 5008 |
$deleted |
| 5009 |
)]); |
| 5010 |
} else { |
| 5011 |
wp_send_json_error(['message' => __('Error deleting logs', 'botwriter')]); |
| 5012 |
} |
| 5013 |
|
| 5014 |
wp_die(); |
| 5015 |
} |
| 5016 |
|
| 5017 |
// AJAX handler for getting taxonomies and terms for a post type |
| 5018 |
add_action('wp_ajax_botwriter_get_taxonomies', 'botwriter_get_taxonomies_ajax'); |
| 5019 |
function botwriter_get_taxonomies_ajax() { |
| 5020 |
// Verify user has permission |
| 5021 |
if (!current_user_can('manage_options')) { |
| 5022 |
wp_send_json_error(['message' => 'Permission denied']); |
| 5023 |
} |
| 5024 |
|
| 5025 |
// Verify nonce |
| 5026 |
check_ajax_referer('botwriter_taxonomies_nonce', 'nonce'); |
| 5027 |
|
| 5028 |
$post_type = isset($_POST['post_type']) ? sanitize_text_field(wp_unslash($_POST['post_type'])) : 'post'; |
| 5029 |
|
| 5030 |
// Get taxonomies for this post type |
| 5031 |
$taxonomies = get_object_taxonomies($post_type, 'objects'); |
| 5032 |
|
| 5033 |
$result = array(); |
| 5034 |
// Skip tag taxonomies since AI generates tags automatically |
| 5035 |
$skip_taxonomies = array('post_tag', 'product_tag'); |
| 5036 |
|
| 5037 |
foreach ($taxonomies as $taxonomy) { |
| 5038 |
// Skip non-public taxonomies |
| 5039 |
if (!$taxonomy->public) { |
| 5040 |
continue; |
| 5041 |
} |
| 5042 |
|
| 5043 |
// Skip tag taxonomies (AI generates tags) |
| 5044 |
if (in_array($taxonomy->name, $skip_taxonomies, true)) { |
| 5045 |
continue; |
| 5046 |
} |
| 5047 |
|
| 5048 |
// Get terms for this taxonomy |
| 5049 |
$terms = get_terms(array( |
| 5050 |
'taxonomy' => $taxonomy->name, |
| 5051 |
'hide_empty' => false, |
| 5052 |
'orderby' => 'name', |
| 5053 |
'order' => 'ASC', |
| 5054 |
)); |
| 5055 |
|
| 5056 |
$terms_data = array(); |
| 5057 |
if (!is_wp_error($terms)) { |
| 5058 |
foreach ($terms as $term) { |
| 5059 |
$terms_data[] = array( |
| 5060 |
'id' => $term->term_id, |
| 5061 |
'name' => $term->name, |
| 5062 |
'slug' => $term->slug, |
| 5063 |
'parent' => $term->parent, |
| 5064 |
); |
| 5065 |
} |
| 5066 |
} |
| 5067 |
|
| 5068 |
$result[] = array( |
| 5069 |
'name' => $taxonomy->name, |
| 5070 |
'label' => $taxonomy->label, |
| 5071 |
'hierarchical' => $taxonomy->hierarchical, |
| 5072 |
'terms' => $terms_data, |
| 5073 |
); |
| 5074 |
} |
| 5075 |
|
| 5076 |
wp_send_json_success($result); |
| 5077 |
} |
| 5078 |
|
| 5079 |
|
| 5080 |
function botwriter_attach_image_to_post($post_id, $image_url, $post_title, $translated_image_slug = '', $image_attribution = null) { |
| 5081 |
if (!$image_url || !$post_id || empty(trim($image_url))) { |
| 5082 |
botwriter_log('Image attachment skipped', [ |
| 5083 |
'post_id' => $post_id, |
| 5084 |
'image_url' => $image_url ?: 'empty', |
| 5085 |
'reason' => 'Invalid post ID or empty image URL' |
| 5086 |
]); |
| 5087 |
return 'Invalid post ID or image URL'; |
| 5088 |
} |
| 5089 |
|
| 5090 |
if ($image_url && $post_id) { |
| 5091 |
//$image_data = file_get_contents($image_url); |
| 5092 |
$image_data = wp_remote_retrieve_body(wp_remote_get($image_url)); |
| 5093 |
$upload_dir = wp_upload_dir(); |
| 5094 |
// Use translated image slug if available, otherwise fall back to title |
| 5095 |
if (!empty($translated_image_slug)) { |
| 5096 |
$base_name = sanitize_file_name($translated_image_slug); |
| 5097 |
} else { |
| 5098 |
$base_name = sanitize_file_name(remove_accents($post_title)); |
| 5099 |
} |
| 5100 |
if (empty($base_name)) { |
| 5101 |
$base_name = 'botwriter-post-' . $post_id; |
| 5102 |
} |
| 5103 |
/** |
| 5104 |
* Filter the filename used for BotWriter generated images. |
| 5105 |
* |
| 5106 |
* @param string $base_name Base filename without extension. |
| 5107 |
* @param int $post_id Post ID. |
| 5108 |
* @param string $post_title Post title. |
| 5109 |
*/ |
| 5110 |
$base_name = apply_filters('botwriter_image_filename', $base_name, $post_id, $post_title); |
| 5111 |
$filename = $base_name . '.jpg'; |
| 5112 |
$filename = wp_unique_filename($upload_dir['path'], $filename); |
| 5113 |
|
| 5114 |
if (wp_mkdir_p($upload_dir['path'])) { |
| 5115 |
$file = $upload_dir['path'] . '/' . $filename; |
| 5116 |
} else { |
| 5117 |
$file = $upload_dir['basedir'] . '/' . $filename; |
| 5118 |
} |
| 5119 |
|
| 5120 |
global $wp_filesystem; |
| 5121 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 5122 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 5123 |
} |
| 5124 |
WP_Filesystem(); |
| 5125 |
if ( $wp_filesystem->put_contents( $file, $image_data, FS_CHMOD_FILE ) ) { |
| 5126 |
//error_log('Imagen guardada exitosamente en: ' . $file); |
| 5127 |
} else { |
| 5128 |
//error_log('Error al guardar la imagen en: ' . $file); |
| 5129 |
} |
| 5130 |
|
| 5131 |
$wp_filetype = wp_check_filetype($filename, null); |
| 5132 |
$attachment = [ |
| 5133 |
'post_mime_type' => $wp_filetype['type'], |
| 5134 |
'post_title' => sanitize_file_name($filename), |
| 5135 |
'post_content' => '', |
| 5136 |
'post_status' => 'inherit', |
| 5137 |
]; |
| 5138 |
|
| 5139 |
$attach_id = wp_insert_attachment($attachment, $file, $post_id); |
| 5140 |
require_once(ABSPATH . 'wp-admin/includes/image.php'); |
| 5141 |
|
| 5142 |
// Apply post-processing if enabled |
| 5143 |
$postprocess_enabled = get_option('botwriter_image_postprocess_enabled', '0'); |
| 5144 |
if ($postprocess_enabled === '1') { |
| 5145 |
$processed_file = botwriter_process_image($file); |
| 5146 |
if ($processed_file && $processed_file !== $file) { |
| 5147 |
// Update file reference if format changed |
| 5148 |
$file = $processed_file; |
| 5149 |
// Update attachment with new file info |
| 5150 |
$wp_filetype = wp_check_filetype(basename($file), null); |
| 5151 |
wp_update_post([ |
| 5152 |
'ID' => $attach_id, |
| 5153 |
'post_mime_type' => $wp_filetype['type'], |
| 5154 |
]); |
| 5155 |
update_attached_file($attach_id, $file); |
| 5156 |
} |
| 5157 |
} |
| 5158 |
|
| 5159 |
$attach_data = wp_generate_attachment_metadata($attach_id, $file); |
| 5160 |
wp_update_attachment_metadata($attach_id, $attach_data); |
| 5161 |
set_post_thumbnail($post_id, $attach_id); |
| 5162 |
|
| 5163 |
// Deterministic featured-image ALT: fill empty ALT from post title. |
| 5164 |
if (get_option('botwriter_seo_featured_image_alt_enabled', '1') === '1') { |
| 5165 |
$current_alt = trim((string) get_post_meta($attach_id, '_wp_attachment_image_alt', true)); |
| 5166 |
if ($current_alt === '') { |
| 5167 |
$fallback_alt = sanitize_text_field($post_title); |
| 5168 |
if ($fallback_alt === '') { |
| 5169 |
$fallback_alt = sanitize_text_field((string) get_the_title($post_id)); |
| 5170 |
} |
| 5171 |
if ($fallback_alt !== '') { |
| 5172 |
update_post_meta($attach_id, '_wp_attachment_image_alt', $fallback_alt); |
| 5173 |
} |
| 5174 |
} |
| 5175 |
} |
| 5176 |
|
| 5177 |
// Apply stock photo attribution as image caption. |
| 5178 |
if (!empty($image_attribution) && is_array($image_attribution)) { |
| 5179 |
$attribution_mode = get_option('botwriter_stockphoto_attribution', 'caption'); |
| 5180 |
if ($attribution_mode !== 'disabled') { |
| 5181 |
$author = sanitize_text_field($image_attribution['author'] ?? ''); |
| 5182 |
$source = sanitize_text_field($image_attribution['source'] ?? ''); |
| 5183 |
|
| 5184 |
// Build caption: "Photo by Author on Source" |
| 5185 |
$caption = ''; |
| 5186 |
if ($author && $source) { |
| 5187 |
$caption = sprintf( |
| 5188 |
/* translators: 1: photographer name, 2: image source name */ |
| 5189 |
esc_html__('Photo by %1$s on %2$s', 'botwriter'), |
| 5190 |
$author, |
| 5191 |
$source |
| 5192 |
); |
| 5193 |
} elseif ($source) { |
| 5194 |
$caption = sprintf( |
| 5195 |
/* translators: %s: image source name. */ |
| 5196 |
esc_html__('Photo from %s', 'botwriter'), |
| 5197 |
$source |
| 5198 |
); |
| 5199 |
} |
| 5200 |
|
| 5201 |
if ($caption && $attribution_mode === 'caption') { |
| 5202 |
wp_update_post(array( |
| 5203 |
'ID' => $attach_id, |
| 5204 |
'post_excerpt' => $caption, |
| 5205 |
)); |
| 5206 |
} |
| 5207 |
|
| 5208 |
// Store full attribution data as post meta for later use |
| 5209 |
update_post_meta($attach_id, '_botwriter_image_attribution', $image_attribution); |
| 5210 |
} |
| 5211 |
} |
| 5212 |
|
| 5213 |
|
| 5214 |
} |
| 5215 |
} |
| 5216 |
|
| 5217 |
/** |
| 5218 |
* Process image for optimization (resize, compress, convert format) |
| 5219 |
* Uses WordPress wp_get_image_editor for maximum hosting compatibility. |
| 5220 |
* |
| 5221 |
* @param string $file_path Full path to the image file. |
| 5222 |
* @return string|false New file path if processed, original path if no changes, false on error. |
| 5223 |
*/ |
| 5224 |
function botwriter_process_image($file_path) { |
| 5225 |
if (!file_exists($file_path)) { |
| 5226 |
botwriter_log('Image post-processing: File not found', ['path' => $file_path]); |
| 5227 |
return false; |
| 5228 |
} |
| 5229 |
|
| 5230 |
// Get settings |
| 5231 |
$output_format = get_option('botwriter_image_output_format', 'webp'); |
| 5232 |
$max_width = intval(get_option('botwriter_image_max_width', 1200)); |
| 5233 |
$compression = intval(get_option('botwriter_image_compression', 85)); |
| 5234 |
$max_filesize = intval(get_option('botwriter_image_max_filesize', 120)) * 1024; // Convert KB to bytes |
| 5235 |
|
| 5236 |
// Log settings at start |
| 5237 |
$original_size = filesize($file_path); |
| 5238 |
botwriter_log('Image post-processing: Starting', [ |
| 5239 |
'file' => basename($file_path), |
| 5240 |
'original_size_kb' => round($original_size / 1024, 1), |
| 5241 |
'settings' => [ |
| 5242 |
'output_format' => $output_format, |
| 5243 |
'max_width' => $max_width, |
| 5244 |
'compression' => $compression, |
| 5245 |
'max_filesize_kb' => $max_filesize / 1024 |
| 5246 |
] |
| 5247 |
]); |
| 5248 |
|
| 5249 |
// Get WordPress image editor |
| 5250 |
$editor = wp_get_image_editor($file_path); |
| 5251 |
if (is_wp_error($editor)) { |
| 5252 |
botwriter_log('Image post-processing: Failed to load editor', [ |
| 5253 |
'path' => $file_path, |
| 5254 |
'error' => $editor->get_error_message() |
| 5255 |
]); |
| 5256 |
return $file_path; // Return original on error |
| 5257 |
} |
| 5258 |
|
| 5259 |
// Log which editor is being used |
| 5260 |
botwriter_log('Image post-processing: Editor loaded', [ |
| 5261 |
'editor_class' => get_class($editor) |
| 5262 |
]); |
| 5263 |
|
| 5264 |
$size = $editor->get_size(); |
| 5265 |
$modified = false; |
| 5266 |
|
| 5267 |
botwriter_log('Image post-processing: Original dimensions', [ |
| 5268 |
'width' => $size['width'], |
| 5269 |
'height' => $size['height'] |
| 5270 |
]); |
| 5271 |
|
| 5272 |
// Resize if wider than max width |
| 5273 |
if ($max_width > 0 && $size['width'] > $max_width) { |
| 5274 |
$new_height = intval($size['height'] * ($max_width / $size['width'])); |
| 5275 |
$result = $editor->resize($max_width, $new_height, false); |
| 5276 |
if (!is_wp_error($result)) { |
| 5277 |
$modified = true; |
| 5278 |
botwriter_log('Image post-processing: Resized', [ |
| 5279 |
'from' => $size['width'] . 'x' . $size['height'], |
| 5280 |
'to' => $max_width . 'x' . $new_height |
| 5281 |
]); |
| 5282 |
} |
| 5283 |
} |
| 5284 |
|
| 5285 |
// Set quality |
| 5286 |
$editor->set_quality($compression); |
| 5287 |
|
| 5288 |
// Determine output file path and mime type |
| 5289 |
$path_info = pathinfo($file_path); |
| 5290 |
$new_extension = $path_info['extension']; |
| 5291 |
$mime_type = null; |
| 5292 |
|
| 5293 |
if ($output_format !== 'original') { |
| 5294 |
switch ($output_format) { |
| 5295 |
case 'webp': |
| 5296 |
// Check if WebP is supported via GD or Imagick |
| 5297 |
$webp_supported = function_exists('imagewebp'); |
| 5298 |
if (!$webp_supported && extension_loaded('imagick') && class_exists('Imagick')) { |
| 5299 |
// Check Imagick WebP support dynamically to avoid static analysis errors |
| 5300 |
$imagick_formats = call_user_func(['Imagick', 'queryFormats'], 'WEBP'); |
| 5301 |
$webp_supported = !empty($imagick_formats); |
| 5302 |
} |
| 5303 |
if ($webp_supported) { |
| 5304 |
$new_extension = 'webp'; |
| 5305 |
$mime_type = 'image/webp'; |
| 5306 |
} else { |
| 5307 |
// Fallback to JPEG if WebP not supported |
| 5308 |
$new_extension = 'jpg'; |
| 5309 |
$mime_type = 'image/jpeg'; |
| 5310 |
botwriter_log('Image post-processing: WebP not supported, falling back to JPEG'); |
| 5311 |
} |
| 5312 |
break; |
| 5313 |
case 'jpeg': |
| 5314 |
$new_extension = 'jpg'; |
| 5315 |
$mime_type = 'image/jpeg'; |
| 5316 |
break; |
| 5317 |
case 'png': |
| 5318 |
$new_extension = 'png'; |
| 5319 |
$mime_type = 'image/png'; |
| 5320 |
break; |
| 5321 |
} |
| 5322 |
} |
| 5323 |
|
| 5324 |
// Build new file path |
| 5325 |
$new_file_path = $path_info['dirname'] . '/' . $path_info['filename'] . '.' . $new_extension; |
| 5326 |
|
| 5327 |
botwriter_log('Image post-processing: Format decision', [ |
| 5328 |
'original_extension' => $path_info['extension'], |
| 5329 |
'new_extension' => $new_extension, |
| 5330 |
'mime_type' => $mime_type, |
| 5331 |
'new_file_path' => basename($new_file_path) |
| 5332 |
]); |
| 5333 |
|
| 5334 |
// Save the image |
| 5335 |
$save_args = []; |
| 5336 |
if ($mime_type) { |
| 5337 |
$save_args['mime_type'] = $mime_type; |
| 5338 |
} |
| 5339 |
|
| 5340 |
$saved = $editor->save($new_file_path, $mime_type); |
| 5341 |
|
| 5342 |
if (is_wp_error($saved)) { |
| 5343 |
botwriter_log('Image post-processing: Failed to save', [ |
| 5344 |
'path' => $new_file_path, |
| 5345 |
'error' => $saved->get_error_message() |
| 5346 |
]); |
| 5347 |
return $file_path; // Return original on error |
| 5348 |
} |
| 5349 |
|
| 5350 |
$new_file_path = $saved['path']; |
| 5351 |
|
| 5352 |
botwriter_log('Image post-processing: Initial save complete', [ |
| 5353 |
'saved_file' => basename($new_file_path), |
| 5354 |
'size_kb' => round(filesize($new_file_path) / 1024, 1) |
| 5355 |
]); |
| 5356 |
|
| 5357 |
// If max filesize is set and file is too large, reduce quality iteratively |
| 5358 |
if ($max_filesize > 0) { |
| 5359 |
$current_size = filesize($new_file_path); |
| 5360 |
$quality = $compression; |
| 5361 |
$attempts = 0; |
| 5362 |
$max_attempts = 5; |
| 5363 |
|
| 5364 |
botwriter_log('Image post-processing: Checking filesize target', [ |
| 5365 |
'current_kb' => round($current_size / 1024, 1), |
| 5366 |
'target_kb' => $max_filesize / 1024, |
| 5367 |
'needs_compression' => $current_size > $max_filesize |
| 5368 |
]); |
| 5369 |
|
| 5370 |
while ($current_size > $max_filesize && $quality > 40 && $attempts < $max_attempts) { |
| 5371 |
$quality -= 10; |
| 5372 |
$attempts++; |
| 5373 |
|
| 5374 |
botwriter_log('Image post-processing: Compression attempt', [ |
| 5375 |
'attempt' => $attempts, |
| 5376 |
'quality' => $quality |
| 5377 |
]); |
| 5378 |
|
| 5379 |
// Reload and resave with lower quality |
| 5380 |
$editor = wp_get_image_editor($new_file_path); |
| 5381 |
if (!is_wp_error($editor)) { |
| 5382 |
$editor->set_quality($quality); |
| 5383 |
$saved = $editor->save($new_file_path, $mime_type); |
| 5384 |
if (!is_wp_error($saved)) { |
| 5385 |
$current_size = filesize($saved['path']); |
| 5386 |
$new_file_path = $saved['path']; |
| 5387 |
} |
| 5388 |
} |
| 5389 |
} |
| 5390 |
|
| 5391 |
if ($attempts > 0) { |
| 5392 |
botwriter_log('Image post-processing: Compressed for filesize target', [ |
| 5393 |
'target_kb' => $max_filesize / 1024, |
| 5394 |
'final_kb' => round($current_size / 1024, 1), |
| 5395 |
'final_quality' => $quality, |
| 5396 |
'attempts' => $attempts |
| 5397 |
]); |
| 5398 |
} |
| 5399 |
} |
| 5400 |
|
| 5401 |
// Delete original file if format changed |
| 5402 |
if ($new_file_path !== $file_path && file_exists($file_path) && file_exists($new_file_path)) { |
| 5403 |
wp_delete_file($file_path); |
| 5404 |
botwriter_log('Image post-processing: Converted format', [ |
| 5405 |
'from' => $path_info['extension'], |
| 5406 |
'to' => $new_extension, |
| 5407 |
'new_size_kb' => round(filesize($new_file_path) / 1024, 1) |
| 5408 |
]); |
| 5409 |
} |
| 5410 |
|
| 5411 |
// Final summary log |
| 5412 |
$final_size = filesize($new_file_path); |
| 5413 |
botwriter_log('Image post-processing: Complete', [ |
| 5414 |
'original_file' => basename($file_path), |
| 5415 |
'final_file' => basename($new_file_path), |
| 5416 |
'original_size_kb' => round($original_size / 1024, 1), |
| 5417 |
'final_size_kb' => round($final_size / 1024, 1), |
| 5418 |
'size_reduction_percent' => round((1 - ($final_size / $original_size)) * 100, 1) |
| 5419 |
]); |
| 5420 |
|
| 5421 |
return $new_file_path; |
| 5422 |
} |
| 5423 |
|
| 5424 |
|
| 5425 |
|
| 5426 |
add_action('plugins_loaded', 'botwriter_check_update'); |
| 5427 |
function botwriter_check_update() { |
| 5428 |
// Ensure install date exists for legacy installs |
| 5429 |
if (get_option('botwriter_install_date') === false) { |
| 5430 |
update_option('botwriter_install_date', current_time('timestamp')); |
| 5431 |
} |
| 5432 |
// Use constant instead of get_plugin_data() to obtain current plugin version |
| 5433 |
$plugin_version = BOTWRITER_VERSION; |
| 5434 |
|
| 5435 |
// Get the previously installed version |
| 5436 |
$version_instalada = get_option('botwriter_version'); |
| 5437 |
|
| 5438 |
if ($version_instalada != $plugin_version) { |
| 5439 |
botwriter_create_table(); |
| 5440 |
|
| 5441 |
// Insert default templates if none exist (for updates from older versions) |
| 5442 |
botwriter_insert_all_default_templates(); |
| 5443 |
|
| 5444 |
// Migration: reset 'custom' provider to defaults (removed in this version) |
| 5445 |
if (get_option('botwriter_text_provider') === 'custom') { |
| 5446 |
update_option('botwriter_text_provider', 'openai'); |
| 5447 |
} |
| 5448 |
if (get_option('botwriter_image_provider') === 'custom') { |
| 5449 |
update_option('botwriter_image_provider', 'stockphoto'); |
| 5450 |
} |
| 5451 |
// Clean up custom provider options |
| 5452 |
delete_option('botwriter_custom_text_url'); |
| 5453 |
delete_option('botwriter_custom_text_api_key'); |
| 5454 |
delete_option('botwriter_custom_text_model'); |
| 5455 |
delete_option('botwriter_custom_text_timeout'); |
| 5456 |
delete_option('botwriter_custom_image_url'); |
| 5457 |
delete_option('botwriter_custom_image_type'); |
| 5458 |
delete_option('botwriter_custom_image_model'); |
| 5459 |
delete_option('botwriter_custom_image_timeout'); |
| 5460 |
|
| 5461 |
// Drop direct mode table if it exists |
| 5462 |
global $wpdb; |
| 5463 |
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}botwriter_direct_tasks"); |
| 5464 |
|
| 5465 |
update_option('botwriter_version', $plugin_version); // Update version in database |
| 5466 |
} |
| 5467 |
} |
| 5468 |
|
| 5469 |
|
| 5470 |
|
| 5471 |
// new super functions |
| 5472 |
add_action('wp_ajax_botwriter_actualizar_articulo', 'botwriter_actualizar_articulo_callback'); |
| 5473 |
|
| 5474 |
function botwriter_actualizar_articulo_callback() { |
| 5475 |
// Verify user has permission |
| 5476 |
if (!current_user_can('manage_options')) { |
| 5477 |
wp_send_json_error('Permission denied'); |
| 5478 |
} |
| 5479 |
|
| 5480 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 5481 |
global $wpdb; |
| 5482 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 5483 |
|
| 5484 |
$id = isset($_POST['id']) ? intval($_POST['id']) : 0; |
| 5485 |
$title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : ''; |
| 5486 |
$content = isset($_POST['content']) ? sanitize_textarea_field(wp_unslash($_POST['content'])) : ''; |
| 5487 |
|
| 5488 |
$result = $wpdb->update( |
| 5489 |
$table_name, |
| 5490 |
array('title' => $title, 'content' => $content), |
| 5491 |
array('id' => $id) |
| 5492 |
); |
| 5493 |
|
| 5494 |
if ($result !== false) { |
| 5495 |
wp_send_json_success('Artículo actualizado correctamente.'); |
| 5496 |
} else { |
| 5497 |
wp_send_json_error('Error al actualizar el artículo.'); |
| 5498 |
} |
| 5499 |
|
| 5500 |
wp_die(); |
| 5501 |
} |
| 5502 |
|
| 5503 |
add_action('wp_ajax_botwriter_eliminar_articulo', 'botwriter_eliminar_articulo_callback'); |
| 5504 |
|
| 5505 |
function botwriter_eliminar_articulo_callback() { |
| 5506 |
// Verify user has permission |
| 5507 |
if (!current_user_can('manage_options')) { |
| 5508 |
wp_send_json_error('Permission denied'); |
| 5509 |
} |
| 5510 |
|
| 5511 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 5512 |
global $wpdb; |
| 5513 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 5514 |
|
| 5515 |
$id = isset($_POST['id']) ? intval($_POST['id']) : 0; |
| 5516 |
|
| 5517 |
$result = $wpdb->delete($table_name, array('id' => $id)); |
| 5518 |
// consultar cuantos registros con id_task=0 |
| 5519 |
$result = $wpdb->get_results("SELECT * FROM $table_name WHERE id_task='0'"); |
| 5520 |
$num_rows = count($result); |
| 5521 |
if ($num_rows == 0) { |
| 5522 |
// borramos en la tabla logs la tarea super1 si no hay articulos |
| 5523 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 5524 |
$wpdb->delete($table_name_logs, array('website_type' => 'super1')); |
| 5525 |
} |
| 5526 |
|
| 5527 |
|
| 5528 |
|
| 5529 |
if ($result !== false) { |
| 5530 |
wp_send_json_success('Artículo eliminado correctamente.'); |
| 5531 |
} else { |
| 5532 |
wp_send_json_error('Error al eliminar el artículo.'); |
| 5533 |
} |
| 5534 |
|
| 5535 |
wp_die(); |
| 5536 |
} |
| 5537 |
|
| 5538 |
add_action('wp_ajax_botwriter_check_super1', 'botwriter_check_super1_callback'); |
| 5539 |
|
| 5540 |
function botwriter_check_super1_callback() { |
| 5541 |
// Verify user has permission |
| 5542 |
if (!current_user_can('manage_options')) { |
| 5543 |
wp_send_json_error('Permission denied'); |
| 5544 |
} |
| 5545 |
|
| 5546 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 5547 |
$estado_super1=botwriter_super1_check_task_finish(); |
| 5548 |
if ($estado_super1=="completed") { |
| 5549 |
$response = botwriter_super1_view_articles_html(); |
| 5550 |
wp_send_json_success($response); |
| 5551 |
return; |
| 5552 |
} |
| 5553 |
|
| 5554 |
if ($estado_super1=="error") { |
| 5555 |
// borramos la tarea super1 |
| 5556 |
global $wpdb; |
| 5557 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 5558 |
$wpdb->delete($table_name, array('id_task' => 0)); |
| 5559 |
wp_send_json_error("error"); |
| 5560 |
return; |
| 5561 |
} |
| 5562 |
wp_send_json_error('inqueue'); |
| 5563 |
|
| 5564 |
|
| 5565 |
} |
| 5566 |
|
| 5567 |
|
| 5568 |
add_action('wp_ajax_botwriter_create_super1', 'botwriter_create_super1_callback'); |
| 5569 |
|
| 5570 |
function botwriter_create_super1_callback() { |
| 5571 |
// Verify user has permission |
| 5572 |
if (!current_user_can('manage_options')) { |
| 5573 |
wp_send_json_error('Permission denied'); |
| 5574 |
} |
| 5575 |
|
| 5576 |
check_ajax_referer('botwriter_super_nonce'); // Validamos el nonce para seguridad |
| 5577 |
if (!isset($_POST['prompt']) || !isset($_POST['numarticles'])) { |
| 5578 |
botwriter_log('Super1 creation request missing parameters'); |
| 5579 |
wp_send_json_error('Missing required parameters.'); |
| 5580 |
wp_die(); |
| 5581 |
} |
| 5582 |
|
| 5583 |
$prompt = sanitize_text_field(wp_unslash($_POST['prompt'])); |
| 5584 |
$numarticles = intval(wp_unslash($_POST['numarticles'])); |
| 5585 |
if ($prompt=="Custom") { |
| 5586 |
$category_id = isset($_POST['category_id']) ? sanitize_text_field(wp_unslash($_POST['category_id'])) : '0'; |
| 5587 |
} |
| 5588 |
|
| 5589 |
botwriter_log('Super1 creation request received', [ |
| 5590 |
'prompt' => $prompt, |
| 5591 |
'num_articles' => $numarticles, |
| 5592 |
'category_id' => isset($category_id) ? $category_id : null, |
| 5593 |
]); |
| 5594 |
|
| 5595 |
$title_prompt = $prompt; |
| 5596 |
|
| 5597 |
if ($prompt=="Custom") { |
| 5598 |
$content_prompt = isset($_POST['custom_prompt']) ? sanitize_text_field(wp_unslash($_POST['custom_prompt'])) : ''; |
| 5599 |
} else { |
| 5600 |
$info_blog = botwriter_get_info_blog(); |
| 5601 |
$json_info_blog = json_encode($info_blog, JSON_PRETTY_PRINT); |
| 5602 |
$content_prompt = $json_info_blog; |
| 5603 |
} |
| 5604 |
$task_name = "Super1 Task " . current_time('Y-m-d H:i:s'); |
| 5605 |
$log_id = botwriter_super1_create_task($task_name, $title_prompt,$content_prompt, $numarticles, $category_id); |
| 5606 |
|
| 5607 |
botwriter_log('Super1 task queued', [ |
| 5608 |
'log_id' => $log_id, |
| 5609 |
'title_prompt' => $title_prompt, |
| 5610 |
'num_articles' => $numarticles, |
| 5611 |
]); |
| 5612 |
|
| 5613 |
wp_send_json_success("Task created successfully: " . $title_prompt . " " . $content_prompt . " " . $numarticles . " " . $category_id); |
| 5614 |
|
| 5615 |
|
| 5616 |
} |
| 5617 |
|
| 5618 |
|
| 5619 |
add_action('wp_ajax_botwriter_eliminar_super1', 'botwriter_eliminar_super1_y_logs0'); |
| 5620 |
|
| 5621 |
function botwriter_eliminar_super1_y_logs0() { |
| 5622 |
// Verify user has permission |
| 5623 |
if (!current_user_can('manage_options')) { |
| 5624 |
wp_send_json_error('Permission denied'); |
| 5625 |
} |
| 5626 |
|
| 5627 |
check_ajax_referer('botwriter_super_nonce'); |
| 5628 |
global $wpdb; |
| 5629 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 5630 |
$table_name_logs = $wpdb->prefix . 'botwriter_logs'; |
| 5631 |
|
| 5632 |
$result = $wpdb->delete($table_name, array('id_task' => 0)); |
| 5633 |
$result = $wpdb->delete($table_name_logs, array('website_type' => 'super1')); |
| 5634 |
|
| 5635 |
if ($result !== false) { |
| 5636 |
wp_send_json_success('Super1 task deleted successfully.'); |
| 5637 |
} else { |
| 5638 |
wp_send_json_error('Error'); |
| 5639 |
} |
| 5640 |
wp_die(); |
| 5641 |
} |
| 5642 |
|
| 5643 |
add_action('wp_ajax_botwriter_create_super1_manual', 'botwriter_create_super1_manual_callback'); |
| 5644 |
|
| 5645 |
function botwriter_create_super1_manual_callback() { |
| 5646 |
// Verify user has permission |
| 5647 |
if (!current_user_can('manage_options')) { |
| 5648 |
wp_send_json_error('Permission denied'); |
| 5649 |
} |
| 5650 |
|
| 5651 |
check_ajax_referer('botwriter_super_nonce'); |
| 5652 |
|
| 5653 |
if (!isset($_POST['manual_titles']) || empty(trim(wp_unslash($_POST['manual_titles'])))) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Only emptiness check; sanitized below before use. |
| 5654 |
wp_send_json_error('No titles provided.'); |
| 5655 |
wp_die(); |
| 5656 |
} |
| 5657 |
|
| 5658 |
$raw_titles = sanitize_textarea_field(wp_unslash($_POST['manual_titles'])); |
| 5659 |
$global_prompt = isset($_POST['global_prompt']) ? sanitize_textarea_field(wp_unslash($_POST['global_prompt'])) : ''; |
| 5660 |
$category_id = isset($_POST['category_id']) ? intval(wp_unslash($_POST['category_id'])) : 0; |
| 5661 |
|
| 5662 |
// Split titles by newline and filter empty lines |
| 5663 |
$titles = array_filter(array_map('trim', explode("\n", $raw_titles)), function($t) { |
| 5664 |
return !empty($t); |
| 5665 |
}); |
| 5666 |
|
| 5667 |
if (empty($titles)) { |
| 5668 |
wp_send_json_error('No valid titles found.'); |
| 5669 |
wp_die(); |
| 5670 |
} |
| 5671 |
|
| 5672 |
// Limit to 100 titles max |
| 5673 |
if (count($titles) > 100) { |
| 5674 |
$titles = array_slice($titles, 0, 100); |
| 5675 |
} |
| 5676 |
|
| 5677 |
botwriter_log('Manual Super1 creation request', [ |
| 5678 |
'num_titles' => count($titles), |
| 5679 |
'category_id' => $category_id, |
| 5680 |
'has_global_prompt' => !empty($global_prompt), |
| 5681 |
]); |
| 5682 |
|
| 5683 |
global $wpdb; |
| 5684 |
|
| 5685 |
// 1. Create a completed super1 log entry (bypassing AI generation) |
| 5686 |
$log_data = array( |
| 5687 |
'task_name' => 'Manual Super1 Task ' . current_time('Y-m-d H:i:s'), |
| 5688 |
'task_status' => 'completed', |
| 5689 |
'website_type' => 'super1', |
| 5690 |
'title_prompt' => 'Manual', |
| 5691 |
'content_prompt' => $global_prompt, |
| 5692 |
'post_count' => count($titles), |
| 5693 |
'category_id' => $category_id, |
| 5694 |
); |
| 5695 |
$log_id = botwriter_logs_register($log_data); |
| 5696 |
|
| 5697 |
if (!$log_id) { |
| 5698 |
wp_send_json_error('Error creating log entry.'); |
| 5699 |
wp_die(); |
| 5700 |
} |
| 5701 |
|
| 5702 |
// 2. Insert each title into wp_botwriter_super |
| 5703 |
$table_name = $wpdb->prefix . 'botwriter_super'; |
| 5704 |
$category_name = ''; |
| 5705 |
if ($category_id > 0) { |
| 5706 |
$category_name = get_cat_name($category_id); |
| 5707 |
} |
| 5708 |
|
| 5709 |
foreach ($titles as $title) { |
| 5710 |
$data = array( |
| 5711 |
'title' => sanitize_text_field($title), |
| 5712 |
'content' => $global_prompt, |
| 5713 |
'category_name' => $category_name, |
| 5714 |
'category_id' => $category_id, |
| 5715 |
'id_log' => $log_id, |
| 5716 |
'id_task' => 0, // draft, will be assigned on save |
| 5717 |
'task_status' => '', |
| 5718 |
); |
| 5719 |
$wpdb->insert($table_name, $data); |
| 5720 |
} |
| 5721 |
|
| 5722 |
botwriter_log('Manual Super1 titles inserted', [ |
| 5723 |
'log_id' => $log_id, |
| 5724 |
'count' => count($titles), |
| 5725 |
]); |
| 5726 |
|
| 5727 |
// 3. Return the articles HTML for immediate review |
| 5728 |
$html = botwriter_super1_view_articles_html(0); |
| 5729 |
wp_send_json_success($html); |
| 5730 |
} |
| 5731 |
|
| 5732 |
|
| 5733 |
// ======================================== |
| 5734 |
// CONTENT REWRITER AJAX HANDLERS |
| 5735 |
// ======================================== |
| 5736 |
add_action('wp_ajax_botwriter_rewriter_fetch', 'botwriter_rewriter_fetch_ajax'); |
| 5737 |
add_action('wp_ajax_botwriter_rewriter_create_task', 'botwriter_rewriter_create_task_ajax'); |
| 5738 |
|
| 5739 |
/** |
| 5740 |
* AJAX: Fetch and extract content from URLs |
| 5741 |
*/ |
| 5742 |
function botwriter_rewriter_fetch_ajax() { |
| 5743 |
check_ajax_referer('botwriter_rewriter_nonce'); |
| 5744 |
|
| 5745 |
if (!current_user_can('manage_options')) { |
| 5746 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 5747 |
} |
| 5748 |
|
| 5749 |
$urls = isset($_POST['urls']) ? array_map('esc_url_raw', (array) wp_unslash($_POST['urls'])) : array(); |
| 5750 |
|
| 5751 |
if (empty($urls)) { |
| 5752 |
wp_send_json_error(__('No URLs provided.', 'botwriter')); |
| 5753 |
} |
| 5754 |
|
| 5755 |
if (count($urls) > 20) { |
| 5756 |
wp_send_json_error(__('Maximum 20 URLs allowed.', 'botwriter')); |
| 5757 |
} |
| 5758 |
|
| 5759 |
$articles = array(); |
| 5760 |
$errors = array(); |
| 5761 |
|
| 5762 |
foreach ($urls as $url) { |
| 5763 |
if (empty($url)) { |
| 5764 |
continue; |
| 5765 |
} |
| 5766 |
|
| 5767 |
$result = botwriter_rewriter_extract_content($url); |
| 5768 |
|
| 5769 |
if (is_wp_error($result)) { |
| 5770 |
$errors[] = array( |
| 5771 |
'url' => $url, |
| 5772 |
'error' => $result->get_error_message(), |
| 5773 |
); |
| 5774 |
} else { |
| 5775 |
$articles[] = $result; |
| 5776 |
} |
| 5777 |
} |
| 5778 |
|
| 5779 |
wp_send_json_success(array( |
| 5780 |
'articles' => $articles, |
| 5781 |
'errors' => $errors, |
| 5782 |
)); |
| 5783 |
} |
| 5784 |
|
| 5785 |
/** |
| 5786 |
* AJAX: Create a Super Task with the articles to rewrite. |
| 5787 |
* |
| 5788 |
* Inserts the task as an active super2 task and stores each article |
| 5789 |
* in the botwriter_super table. The cron loop picks them up via |
| 5790 |
* botwriter_super_prepare_event() just like normal Super Tasks. |
| 5791 |
*/ |
| 5792 |
function botwriter_rewriter_create_task_ajax() { |
| 5793 |
check_ajax_referer('botwriter_rewriter_nonce'); |
| 5794 |
|
| 5795 |
if (!current_user_can('manage_options')) { |
| 5796 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 5797 |
} |
| 5798 |
|
| 5799 |
// wp_unslash only — sanitize_text_field corrupts JSON (strips tags/newlines) |
| 5800 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON is decoded first; each title/content field is sanitized below before use. |
| 5801 |
$articles_json = isset($_POST['articles']) ? wp_unslash($_POST['articles']) : ''; |
| 5802 |
$rewrite_prompt = isset($_POST['rewrite_prompt']) ? sanitize_textarea_field(wp_unslash($_POST['rewrite_prompt'])) : ''; |
| 5803 |
$category_id = isset($_POST['category_id']) ? absint(wp_unslash($_POST['category_id'])) : 0; |
| 5804 |
|
| 5805 |
// Task properties from Step 3 form |
| 5806 |
$post_status = isset($_POST['post_status']) ? sanitize_text_field(wp_unslash($_POST['post_status'])) : 'draft'; |
| 5807 |
$post_language = isset($_POST['post_language']) ? sanitize_text_field(wp_unslash($_POST['post_language'])) : substr(get_locale(), 0, 2); |
| 5808 |
$author_selection = isset($_POST['author_selection']) ? sanitize_text_field(wp_unslash($_POST['author_selection'])) : strval(get_current_user_id()); |
| 5809 |
$post_length = isset($_POST['post_length']) ? sanitize_text_field(wp_unslash($_POST['post_length'])) : '800'; |
| 5810 |
$custom_post_length = isset($_POST['custom_post_length']) ? sanitize_text_field(wp_unslash($_POST['custom_post_length'])) : ''; |
| 5811 |
$template_id = isset($_POST['template_id']) && !empty($_POST['template_id']) ? absint(wp_unslash($_POST['template_id'])) : null; |
| 5812 |
$disable_ai_images = isset($_POST['disable_ai_images']) ? absint(wp_unslash($_POST['disable_ai_images'])) : 0; |
| 5813 |
$days = isset($_POST['days']) ? sanitize_text_field(wp_unslash($_POST['days'])) : 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'; |
| 5814 |
$times_per_day = isset($_POST['times_per_day']) ? absint(wp_unslash($_POST['times_per_day'])) : 1; |
| 5815 |
$task_name_custom = isset($_POST['task_name']) ? sanitize_text_field(wp_unslash($_POST['task_name'])) : ''; |
| 5816 |
|
| 5817 |
// Validate post_status |
| 5818 |
if (!in_array($post_status, array('draft', 'publish'), true)) { |
| 5819 |
$post_status = 'draft'; |
| 5820 |
} |
| 5821 |
|
| 5822 |
$articles = json_decode($articles_json, true); |
| 5823 |
if (!is_array($articles) || empty($articles)) { |
| 5824 |
wp_send_json_error(__('No articles provided.', 'botwriter')); |
| 5825 |
} |
| 5826 |
|
| 5827 |
global $wpdb; |
| 5828 |
$tasks_table = $wpdb->prefix . 'botwriter_tasks'; |
| 5829 |
$super_table = $wpdb->prefix . 'botwriter_super'; |
| 5830 |
|
| 5831 |
// Build default rewrite prompt if user didn't provide one |
| 5832 |
if (empty($rewrite_prompt)) { |
| 5833 |
$rewrite_prompt = 'Rewrite this article completely in your own words while preserving all key information. Make it original, engaging, and well-structured.'; |
| 5834 |
} |
| 5835 |
|
| 5836 |
// Count valid articles (filter empties before inserting) |
| 5837 |
$valid_articles = array(); |
| 5838 |
foreach ($articles as $article) { |
| 5839 |
$title = sanitize_text_field($article['title'] ?? ''); |
| 5840 |
$content = wp_kses_post($article['content'] ?? ''); |
| 5841 |
if (!empty($title) || !empty($content)) { |
| 5842 |
$valid_articles[] = array('title' => $title, 'content' => $content); |
| 5843 |
} |
| 5844 |
} |
| 5845 |
|
| 5846 |
if (empty($valid_articles)) { |
| 5847 |
wp_send_json_error(__('All articles are empty.', 'botwriter')); |
| 5848 |
} |
| 5849 |
|
| 5850 |
$task_name = !empty($task_name_custom) ? $task_name_custom : 'Rewriter: ' . count($valid_articles) . ' articles - ' . wp_date('M j, Y H:i'); |
| 5851 |
|
| 5852 |
// Use the days and times_per_day from the form |
| 5853 |
$wpdb->insert($tasks_table, array( |
| 5854 |
'task_name' => $task_name, |
| 5855 |
'post_status' => $post_status, |
| 5856 |
'writer' => 'ai_cerebro', |
| 5857 |
'narration' => 'Descriptive', |
| 5858 |
'custom_style' => '', |
| 5859 |
'post_language' => $post_language, |
| 5860 |
'post_length' => $post_length, |
| 5861 |
'custom_post_length' => $custom_post_length, |
| 5862 |
'days' => $days, |
| 5863 |
'times_per_day' => $times_per_day > 0 ? $times_per_day : 1, |
| 5864 |
'status' => 1, |
| 5865 |
'website_type' => 'super2', |
| 5866 |
'task_type' => 'rewriter', |
| 5867 |
'domain_name' => get_site_url(), |
| 5868 |
'category_id' => $category_id > 0 ? strval($category_id) : '', |
| 5869 |
'title_prompt' => '', |
| 5870 |
'content_prompt' => $rewrite_prompt, |
| 5871 |
'tags_prompt' => '', |
| 5872 |
'image_prompt' => '', |
| 5873 |
'aigenerated_title' => '', |
| 5874 |
'aigenerated_content' => '', |
| 5875 |
'aigenerated_tags' => '', |
| 5876 |
'aigenerated_image' => '', |
| 5877 |
'ai_keywords' => '', |
| 5878 |
'author_selection' => $author_selection, |
| 5879 |
'disable_ai_images' => $disable_ai_images, |
| 5880 |
'template_id' => $template_id, |
| 5881 |
)); |
| 5882 |
|
| 5883 |
$task_id = $wpdb->insert_id; |
| 5884 |
|
| 5885 |
if (!$task_id) { |
| 5886 |
wp_send_json_error(__('Failed to create task.', 'botwriter')); |
| 5887 |
} |
| 5888 |
|
| 5889 |
// Insert each article into the super table. |
| 5890 |
// Only the article content goes here — rewrite instructions stay in the task's content_prompt. |
| 5891 |
// super_prepare_event() preserves the rewrite_prompt, and |
| 5892 |
// botwriter_build_client_prompt() outputs it BEFORE the ENDARTICLE-wrapped content. |
| 5893 |
$inserted = 0; |
| 5894 |
foreach ($valid_articles as $art) { |
| 5895 |
$wpdb->insert($super_table, array( |
| 5896 |
'id_task' => $task_id, |
| 5897 |
'id_log' => 0, |
| 5898 |
'title' => $art['title'], |
| 5899 |
'content' => $art['content'], |
| 5900 |
)); |
| 5901 |
// task_status left NULL — super_prepare_event picks rows with NULL/empty task_status |
| 5902 |
$inserted++; |
| 5903 |
} |
| 5904 |
|
| 5905 |
botwriter_log('Content Rewriter: Task created', [ |
| 5906 |
'task_id' => $task_id, |
| 5907 |
'article_count' => $inserted, |
| 5908 |
]); |
| 5909 |
|
| 5910 |
wp_send_json_success(array( |
| 5911 |
'task_id' => $task_id, |
| 5912 |
'count' => $inserted, |
| 5913 |
'edit_url' => wp_nonce_url(admin_url('admin.php?page=botwriter_super_page&id=' . $task_id), 'botwriter_tasks_action'), |
| 5914 |
)); |
| 5915 |
} |
| 5916 |
|
| 5917 |
// ======================================== |
| 5918 |
// SITE REWRITER AJAX HANDLERS |
| 5919 |
// ======================================== |
| 5920 |
add_action('wp_ajax_botwriter_siterewriter_crawl', 'botwriter_siterewriter_crawl_ajax'); |
| 5921 |
add_action('wp_ajax_botwriter_siterewriter_fetch', 'botwriter_siterewriter_fetch_ajax'); |
| 5922 |
add_action('wp_ajax_botwriter_siterewriter_create_task', 'botwriter_siterewriter_create_task_ajax'); |
| 5923 |
|
| 5924 |
/** |
| 5925 |
* AJAX: Crawl a single page — returns title + internal links. |
| 5926 |
* The JS manages the BFS queue for live/progressive UI updates. |
| 5927 |
*/ |
| 5928 |
function botwriter_siterewriter_crawl_ajax() { |
| 5929 |
check_ajax_referer('botwriter_siterewriter_nonce'); |
| 5930 |
|
| 5931 |
if (!current_user_can('manage_options')) { |
| 5932 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 5933 |
} |
| 5934 |
|
| 5935 |
$url = isset($_POST['url']) ? esc_url_raw(wp_unslash($_POST['url'])) : ''; |
| 5936 |
$base_domain = isset($_POST['base_domain']) ? sanitize_text_field(wp_unslash($_POST['base_domain'])) : ''; |
| 5937 |
|
| 5938 |
if (empty($url) || empty($base_domain)) { |
| 5939 |
wp_send_json_error(__('Missing URL or domain.', 'botwriter')); |
| 5940 |
} |
| 5941 |
|
| 5942 |
$result = botwriter_siterewriter_crawl_page($url, $base_domain); |
| 5943 |
|
| 5944 |
if (is_wp_error($result)) { |
| 5945 |
wp_send_json_error($result->get_error_message()); |
| 5946 |
} |
| 5947 |
|
| 5948 |
wp_send_json_success($result); |
| 5949 |
} |
| 5950 |
|
| 5951 |
/** |
| 5952 |
* AJAX: Fetch and extract content from selected URLs. |
| 5953 |
* Reuses botwriter_rewriter_extract_content() for full content extraction. |
| 5954 |
*/ |
| 5955 |
function botwriter_siterewriter_fetch_ajax() { |
| 5956 |
check_ajax_referer('botwriter_siterewriter_nonce'); |
| 5957 |
|
| 5958 |
if (!current_user_can('manage_options')) { |
| 5959 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 5960 |
} |
| 5961 |
|
| 5962 |
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Long-running image batch operation; safe inside an admin AJAX handler. |
| 5963 |
@set_time_limit(0); |
| 5964 |
|
| 5965 |
$urls = isset($_POST['urls']) ? array_map('esc_url_raw', (array) wp_unslash($_POST['urls'])) : array(); |
| 5966 |
|
| 5967 |
if (empty($urls)) { |
| 5968 |
wp_send_json_error(__('No URLs provided.', 'botwriter')); |
| 5969 |
} |
| 5970 |
|
| 5971 |
$articles = array(); |
| 5972 |
$errors = array(); |
| 5973 |
|
| 5974 |
foreach ($urls as $url) { |
| 5975 |
if (empty($url)) continue; |
| 5976 |
|
| 5977 |
$result = botwriter_rewriter_extract_content($url); |
| 5978 |
|
| 5979 |
if (is_wp_error($result)) { |
| 5980 |
$errors[] = array( |
| 5981 |
'url' => $url, |
| 5982 |
'error' => $result->get_error_message(), |
| 5983 |
); |
| 5984 |
} else { |
| 5985 |
$articles[] = $result; |
| 5986 |
} |
| 5987 |
} |
| 5988 |
|
| 5989 |
wp_send_json_success(array( |
| 5990 |
'articles' => $articles, |
| 5991 |
'errors' => $errors, |
| 5992 |
)); |
| 5993 |
} |
| 5994 |
|
| 5995 |
/** |
| 5996 |
* AJAX: Create a Super Task with the articles to rewrite. |
| 5997 |
* Identical flow to Content Rewriter but with task_type = 'siterewriter'. |
| 5998 |
*/ |
| 5999 |
function botwriter_siterewriter_create_task_ajax() { |
| 6000 |
check_ajax_referer('botwriter_siterewriter_nonce'); |
| 6001 |
|
| 6002 |
if (!current_user_can('manage_options')) { |
| 6003 |
wp_send_json_error(__('Permission denied.', 'botwriter')); |
| 6004 |
} |
| 6005 |
|
| 6006 |
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON is decoded first; each title/content field is sanitized below before use. |
| 6007 |
$articles_json = isset($_POST['articles']) ? wp_unslash($_POST['articles']) : ''; |
| 6008 |
$rewrite_prompt = isset($_POST['rewrite_prompt']) ? sanitize_textarea_field(wp_unslash($_POST['rewrite_prompt'])) : ''; |
| 6009 |
$category_id = isset($_POST['category_id']) ? absint(wp_unslash($_POST['category_id'])) : 0; |
| 6010 |
|
| 6011 |
$post_status = isset($_POST['post_status']) ? sanitize_text_field(wp_unslash($_POST['post_status'])) : 'draft'; |
| 6012 |
$post_language = isset($_POST['post_language']) ? sanitize_text_field(wp_unslash($_POST['post_language'])) : substr(get_locale(), 0, 2); |
| 6013 |
$author_selection = isset($_POST['author_selection']) ? sanitize_text_field(wp_unslash($_POST['author_selection'])) : strval(get_current_user_id()); |
| 6014 |
$post_length = isset($_POST['post_length']) ? sanitize_text_field(wp_unslash($_POST['post_length'])) : '800'; |
| 6015 |
$custom_post_length = isset($_POST['custom_post_length'])? sanitize_text_field(wp_unslash($_POST['custom_post_length'])): ''; |
| 6016 |
$template_id = isset($_POST['template_id']) && !empty($_POST['template_id']) ? absint(wp_unslash($_POST['template_id'])) : null; |
| 6017 |
$disable_ai_images = isset($_POST['disable_ai_images']) ? absint(wp_unslash($_POST['disable_ai_images'])) : 0; |
| 6018 |
$days = isset($_POST['days']) ? sanitize_text_field(wp_unslash($_POST['days'])) : 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'; |
| 6019 |
$times_per_day = isset($_POST['times_per_day']) ? absint(wp_unslash($_POST['times_per_day'])) : 1; |
| 6020 |
$task_name_custom = isset($_POST['task_name']) ? sanitize_text_field(wp_unslash($_POST['task_name'])) : ''; |
| 6021 |
|
| 6022 |
if (!in_array($post_status, array('draft', 'publish'), true)) { |
| 6023 |
$post_status = 'draft'; |
| 6024 |
} |
| 6025 |
|
| 6026 |
$articles = json_decode($articles_json, true); |
| 6027 |
if (!is_array($articles) || empty($articles)) { |
| 6028 |
wp_send_json_error(__('No articles provided.', 'botwriter')); |
| 6029 |
} |
| 6030 |
|
| 6031 |
global $wpdb; |
| 6032 |
$tasks_table = $wpdb->prefix . 'botwriter_tasks'; |
| 6033 |
$super_table = $wpdb->prefix . 'botwriter_super'; |
| 6034 |
|
| 6035 |
if (empty($rewrite_prompt)) { |
| 6036 |
$rewrite_prompt = 'Rewrite this article completely in your own words while preserving all key information. Make it original, engaging, and well-structured.'; |
| 6037 |
} |
| 6038 |
|
| 6039 |
$valid_articles = array(); |
| 6040 |
foreach ($articles as $article) { |
| 6041 |
$title = sanitize_text_field($article['title'] ?? ''); |
| 6042 |
$content = wp_kses_post($article['content'] ?? ''); |
| 6043 |
if (!empty($title) || !empty($content)) { |
| 6044 |
$valid_articles[] = array('title' => $title, 'content' => $content); |
| 6045 |
} |
| 6046 |
} |
| 6047 |
|
| 6048 |
if (empty($valid_articles)) { |
| 6049 |
wp_send_json_error(__('All articles are empty.', 'botwriter')); |
| 6050 |
} |
| 6051 |
|
| 6052 |
$task_name = !empty($task_name_custom) |
| 6053 |
? $task_name_custom |
| 6054 |
: 'Site Rewriter: ' . count($valid_articles) . ' articles - ' . wp_date('M j, Y H:i'); |
| 6055 |
|
| 6056 |
$wpdb->insert($tasks_table, array( |
| 6057 |
'task_name' => $task_name, |
| 6058 |
'post_status' => $post_status, |
| 6059 |
'writer' => 'ai_cerebro', |
| 6060 |
'narration' => 'Descriptive', |
| 6061 |
'custom_style' => '', |
| 6062 |
'post_language' => $post_language, |
| 6063 |
'post_length' => $post_length, |
| 6064 |
'custom_post_length' => $custom_post_length, |
| 6065 |
'days' => $days, |
| 6066 |
'times_per_day' => $times_per_day > 0 ? $times_per_day : 1, |
| 6067 |
'status' => 1, |
| 6068 |
'website_type' => 'super2', |
| 6069 |
'task_type' => 'siterewriter', |
| 6070 |
'domain_name' => get_site_url(), |
| 6071 |
'category_id' => $category_id > 0 ? strval($category_id) : '', |
| 6072 |
'title_prompt' => '', |
| 6073 |
'content_prompt' => $rewrite_prompt, |
| 6074 |
'tags_prompt' => '', |
| 6075 |
'image_prompt' => '', |
| 6076 |
'aigenerated_title' => '', |
| 6077 |
'aigenerated_content' => '', |
| 6078 |
'aigenerated_tags' => '', |
| 6079 |
'aigenerated_image' => '', |
| 6080 |
'ai_keywords' => '', |
| 6081 |
'author_selection' => $author_selection, |
| 6082 |
'disable_ai_images' => $disable_ai_images, |
| 6083 |
'template_id' => $template_id, |
| 6084 |
)); |
| 6085 |
|
| 6086 |
$task_id = $wpdb->insert_id; |
| 6087 |
|
| 6088 |
if (!$task_id) { |
| 6089 |
wp_send_json_error(__('Failed to create task.', 'botwriter')); |
| 6090 |
} |
| 6091 |
|
| 6092 |
// Insert each article into the super table. |
| 6093 |
// Only the article content goes here — rewrite instructions stay in the task's content_prompt. |
| 6094 |
// super_prepare_event() preserves the rewrite_prompt, and |
| 6095 |
// botwriter_build_client_prompt() outputs it BEFORE the ENDARTICLE-wrapped content. |
| 6096 |
$inserted = 0; |
| 6097 |
foreach ($valid_articles as $art) { |
| 6098 |
$wpdb->insert($super_table, array( |
| 6099 |
'id_task' => $task_id, |
| 6100 |
'id_log' => 0, |
| 6101 |
'title' => $art['title'], |
| 6102 |
'content' => $art['content'], |
| 6103 |
)); |
| 6104 |
$inserted++; |
| 6105 |
} |
| 6106 |
|
| 6107 |
botwriter_log('Site Rewriter: Task created', [ |
| 6108 |
'task_id' => $task_id, |
| 6109 |
'article_count' => $inserted, |
| 6110 |
]); |
| 6111 |
|
| 6112 |
wp_send_json_success(array( |
| 6113 |
'task_id' => $task_id, |
| 6114 |
'count' => $inserted, |
| 6115 |
'edit_url' => wp_nonce_url(admin_url('admin.php?page=botwriter_super_page&id=' . $task_id), 'botwriter_tasks_action'), |
| 6116 |
)); |
| 6117 |
} |
| 6118 |
|
| 6119 |
?> |
| 6120 |
|