Plugin Directory

Changeset 3464366


Ignore:
Timestamp:
02/18/2026 01:40:53 PM (36 hours ago)
Author:
naumov22
Message:

Version 1.3.1

Location:
botsubmit
Files:
18 added
2 edited

Legend:

Unmodified
Added
Removed
  • botsubmit/trunk/botsubmit.php

    r3464337 r3464366  
    33 * Plugin Name: BotSubmit
    44 * 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.0
     5 * Version: 1.3.1
    66 * Requires at least: 5.0
    77 * Requires PHP: 7.2
     
    102102
    103103        // 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);
    108108
    109109        // "Settings" link in plugins list
     
    125125        // AJAX for resending from log
    126126        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']);
    127130
    128131        // AJAX for export/import settings
     
    216219        $log_script = "
    217220            jQuery(document).ready(function($) {
     221                // Resend button handler
    218222                $('.botsubmit-resend-btn').on('click', function() {
    219223                    var btn = $(this);
     
    252256                    });
    253257                });
     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                });
    254310            });
    255311        ";
    256312
    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 );
    258314        wp_enqueue_script( 'botsubmit-log' );
    259315        wp_add_inline_script( 'botsubmit-log', $log_script );
     
    338394        $this->create_queue_table();
    339395
     396        // Create logs table
     397        $this->create_logs_table();
     398
    340399        // Schedule queue processing
    341400        if (!wp_next_scheduled('botsubmit_process_queue')) {
     
    361420
    362421    /**
    363      * Check and create queue table if needed
    364      */
    365     /**
    366422     * Check and create/update queue table if needed
    367423     */
    368424    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
    369433        $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', '<' ) ) {
    372437            $this->create_queue_table();
    373438        }
     
    416481     */
    417482    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
    418491        $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', '<' ) ) {
    420495            $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            }
    422500        }
    423501    }
     
    10231101
    10241102            <?php
     1103            // Get queue items (pending and retry)
     1104            $queue_items = $this->get_queue_items(null, 100);
     1105            $queue_count = count($queue_items);
     1106
    10251107            // Pagination - now using database
    10261108            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Pagination parameter, no action taken
     
    10381120            }
    10391121            ?>
     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>
    10401213
    10411214            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
     
    14211594
    14221595    // 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) {
    14271600        $upd = (array) get_option(self::OPTION_SEND_UPDATE, []);
    14281601        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');
    14301603    }
    14311604
    14321605    // 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) {
    14371610        $upd = (array) get_option(self::OPTION_SEND_UPDATE, []);
    14381611        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');
    14401613    }
    14411614
     
    18282001        $table_name = $wpdb->prefix . self::LOGS_TABLE;
    18292002
     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
    18302013        // Determine success status
    18312014        $is_success = $this->is_result_successful( $message ) ? 1 : 0;
     
    18602043        // Cleanup old logs beyond MAX_LOG_ROWS
    18612044        $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 );
    18622060    }
    18632061
     
    23162514    }
    23172515
    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    }
    23212606
    23222607    /* =======================
     
    24052690    private function get_exportable_settings( $include_indexnow_key = false, $include_api_keys = false ) {
    24062691        $settings = [
    2407             'plugin_version' => '1.3.0',
     2692            'plugin_version' => '1.3.1',
    24082693            'export_date' => wp_date( 'Y-m-d H:i:s' ),
    24092694            'services' => get_option( self::OPTION_SERVICES, [] ),
     
    28343119        $table_name = $wpdb->prefix . self::QUEUE_TABLE;
    28353120
     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
    28363128        if ($status) {
    28373129            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     
    28453137        }
    28463138
     3139        // Get pending, retry, and processing items (exclude completed and failed)
    28473140        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    28483141        return $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe
    28493142            $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",
    28513144                $limit
    28523145            )
  • botsubmit/trunk/readme.txt

    r3464337 r3464366  
    55Requires at least: 5.0
    66Tested up to: 6.9
    7 Stable tag: 1.3.0
     7Stable tag: 1.3.1
    88Requires PHP: 7.2
    99License: GPLv2 or later
     
    254254== Changelog ==
    255255
     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
    256264= 1.3.0 =
    257265* Added IndexNow support (FREE) - instant URL submission to Bing, Yandex, Naver, Seznam
     
    292300== Upgrade Notice ==
    293301
     302= 1.3.1 =
     303Bug fix release. Fixes taxonomy submission errors and database table creation issues. New: queue display with "Send Now" button in Log tab.
     304
    294305= 1.3.0 =
    295306Major 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.