Changeset 3464366
- Timestamp:
- 02/18/2026 01:40:53 PM (36 hours ago)
- Location:
- botsubmit
- Files:
-
- 18 added
- 2 edited
-
tags/1.3.1 (added)
-
tags/1.3.1/assets (added)
-
tags/1.3.1/assets/css (added)
-
tags/1.3.1/assets/css/admin.css (added)
-
tags/1.3.1/assets/js (added)
-
tags/1.3.1/assets/js/admin.js (added)
-
tags/1.3.1/botsubmit.php (added)
-
tags/1.3.1/includes (added)
-
tags/1.3.1/includes/class-botsubmit-export.php (added)
-
tags/1.3.1/includes/class-botsubmit-log.php (added)
-
tags/1.3.1/includes/class-botsubmit-queue.php (added)
-
tags/1.3.1/includes/class-botsubmit-services.php (added)
-
tags/1.3.1/languages (added)
-
tags/1.3.1/languages/botsubmit-ru_RU.mo (added)
-
tags/1.3.1/languages/botsubmit-ru_RU.po (added)
-
tags/1.3.1/languages/botsubmit.pot (added)
-
tags/1.3.1/readme.txt (added)
-
tags/1.3.1/uninstall.php (added)
-
trunk/botsubmit.php (modified) (17 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
botsubmit/trunk/botsubmit.php
r3464337 r3464366 3 3 * Plugin Name: BotSubmit 4 4 * Description: Automatically submits URLs to multiple indexing services (SpeedyIndex, Link Indexing Bot, IndexBotik) for fast search engine indexing. Supports Google, Yandex, and Bing. 5 * Version: 1.3. 05 * Version: 1.3.1 6 6 * Requires at least: 5.0 7 7 * Requires PHP: 7.2 … … 102 102 103 103 // Taxonomies: categories/tags (term archives on create/edit) 104 add_action('created_category', [$this, 'handle_created_category'], 10, 3);105 add_action('edited_category', [$this, 'handle_edited_category'], 10, 3);106 add_action('created_post_tag', [$this, 'handle_created_post_tag'], 10, 3);107 add_action('edited_post_tag', [$this, 'handle_edited_post_tag'], 10, 3);104 add_action('created_category', [$this, 'handle_created_category'], 10, 2); 105 add_action('edited_category', [$this, 'handle_edited_category'], 10, 2); 106 add_action('created_post_tag', [$this, 'handle_created_post_tag'], 10, 2); 107 add_action('edited_post_tag', [$this, 'handle_edited_post_tag'], 10, 2); 108 108 109 109 // "Settings" link in plugins list … … 125 125 // AJAX for resending from log 126 126 add_action('wp_ajax_botsubmit_resend_url', [$this, 'ajax_resend_url']); 127 128 // AJAX for sending queue item immediately 129 add_action('wp_ajax_botsubmit_send_queue_item', [$this, 'ajax_send_queue_item']); 127 130 128 131 // AJAX for export/import settings … … 216 219 $log_script = " 217 220 jQuery(document).ready(function($) { 221 // Resend button handler 218 222 $('.botsubmit-resend-btn').on('click', function() { 219 223 var btn = $(this); … … 252 256 }); 253 257 }); 258 259 // Send Now button handler (queue items) 260 $('.botsubmit-send-now-btn').on('click', function() { 261 var btn = $(this); 262 var itemId = btn.data('id'); 263 var nonce = btn.data('nonce'); 264 var row = btn.closest('tr'); 265 266 btn.prop('disabled', true).text(botsubmit_ajax.sending); 267 268 $.ajax({ 269 url: ajaxurl, 270 type: 'POST', 271 data: { 272 action: 'botsubmit_send_queue_item', 273 item_id: itemId, 274 nonce: nonce 275 }, 276 success: function(response) { 277 if (response.success) { 278 // Check if partial success (error during send) 279 if (response.data && response.data.partial) { 280 btn.text(botsubmit_ajax.sent + ' (!)').css('background-color', '#fd7e14'); 281 } else { 282 btn.text(botsubmit_ajax.sent).css('background-color', '#46b450'); 283 } 284 row.fadeOut(500, function() { 285 $(this).remove(); 286 // Check if queue is empty 287 if ($('.botsubmit-queue-section tbody tr').length === 0) { 288 $('.botsubmit-queue-section').fadeOut(300, function() { 289 $(this).remove(); 290 }); 291 } 292 }); 293 // Reload after short delay to update log 294 setTimeout(function() { 295 location.reload(); 296 }, 2000); 297 } else { 298 btn.text(botsubmit_ajax.error).css('background-color', '#dc3232'); 299 alert(response.data || botsubmit_ajax.error_sending); 300 btn.prop('disabled', false); 301 } 302 }, 303 error: function() { 304 btn.text(botsubmit_ajax.error).css('background-color', '#dc3232'); 305 alert(botsubmit_ajax.network_error); 306 btn.prop('disabled', false); 307 } 308 }); 309 }); 254 310 }); 255 311 "; 256 312 257 wp_register_script( 'botsubmit-log', false, array( 'jquery' ), '1.3. 0', true );313 wp_register_script( 'botsubmit-log', false, array( 'jquery' ), '1.3.1', true ); 258 314 wp_enqueue_script( 'botsubmit-log' ); 259 315 wp_add_inline_script( 'botsubmit-log', $log_script ); … … 338 394 $this->create_queue_table(); 339 395 396 // Create logs table 397 $this->create_logs_table(); 398 340 399 // Schedule queue processing 341 400 if (!wp_next_scheduled('botsubmit_process_queue')) { … … 361 420 362 421 /** 363 * Check and create queue table if needed364 */365 /**366 422 * Check and create/update queue table if needed 367 423 */ 368 424 public function maybe_create_queue_table() { 425 global $wpdb; 426 427 $table_name = $wpdb->prefix . self::QUEUE_TABLE; 428 429 // Check if table physically exists 430 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 431 $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name; 432 369 433 $db_version = get_option( self::OPTION_QUEUE_DB_VERSION, '0' ); 370 // Bug fix #14: Updated to version 1.1 for service_id index 371 if ( version_compare( $db_version, '1.1', '<' ) ) { 434 435 // Create table if it doesn't exist OR if version is outdated 436 if ( ! $table_exists || version_compare( $db_version, '1.1', '<' ) ) { 372 437 $this->create_queue_table(); 373 438 } … … 416 481 */ 417 482 public function maybe_create_logs_table() { 483 global $wpdb; 484 485 $table_name = $wpdb->prefix . self::LOGS_TABLE; 486 487 // Check if table physically exists 488 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 489 $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name; 490 418 491 $db_version = get_option( self::OPTION_LOGS_DB_VERSION, '0' ); 419 if ( version_compare( $db_version, '1.0', '<' ) ) { 492 493 // Create table if it doesn't exist OR if version is outdated 494 if ( ! $table_exists || version_compare( $db_version, '1.0', '<' ) ) { 420 495 $this->create_logs_table(); 421 $this->migrate_logs_from_options(); 496 // Only migrate if table was just created (not an update) 497 if ( ! $table_exists ) { 498 $this->migrate_logs_from_options(); 499 } 422 500 } 423 501 } … … 1023 1101 1024 1102 <?php 1103 // Get queue items (pending and retry) 1104 $queue_items = $this->get_queue_items(null, 100); 1105 $queue_count = count($queue_items); 1106 1025 1107 // Pagination - now using database 1026 1108 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Pagination parameter, no action taken … … 1038 1120 } 1039 1121 ?> 1122 1123 <?php if ($queue_count > 0): ?> 1124 <!-- Queue Section --> 1125 <div class="botsubmit-queue-section" style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 20px; margin-bottom: 25px;"> 1126 <h3 style="margin: 0 0 10px 0; color: #856404; display: flex; align-items: center; gap: 8px;"> 1127 <span class="dashicons dashicons-clock" style="font-size: 20px;"></span> 1128 <?php 1129 /* translators: %d: number of items in queue */ 1130 printf(esc_html__('Queue: %d tasks waiting', 'botsubmit'), $queue_count); 1131 ?> 1132 </h3> 1133 <p style="margin: 0 0 15px 0; color: #856404; font-size: 13px;"> 1134 <?php 1135 /* translators: 1: batch size, 2: interval */ 1136 printf( 1137 esc_html__('Processing: %1$d tasks every %2$d minutes (WP Cron)', 'botsubmit'), 1138 self::QUEUE_BATCH_SIZE, 1139 5 1140 ); 1141 ?> 1142 </p> 1143 1144 <?php 1145 // Get service names for display 1146 $available_services = $this->get_available_services(); 1147 $service_names = ['indexnow' => 'IndexNow']; 1148 foreach ($available_services as $sid => $sinfo) { 1149 $service_names[$sid] = $sinfo['name']; 1150 } 1151 ?> 1152 <table class="widefat" style="border: none; background: transparent;"> 1153 <thead> 1154 <tr style="background: rgba(0,0,0,0.05);"> 1155 <th style="padding: 10px; border: none;"><?php esc_html_e('URL', 'botsubmit'); ?></th> 1156 <th style="padding: 10px; border: none; width: 120px;"><?php esc_html_e('Service', 'botsubmit'); ?></th> 1157 <th style="padding: 10px; border: none; width: 100px;"><?php esc_html_e('Status', 'botsubmit'); ?></th> 1158 <th style="padding: 10px; border: none; width: 150px;"><?php esc_html_e('Added', 'botsubmit'); ?></th> 1159 <th style="padding: 10px; border: none; width: 120px;"><?php esc_html_e('Actions', 'botsubmit'); ?></th> 1160 </tr> 1161 </thead> 1162 <tbody> 1163 <?php foreach ($queue_items as $item): ?> 1164 <tr style="background: #fff;"> 1165 <td style="padding: 10px; border-bottom: 1px solid #ffc107;"> 1166 <a href="<?php echo esc_url($item->url); ?>" target="_blank" rel="noopener noreferrer" style="color: #856404;"> 1167 <?php echo esc_html($item->url); ?> 1168 </a> 1169 <?php if (!empty($item->context)): ?> 1170 <br><small style="color: #a08000;"><?php echo esc_html($item->context); ?></small> 1171 <?php endif; ?> 1172 </td> 1173 <td style="padding: 10px; border-bottom: 1px solid #ffc107;"> 1174 <strong style="color: #856404;"> 1175 <?php echo esc_html(isset($service_names[$item->service_id]) ? $service_names[$item->service_id] : $item->service_id); ?> 1176 </strong> 1177 </td> 1178 <td style="padding: 10px; border-bottom: 1px solid #ffc107;"> 1179 <?php 1180 $status_class = $item->status === 'pending' ? 'background: #ffc107; color: #000;' : 'background: #fd7e14; color: #fff;'; 1181 ?> 1182 <span style="display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; <?php echo esc_attr($status_class); ?>"> 1183 <?php echo esc_html(ucfirst($item->status)); ?> 1184 </span> 1185 <?php if ($item->attempts > 0): ?> 1186 <br><small style="color: #a08000;"><?php 1187 /* translators: %d: number of attempts */ 1188 printf(esc_html__('Attempts: %d', 'botsubmit'), $item->attempts); 1189 ?></small> 1190 <?php endif; ?> 1191 </td> 1192 <td style="padding: 10px; border-bottom: 1px solid #ffc107; color: #856404; font-size: 12px;"> 1193 <?php echo esc_html($item->created_at); ?> 1194 </td> 1195 <td style="padding: 10px; border-bottom: 1px solid #ffc107;"> 1196 <button type="button" 1197 class="botsubmit-send-now-btn button button-small" 1198 data-id="<?php echo esc_attr($item->id); ?>" 1199 data-nonce="<?php echo esc_attr(wp_create_nonce('botsubmit_send_queue_item_' . $item->id)); ?>" 1200 style="background: #28a745; border-color: #28a745; color: #fff;"> 1201 <?php esc_html_e('Send Now', 'botsubmit'); ?> 1202 </button> 1203 </td> 1204 </tr> 1205 <?php endforeach; ?> 1206 </tbody> 1207 </table> 1208 </div> 1209 <?php endif; ?> 1210 1211 <!-- Submission Log Section --> 1212 <h3 class="botsubmit-section-title"><?php esc_html_e('Submission Log', 'botsubmit'); ?></h3> 1040 1213 1041 1214 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> … … 1421 1594 1422 1595 // Categories 1423 public function handle_created_category($term_id, $tt_id = 0 , $taxonomy = 'category') {1424 $this->maybe_send_term($term_id, $taxonomy, 'CATEGORY CREATED');1425 } 1426 public function handle_edited_category($term_id, $tt_id = 0 , $taxonomy = 'category') {1596 public function handle_created_category($term_id, $tt_id = 0) { 1597 $this->maybe_send_term($term_id, 'category', 'CATEGORY CREATED'); 1598 } 1599 public function handle_edited_category($term_id, $tt_id = 0) { 1427 1600 $upd = (array) get_option(self::OPTION_SEND_UPDATE, []); 1428 1601 if (empty($upd['categories'])) return; 1429 $this->maybe_send_term($term_id, $taxonomy, 'CATEGORY EDITED');1602 $this->maybe_send_term($term_id, 'category', 'CATEGORY EDITED'); 1430 1603 } 1431 1604 1432 1605 // Tags 1433 public function handle_created_post_tag($term_id, $tt_id = 0 , $taxonomy = 'post_tag') {1434 $this->maybe_send_term($term_id, $taxonomy, 'TAG CREATED');1435 } 1436 public function handle_edited_post_tag($term_id, $tt_id = 0 , $taxonomy = 'post_tag') {1606 public function handle_created_post_tag($term_id, $tt_id = 0) { 1607 $this->maybe_send_term($term_id, 'post_tag', 'TAG CREATED'); 1608 } 1609 public function handle_edited_post_tag($term_id, $tt_id = 0) { 1437 1610 $upd = (array) get_option(self::OPTION_SEND_UPDATE, []); 1438 1611 if (empty($upd['tags'])) return; 1439 $this->maybe_send_term($term_id, $taxonomy, 'TAG EDITED');1612 $this->maybe_send_term($term_id, 'post_tag', 'TAG EDITED'); 1440 1613 } 1441 1614 … … 1828 2001 $table_name = $wpdb->prefix . self::LOGS_TABLE; 1829 2002 2003 // Check if table exists 2004 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 2005 $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ); 2006 2007 if ( $table_exists !== $table_name ) { 2008 // Fallback to wp_options if table doesn't exist 2009 $this->save_global_log_legacy( $url, $message ); 2010 return; 2011 } 2012 1830 2013 // Determine success status 1831 2014 $is_success = $this->is_result_successful( $message ) ? 1 : 0; … … 1860 2043 // Cleanup old logs beyond MAX_LOG_ROWS 1861 2044 $this->cleanup_old_logs(); 2045 } 2046 2047 /** 2048 * Legacy log storage in wp_options (fallback when table doesn't exist) 2049 */ 2050 private function save_global_log_legacy($url, $message) { 2051 $log = get_option( self::OPTION_GLOBAL_LOG, [] ); 2052 $log[] = wp_date( 'Y-m-d H:i:s' ) . ' — ' . $url . ' — ' . $message; 2053 2054 // Keep only last MAX_LOG_ROWS entries 2055 if ( count( $log ) > self::MAX_LOG_ROWS ) { 2056 $log = array_slice( $log, -self::MAX_LOG_ROWS ); 2057 } 2058 2059 update_option( self::OPTION_GLOBAL_LOG, $log ); 1862 2060 } 1863 2061 … … 2316 2514 } 2317 2515 2318 /* ======================= 2319 * API Key Validation 2320 * ======================= */ 2516 /** 2517 * AJAX handler for sending queue item immediately 2518 */ 2519 public function ajax_send_queue_item() { 2520 // Check permissions 2521 if ( ! current_user_can( 'manage_options' ) ) { 2522 wp_send_json_error( __( 'Access denied', 'botsubmit' ) ); 2523 } 2524 2525 $item_id = isset( $_POST['item_id'] ) ? intval( $_POST['item_id'] ) : 0; 2526 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; 2527 2528 if ( empty( $item_id ) ) { 2529 wp_send_json_error( __( 'Missing parameters', 'botsubmit' ) ); 2530 } 2531 2532 // Verify nonce 2533 if ( ! wp_verify_nonce( $nonce, 'botsubmit_send_queue_item_' . $item_id ) ) { 2534 wp_send_json_error( __( 'Security check failed', 'botsubmit' ) ); 2535 } 2536 2537 global $wpdb; 2538 $table_name = $wpdb->prefix . self::QUEUE_TABLE; 2539 2540 // Get the queue item 2541 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 2542 $item = $wpdb->get_row( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe 2543 $wpdb->prepare( 2544 "SELECT * FROM $table_name WHERE id = %d", 2545 $item_id 2546 ) 2547 ); 2548 2549 if ( ! $item ) { 2550 wp_send_json_error( __( 'Queue item not found', 'botsubmit' ) ); 2551 } 2552 2553 // Process this specific item - each queue item is for ONE service 2554 $url = $item->url; 2555 $context = $item->context; 2556 $service_id = $item->service_id; 2557 2558 $result = ''; 2559 $service_name = ''; 2560 2561 // Send to the specific service 2562 if ( $service_id === 'indexnow' ) { 2563 $result = $this->send_to_indexnow( $url ); 2564 $service_name = 'IndexNow'; 2565 } else { 2566 // Paid service 2567 $api_keys = $this->get_api_keys(); 2568 $available_services = $this->get_available_services(); 2569 2570 if ( ! isset( $api_keys[ $service_id ] ) || empty( $api_keys[ $service_id ] ) ) { 2571 wp_send_json_error( __( 'API key not set for this service', 'botsubmit' ) ); 2572 } 2573 2574 if ( ! isset( $available_services[ $service_id ] ) ) { 2575 wp_send_json_error( __( 'Unknown service', 'botsubmit' ) ); 2576 } 2577 2578 $result = $this->send_to_service( $url, $service_id, $api_keys[ $service_id ], $available_services[ $service_id ] ); 2579 $service_name = $available_services[ $service_id ]['name']; 2580 } 2581 2582 $is_success = $this->is_result_successful( $result ); 2583 2584 // Log the result (format consistent with cron processing) 2585 $log_msg = ( ! empty( $context ) ? $context . "\n" : 'MANUAL SEND\n' ) . $service_name . ': ' . $result; 2586 $this->save_global_log( $url, $log_msg ); 2587 2588 // Remove from queue (regardless of success/failure - user initiated immediate send) 2589 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 2590 $wpdb->delete( $table_name, [ 'id' => $item_id ], [ '%d' ] ); 2591 2592 if ( $is_success ) { 2593 wp_send_json_success( [ 2594 'message' => __( 'URL successfully sent', 'botsubmit' ), 2595 'result' => $service_name . ': ' . $result, 2596 ] ); 2597 } else { 2598 // Still return success for JS (item was processed), but mark as partial 2599 wp_send_json_success( [ 2600 'message' => __( 'URL sent with errors', 'botsubmit' ), 2601 'result' => $service_name . ': ' . $result, 2602 'partial' => true, 2603 ] ); 2604 } 2605 } 2321 2606 2322 2607 /* ======================= … … 2405 2690 private function get_exportable_settings( $include_indexnow_key = false, $include_api_keys = false ) { 2406 2691 $settings = [ 2407 'plugin_version' => '1.3. 0',2692 'plugin_version' => '1.3.1', 2408 2693 'export_date' => wp_date( 'Y-m-d H:i:s' ), 2409 2694 'services' => get_option( self::OPTION_SERVICES, [] ), … … 2834 3119 $table_name = $wpdb->prefix . self::QUEUE_TABLE; 2835 3120 3121 // Check if table exists 3122 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 3123 $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ); 3124 if ( $table_exists !== $table_name ) { 3125 return []; 3126 } 3127 2836 3128 if ($status) { 2837 3129 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching … … 2845 3137 } 2846 3138 3139 // Get pending, retry, and processing items (exclude completed and failed) 2847 3140 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 2848 3141 return $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe 2849 3142 $wpdb->prepare( 2850 "SELECT * FROM $table_name WHERE status NOT IN ('completed') ORDER BY created_at DESC LIMIT %d",3143 "SELECT * FROM $table_name WHERE status IN ('pending', 'retry', 'processing') ORDER BY created_at ASC LIMIT %d", 2851 3144 $limit 2852 3145 ) -
botsubmit/trunk/readme.txt
r3464337 r3464366 5 5 Requires at least: 5.0 6 6 Tested up to: 6.9 7 Stable tag: 1.3. 07 Stable tag: 1.3.1 8 8 Requires PHP: 7.2 9 9 License: GPLv2 or later … … 254 254 == Changelog == 255 255 256 = 1.3.1 = 257 * Bug fix: Fixed taxonomy hooks incorrectly receiving array parameter causing "Invalid taxonomy" error 258 * Bug fix: Fixed logs table not being created on fresh installations 259 * Bug fix: Added physical table existence check for seamless updates from older versions 260 * Bug fix: Added fallback to wp_options when logs table doesn't exist 261 * NEW: Queue display in Log tab - see pending tasks with status and timing info 262 * NEW: "Send Now" button - send queued tasks immediately without waiting for cron 263 256 264 = 1.3.0 = 257 265 * Added IndexNow support (FREE) - instant URL submission to Bing, Yandex, Naver, Seznam … … 292 300 == Upgrade Notice == 293 301 302 = 1.3.1 = 303 Bug fix release. Fixes taxonomy submission errors and database table creation issues. New: queue display with "Send Now" button in Log tab. 304 294 305 = 1.3.0 = 295 306 Major update! Free IndexNow support, export/import settings, and encrypted API key storage. Existing settings will be automatically migrated.
Note: See TracChangeset
for help on using the changeset viewer.