Plugin Directory

Changeset 3373724


Ignore:
Timestamp:
10/06/2025 01:17:03 PM (5 months ago)
Author:
secuseek
Message:

Update plugin to version 1.0.1 - improved filter interface

Location:
secuseek/trunk
Files:
14 edited

Legend:

Unmodified
Added
Removed
  • secuseek/trunk/README.md

    r3373197 r3373724  
    6161**Note:** All minified files are third-party libraries. The plugin's own JavaScript and CSS files are not minified and are included in their readable form in the assets directory.
    6262
     63### Source and Human-Readable Code
     64This plugin ships with human-readable source code. All custom JavaScript and CSS are provided in unminified form under `assets/js/` and `assets/css/`.
     65
     66The plugin also bundles a small number of third-party, minified assets. Their human-readable sources are publicly available here:
     67
     68 - `assets/js/alpinejs.min.js` — Source: `https://github.com/alpinejs/alpine` (Docs/Homepage: `https://alpinejs.dev`)
     69- `assets/css/fontawesome.min.css` — Source: `https://github.com/FortAwesome/Font-Awesome`
     70
     71Build tools are not required to work on this plugin. If build tooling is added in the future, instructions will be documented here.
     72
     73
     74### Direct downloads (exact versions used)
     75- Alpine.js 3.13.10 (minified): `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js`
     76- Font Awesome Free 6.5.0 (fontawesome.min.css): `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css`
     77
     78
    6379### Build Process
    6480
     
    8298- API keys are encrypted using AES-256-CBC encryption
    8399
     100### Used technologies
     101- Alpine.js 3.13.10 — Docs: `https://alpinejs.dev` — Source: `https://github.com/alpinejs/alpine` — Minified CDN: `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js`
     102- Font Awesome Free 6.5.0 — Site: `https://fontawesome.com` — Source: `https://github.com/FortAwesome/Font-Awesome` — CSS CDN: `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css`
     103- WordPress HTTP API (remote requests) — Docs: `https://developer.wordpress.org/plugins/http-api/`
     104- WordPress Cron API (scheduling) — Docs: `https://developer.wordpress.org/plugins/cron/`
     105- WordPress AJAX (admin-ajax) — Docs: `https://developer.wordpress.org/plugins/javascript/ajax/`
     106
    84107### API Integration
    85108
     
    93116  - `GET /api/v1/external/issues/{id}` - Get scan results
    94117  - `DELETE /api/v1/external/schedule-scan/{id}` - Delete scan
     118
     119
    95120
    96121### Frontend Architecture
  • secuseek/trunk/assets/css/findings-filters.css

    r3373197 r3373724  
    130130.secuseek-multiselect .dropdown-item span:last-child {
    131131  margin-left: auto;
    132   color: #22ad5a;
    133   font-size: 13px;
     132  color: #1e9e4a;
     133  font-size: 11px;
    134134}
  • secuseek/trunk/assets/js/alpine-init.js

    r3373197 r3373724  
    1515    }
    1616   
    17     console.log('SecuSeek: Registering secuSeekFindings component');
    1817   
    1918    Alpine.data('secuSeekFindings', function() {
    20         console.log('SecuSeek: Component factory function called');
    2119        return {
    2220            // Initialize with default values to prevent undefined errors
     
    5250           
    5351            init() {
    54                 console.log('SecuSeek: Alpine component init() called');
    5552               
    5653                // Try to get data immediately
     
    148145           
    149146            toggleFilter(type, value) {
    150                 console.log('SecuSeek: toggleFilter called', type, value);
    151147                if (this.filters[type].includes(value)) {
    152148                    this.filters[type] = this.filters[type].filter(function(x) {
     
    155151                } else {
    156152                    this.filters[type].push(value);
     153                }
     154            },
     155           
     156            removeFilterItem(type, value) {
     157                // Alpine.js reactivity: create new array to trigger reactivity
     158                const newFilters = [...this.filters[type]];
     159                const index = newFilters.indexOf(value);
     160                if (index > -1) {
     161                    newFilters.splice(index, 1);
     162                    this.filters[type] = newFilters;
    157163                }
    158164            },
  • secuseek/trunk/assets/js/findings-alpine-component.js

    r3373197 r3373724  
    1111// Prevent Alpine.js from auto-starting until components are registered
    1212if (typeof Alpine !== 'undefined') {
    13     console.log('SecuSeek: Deferring Alpine.js start until component registration');
    1413    Alpine.store('__secuseek_component_ready', false);
    1514}
     
    1716// Ensure Alpine.js is available and register component immediately
    1817if (typeof Alpine !== 'undefined') {
    19     console.log('SecuSeek: Alpine.js already available, registering component immediately');
    2018    registerSecuSeekComponent();
    2119} else {
    2220    // Wait for Alpine.js if not yet loaded
    2321    document.addEventListener('alpine:init', function() {
    24         console.log('SecuSeek: Alpine.js init event received, registering component');
    2522        registerSecuSeekComponent();
    2623    });
     
    2926    document.addEventListener('DOMContentLoaded', function() {
    3027        if (typeof Alpine !== 'undefined' && !Alpine.store('__secuseek_component_ready')) {
    31             console.log('SecuSeek: DOM ready, attempting component registration');
    3228            registerSecuSeekComponent();
    3329        }
     
    3632
    3733function registerSecuSeekComponent() {
     34    // Component already registered in alpine-init.js, skip this registration
     35    return;
     36   
    3837    Alpine.data('secuSeekFindings', function() {
    39         console.log('SecuSeek: Component factory function called');
    4038        return {
    4139            // Initialize with default values to prevent undefined errors
     
    6462           
    6563            init() {
    66                 console.log('SecuSeek: Alpine component init() called');
    6764               
    6865                // Initialize component when data is ready
     
    7168                // Listen for data ready event
    7269                document.addEventListener('secuseek:dataReady', function(event) {
    73                     console.log('SecuSeek: Received dataReady event', event.detail);
    7470                    const data = event.detail[0];
    7571                    if (data && data.findings) {
     
    8177                if (window.SecuSeekData && window.SecuSeekData.getFindingsData) {
    8278                    const findingsData = window.SecuSeekData.getFindingsData();
    83                     console.log('SecuSeek: Data already available, initializing', findingsData);
    8479                    this.initializeWithData(findingsData);
    8580                }
     
    8782                // Fallback: Try to get data from localized variables
    8883                if (!this.dataInitialized && typeof secuseek_findings_data !== 'undefined') {
    89                     console.log('SecuSeek: Using localized data directly', secuseek_findings_data);
    9084                    this.initializeWithData(secuseek_findings_data);
    9185                }
     
    9387           
    9488            initializeWithData(data) {
    95                 console.log('SecuSeek: Initializing component with data', data);
    9689               
    9790                if (!data || typeof data !== 'object') {
     
    115108                }, { deep: true });
    116109               
    117                 console.log('SecuSeek: Component initialized successfully', {
    118110                    findings: this.allFindings.length,
    119111                    categories: this.categories.length,
     
    124116           
    125117            toggleSelectCard(idx) {
    126                 console.log('SecuSeek: toggleSelectCard called with idx:', idx);
    127118                this.selected = idx;
    128119            },
    129120           
    130121            toggleFilter(type, value) {
    131                 console.log('SecuSeek: toggleFilter called', type, value);
    132122                if (this.filters[type].includes(value)) {
    133123                    this.filters[type] = this.filters[type].filter(function(x) {
     
    137127                    this.filters[type].push(value);
    138128                }
     129            },
     130           
     131            removeFilterItem(type, value) {
     132                this.filters[type] = this.filters[type].filter(function(x) {
     133                    return x !== value;
     134                });
    139135            },
    140136           
     
    158154                }
    159155               
    160                 console.log('SecuSeek: Filtered findings updated', this.filteredFindings.length, 'of', this.allFindings.length);
    161156            }
    162157        };
    163158    });
    164     console.log('SecuSeek: Component registered successfully');
    165159   
    166160    // Mark component as ready
     
    169163            Alpine.store('__secuseek_component_ready', true);
    170164        } catch (e) {
    171             console.log('SecuSeek: Could not set Alpine store, continuing anyway');
    172165        }
    173166    }
  • secuseek/trunk/assets/js/findings-initialization.js

    r3373197 r3373724  
    1616    const container = document.getElementById('secuseek-findings-container');
    1717   
    18     console.log('SecuSeek: DOM ready, loader should be visible');
    1918   
    2019    // Wait for component registration
     
    3029   
    3130    waitForComponent(function() {
    32         console.log('SecuSeek: Initializing Alpine.js component manually');
    3331        const element = document.getElementById('secuseek-findings-container');
    3432        if (element && typeof Alpine !== 'undefined') {
     
    4139            // Listen for component ready event for precise timing
    4240            document.addEventListener('secuseek:componentReady', function(event) {
    43                 console.log('SecuSeek: Component fully ready, hiding loader');
    4441                // Hide loader with smooth fade out
    4542                if (loader) {
     
    5552            setTimeout(function() {
    5653                if (loader && loader.style.display !== 'none') {
    57                     console.log('SecuSeek: Fallback loader hide after timeout');
    5854                    loader.style.transition = 'opacity 0.3s ease';
    5955                    loader.style.opacity = '0';
  • secuseek/trunk/assets/js/no-findings-inline.js

    r3373197 r3373724  
    1919    button.disabled = true;
    2020   
     21    // First clear cache, then start scan
    2122    jQuery.post(sekuseek_manual_scan.ajaxurl, {
    22         action: 'secuseek_start_scan',
     23        action: 'secuseek_clear_cache',
    2324        nonce: sekuseek_manual_scan.nonce
    24     }, function(response) {
    25         if (response.success) {
    26             alert('Scan started successfully! The page will refresh in a moment...');
    27             setTimeout(function() {
    28                 window.location.reload();
    29             }, 2000);
    30         } else {
    31             alert('An error occurred! ' + (response.data && response.data.message ? response.data.message : 'Please try again.'));
     25    }, function(cacheResponse, status, xhr) {
     26        // Now start the scan
     27        startNewScan();
     28    }).fail(function(xhr, status, error) {
     29        // Proceed with scan even if cache clear fails
     30        startNewScan();
     31    });
     32   
     33    function startNewScan() {
     34        jQuery.post(sekuseek_manual_scan.ajaxurl, {
     35            action: 'secuseek_start_scan',
     36            nonce: sekuseek_manual_scan.nonce
     37        }, function(response, status, xhr) {
     38           
     39            if (response.success) {
     40                if (response.data.countdown) {
     41                    // Clear any existing notices first
     42                    var oldNotice = document.querySelector('.secuseek-inline-notice');
     43                    if (oldNotice) oldNotice.remove();
     44                   
     45                    // Show countdown message and reload after 5 seconds
     46                    var countdownMessage = 'Scan started successfully! Refreshing in <span id="countdown">5</span> seconds...';
     47                    renderNoticeInline(countdownMessage, '', false, true);
     48                   
     49                    // Start countdown
     50                    let timeLeft = 5;
     51                    const countdownElement = document.getElementById("countdown");
     52                    if (countdownElement) {
     53                        const countdownInterval = setInterval(function() {
     54                            timeLeft--;
     55                            if (countdownElement) {
     56                                countdownElement.textContent = timeLeft;
     57                            }
     58                            if (timeLeft <= 0) {
     59                                clearInterval(countdownInterval);
     60                                window.location.reload();
     61                            }
     62                        }, 1000);
     63                    } else {
     64                        // Fallback: reload after 5 seconds
     65                        setTimeout(function() {
     66                            window.location.reload();
     67                        }, 5000);
     68                    }
     69                } else {
     70                    // Success notice (optional)
     71                    setTimeout(function() { window.location.reload(); }, 1200);
     72                }
     73            } else {
     74                var apiMessage = (response && response.data && response.data.message) ? response.data.message : (response && response.data) ? response.data : 'Please try again.';
     75                var apiDetails = (response && response.data && response.data.details) ? response.data.details : (response && response.data && response.data.debug) ? response.data.debug : '';
     76                var isNoScansFound = (response && response.data && response.data.error_type === 'no_scans_found');
     77               
     78                // If data is a string (wp_send_json_error format), use it directly
     79                if (typeof response?.data === 'string') {
     80                    apiMessage = response.data;
     81                    apiDetails = '';
     82                }
     83               
     84               
     85                renderNoticeInline(apiMessage, apiDetails, isNoScansFound);
     86                // Restore button
     87                button.innerHTML = originalText;
     88                button.disabled = false;
     89            }
     90        }).fail(function(xhr, status, error) {
     91            renderNoticeInline('Network error. Please check your connection and try again.', '', false);
    3292            // Restore button
    3393            button.innerHTML = originalText;
    3494            button.disabled = false;
    35         }
    36     }).fail(function(xhr, status, error) {
    37         console.error('SecuSeek AJAX Error:', error);
    38         alert('Network error. Please check your connection and try again.');
    39         // Restore button
    40         button.innerHTML = originalText;
    41         button.disabled = false;
    42     });
     95        });
     96    }
     97}
     98
     99function renderNoticeInline(message, details, isNoScansFound, isSuccess) {
     100    var old = document.querySelector('.secuseek-inline-notice');
     101    if (old) old.remove();
     102   
     103    // Determine colors and icons based on type
     104    var color, bgColor, borderColor, icon, title, noticeClass;
     105    if (isSuccess) {
     106        color = '#00a32a';
     107        bgColor = '#c3e6cb';
     108        borderColor = '#00a32a';
     109        icon = '✅';
     110        title = 'Scan Started Successfully';
     111        noticeClass = 'notice-success';
     112    } else if (isNoScansFound) {
     113        color = '#0073aa';
     114        bgColor = '#c5d9ed';
     115        borderColor = '#0073aa';
     116        icon = 'ℹ️';
     117        title = 'No Scans Found';
     118        noticeClass = 'notice-info';
     119    } else {
     120        color = '#d63638';
     121        bgColor = '#f5c2c7';
     122        borderColor = '#d63638';
     123        icon = '❌';
     124        title = 'Scan Failed';
     125        noticeClass = 'notice-error';
     126    }
     127   
     128    // Show notice above the scan button
     129    var scanButton = document.querySelector('#start-scan') || document.querySelector('button[onclick*="myCustomScanFunction"]');
     130    var targetContainer = scanButton ? scanButton.parentElement : (document.querySelector('#secuseek-findings-container') || document.querySelector('.wrap') || document.body);
     131    var div = document.createElement('div');
     132    div.className = 'secuseek-inline-notice notice ' + noticeClass + ' is-dismissible';
     133    div.style.cssText = 'margin:20px auto;padding:12px 15px;border-left:4px solid ' + borderColor + ';background:#fff;border:1px solid ' + bgColor + ';border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,.04);max-width:600px;width:100%;display:block;';
     134    div.innerHTML =
     135        '<div style="display:flex;align-items:flex-start;">' +
     136            '<div style="margin-right:8px;font-size:16px;color:' + color + ';">' + icon + '</div>' +
     137            '<div style="flex:1;">' +
     138                '<strong style="color:' + color + ';font-size:14px;display:block;margin-bottom:4px;">' + title + '</strong>' +
     139                '<div style="color:#2c3338;font-size:13px;line-height:1.4;">' + (message || 'Unknown error') + '</div>' +
     140                (details ? '<div style="margin-top:8px;padding:8px 12px;background:#f6f7f7;border-radius:3px;font-size:12px;color:#50575e;font-family:monospace;white-space:pre-wrap;border:1px solid #c3c4c7;">' + details + '</div>' : '') +
     141            '</div>' +
     142        '</div>';
     143   
     144    // Insert above the scan button
     145    if (scanButton) {
     146        scanButton.parentNode.insertBefore(div, scanButton);
     147    } else {
     148        targetContainer.insertBefore(div, targetContainer.firstChild);
     149    }
     150   
     151    // Auto-dismiss after 10 seconds for errors and info (not for success)
     152    if (!isSuccess) {
     153        setTimeout(function() {
     154            div.style.transition = 'opacity 0.3s';
     155            div.style.opacity = '0';
     156            setTimeout(function() { if (div.parentNode) div.parentNode.removeChild(div); }, 300);
     157        }, 10000);
     158    }
    43159}
    44160
  • secuseek/trunk/assets/js/scan-control.js

    r3373197 r3373724  
    44    console.log('Nonce:', secuseek_ajax.nonce);
    55   
     6    // Render notice function with better styling and positioning
     7    function renderNotice($container, type, message, details) {
     8        const existing = $('.secuseek-inline-notice');
     9        if (existing.length) existing.remove();
     10       
     11        // Determine colors and icons based on type
     12        let color, bgColor, borderColor, icon, title;
     13        if (type === 'success') {
     14            color = '#00a32a';
     15            bgColor = '#c3e6cb';
     16            borderColor = '#00a32a';
     17            icon = '✅';
     18            title = 'Scan Started Successfully';
     19        } else if (type === 'info') {
     20            color = '#0073aa';
     21            bgColor = '#c5d9ed';
     22            borderColor = '#0073aa';
     23            icon = 'ℹ️';
     24            title = 'No Scans Found';
     25        } else {
     26            color = '#d63638';
     27            bgColor = '#f5c2c7';
     28            borderColor = '#d63638';
     29            icon = '❌';
     30            title = 'Scan Failed';
     31        }
     32       
     33        // Show notice above the scan button
     34        const $scanButton = $('#start-scan');
     35        const $targetContainer = $scanButton.length ? $scanButton.parent() : ($('#secuseek-findings-container').length ? $('#secuseek-findings-container') : $('.wrap'));
     36        const $notice = $(
     37            '<div class="secuseek-inline-notice notice notice-' + (type || 'error') + ' is-dismissible" style="margin:20px auto;padding:12px 15px;border-left:4px solid ' + borderColor + ';background:#fff;border:1px solid ' + bgColor + ';border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,.04);max-width:600px;width:100%;display:block;">' +
     38                '<div style="display:flex;align-items:flex-start;">' +
     39                    '<div style="margin-right:8px;font-size:16px;color:' + color + ';">' + icon + '</div>' +
     40                    '<div style="flex:1;">' +
     41                        '<strong style="color:' + color + ';font-size:14px;display:block;margin-bottom:4px;">' + title + '</strong>' +
     42                        '<div style="color:#2c3338;font-size:13px;line-height:1.4;">' + $('<div/>').text(message || 'Unknown error').html() + '</div>' +
     43                        (details ? '<div style="margin-top:8px;padding:8px 12px;background:#f6f7f7;border-radius:3px;font-size:12px;color:#50575e;font-family:monospace;white-space:pre-wrap;border:1px solid #c3c4c7;">' + $('<div/>').text(details).html() + '</div>' : '') +
     44                    '</div>' +
     45                '</div>' +
     46            '</div>'
     47        );
     48       
     49        // Insert above the scan button
     50        if ($scanButton.length) {
     51            $scanButton.before($notice);
     52        } else {
     53            $targetContainer.prepend($notice);
     54        }
     55       
     56        // Auto-dismiss after 10 seconds for errors and info
     57        if (type !== 'success') {
     58            setTimeout(function() {
     59                $notice.fadeOut(300, function() { $(this).remove(); });
     60            }, 10000);
     61        }
     62    }
     63   
    664    $('#start-scan').on('click', function(e) {
    765        e.preventDefault();
     
    1977        $message.text('Starting scan...');
    2078       
    21         // Start new scan with POST request
     79        // First clear cache, then start new scan
    2280        $.ajax({
    2381            url: secuseek_ajax.ajax_url,
     
    2684            dataType: 'json',
    2785            data: {
    28                 action: 'secuseek_start_scan',
     86                action: 'secuseek_clear_cache',
    2987                nonce: secuseek_ajax.nonce
    3088            },
    31             beforeSend: function(xhr) {
    32                 console.log('Sending AJAX request to:', secuseek_ajax.ajax_url);
    33                 console.log('Request data:', {
     89            success: function(cacheResponse, status, xhr) {
     90                // Now start the scan
     91                startScan();
     92            },
     93            error: function(xhr, status, error) {
     94                // Proceed with scan even if cache clear fails
     95                startScan();
     96            }
     97        });
     98       
     99        function startScan() {
     100            // Start new scan with POST request
     101            $.ajax({
     102                url: secuseek_ajax.ajax_url,
     103                type: 'POST',
     104                method: 'POST',
     105                dataType: 'json',
     106                data: {
    34107                    action: 'secuseek_start_scan',
    35108                    nonce: secuseek_ajax.nonce
    36                 });
     109                },
     110            beforeSend: function(xhr) {
     111                // Request starting
    37112            },
    38             success: function(response) {
    39                 console.log('Start scan response:', response);
     113            success: function(response, status, xhr) {
    40114               
    41115                if (response.success && response.data.schedule_id) {
    42                     $message.text('Scan started successfully. Checking status...');
    43                     startStatusCheck(response.data.schedule_id);
     116                    if (response.data.countdown) {
     117                        // Clear any existing notices first
     118                        $('.secuseek-inline-notice').remove();
     119                       
     120                        // Show countdown message and reload after 5 seconds
     121                        $message.html('✅ Scan started successfully! Refreshing in <span id="countdown">5</span> seconds...');
     122                       
     123                        // Start countdown
     124                        let timeLeft = 5;
     125                        const countdownElement = document.getElementById("countdown");
     126                        if (countdownElement) {
     127                            const countdownInterval = setInterval(function() {
     128                                timeLeft--;
     129                                if (countdownElement) {
     130                                    countdownElement.textContent = timeLeft;
     131                                }
     132                                if (timeLeft <= 0) {
     133                                    clearInterval(countdownInterval);
     134                                    location.reload();
     135                                }
     136                            }, 1000);
     137                        } else {
     138                            // Fallback: reload after 5 seconds
     139                            setTimeout(function() {
     140                                location.reload();
     141                            }, 5000);
     142                        }
     143                    } else {
     144                        $message.text('Scan started successfully. Checking status...');
     145                        startStatusCheck(response.data.schedule_id);
     146                    }
    44147                } else {
    45                     $message.text('Failed to start scan: ' + (response.data?.message || 'Unknown error'));
     148                    // Handle wp_send_json_error response format
     149                    let apiMessage = response?.data?.message || response?.data || 'Unknown error';
     150                    let apiDetails = response?.data?.details || response?.data?.debug || undefined;
     151                    let isNoScansFound = response?.data?.error_type === 'no_scans_found';
     152                   
     153                    // If data is a string (wp_send_json_error format), use it directly
     154                    if (typeof response?.data === 'string') {
     155                        apiMessage = response.data;
     156                        apiDetails = undefined;
     157                    }
     158                   
     159                   
     160                    if (isNoScansFound) {
     161                        // Show as info notice for no scans found
     162                        renderNotice($status, 'info', apiMessage, apiDetails);
     163                    } else {
     164                        renderNotice($status, 'error', apiMessage, apiDetails);
     165                    }
     166                    $message.text('');
    46167                    $button.prop('disabled', false);
    47168                }
    48             },
     169                },
    49170            error: function(xhr, status, error) {
    50                 console.error('Start scan error:', error);
    51                 console.error('Status:', status);
    52                 console.error('Response:', xhr.responseText);
    53                 $message.text('Error starting scan: ' + error);
    54                 $button.prop('disabled', false);
    55             }
    56         });
     171                    renderNotice($status, 'error', 'Network/Server error while starting scan', error);
     172                    $message.text('');
     173                    $button.prop('disabled', false);
     174                }
     175            });
     176        }
    57177    });
    58178   
     
    77197                },
    78198                beforeSend: function(xhr) {
    79                     console.log('Sending status check request');
    80                 },
    81                 success: function(response) {
    82                     console.log('Status check response:', response);
     199                    // Status check starting
     200                },
     201                success: function(response, status, xhr) {
    83202                   
    84203                    if (response.status?.isCompleted) {
     
    102221                },
    103222                error: function(xhr, status, error) {
    104                     console.error('Status check error:', error);
    105                     console.error('Status:', status);
    106                     console.error('Response:', xhr.responseText);
    107223                    $message.text('Error checking status: ' + error);
    108224                    $button.prop('disabled', false);
  • secuseek/trunk/includes/auth-handler.php

    r3373197 r3373724  
    517517                error_log('secuseek_run_first_scan_if_needed: POST failed: ' . $scan_result['error']);
    518518            }
     519            // Store error message for display on page refresh
     520            set_transient('secuseek_scan_error_' . get_current_user_id(), $scan_result['error'], 300); // 5 minutes
    519521        }
    520522    }
  • secuseek/trunk/includes/control.php

    r3373197 r3373724  
    299299    }
    300300   
    301     // Start a new scan
     301    // Create a new scan (POST request)
     302    $create_result = secuseek_create_schedule_scan();
     303    if (isset($create_result['error'])) {
     304        return array('error' => $create_result['error']);
     305    }
     306   
     307    // Get the schedule information (GET request)
    302308    $result = secuseek_fetch_schedule_scan();
    303309    if (isset($result['error'])) {
  • secuseek/trunk/includes/fetcher.php

    r3373197 r3373724  
    4040 */
    4141function secuseek_create_schedule_scan() {
    42     if (defined('WP_DEBUG') && WP_DEBUG) {
    43         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    44         error_log('secuseek_create_schedule_scan: called');
    45     }
    4642    $headers = secuseek_get_api_headers(true);
    4743    if (!$headers) {
    48         if (defined('WP_DEBUG') && WP_DEBUG) {
    49             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    50             error_log('secuseek_create_schedule_scan: API key not found');
    51         }
    5244        return array('error' => 'API key not found', 'debug' => 'API key not found');
    5345    }
     
    5648    $site_url = preg_replace('#^https?://#', '', $site_url);
    5749    $body = array('url' => $site_url, 'scanFrequency' => 1);
    58     if (defined('WP_DEBUG') && WP_DEBUG) {
    59         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    60         error_log('API REQUEST [create_schedule_scan] URL: ' . $api_url);
    61     }
    62     if (defined('WP_DEBUG') && WP_DEBUG) {
    63         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
    64         error_log('API REQUEST [create_schedule_scan] HEADERS: ' . print_r($headers, true));
    65     }
    66     if (defined('WP_DEBUG') && WP_DEBUG) {
    67         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    68         error_log('API REQUEST [create_schedule_scan] BODY: ' . json_encode($body));
    69     }
    7050            $result = Secuseek_Fetcher_Service::post($api_url, json_encode($body), $headers);
    71         if (!$result['success']) {
    72             if (defined('WP_DEBUG') && WP_DEBUG) {
    73                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    74                 error_log('secuseek_create_schedule_scan: Secuseek_Fetcher_Service::post error: ' . $result['error']);
    75             }
     51        // Handle network/connection errors only (not HTTP status errors like 400, 403)
     52        if (!$result['success'] && $result['http_code'] === 0) {
    7653        return array('error' => $result['error'], 'debug' => $result['error']);
    7754    }
    7855    $status_code = $result['http_code'];
    7956    $body_resp = $result['response'];
    80     if (defined('WP_DEBUG') && WP_DEBUG) {
    81         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
    82         error_log('API RESPONSE [create_schedule_scan]: ' . print_r($result, true));
    83     }
    84     if (defined('WP_DEBUG') && WP_DEBUG) {
    85         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    86         error_log('API RESPONSE BODY [create_schedule_scan]: ' . $body_resp);
    87     }
    8857    if ($status_code === 200) {
    8958        $data = json_decode($body_resp, true);
     59       
    9060        if (is_string($data)) {
    9161            // API returns id string directly
    9262            secuseek_save_user_job_schedule_id($data);
     63            // Clear old scan data and save new scan ID
     64            secuseek_delete_last_scan_id();
     65            secuseek_save_last_scan_id($data);
    9366            // Record scan creation timestamp for timing-aware deletion detection
    9467            if (function_exists('secuseek_record_scan_creation')) {
     
    9972            // If the API returns as an object
    10073            secuseek_save_user_job_schedule_id($data['id']);
     74            // Clear old scan data and save new scan ID
     75            secuseek_delete_last_scan_id();
     76            secuseek_save_last_scan_id($data['id']);
    10177            // Record scan creation timestamp for timing-aware deletion detection
    10278            if (function_exists('secuseek_record_scan_creation')) {
     
    10480            }
    10581            return array('success' => true, 'id' => $data['id'], 'debug' => 'Scan created successfully');
    106         }
    107         return array('error' => 'No id returned from API', 'debug' => $body_resp);
     82        } elseif (isset($data['scanId'])) {
     83            // Alternative field name
     84            secuseek_save_user_job_schedule_id($data['scanId']);
     85            secuseek_delete_last_scan_id();
     86            secuseek_save_last_scan_id($data['scanId']);
     87            if (function_exists('secuseek_record_scan_creation')) {
     88                secuseek_record_scan_creation();
     89            }
     90            return array('success' => true, 'id' => $data['scanId'], 'debug' => 'Scan created successfully with scanId field');
     91        } elseif (isset($data['result']['id'])) {
     92            // Nested result object
     93            secuseek_save_user_job_schedule_id($data['result']['id']);
     94            secuseek_delete_last_scan_id();
     95            secuseek_save_last_scan_id($data['result']['id']);
     96            if (function_exists('secuseek_record_scan_creation')) {
     97                secuseek_record_scan_creation();
     98            }
     99            return array('success' => true, 'id' => $data['result']['id'], 'debug' => 'Scan created successfully with nested result');
     100        }
     101        // Enhanced debug info for troubleshooting
     102        $debug_info = array(
     103            'status_code' => $status_code,
     104            'response_body' => $body_resp,
     105            'decoded_data' => $data,
     106            'data_type' => gettype($data),
     107            'available_keys' => is_array($data) ? array_keys($data) : 'not_array'
     108        );
     109        return array(
     110            'error' => 'No id returned from API',
     111            'debug' => json_encode($debug_info),
     112            'debug_raw' => $body_resp
     113        );
     114    } elseif ($status_code === 400) {
     115        // Handle 400 Bad Request - return error immediately
     116        $error_data = json_decode($body_resp, true);
     117        if ($error_data && isset($error_data['errors'])) {
     118            $error_messages = array();
     119            foreach ($error_data['errors'] as $field => $messages) {
     120                if (is_array($messages)) {
     121                    foreach ($messages as $message) {
     122                        if ($field === 'url') {
     123                            if (strpos($message, 'User already has a scan') !== false) {
     124                                $error_messages[] = 'This website already has an active scan. Please wait for it to complete or try again later.';
     125                            } else {
     126                                $error_messages[] = 'Scan Limit: ' . $message;
     127                            }
     128                        } elseif ($field === '') {
     129                            if (strpos($message, 'Maximum user domain count') !== false) {
     130                                $error_messages[] = 'You have reached the maximum number of websites you can scan. Please delete some existing scans or contact support to increase your limit.';
     131                            } else {
     132                                $error_messages[] = 'Domain Limit: ' . $message;
     133                            }
     134                        } else {
     135                            $error_messages[] = ucfirst($field) . ': ' . $message;
     136                        }
     137                    }
     138                } else {
     139                    $error_messages[] = $messages;
     140                }
     141            }
     142            return array('error' => implode(' | ', $error_messages), 'debug' => $body_resp);
     143        }
     144        return array('error' => $error_data['message'] ?? 'Bad request', 'debug' => $body_resp);
    108145    } elseif ($status_code === 403) {
    109146        return array('error' => 'Invalid API key or access denied', 'debug' => '403 Forbidden');
     
    116153 */
    117154function secuseek_fetch_schedule_scan($user_job_schedule_id = null) {
    118     if (defined('WP_DEBUG') && WP_DEBUG) {
    119         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    120         error_log('secuseek_fetch_schedule_scan: called');
    121     }
    122155    $headers = secuseek_get_api_headers();
    123156    if (!$headers) {
    124         if (defined('WP_DEBUG') && WP_DEBUG) {
    125             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    126             error_log('secuseek_fetch_schedule_scan: API key not found');
    127         }
    128157        return array('error' => 'API key not found', 'debug' => 'API key not found');
    129158    }
     
    133162    }
    134163    if (!$user_job_schedule_id) {
    135         return array('error' => 'No UserJobScheduleId available', 'debug' => 'No UserJobScheduleId available');
     164        return array('error' => 'No scans found. It appears scans have been deleted or no scan has been started yet. Please start a new scan.', 'debug' => 'No UserJobScheduleId available');
    136165    }
    137166    $api_url = secuseek_get_api_base_url() . '/api/v1/external/schedule-scan/' . urlencode($user_job_schedule_id);
    138     if (defined('WP_DEBUG') && WP_DEBUG) {
    139         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    140         error_log('API REQUEST [fetch_schedule_scan] URL: ' . $api_url);
    141     }
    142     if (defined('WP_DEBUG') && WP_DEBUG) {
    143         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
    144         error_log('API REQUEST [fetch_schedule_scan] HEADERS: ' . print_r($headers, true));
    145     }
    146167            $result = Secuseek_Fetcher_Service::get($api_url, $headers);
    147168        if (!$result['success']) {
    148             if (defined('WP_DEBUG') && WP_DEBUG) {
    149                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    150                 error_log('secuseek_fetch_schedule_scan: Secuseek_Fetcher_Service::get error: ' . $result['error']);
    151             }
    152169        return array('error' => $result['error'], 'debug' => $result['error']);
    153170    }
     
    202219
    203220/**
     221 * Get all schedule scan IDs for the user (GetScheduleScanIds endpoint)
     222 */
     223function secuseek_fetch_schedule_scan_ids() {
     224    if (defined('WP_DEBUG') && WP_DEBUG) {
     225        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     226        error_log('secuseek_fetch_schedule_scan_ids: called');
     227    }
     228    $headers = secuseek_get_api_headers();
     229    if (!$headers) {
     230        if (defined('WP_DEBUG') && WP_DEBUG) {
     231            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     232            error_log('secuseek_fetch_schedule_scan_ids: API key not found');
     233        }
     234        return array('error' => 'API key not found', 'debug' => 'API key not found');
     235    }
     236    $api_url = secuseek_get_api_base_url() . '/api/v1/external/schedule-scan-ids';
     237    if (defined('WP_DEBUG') && WP_DEBUG) {
     238        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     239        error_log('API REQUEST [fetch_schedule_scan_ids] URL: ' . $api_url);
     240    }
     241    if (defined('WP_DEBUG') && WP_DEBUG) {
     242        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
     243        error_log('API REQUEST [fetch_schedule_scan_ids] HEADERS: ' . print_r($headers, true));
     244    }
     245    $result = Secuseek_Fetcher_Service::get($api_url, $headers);
     246    if (!$result['success']) {
     247        if (defined('WP_DEBUG') && WP_DEBUG) {
     248            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     249            error_log('secuseek_fetch_schedule_scan_ids: Secuseek_Fetcher_Service::get error: ' . $result['error']);
     250        }
     251        return array('error' => $result['error'], 'debug' => $result['error']);
     252    }
     253    $status_code = $result['http_code'];
     254    $body_resp = $result['response'];
     255    if (defined('WP_DEBUG') && WP_DEBUG) {
     256        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
     257        error_log('API RESPONSE [fetch_schedule_scan_ids]: ' . print_r($result, true));
     258    }
     259    if (defined('WP_DEBUG') && WP_DEBUG) {
     260        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     261        error_log('API RESPONSE BODY [fetch_schedule_scan_ids]: ' . $body_resp);
     262    }
     263    if ($status_code === 200) {
     264        $data = json_decode($body_resp, true);
     265        if (defined('WP_DEBUG') && WP_DEBUG) {
     266            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     267            error_log('secuseek_fetch_schedule_scan_ids: data=' . substr($body_resp,0,300));
     268        }
     269        if (is_array($data)) {
     270            return $data + array('debug' => 'Schedule scan IDs fetched successfully');
     271        }
     272    } elseif ($status_code === 403) {
     273        return array('error' => 'Invalid API key or access denied', 'debug' => '403 Forbidden');
     274    }
     275    return array('error' => 'Failed to get schedule scan IDs. Status: ' . $status_code, 'debug' => 'Status: ' . $status_code);
     276}
     277
     278/**
    204279 * Get scheduled scan issues (GetScheduleScanIssues endpoint)
    205280 */
     
    319394add_action('wp_ajax_secuseek_get_scan_status', 'secuseek_ajax_get_scan_status');
    320395add_action('wp_ajax_secuseek_get_scan_results', 'secuseek_ajax_get_scan_results');
     396add_action('wp_ajax_secuseek_clear_cache', 'secuseek_ajax_clear_cache');
     397
     398/**
     399 * AJAX endpoint for clearing cache and old scan data
     400 */
     401function secuseek_ajax_clear_cache() {
     402    // Capability check
     403    if (!current_user_can('secuseek_manage_scans') && !current_user_can('manage_options')) {
     404        wp_send_json_error('Insufficient permissions', 403);
     405        wp_die();
     406    }
     407   
     408    // Nonce verification
     409    if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) {
     410        wp_send_json_error('Security check failed', 403);
     411        wp_die();
     412    }
     413   
     414    // Clear all scan-related data
     415    secuseek_delete_last_scan_id();
     416    delete_option('SecuseekUserJobScheduleId');
     417   
     418    // Clear any transients
     419    $user_id = get_current_user_id();
     420    delete_transient('secuseek_scan_rate_limit_' . $user_id);
     421    delete_transient('secuseek_scan_error_' . $user_id);
     422   
     423    wp_send_json_success('Cache cleared successfully');
     424    wp_die();
     425}
    321426
    322427/**
     
    327432    if (!current_user_can('secuseek_manage_scans') && !current_user_can('manage_options')) {
    328433        wp_send_json_error('Insufficient permissions', 403);
     434        wp_die();
    329435    }
    330436   
     
    332438    if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) {
    333439        wp_send_json_error('Security check failed', 403);
     440        wp_die();
    334441    }
    335442   
     
    339446    if (get_transient($transient_key)) {
    340447        wp_send_json_error('Rate limit exceeded. Please wait before trying again.', 429);
     448        wp_die();
    341449    }
    342450    set_transient($transient_key, true, 60); // 1 minute wait
     
    345453    $create_result = secuseek_create_schedule_scan();
    346454    if (isset($create_result['error'])) {
    347         wp_send_json_error(array(
    348             'message' => esc_html($create_result['error'])
    349         ));
    350         return;
    351        
     455        // Stop immediately if create_scan fails - show the actual error
     456        wp_send_json_error(esc_html($create_result['error']));
     457        wp_die();
     458    }
     459   
     460    // Only continue if create_scan was successful
     461    if (!isset($create_result['id'])) {
     462        // Debug: log the actual response
     463        wp_send_json_error('Failed to create scan - no ID returned. Debug: ' . (isset($create_result['debug']) ? $create_result['debug'] : 'No debug info'));
     464        wp_die();
    352465    }
    353466   
     
    357470    }
    358471   
    359     // Then get the schedule information (GET request)
    360     $result = secuseek_fetch_schedule_scan();
    361    
    362     if (isset($result['error'])) {
    363         wp_send_json_error(array(
    364             'message' => esc_html($result['error'])
    365         ));
    366         return;
    367     }
    368    
    369     if (isset($result['id'])) {
     472    // Use the new scan ID from creation
     473    $new_scan_id = $create_result['id'];
     474    // Save the new scan ID
     475    secuseek_save_last_scan_id($new_scan_id);
     476   
    370477        wp_send_json_success(array(
    371478            'message' => 'Scan started successfully',
    372             'schedule_id' => sanitize_text_field($result['id'])
     479            'schedule_id' => sanitize_text_field($new_scan_id),
     480            'countdown' => true
    373481        ));
    374     } else {
    375         wp_send_json_error(array(
    376             'message' => 'No schedule ID received'
    377         ));
    378     }
     482    wp_die();
    379483}
    380484
     
    386490    if (!current_user_can('secuseek_view_results') && !current_user_can('manage_options')) {
    387491        wp_send_json_error('Insufficient permissions', 403);
     492        wp_die();
    388493    }
    389494   
     
    391496    if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) {
    392497        wp_send_json_error('Security check failed', 403);
     498        wp_die();
    393499    }
    394500   
     
    397503    if (!$schedule_id) {
    398504        wp_send_json_error('No schedule ID available');
    399         return;
     505        wp_die();
    400506    }
    401507   
     
    411517    if (!current_user_can('secuseek_view_results') && !current_user_can('manage_options')) {
    412518        wp_send_json_error('Insufficient permissions', 403);
     519        wp_die();
    413520    }
    414521   
     
    416523    if (!check_ajax_referer('secuseek_ajax_nonce', 'nonce', false)) {
    417524        wp_send_json_error('Security check failed', 403);
     525        wp_die();
    418526    }
    419527   
     
    422530    if (!$schedule_id) {
    423531        wp_send_json_error('No schedule ID available');
    424         return;
     532        wp_die();
    425533    }
    426534   
  • secuseek/trunk/readme.txt

    r3373197 r3373724  
    1 === SecuSeek ===
     1=== SecuSeek - Web Security Scanner & Vulnerability Assessment ===
    22Contributors: secuseek
    3 Tags: security, scan, findings, vulnerability
     3Tags: security, scan, findings, vulnerability, web security, threat detection, vulnerability assessment, security analysis
    44Requires at least: 5.0
    55Tested up to: 6.8
    6 Stable tag: 1.0
     6Stable tag: 1.0.1
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
    99
    10 View and manage security scan results from SecuSeek in your WordPress dashboard.
     10Professional web security scanning and vulnerability assessment plugin. Comprehensive security analysis, real-time threat detection, and detailed security reports for WordPress websites.
    1111
    1212== Description ==
    1313
    14 **SecuSeek** brings comprehensive security scanning and vulnerability management directly into your WordPress admin dashboard. Monitor, analyze, and manage security findings from multiple scans in a centralized, user-friendly interface.
     14**SecuSeek - Web Security Scanner & Vulnerability Assessment** brings comprehensive security scanning and vulnerability management directly into your WordPress admin dashboard. Monitor, analyze, and manage security findings from multiple scans in a centralized, user-friendly interface.
    1515
    1616= 🔍 Comprehensive Security Scanning =
     
    5555* **Anyone using SecuSeek** - Seamlessly integrate scan results into WordPress admin
    5656
    57 = 🔗 Privacy & External Services =
     57= 🔗 External Services =
    5858
    59 This plugin connects to SecuSeek's external API to fetch and display security scan results. For complete transparency:
    60 
    61 * **Service**: SecuSeek API (https://api.secuseek.com)
    62 * **Data Sent**: Your API key, site URL, and scan configuration
    63 * **Data Received**: Security findings, vulnerability details, and scan status
    64 * **When**: On schedule or manual refresh
    65 
    66 Please review our [Terms of Service](https://secuseek.com/terms-and-conditions/) and [Privacy Policy](https://secuseek.com/privacy-policy/) for complete information.
     59This plugin connects to SecuSeek's external API to fetch and display security scan results - for complete details about data handling, privacy practices, and service transparency, visit [https://secuseek.com/](https://secuseek.com/).
    6760
    6861= 💻 Modern Technology Stack =
     
    7669All source code is human-readable and available in the plugin directory.
    7770
    78 == External services ==
    79 This plugin connects to SecuSeek's external API to create scheduled scans, fetch scan status and results, validate API keys, and clean up schedules during uninstall.
    8071
    81 - **Service**: SecuSeek API (Base URL: `https://api.secuseek.com`)
    82 - **What it’s used for**:
    83   - Start a scan and create a schedule: `POST /api/v1/external/schedule-scan`
    84   - Get schedule info: `GET /api/v1/external/schedule-scan/{UserJobScheduleId}`
    85   - Get scan issues: `GET /api/v1/external/issues/{ScheduleId}`
    86   - Validate API key: `GET /api/v1/external/validate`
    87   - Remove schedule on uninstall: `DELETE /api/v1/external/schedule-scan/{UserJobScheduleId}`
    88 - **What data is sent and when**:
    89   - When starting a scan: site URL (domain only) and scan frequency in request body.
    90   - On authenticated API calls: your API key in the `x-api-key` header.
    91   - When polling status and fetching results: the schedule/scan identifier in the request path.
    92   - During uninstall cleanup: the schedule identifier; WordPress may include a standard `User-Agent` header (e.g., `WordPress/{version}; {site_url}`).
    93 - **Terms and Privacy**:
     72== Terms and Privacy ==
    9473  - Terms of Service: `https://secuseek.com/terms-and-conditions/`
    9574  - Privacy Policy: `https://secuseek.com/privacy-policy/`
    96 
    97 == Used technologies ==
    98 - Alpine.js 3.13.10 — Docs: `https://alpinejs.dev` — Source: `https://github.com/alpinejs/alpine` — Minified CDN: `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js`
    99 - Font Awesome Free 6.5.0 — Site: `https://fontawesome.com` — Source: `https://github.com/FortAwesome/Font-Awesome` — CSS CDN: `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css`
    100 - WordPress HTTP API (remote requests) — Docs: `https://developer.wordpress.org/plugins/http-api/`
    101 - WordPress Cron API (scheduling) — Docs: `https://developer.wordpress.org/plugins/cron/`
    102 - WordPress AJAX (admin-ajax) — Docs: `https://developer.wordpress.org/plugins/javascript/ajax/`
    103 
    104 == Source and Human-Readable Code ==
    105 This plugin ships with human-readable source code. All custom JavaScript and CSS are provided in unminified form under `assets/js/` and `assets/css/`.
    106 
    107 The plugin also bundles a small number of third-party, minified assets. Their human-readable sources are publicly available here:
    108 
    109  - `assets/js/alpinejs.min.js` — Source: `https://github.com/alpinejs/alpine` (Docs/Homepage: `https://alpinejs.dev`)
    110 - `assets/css/fontawesome.min.css` — Source: `https://github.com/FortAwesome/Font-Awesome`
    111 
    112 Build tools are not required to work on this plugin. If build tooling is added in the future, instructions will be documented here.
    113 
    114 === Direct downloads (exact versions used) ===
    115 - Alpine.js 3.13.10 (minified): `https://cdn.jsdelivr.net/npm/alpinejs@3.13.10/dist/cdn.min.js`
    116 - Font Awesome Free 6.5.0 (fontawesome.min.css): `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.0/css/fontawesome.min.css`
    11775
    11876== Installation ==
     
    13088== Screenshots ==
    131891. Findings dashboard
    132 2. API key entry screen
     902. Findings dashboard - 2
    13391
    13492== Changelog ==
     93= 1.0.1 =
     94* Improved filter interface with simplified chip display
     95* Enhanced user experience with better filter management
     96* Fixed filter removal functionality
     97* Updated plugin branding and SEO optimization
     98
    13599= 1.0 =
    136100* Initial release.
    137101
    138102== Upgrade Notice ==
     103= 1.0.1 =
     104Improved filter interface and enhanced user experience.
     105
    139106= 1.0 =
    140107First public release.
  • secuseek/trunk/secuseek.php

    r3373197 r3373724  
    11<?php
    22/**
    3  * Plugin Name: SecuSeek
    4  * Description: Admin panel integration for SecuSeek's external scan services.
    5  * Version: 1.0
     3 * Plugin Name: SecuSeek - Web Security Scanner & Vulnerability Assessment
     4 * Description: Professional web security scanning and vulnerability assessment plugin. Comprehensive security analysis, real-time threat detection, and detailed security reports for WordPress websites.
     5 * Version: 1.0.1
    66 * Author: SecuSeek
    77 * License: GPL2
  • secuseek/trunk/templates/findings/findings-filters.php

    r3373197 r3373724  
    1111        <div class="input-area compact" @click="filtersOpen.category = !filtersOpen.category">
    1212            <span class="input-label" :class="{'floating': filters.category.length > 0}">Category</span>
    13             <template x-for="item in filters.category" :key="item">
    14                 <span class="chip">
    15                     <span x-text="item"></span>
    16                     <button @click.stop="filters.category = filters.category.filter(function(x) { return x !== item; })">×</button>
    17                 </span>
    18             </template>
     13            <span x-show="filters.category.length > 0" class="filter-chips">
     14                <span x-text="filters.category.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span>
     15                <button @click.stop="filters.category = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button>
     16            </span>
    1917            <span class="arrow" style="margin-left:auto;">
    2018                <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
     
    3432        <div class="input-area compact" @click="filtersOpen.severity = !filtersOpen.severity">
    3533            <span class="input-label" :class="{'floating': filters.severity.length > 0}">Severity</span>
    36             <template x-for="item in filters.severity" :key="item">
    37                 <span class="chip">
    38                     <span x-text="item"></span>
    39                     <button @click.stop="filters.severity = filters.severity.filter(function(x) { return x !== item; })">×</button>
    40                 </span>
    41             </template>
     34            <span x-show="filters.severity.length > 0" class="filter-chips">
     35                <span x-text="filters.severity.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span>
     36                <button @click.stop="filters.severity = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button>
     37            </span>
    4238            <span class="arrow" style="margin-left:auto;">
    4339                <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
     
    5753        <div class="input-area compact" @click="filtersOpen.confidence = !filtersOpen.confidence">
    5854            <span class="input-label" :class="{'floating': filters.confidence.length > 0}">Confidence</span>
    59             <template x-for="item in filters.confidence" :key="item">
    60                 <span class="chip">
    61                     <span x-text="item"></span>
    62                     <button @click.stop="filters.confidence = filters.confidence.filter(function(x) { return x !== item; })">×</button>
    63                 </span>
    64             </template>
     55            <span x-show="filters.confidence.length > 0" class="filter-chips">
     56                <span x-text="filters.confidence.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span>
     57                <button @click.stop="filters.confidence = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button>
     58            </span>
    6559            <span class="arrow" style="margin-left:auto;">
    6660                <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
     
    8074        <div class="input-area compact" @click="filtersOpen.status = !filtersOpen.status">
    8175            <span class="input-label" :class="{'floating': filters.status.length > 0}">Status</span>
    82             <template x-for="item in filters.status" :key="item">
    83                 <span class="chip">
    84                     <span x-text="item"></span>
    85                     <button @click.stop="filters.status = filters.status.filter(function(x) { return x !== item; })">×</button>
    86                 </span>
    87             </template>
     76            <span x-show="filters.status.length > 0" class="filter-chips">
     77                <span x-text="filters.status.join(', ')" style="color:#22ad5a;font-size:13px;margin-right:8px;"></span>
     78                <button @click.stop="filters.status = []" style="background:none;border:none;color:#22ad5a;cursor:pointer;font-size:13px;font-weight:bold;">×</button>
     79            </span>
    8880            <span class="arrow" style="margin-left:auto;">
    8981                <svg width="12" height="12" viewBox="0 0 20 20" fill="none"><path d="M5 8l5 5 5-5" stroke="#374151" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
  • secuseek/trunk/templates/login-form.php

    r3373197 r3373724  
    55<?php
    66$error_message = ''; // Assign an empty value for the error message.
     7
     8// Check for scan errors from automatic scan start
     9$scan_error = get_transient('secuseek_scan_error_' . get_current_user_id());
     10if ($scan_error) {
     11    delete_transient('secuseek_scan_error_' . get_current_user_id());
     12    $error_message = $scan_error;
     13}
    714
    815if (!empty($_POST['secuseek_scan_api_key'])) {
     
    2835                }
    2936                // Show info message to the user
    30                 echo '<div class="notice notice-success"><p>✅ API Key saved successfully! Refreshing...</p></div>';
    31                 echo '<meta http-equiv="refresh" content="2">';
     37                echo '<div class="notice notice-success"><p>✅ API Key saved successfully! Refreshing in <span id="countdown">5</span> seconds...</p></div>';
     38                echo '<meta http-equiv="refresh" content="5">';
     39                // Add countdown script inline for immediate execution
     40                wp_enqueue_script('secuseek-login-countdown', SECUSEEK_PLUGIN_URL . 'assets/js/login-countdown.js', array(), false, true);
    3241            }
    3342        }
Note: See TracChangeset for help on using the changeset viewer.