From e6cba7ef6aa6f551948982665007312ee7ff4618 Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Sun, 23 Aug 2026 07:08:42 +0000 Subject: [PATCH] v5.1.6 --- assets/admin.js | 6 +- changelog.txt | 129 ++ languages/wpvulnerability.pot | 2084 ++++++++++++++++------------- readme.txt | 109 +- uninstall.php | 1 + wpvulnerability-admin.php | 123 +- wpvulnerability-adminms.php | 192 ++- wpvulnerability-api.php | 6 +- wpvulnerability-compat.php | 372 +++++ wpvulnerability-core.php | 2 +- wpvulnerability-debug.php | 449 ++++++- wpvulnerability-general.php | 156 ++- wpvulnerability-notifications.php | 26 +- wpvulnerability-plugins.php | 31 +- wpvulnerability-process.php | 12 +- wpvulnerability-run.php | 74 +- wpvulnerability-schedule.php | 47 +- wpvulnerability-sitehealth.php | 6 + wpvulnerability-software.php | 2 +- wpvulnerability-themes.php | 3 +- wpvulnerability.php | 19 +- 21 files changed, 2561 insertions(+), 1288 deletions(-) create mode 100644 wpvulnerability-compat.php diff --git a/assets/admin.js b/assets/admin.js index 71afe17..596e86b 100644 --- a/assets/admin.js +++ b/assets/admin.js @@ -49,15 +49,15 @@ document.addEventListener( var notifyOptions = [ { - checkboxSelector: 'input[name="wpvulnerability-config[notify][email]"]', + checkboxSelector: 'input[type="checkbox"][name="wpvulnerability-config[notify][email]"]', fieldId: 'wpvulnerability_emails', }, { - checkboxSelector: 'input[name="wpvulnerability-config[notify][slack]"]', + checkboxSelector: 'input[type="checkbox"][name="wpvulnerability-config[notify][slack]"]', fieldId: 'wpvulnerability_slack_webhook', }, { - checkboxSelector: 'input[name="wpvulnerability-config[notify][teams]"]', + checkboxSelector: 'input[type="checkbox"][name="wpvulnerability-config[notify][teams]"]', fieldId: 'wpvulnerability_teams_webhook', }, ]; diff --git a/changelog.txt b/changelog.txt index 5fe1c17..83e57ea 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,134 @@ == Changelog == += [5.1.6] - 2026-08-22 = + + +**Fixed** + +* Saving the "Delete all plugin data on uninstall" preference no longer triggers a fatal error (`add_settings_error()` was called before the WordPress admin API was loaded). +* Notification channels can now be disabled entirely: unchecking every channel and saving previously kept the old channels active, because unchecked checkboxes are never submitted. +* "Reset Plugin" and "Delete all logs" now purge both log stores (API request logs and shell execution audit logs). Previously the shell audit store was never cleaned, and both deletions silently did nothing because the log post types were not registered yet when the actions ran. +* The Debug tab API connectivity tests now use the real API routes: WordPress core collapses pre-release versions (e.g. 7.1-alpha) to their stable milestone, and plugins/themes are tested against their actual slug routes instead of nonexistent plural ones (HTTP 404). +* Component icons now carry explicit width/height attributes and a meaningful alt text. The same icons are embedded in notification emails and Site Health, where the plugin CSS is not loaded and unsized SVGs rendered at their intrinsic 800x800 size. + +**Changed** + +* `languages/wpvulnerability.pot` regenerated with the new strings. + +**Compatibility** + +* WordPress: 4.7 - 7.1 +* PHP: 7.0 - 8.5 +* WP-CLI: 2.3.0 - 2.12.0 + +**Tests** + +* PHP Coding Standards: 3.13.6 (0 errors) +* WordPress Coding Standards: 3.4.1 +* PHPStan: 2.2.9 (level 9, 0 errors) +* PHPUnit: 9.6.36 (54 tests) + += [5.1.5] - 2026-08-22 = + + +**Changed** + +* The contributor list now leads with the ROBOTSTXT organization account, followed by Javier Casares; the remaining contributors are unchanged. +* Regenerated `languages/wpvulnerability.pot` so its source references match the current code. The string set is unchanged (450 strings); all shipped translations were verified against it and remain fully up to date. + +**Compatibility** + +* WordPress: 4.7 - 7.1 +* PHP: 7.0 - 8.5 +* WP-CLI: 2.3.0 - 2.12.0 + +**Tests** + +* PHP Coding Standards: 3.13.6 +* WordPress Coding Standards: 3.4.1 +* PHPStan: 2.2.9 (level 9, 0 errors) +* PHPUnit: 9.6.36 (45 tests) += [5.1.4] - 2026-08-22 = + +**Highlights** + +* The Site Health vulnerability tests work again on every WordPress version: they were silently disabled everywhere by an availability gate that could never pass. Secrets are now masked in the admin forms, and compatibility was verified against real PHP interpreters from 5.6 to 8.5. + +**Fixed** + +* The Site Health integration registered its tests behind `class_exists( 'WP_Site_Health' )`, but core loads that class lazily, after `init`, where the plugin loads its modules — so the guard always evaluated to false and none of the 13 vulnerability tests ever appeared in Site Health. The filter is now registered unconditionally; below WordPress 5.2 it simply never fires. +* "Clear all caches" in the debug tools now also deletes the `wpvulnerability-plugins-cache-data` timestamp option, so the plugins dataset is fully refreshed afterwards. +* The notification email "From:" address is now sanitized with `sanitize_email()` on all code paths. +* The plugins list table "Last updated" column decoded the whole plugins dataset for every table row; it is now decoded once per request. +* Removed a duplicated `json_decode()` call when reading the vulnerable-themes count. +* The `wp_date()` compatibility polyfill no longer fatals on WordPress 4.7–5.3: core calls `wp_maybe_decline_date()` unconditionally, but that function only exists since 5.4; the call is now availability-guarded. +* The core and software JSON getters return `'[]'` instead of an empty string when encoding fails, matching the plugins and themes getters. + +**Security** + +* Webhook URLs (Slack, Teams, Discord) and the Telegram bot token are masked in the admin settings forms (single-site and network), showing only their last 4 characters — previously they were rendered in full into the page HTML. The inputs are plain text fields, so masked values never trigger browser URL validation. Saving a form with unchanged masked values keeps the stored secret; clearing a field still removes it. +* Removed the dead `register_uninstall_hook()` registration: `uninstall.php` supersedes it and the registration added a stray autoloaded option row. +* Single-site settings registrations now explicitly set `show_in_rest => false`, matching the multisite configuration. + +**Changed** + +* Admin CSS/JS assets load only on the WPVulnerability settings pages, the dashboard and the plugins/themes list tables instead of on every wp-admin screen. +* The shell availability probe (`wpvulnerability_can_shell_exec()`) result is cached per request instead of spawning a process on every call. + +**Compatibility** + +* WordPress: 4.7 - 7.1 +* PHP: 7.0 - 8.5 +* WP-CLI: 2.3.0 - 2.12.0 +* Verified against real PHP interpreters 5.6.40, 7.0.33, 7.1.33, 7.2.34, 7.3.33, 7.4.33, 8.0.30, 8.1.34, 8.2.33, 8.3.33, 8.4.24 and 8.5.9: every plugin file lints clean from 7.0 up and fails on 5.6, confirming the declared floor. PHPCompatibility gate 7.0-8.5 clean; wp-compat WordPress floor 4.7 clean. + +**Tests** + +* PHP Coding Standards: 3.13.6 +* WordPress Coding Standards: 3.4.1 +* PHPStan: 2.2.9 (level 9, 0 errors) +* PHPUnit: 9.6.36 (45 tests) + += [5.1.3] - 2026-08-22 = + +**Added** + +* `wpvulnerability-compat.php`: verbatim WordPress core function polyfills (`wp_doing_cron`, `get_main_site_id`, `wp_is_json_request`, `sanitize_locale_name`, `determine_locale`, `wp_timezone_string`, `wp_timezone`, `wp_date`, `get_post_datetime`, `get_post_timestamp`), each guarded by `function_exists()`. The plugin no longer fatals on WordPress versions that lack these functions. + +**Security** + +* The API connectivity test results (message, HTTP code, response time and the raw response preview) are now escaped client-side before being inserted into the page, in both single-site and network admin. Remote API responses could previously inject arbitrary HTML. +* The debug export and the Database Options Viewer now mask webhook URLs, Telegram bot tokens/chat IDs and notification email addresses, so shared debug files never contain usable secrets. +* The multisite "components to hide" save now routes through the strict sanitizer, so a crafted request can no longer re-enable analysis of components force-hidden with `WPVULNERABILITY_HIDE_*` constants. +* API responses are only cached when the HTTP status is 200 and the body is valid JSON. Error responses (4xx/5xx, HTML error pages) were previously cached for the full TTL and blocked retries. +* API request URLs are now built from whitelisted types and sanitized slugs/versions (`wpvulnerability_get()`, `wpvulnerability_get_vulnerabilities()`). + +**Fixed** + +* Cron scheduling ran `wp_get_schedules()` (and other plugins' `cron_schedules` callbacks, including their translation calls) before `init` on every request. Core and notification event scheduling moved to `init`, matching the WordPress 6.7+ translation-loading requirements. +* Deactivation now removes the `wpvulnerability-plugins-data` and `wpvulnerability-plugins-data-cache` options created at activation. +* The notification cron period is validated against `daily`/`weekly` before `wp_schedule_event()`, so a corrupted configuration value can no longer silently unschedule notifications. +* The admin menu vulnerability counters escape the badge title attribute, and the plugin row "Settings" link escapes its URL and label. +* Application Passwords REST authentication is gated on `function_exists( 'wp_is_application_passwords_available' )` so WordPress < 5.6 no longer fatals on unauthenticated REST requests; cookie-authenticated admin sessions keep working and the Application Passwords functionality itself is simply unavailable on old versions. +* The Site Health integration registers its tests only when `WP_Site_Health` exists (WordPress 5.2+); on older versions the plugin runs without the Site Health section. + +**Changed** + +* Minimum WordPress version lowered from 5.6 to 4.7. Whole functionalities degrade gracefully on old versions: Application Passwords REST authentication (WordPress 5.6+) and the Site Health integration (WordPress 5.2+) are only registered when available; everything else works through the polyfills. + +**Compatibility** + +* WordPress: 4.7 - 7.1 +* PHP: 7.0 - 8.5 +* WP-CLI: 2.3.0 - 2.12.0 + +**Tests** + +* PHP Coding Standards: 3.13.6 +* WordPress Coding Standards: 3.4.1 +* PHPStan: 2.2.9 (level 9, 0 errors) +* PHPUnit: 9.6.36 (37 tests) + = [5.1.2] - 2026-08-07 = **Fixed** diff --git a/languages/wpvulnerability.pot b/languages/wpvulnerability.pot index 10ae55d..aa65d2e 100644 --- a/languages/wpvulnerability.pot +++ b/languages/wpvulnerability.pot @@ -2,25 +2,25 @@ # This file is distributed under the GPL-3.0-or-later. msgid "" msgstr "" -"Project-Id-Version: WPVulnerability 5.1.0\n" +"Project-Id-Version: WPVulnerability 5.1.6\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wpvulnerability\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-07-08T16:23:21+00:00\n" +"POT-Creation-Date: 2026-08-22T20:43:34+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: wpvulnerability\n" #. Plugin Name of the plugin #: wpvulnerability.php -#: wpvulnerability-admin.php:1783 -#: wpvulnerability-admin.php:1784 -#: wpvulnerability-adminms.php:1948 -#: wpvulnerability-adminms.php:1949 -#: wpvulnerability-notifications.php:105 +#: wpvulnerability-admin.php:1796 +#: wpvulnerability-admin.php:1797 +#: wpvulnerability-adminms.php:1871 +#: wpvulnerability-adminms.php:1872 +#: wpvulnerability-notifications.php:51 msgid "WPVulnerability" msgstr "" @@ -67,193 +67,193 @@ msgstr "" msgid "No %s vulnerabilities found." msgstr "" -#: wpvulnerability-admin.php:63 -#: wpvulnerability-adminms.php:227 +#: wpvulnerability-admin.php:72 +#: wpvulnerability-adminms.php:153 msgid "Data from source has been reloaded." msgstr "" -#: wpvulnerability-admin.php:65 -#: wpvulnerability-adminms.php:229 +#: wpvulnerability-admin.php:74 +#: wpvulnerability-adminms.php:155 msgid "You do not have permission to reload data." msgstr "" -#: wpvulnerability-admin.php:93 -#: wpvulnerability-adminms.php:255 +#: wpvulnerability-admin.php:102 +#: wpvulnerability-adminms.php:181 msgid "Test email has been sent." msgstr "" -#: wpvulnerability-admin.php:95 -#: wpvulnerability-adminms.php:259 +#: wpvulnerability-admin.php:104 +#: wpvulnerability-adminms.php:185 msgid "Test email has failed. Please, check your email settings." msgstr "" -#: wpvulnerability-admin.php:98 -#: wpvulnerability-adminms.php:263 +#: wpvulnerability-admin.php:107 +#: wpvulnerability-adminms.php:189 msgid "You do not have permission to send test emails." msgstr "" -#: wpvulnerability-admin.php:113 +#: wpvulnerability-admin.php:122 msgid "WPVulnerability cron events have been repaired." msgstr "" -#: wpvulnerability-admin.php:115 -#: wpvulnerability-adminms.php:279 +#: wpvulnerability-admin.php:124 +#: wpvulnerability-adminms.php:205 msgid "You do not have permission to repair cron events." msgstr "" -#: wpvulnerability-admin.php:127 -#: wpvulnerability-adminms.php:291 +#: wpvulnerability-admin.php:136 +#: wpvulnerability-adminms.php:217 msgid "All logs have been deleted." msgstr "" -#: wpvulnerability-admin.php:129 -#: wpvulnerability-adminms.php:293 +#: wpvulnerability-admin.php:138 +#: wpvulnerability-adminms.php:219 msgid "You do not have permission to delete logs." msgstr "" -#: wpvulnerability-admin.php:146 +#: wpvulnerability-admin.php:155 msgid "Uninstall preference saved." msgstr "" -#: wpvulnerability-admin.php:153 -#: wpvulnerability-adminms.php:316 +#: wpvulnerability-admin.php:162 +#: wpvulnerability-adminms.php:242 msgid "WPVulnerability has been reset to defaults and reloaded." msgstr "" -#: wpvulnerability-admin.php:155 -#: wpvulnerability-adminms.php:318 +#: wpvulnerability-admin.php:164 +#: wpvulnerability-adminms.php:244 msgid "You do not have permission to reset WPVulnerability." msgstr "" -#: wpvulnerability-admin.php:170 -#: wpvulnerability-adminms.php:333 +#: wpvulnerability-admin.php:179 +#: wpvulnerability-adminms.php:259 msgid "All caches have been cleared." msgstr "" -#: wpvulnerability-admin.php:172 -#: wpvulnerability-adminms.php:335 +#: wpvulnerability-admin.php:181 +#: wpvulnerability-adminms.php:261 msgid "You do not have permission to clear caches." msgstr "" -#: wpvulnerability-admin.php:187 -#: wpvulnerability-adminms.php:350 +#: wpvulnerability-admin.php:196 +#: wpvulnerability-adminms.php:276 msgid "Plugin and theme signatures have been reset." msgstr "" -#: wpvulnerability-admin.php:189 -#: wpvulnerability-adminms.php:352 +#: wpvulnerability-admin.php:198 +#: wpvulnerability-adminms.php:278 msgid "You do not have permission to reset signatures." msgstr "" -#: wpvulnerability-admin.php:212 -#: wpvulnerability-adminms.php:375 +#: wpvulnerability-admin.php:221 +#: wpvulnerability-adminms.php:301 msgid "You do not have permission to export debug information." msgstr "" -#: wpvulnerability-admin.php:224 -#: wpvulnerability-adminms.php:387 +#: wpvulnerability-admin.php:233 +#: wpvulnerability-adminms.php:313 msgid "Database update has been executed." msgstr "" -#: wpvulnerability-admin.php:226 -#: wpvulnerability-adminms.php:389 +#: wpvulnerability-admin.php:235 +#: wpvulnerability-adminms.php:315 msgid "You do not have permission to run database updates." msgstr "" -#: wpvulnerability-admin.php:247 -#: wpvulnerability-adminms.php:410 +#: wpvulnerability-admin.php:256 +#: wpvulnerability-adminms.php:336 msgid "Notification has been sent." msgstr "" -#: wpvulnerability-admin.php:249 -#: wpvulnerability-adminms.php:412 +#: wpvulnerability-admin.php:258 +#: wpvulnerability-adminms.php:338 msgid "Notification sending failed." msgstr "" -#: wpvulnerability-admin.php:252 -#: wpvulnerability-adminms.php:415 +#: wpvulnerability-admin.php:261 +#: wpvulnerability-adminms.php:341 msgid "You do not have permission to send notifications." msgstr "" -#: wpvulnerability-admin.php:266 -#: wpvulnerability-adminms.php:539 -#: wpvulnerability-schedule.php:323 +#: wpvulnerability-admin.php:275 +#: wpvulnerability-adminms.php:465 +#: wpvulnerability-schedule.php:377 msgid "Notifications" msgstr "" -#: wpvulnerability-admin.php:269 -#: wpvulnerability-adminms.php:542 +#: wpvulnerability-admin.php:278 +#: wpvulnerability-adminms.php:468 msgid "Analysis" msgstr "" -#: wpvulnerability-admin.php:272 -#: wpvulnerability-adminms.php:545 +#: wpvulnerability-admin.php:281 +#: wpvulnerability-adminms.php:471 msgid "Logs" msgstr "" -#: wpvulnerability-admin.php:275 -#: wpvulnerability-adminms.php:548 +#: wpvulnerability-admin.php:284 +#: wpvulnerability-adminms.php:474 #: wpvulnerability-sitehealth.php:25 #: wpvulnerability-sitehealth.php:77 #: wpvulnerability-sitehealth.php:129 -#: wpvulnerability-sitehealth.php:210 +#: wpvulnerability-sitehealth.php:213 msgid "Security" msgstr "" -#: wpvulnerability-admin.php:278 -#: wpvulnerability-adminms.php:551 +#: wpvulnerability-admin.php:287 +#: wpvulnerability-adminms.php:477 msgid "Tools" msgstr "" -#: wpvulnerability-admin.php:285 -#: wpvulnerability-adminms.php:558 +#: wpvulnerability-admin.php:294 +#: wpvulnerability-adminms.php:484 msgid "Debug" msgstr "" -#: wpvulnerability-admin.php:290 -#: wpvulnerability-adminms.php:563 +#: wpvulnerability-admin.php:299 +#: wpvulnerability-adminms.php:489 msgid "About" msgstr "" -#: wpvulnerability-admin.php:427 -#: wpvulnerability-adminms.php:700 +#: wpvulnerability-admin.php:436 +#: wpvulnerability-adminms.php:626 msgid "Notification Settings" msgstr "" -#: wpvulnerability-admin.php:430 -#: wpvulnerability-adminms.php:703 +#: wpvulnerability-admin.php:439 +#: wpvulnerability-adminms.php:629 msgid "Configure how and when you want to receive vulnerability notifications." msgstr "" -#: wpvulnerability-admin.php:431 -#: wpvulnerability-adminms.php:704 +#: wpvulnerability-admin.php:440 +#: wpvulnerability-adminms.php:630 msgid "Stay informed about security vulnerabilities in your WordPress installation, plugins, themes, and server software." msgstr "" -#: wpvulnerability-admin.php:438 -#: wpvulnerability-adminms.php:711 +#: wpvulnerability-admin.php:447 +#: wpvulnerability-adminms.php:637 msgid "Cache Expiration Time" msgstr "" -#: wpvulnerability-admin.php:440 -#: wpvulnerability-admin.php:814 -#: wpvulnerability-adminms.php:713 -#: wpvulnerability-adminms.php:1087 +#: wpvulnerability-admin.php:449 +#: wpvulnerability-admin.php:828 +#: wpvulnerability-adminms.php:639 +#: wpvulnerability-adminms.php:1018 msgid "FORCED" msgstr "" -#: wpvulnerability-admin.php:444 -#: wpvulnerability-adminms.php:717 +#: wpvulnerability-admin.php:453 +#: wpvulnerability-adminms.php:643 msgid "How long to cache vulnerability data before refreshing from the API." msgstr "" #. translators: %d: number of hours -#: wpvulnerability-admin.php:453 -#: wpvulnerability-admin.php:1892 -#: wpvulnerability-admin.php:3638 -#: wpvulnerability-adminms.php:726 -#: wpvulnerability-adminms.php:2042 -#: wpvulnerability-adminms.php:3834 +#: wpvulnerability-admin.php:462 +#: wpvulnerability-admin.php:1905 +#: wpvulnerability-admin.php:3672 +#: wpvulnerability-adminms.php:652 +#: wpvulnerability-adminms.php:1965 +#: wpvulnerability-adminms.php:3746 #, php-format msgid "%d hour" msgid_plural "%d hours" @@ -261,801 +261,804 @@ msgstr[0] "" msgstr[1] "" #. translators: %s: documentation URL -#: wpvulnerability-admin.php:469 -#: wpvulnerability-adminms.php:742 +#: wpvulnerability-admin.php:478 +#: wpvulnerability-adminms.php:668 #, php-format msgid "You can force the cache time via wp-config.php constant. Learn more →" msgstr "" -#: wpvulnerability-admin.php:481 -#: wpvulnerability-admin.php:3672 -#: wpvulnerability-adminms.php:754 -#: wpvulnerability-adminms.php:3868 +#: wpvulnerability-admin.php:490 +#: wpvulnerability-admin.php:3706 +#: wpvulnerability-adminms.php:680 +#: wpvulnerability-adminms.php:3780 msgid "Notification Frequency" msgstr "" -#: wpvulnerability-admin.php:484 -#: wpvulnerability-adminms.php:757 +#: wpvulnerability-admin.php:493 +#: wpvulnerability-adminms.php:683 msgid "Choose how often you want to receive vulnerability notifications." msgstr "" -#: wpvulnerability-admin.php:489 -#: wpvulnerability-adminms.php:762 +#: wpvulnerability-admin.php:498 +#: wpvulnerability-adminms.php:688 msgid "Never - Disable automatic notifications" msgstr "" -#: wpvulnerability-admin.php:493 -#: wpvulnerability-adminms.php:766 +#: wpvulnerability-admin.php:502 +#: wpvulnerability-adminms.php:692 msgid "Daily - Receive notifications every day" msgstr "" -#: wpvulnerability-admin.php:497 -#: wpvulnerability-adminms.php:770 +#: wpvulnerability-admin.php:506 +#: wpvulnerability-adminms.php:696 msgid "Weekly - Receive notifications once a week" msgstr "" -#: wpvulnerability-admin.php:503 -#: wpvulnerability-adminms.php:776 +#: wpvulnerability-admin.php:512 +#: wpvulnerability-adminms.php:702 msgid "Day:" msgstr "" -#: wpvulnerability-admin.php:505 -#: wpvulnerability-admin.php:1956 -#: wpvulnerability-adminms.php:778 -#: wpvulnerability-adminms.php:2118 +#: wpvulnerability-admin.php:514 +#: wpvulnerability-admin.php:1969 +#: wpvulnerability-adminms.php:704 +#: wpvulnerability-adminms.php:2041 msgid "Monday" msgstr "" -#: wpvulnerability-admin.php:506 -#: wpvulnerability-admin.php:1957 -#: wpvulnerability-adminms.php:779 -#: wpvulnerability-adminms.php:2119 +#: wpvulnerability-admin.php:515 +#: wpvulnerability-admin.php:1970 +#: wpvulnerability-adminms.php:705 +#: wpvulnerability-adminms.php:2042 msgid "Tuesday" msgstr "" -#: wpvulnerability-admin.php:507 -#: wpvulnerability-admin.php:1958 -#: wpvulnerability-adminms.php:780 -#: wpvulnerability-adminms.php:2120 +#: wpvulnerability-admin.php:516 +#: wpvulnerability-admin.php:1971 +#: wpvulnerability-adminms.php:706 +#: wpvulnerability-adminms.php:2043 msgid "Wednesday" msgstr "" -#: wpvulnerability-admin.php:508 -#: wpvulnerability-admin.php:1959 -#: wpvulnerability-adminms.php:781 -#: wpvulnerability-adminms.php:2121 +#: wpvulnerability-admin.php:517 +#: wpvulnerability-admin.php:1972 +#: wpvulnerability-adminms.php:707 +#: wpvulnerability-adminms.php:2044 msgid "Thursday" msgstr "" -#: wpvulnerability-admin.php:509 -#: wpvulnerability-admin.php:1960 -#: wpvulnerability-adminms.php:782 -#: wpvulnerability-adminms.php:2122 +#: wpvulnerability-admin.php:518 +#: wpvulnerability-admin.php:1973 +#: wpvulnerability-adminms.php:708 +#: wpvulnerability-adminms.php:2045 msgid "Friday" msgstr "" -#: wpvulnerability-admin.php:510 -#: wpvulnerability-admin.php:1961 -#: wpvulnerability-adminms.php:783 -#: wpvulnerability-adminms.php:2123 +#: wpvulnerability-admin.php:519 +#: wpvulnerability-admin.php:1974 +#: wpvulnerability-adminms.php:709 +#: wpvulnerability-adminms.php:2046 msgid "Saturday" msgstr "" -#: wpvulnerability-admin.php:511 -#: wpvulnerability-admin.php:1962 -#: wpvulnerability-adminms.php:784 -#: wpvulnerability-adminms.php:2124 +#: wpvulnerability-admin.php:520 +#: wpvulnerability-admin.php:1975 +#: wpvulnerability-adminms.php:710 +#: wpvulnerability-adminms.php:2047 msgid "Sunday" msgstr "" -#: wpvulnerability-admin.php:515 -#: wpvulnerability-adminms.php:788 +#: wpvulnerability-admin.php:524 +#: wpvulnerability-adminms.php:714 msgid "Time:" msgstr "" -#: wpvulnerability-admin.php:519 -#: wpvulnerability-adminms.php:792 +#: wpvulnerability-admin.php:528 +#: wpvulnerability-adminms.php:718 msgid "(24-hour format, server timezone)" msgstr "" -#: wpvulnerability-admin.php:528 -#: wpvulnerability-adminms.php:801 +#: wpvulnerability-admin.php:537 +#: wpvulnerability-adminms.php:727 msgid "Notification Channels" msgstr "" -#: wpvulnerability-admin.php:531 -#: wpvulnerability-adminms.php:804 +#: wpvulnerability-admin.php:540 +#: wpvulnerability-adminms.php:730 msgid "Select where you want to receive notifications." msgstr "" -#: wpvulnerability-admin.php:536 -#: wpvulnerability-admin.php:2012 -#: wpvulnerability-adminms.php:809 -#: wpvulnerability-adminms.php:2172 +#: wpvulnerability-admin.php:546 +#: wpvulnerability-admin.php:2025 +#: wpvulnerability-adminms.php:736 +#: wpvulnerability-adminms.php:2095 msgid "Email" msgstr "" -#: wpvulnerability-admin.php:539 -#: wpvulnerability-adminms.php:812 +#: wpvulnerability-admin.php:549 +#: wpvulnerability-adminms.php:739 msgid "Email Addresses (separated by commas):" msgstr "" -#: wpvulnerability-admin.php:541 -#: wpvulnerability-adminms.php:814 +#: wpvulnerability-admin.php:551 +#: wpvulnerability-adminms.php:741 msgid "Default:" msgstr "" -#: wpvulnerability-admin.php:546 -#: wpvulnerability-admin.php:2017 -#: wpvulnerability-adminms.php:819 -#: wpvulnerability-adminms.php:2177 +#: wpvulnerability-admin.php:557 +#: wpvulnerability-admin.php:2030 +#: wpvulnerability-adminms.php:747 +#: wpvulnerability-adminms.php:2100 msgid "Slack" msgstr "" -#: wpvulnerability-admin.php:549 -#: wpvulnerability-adminms.php:822 +#: wpvulnerability-admin.php:560 +#: wpvulnerability-adminms.php:750 msgid "Slack Webhook URL:" msgstr "" #. translators: %s: Slack documentation URL -#: wpvulnerability-admin.php:555 -#: wpvulnerability-adminms.php:828 +#: wpvulnerability-admin.php:566 +#: wpvulnerability-adminms.php:756 #, php-format msgid "Learn how to create a Slack incoming webhook →" msgstr "" -#: wpvulnerability-admin.php:564 -#: wpvulnerability-admin.php:2022 -#: wpvulnerability-adminms.php:837 -#: wpvulnerability-adminms.php:2182 +#: wpvulnerability-admin.php:576 +#: wpvulnerability-admin.php:2035 +#: wpvulnerability-adminms.php:766 +#: wpvulnerability-adminms.php:2105 msgid "Microsoft Teams" msgstr "" -#: wpvulnerability-admin.php:567 -#: wpvulnerability-adminms.php:840 +#: wpvulnerability-admin.php:579 +#: wpvulnerability-adminms.php:769 msgid "Teams Webhook URL:" msgstr "" #. translators: %s: Microsoft Teams documentation URL -#: wpvulnerability-admin.php:573 -#: wpvulnerability-adminms.php:846 +#: wpvulnerability-admin.php:585 +#: wpvulnerability-adminms.php:775 #, php-format msgid "Learn how to create a Teams incoming webhook →" msgstr "" -#: wpvulnerability-admin.php:582 -#: wpvulnerability-adminms.php:855 +#: wpvulnerability-admin.php:595 +#: wpvulnerability-adminms.php:785 msgid "Discord" msgstr "" -#: wpvulnerability-admin.php:585 -#: wpvulnerability-adminms.php:858 +#: wpvulnerability-admin.php:598 +#: wpvulnerability-adminms.php:788 msgid "Discord Webhook URL:" msgstr "" #. translators: %s: Discord documentation URL -#: wpvulnerability-admin.php:591 -#: wpvulnerability-adminms.php:864 +#: wpvulnerability-admin.php:604 +#: wpvulnerability-adminms.php:794 #, php-format msgid "Learn how to create a Discord webhook →" msgstr "" -#: wpvulnerability-admin.php:600 -#: wpvulnerability-adminms.php:873 +#: wpvulnerability-admin.php:614 +#: wpvulnerability-adminms.php:804 msgid "Telegram" msgstr "" -#: wpvulnerability-admin.php:603 -#: wpvulnerability-adminms.php:876 +#: wpvulnerability-admin.php:617 +#: wpvulnerability-adminms.php:807 msgid "Telegram Bot Token:" msgstr "" -#: wpvulnerability-admin.php:605 -#: wpvulnerability-adminms.php:878 +#: wpvulnerability-admin.php:619 +#: wpvulnerability-adminms.php:809 msgid "Format: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" msgstr "" -#: wpvulnerability-admin.php:607 -#: wpvulnerability-adminms.php:880 +#: wpvulnerability-admin.php:621 +#: wpvulnerability-adminms.php:811 msgid "Telegram Chat ID:" msgstr "" #. translators: %s: Telegram documentation URL -#: wpvulnerability-admin.php:613 -#: wpvulnerability-adminms.php:886 +#: wpvulnerability-admin.php:627 +#: wpvulnerability-adminms.php:817 #, php-format msgid "Learn how to create a Telegram bot and get Chat ID →" msgstr "" -#: wpvulnerability-admin.php:623 -#: wpvulnerability-adminms.php:896 +#: wpvulnerability-admin.php:637 +#: wpvulnerability-adminms.php:827 msgid "Save Notification Settings" msgstr "" -#: wpvulnerability-admin.php:700 -#: wpvulnerability-admin.php:2553 -#: wpvulnerability-adminms.php:973 -#: wpvulnerability-adminms.php:2462 +#: wpvulnerability-admin.php:714 +#: wpvulnerability-admin.php:2582 +#: wpvulnerability-adminms.php:904 +#: wpvulnerability-adminms.php:2385 msgid "WordPress Components" msgstr "" -#: wpvulnerability-admin.php:703 -#: wpvulnerability-admin.php:2561 -#: wpvulnerability-adminms.php:976 -#: wpvulnerability-adminms.php:2470 +#: wpvulnerability-admin.php:717 +#: wpvulnerability-admin.php:2590 +#: wpvulnerability-adminms.php:907 +#: wpvulnerability-adminms.php:2393 msgid "WordPress Core" msgstr "" -#: wpvulnerability-admin.php:707 -#: wpvulnerability-admin.php:1473 -#: wpvulnerability-admin.php:1574 -#: wpvulnerability-admin.php:2110 -#: wpvulnerability-admin.php:2572 -#: wpvulnerability-adminms.php:980 -#: wpvulnerability-adminms.php:1745 -#: wpvulnerability-adminms.php:1846 -#: wpvulnerability-adminms.php:2269 -#: wpvulnerability-adminms.php:2481 +#: wpvulnerability-admin.php:721 +#: wpvulnerability-admin.php:1486 +#: wpvulnerability-admin.php:1587 +#: wpvulnerability-admin.php:2123 +#: wpvulnerability-admin.php:2601 +#: wpvulnerability-adminms.php:911 +#: wpvulnerability-adminms.php:1668 +#: wpvulnerability-adminms.php:1769 +#: wpvulnerability-adminms.php:2192 +#: wpvulnerability-adminms.php:2404 msgid "Plugins" msgstr "" -#: wpvulnerability-admin.php:711 -#: wpvulnerability-admin.php:1474 -#: wpvulnerability-admin.php:1575 -#: wpvulnerability-admin.php:2115 -#: wpvulnerability-admin.php:2588 -#: wpvulnerability-adminms.php:984 -#: wpvulnerability-adminms.php:1746 -#: wpvulnerability-adminms.php:1847 -#: wpvulnerability-adminms.php:2274 -#: wpvulnerability-adminms.php:2497 +#: wpvulnerability-admin.php:725 +#: wpvulnerability-admin.php:1487 +#: wpvulnerability-admin.php:1588 +#: wpvulnerability-admin.php:2128 +#: wpvulnerability-admin.php:2617 +#: wpvulnerability-adminms.php:915 +#: wpvulnerability-adminms.php:1669 +#: wpvulnerability-adminms.php:1770 +#: wpvulnerability-adminms.php:2197 +#: wpvulnerability-adminms.php:2420 msgid "Themes" msgstr "" -#: wpvulnerability-admin.php:717 -#: wpvulnerability-adminms.php:990 +#: wpvulnerability-admin.php:731 +#: wpvulnerability-adminms.php:921 msgid "Software & Languages" msgstr "" -#: wpvulnerability-admin.php:720 -#: wpvulnerability-admin.php:1475 -#: wpvulnerability-admin.php:2120 -#: wpvulnerability-adminms.php:993 -#: wpvulnerability-adminms.php:1747 -#: wpvulnerability-adminms.php:2279 +#: wpvulnerability-admin.php:734 +#: wpvulnerability-admin.php:1488 +#: wpvulnerability-admin.php:2133 +#: wpvulnerability-adminms.php:924 +#: wpvulnerability-adminms.php:1670 +#: wpvulnerability-adminms.php:2202 #: wpvulnerability-sitehealth.php:178 msgid "PHP" msgstr "" -#: wpvulnerability-admin.php:726 -#: wpvulnerability-adminms.php:999 +#: wpvulnerability-admin.php:740 +#: wpvulnerability-adminms.php:930 msgid "Web Servers" msgstr "" -#: wpvulnerability-admin.php:729 -#: wpvulnerability-admin.php:1476 -#: wpvulnerability-admin.php:2125 -#: wpvulnerability-adminms.php:1002 -#: wpvulnerability-adminms.php:1748 -#: wpvulnerability-adminms.php:2284 +#: wpvulnerability-admin.php:743 +#: wpvulnerability-admin.php:1489 +#: wpvulnerability-admin.php:2138 +#: wpvulnerability-adminms.php:933 +#: wpvulnerability-adminms.php:1671 +#: wpvulnerability-adminms.php:2207 #: wpvulnerability-sitehealth.php:179 msgid "Apache HTTPD" msgstr "" -#: wpvulnerability-admin.php:733 -#: wpvulnerability-admin.php:1477 -#: wpvulnerability-admin.php:2130 -#: wpvulnerability-adminms.php:1006 -#: wpvulnerability-adminms.php:1749 -#: wpvulnerability-adminms.php:2289 +#: wpvulnerability-admin.php:747 +#: wpvulnerability-admin.php:1490 +#: wpvulnerability-admin.php:2143 +#: wpvulnerability-adminms.php:937 +#: wpvulnerability-adminms.php:1672 +#: wpvulnerability-adminms.php:2212 msgid "nginx" msgstr "" -#: wpvulnerability-admin.php:739 -#: wpvulnerability-adminms.php:1012 +#: wpvulnerability-admin.php:753 +#: wpvulnerability-adminms.php:943 msgid "Databases" msgstr "" -#: wpvulnerability-admin.php:742 -#: wpvulnerability-admin.php:1478 -#: wpvulnerability-admin.php:2135 -#: wpvulnerability-adminms.php:1015 -#: wpvulnerability-adminms.php:1750 -#: wpvulnerability-adminms.php:2294 +#: wpvulnerability-admin.php:756 +#: wpvulnerability-admin.php:1491 +#: wpvulnerability-admin.php:2148 +#: wpvulnerability-adminms.php:946 +#: wpvulnerability-adminms.php:1673 +#: wpvulnerability-adminms.php:2217 #: wpvulnerability-sitehealth.php:181 msgid "MariaDB" msgstr "" -#: wpvulnerability-admin.php:746 -#: wpvulnerability-admin.php:1479 -#: wpvulnerability-admin.php:2140 -#: wpvulnerability-adminms.php:1019 -#: wpvulnerability-adminms.php:1751 -#: wpvulnerability-adminms.php:2299 +#: wpvulnerability-admin.php:760 +#: wpvulnerability-admin.php:1492 +#: wpvulnerability-admin.php:2153 +#: wpvulnerability-adminms.php:950 +#: wpvulnerability-adminms.php:1674 +#: wpvulnerability-adminms.php:2222 #: wpvulnerability-sitehealth.php:182 msgid "MySQL" msgstr "" -#: wpvulnerability-admin.php:750 -#: wpvulnerability-admin.php:1484 -#: wpvulnerability-admin.php:2165 -#: wpvulnerability-admin.php:3105 -#: wpvulnerability-adminms.php:1023 -#: wpvulnerability-adminms.php:1756 -#: wpvulnerability-adminms.php:2324 -#: wpvulnerability-adminms.php:3284 +#: wpvulnerability-admin.php:764 +#: wpvulnerability-admin.php:1497 +#: wpvulnerability-admin.php:2178 +#: wpvulnerability-admin.php:3136 +#: wpvulnerability-adminms.php:954 +#: wpvulnerability-adminms.php:1679 +#: wpvulnerability-adminms.php:2247 +#: wpvulnerability-adminms.php:3193 +#: wpvulnerability-sitehealth.php:187 msgid "SQLite" msgstr "" -#: wpvulnerability-admin.php:756 -#: wpvulnerability-adminms.php:1029 +#: wpvulnerability-admin.php:770 +#: wpvulnerability-adminms.php:960 msgid "Additional Tools" msgstr "" -#: wpvulnerability-admin.php:759 -#: wpvulnerability-admin.php:1480 -#: wpvulnerability-admin.php:2145 -#: wpvulnerability-admin.php:3102 -#: wpvulnerability-adminms.php:1032 -#: wpvulnerability-adminms.php:1752 -#: wpvulnerability-adminms.php:2304 -#: wpvulnerability-adminms.php:3281 +#: wpvulnerability-admin.php:773 +#: wpvulnerability-admin.php:1493 +#: wpvulnerability-admin.php:2158 +#: wpvulnerability-admin.php:3133 +#: wpvulnerability-adminms.php:963 +#: wpvulnerability-adminms.php:1675 +#: wpvulnerability-adminms.php:2227 +#: wpvulnerability-adminms.php:3190 #: wpvulnerability-sitehealth.php:183 msgid "ImageMagick" msgstr "" -#: wpvulnerability-admin.php:763 -#: wpvulnerability-admin.php:1481 -#: wpvulnerability-admin.php:2150 -#: wpvulnerability-adminms.php:1036 -#: wpvulnerability-adminms.php:1753 -#: wpvulnerability-adminms.php:2309 +#: wpvulnerability-admin.php:777 +#: wpvulnerability-admin.php:1494 +#: wpvulnerability-admin.php:2163 +#: wpvulnerability-adminms.php:967 +#: wpvulnerability-adminms.php:1676 +#: wpvulnerability-adminms.php:2232 #: wpvulnerability-sitehealth.php:184 msgid "curl" msgstr "" -#: wpvulnerability-admin.php:767 -#: wpvulnerability-admin.php:1482 -#: wpvulnerability-admin.php:2155 -#: wpvulnerability-adminms.php:1040 -#: wpvulnerability-adminms.php:1754 -#: wpvulnerability-adminms.php:2314 +#: wpvulnerability-admin.php:781 +#: wpvulnerability-admin.php:1495 +#: wpvulnerability-admin.php:2168 +#: wpvulnerability-adminms.php:971 +#: wpvulnerability-adminms.php:1677 +#: wpvulnerability-adminms.php:2237 +#: wpvulnerability-sitehealth.php:185 msgid "memcached" msgstr "" -#: wpvulnerability-admin.php:771 -#: wpvulnerability-admin.php:1483 -#: wpvulnerability-admin.php:2160 -#: wpvulnerability-admin.php:3103 -#: wpvulnerability-adminms.php:1044 -#: wpvulnerability-adminms.php:1755 -#: wpvulnerability-adminms.php:2319 -#: wpvulnerability-adminms.php:3282 +#: wpvulnerability-admin.php:785 +#: wpvulnerability-admin.php:1496 +#: wpvulnerability-admin.php:2173 +#: wpvulnerability-admin.php:3134 +#: wpvulnerability-adminms.php:975 +#: wpvulnerability-adminms.php:1678 +#: wpvulnerability-adminms.php:2242 +#: wpvulnerability-adminms.php:3191 +#: wpvulnerability-sitehealth.php:186 msgid "Redis" msgstr "" -#: wpvulnerability-admin.php:784 -#: wpvulnerability-adminms.php:1057 +#: wpvulnerability-admin.php:798 +#: wpvulnerability-adminms.php:988 msgid "Component Analysis Settings" msgstr "" -#: wpvulnerability-admin.php:787 -#: wpvulnerability-adminms.php:1060 +#: wpvulnerability-admin.php:801 +#: wpvulnerability-adminms.php:991 msgid "Enable or disable vulnerability analysis for specific components." msgstr "" -#: wpvulnerability-admin.php:788 -#: wpvulnerability-adminms.php:1061 +#: wpvulnerability-admin.php:802 +#: wpvulnerability-adminms.php:992 msgid "Check the components you want to HIDE from vulnerability scans and reports. Unchecked components will be actively analyzed. Components with active analysis are shown in green, hidden components in red." msgstr "" -#: wpvulnerability-admin.php:820 -#: wpvulnerability-adminms.php:1093 +#: wpvulnerability-admin.php:834 +#: wpvulnerability-adminms.php:1024 msgid "Hidden (forced by constant)" msgstr "" -#: wpvulnerability-admin.php:823 -#: wpvulnerability-adminms.php:1096 +#: wpvulnerability-admin.php:837 +#: wpvulnerability-adminms.php:1027 msgid "🔴 Analysis disabled" msgstr "" -#: wpvulnerability-admin.php:826 -#: wpvulnerability-adminms.php:1099 +#: wpvulnerability-admin.php:840 +#: wpvulnerability-adminms.php:1030 msgid "🟢 Analysis active" msgstr "" -#: wpvulnerability-admin.php:848 -#: wpvulnerability-adminms.php:1121 +#: wpvulnerability-admin.php:862 +#: wpvulnerability-adminms.php:1052 msgid "Tip:" msgstr "" #. translators: %s: documentation URL -#: wpvulnerability-admin.php:852 -#: wpvulnerability-adminms.php:1125 +#: wpvulnerability-admin.php:866 +#: wpvulnerability-adminms.php:1056 #, php-format msgid "You can force-disable components via wp-config.php constants. Learn more about force-hiding checks →" msgstr "" -#: wpvulnerability-admin.php:860 -#: wpvulnerability-adminms.php:1133 +#: wpvulnerability-admin.php:874 +#: wpvulnerability-adminms.php:1064 msgid "Save Analysis Settings" msgstr "" -#: wpvulnerability-admin.php:920 -#: wpvulnerability-adminms.php:1193 +#: wpvulnerability-admin.php:934 +#: wpvulnerability-adminms.php:1124 msgid "« Previous" msgstr "" -#: wpvulnerability-admin.php:921 -#: wpvulnerability-adminms.php:1194 +#: wpvulnerability-admin.php:935 +#: wpvulnerability-adminms.php:1125 msgid "Next »" msgstr "" -#: wpvulnerability-admin.php:932 -#: wpvulnerability-adminms.php:1205 +#: wpvulnerability-admin.php:946 +#: wpvulnerability-adminms.php:1136 msgid "Log entry not found or has been removed." msgstr "" -#: wpvulnerability-admin.php:935 -#: wpvulnerability-adminms.php:1208 +#: wpvulnerability-admin.php:949 +#: wpvulnerability-adminms.php:1139 msgid "Unable to load the requested log entry." msgstr "" -#: wpvulnerability-admin.php:945 -#: wpvulnerability-adminms.php:1218 +#: wpvulnerability-admin.php:959 +#: wpvulnerability-adminms.php:1149 msgid "Log Configuration" msgstr "" -#: wpvulnerability-admin.php:950 -#: wpvulnerability-adminms.php:1223 +#: wpvulnerability-admin.php:964 +#: wpvulnerability-adminms.php:1154 msgid "Total Logs" msgstr "" -#: wpvulnerability-admin.php:954 -#: wpvulnerability-adminms.php:1227 +#: wpvulnerability-admin.php:968 +#: wpvulnerability-adminms.php:1158 msgid "Retention Period" msgstr "" -#: wpvulnerability-admin.php:959 -#: wpvulnerability-admin.php:1302 -#: wpvulnerability-admin.php:3030 -#: wpvulnerability-admin.php:3044 -#: wpvulnerability-admin.php:3482 -#: wpvulnerability-admin.php:3490 -#: wpvulnerability-adminms.php:1232 -#: wpvulnerability-adminms.php:1575 -#: wpvulnerability-adminms.php:3209 -#: wpvulnerability-adminms.php:3223 -#: wpvulnerability-adminms.php:3678 -#: wpvulnerability-adminms.php:3686 +#: wpvulnerability-admin.php:973 +#: wpvulnerability-admin.php:1316 +#: wpvulnerability-admin.php:3061 +#: wpvulnerability-admin.php:3075 +#: wpvulnerability-admin.php:3516 +#: wpvulnerability-admin.php:3524 +#: wpvulnerability-adminms.php:1163 +#: wpvulnerability-adminms.php:1498 +#: wpvulnerability-adminms.php:3118 +#: wpvulnerability-adminms.php:3132 +#: wpvulnerability-adminms.php:3590 +#: wpvulnerability-adminms.php:3598 msgid "Disabled" msgstr "" #. translators: %d: Number of days. -#: wpvulnerability-admin.php:962 -#: wpvulnerability-admin.php:984 -#: wpvulnerability-admin.php:3648 -#: wpvulnerability-adminms.php:1235 -#: wpvulnerability-adminms.php:1257 -#: wpvulnerability-adminms.php:3844 +#: wpvulnerability-admin.php:976 +#: wpvulnerability-admin.php:998 +#: wpvulnerability-admin.php:3682 +#: wpvulnerability-adminms.php:1166 +#: wpvulnerability-adminms.php:1188 +#: wpvulnerability-adminms.php:3756 #, php-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: wpvulnerability-admin.php:975 -#: wpvulnerability-adminms.php:1248 +#: wpvulnerability-admin.php:989 +#: wpvulnerability-adminms.php:1179 msgid "Log Retention Period" msgstr "" -#: wpvulnerability-admin.php:981 -#: wpvulnerability-adminms.php:1254 +#: wpvulnerability-admin.php:995 +#: wpvulnerability-adminms.php:1185 msgid "None (Disabled)" msgstr "" -#: wpvulnerability-admin.php:1004 -#: wpvulnerability-adminms.php:1277 +#: wpvulnerability-admin.php:1018 +#: wpvulnerability-adminms.php:1208 msgid "This value is enforced by the WPVULNERABILITY_LOG_RETENTION_DAYS constant." msgstr "" -#: wpvulnerability-admin.php:1009 -#: wpvulnerability-adminms.php:1282 +#: wpvulnerability-admin.php:1023 +#: wpvulnerability-adminms.php:1213 msgid "Choose how long WPVulnerability should keep API response logs. Older logs are automatically deleted." msgstr "" -#: wpvulnerability-admin.php:1013 -#: wpvulnerability-adminms.php:1286 +#: wpvulnerability-admin.php:1027 +#: wpvulnerability-adminms.php:1217 msgid "Save Configuration" msgstr "" -#: wpvulnerability-admin.php:1018 -#: wpvulnerability-adminms.php:1291 +#: wpvulnerability-admin.php:1032 +#: wpvulnerability-adminms.php:1222 msgid "Log retention is currently disabled." msgstr "" -#: wpvulnerability-admin.php:1018 -#: wpvulnerability-adminms.php:1291 +#: wpvulnerability-admin.php:1032 +#: wpvulnerability-adminms.php:1222 msgid "API responses are not being saved." msgstr "" -#: wpvulnerability-admin.php:1031 -#: wpvulnerability-adminms.php:1304 +#: wpvulnerability-admin.php:1045 +#: wpvulnerability-adminms.php:1235 msgid "Log Details" msgstr "" -#: wpvulnerability-admin.php:1035 -#: wpvulnerability-admin.php:1104 +#: wpvulnerability-admin.php:1049 +#: wpvulnerability-admin.php:1118 +#: wpvulnerability-adminms.php:1239 #: wpvulnerability-adminms.php:1308 -#: wpvulnerability-adminms.php:1377 msgid "Date" msgstr "" -#: wpvulnerability-admin.php:1039 -#: wpvulnerability-admin.php:1105 -#: wpvulnerability-adminms.php:1312 -#: wpvulnerability-adminms.php:1378 +#: wpvulnerability-admin.php:1053 +#: wpvulnerability-admin.php:1119 +#: wpvulnerability-adminms.php:1243 +#: wpvulnerability-adminms.php:1309 msgid "API Endpoint" msgstr "" -#: wpvulnerability-admin.php:1044 -#: wpvulnerability-adminms.php:1317 +#: wpvulnerability-admin.php:1058 +#: wpvulnerability-adminms.php:1248 msgid "API Response" msgstr "" -#: wpvulnerability-admin.php:1049 -#: wpvulnerability-adminms.php:1322 +#: wpvulnerability-admin.php:1063 +#: wpvulnerability-adminms.php:1253 msgid "Back to Logs" msgstr "" -#: wpvulnerability-admin.php:1058 -#: wpvulnerability-adminms.php:1331 +#: wpvulnerability-admin.php:1072 +#: wpvulnerability-adminms.php:1262 msgid "No logs available" msgstr "" -#: wpvulnerability-admin.php:1059 -#: wpvulnerability-adminms.php:1332 +#: wpvulnerability-admin.php:1073 +#: wpvulnerability-adminms.php:1263 msgid "API response logs will appear here once the vulnerability scanner runs." msgstr "" -#: wpvulnerability-admin.php:1063 -#: wpvulnerability-adminms.php:1336 +#: wpvulnerability-admin.php:1077 +#: wpvulnerability-adminms.php:1267 msgid "API Response Logs" msgstr "" -#: wpvulnerability-admin.php:1073 -#: wpvulnerability-adminms.php:1346 +#: wpvulnerability-admin.php:1087 +#: wpvulnerability-adminms.php:1277 msgid "Show:" msgstr "" -#: wpvulnerability-admin.php:1082 -#: wpvulnerability-adminms.php:1355 +#: wpvulnerability-admin.php:1096 +#: wpvulnerability-adminms.php:1286 msgid "Apply" msgstr "" #. translators: 1: first item, 2: last item, 3: total items -#: wpvulnerability-admin.php:1090 -#: wpvulnerability-adminms.php:1363 +#: wpvulnerability-admin.php:1104 +#: wpvulnerability-adminms.php:1294 #, php-format msgid "Showing %1$s–%2$s of %3$s" msgstr "" -#: wpvulnerability-admin.php:1106 -#: wpvulnerability-adminms.php:1379 +#: wpvulnerability-admin.php:1120 +#: wpvulnerability-adminms.php:1310 msgid "Actions" msgstr "" -#: wpvulnerability-admin.php:1133 -#: wpvulnerability-admin.php:3964 -#: wpvulnerability-adminms.php:1406 -#: wpvulnerability-adminms.php:4160 +#: wpvulnerability-admin.php:1147 +#: wpvulnerability-admin.php:4006 +#: wpvulnerability-adminms.php:1337 +#: wpvulnerability-adminms.php:4080 msgid "View" msgstr "" -#: wpvulnerability-admin.php:1150 -#: wpvulnerability-adminms.php:1423 +#: wpvulnerability-admin.php:1164 +#: wpvulnerability-adminms.php:1354 msgid "Danger Zone" msgstr "" -#: wpvulnerability-admin.php:1152 -#: wpvulnerability-adminms.php:1425 +#: wpvulnerability-admin.php:1166 +#: wpvulnerability-adminms.php:1356 msgid "Permanently delete all API response logs. This action cannot be undone." msgstr "" -#: wpvulnerability-admin.php:1154 -#: wpvulnerability-adminms.php:1427 +#: wpvulnerability-admin.php:1168 +#: wpvulnerability-adminms.php:1358 msgid "Are you sure you want to delete all logs? This action cannot be undone." msgstr "" -#: wpvulnerability-admin.php:1157 -#: wpvulnerability-adminms.php:1430 +#: wpvulnerability-admin.php:1171 +#: wpvulnerability-adminms.php:1361 msgid "Delete All Logs" msgstr "" -#: wpvulnerability-admin.php:1202 -#: wpvulnerability-adminms.php:1475 +#: wpvulnerability-admin.php:1216 +#: wpvulnerability-adminms.php:1398 msgid "Reload Data from API" msgstr "" -#: wpvulnerability-admin.php:1205 -#: wpvulnerability-adminms.php:1478 +#: wpvulnerability-admin.php:1219 +#: wpvulnerability-adminms.php:1401 msgid "Reload all Core, Plugins, Themes and other components information directly from the API to have updated data. This will clear the cache and fetch fresh vulnerability information." msgstr "" -#: wpvulnerability-admin.php:1210 -#: wpvulnerability-adminms.php:1483 +#: wpvulnerability-admin.php:1224 +#: wpvulnerability-adminms.php:1406 msgid "Reload Data" msgstr "" -#: wpvulnerability-admin.php:1219 -#: wpvulnerability-adminms.php:1492 +#: wpvulnerability-admin.php:1233 +#: wpvulnerability-adminms.php:1415 msgid "Test Email Notifications" msgstr "" -#: wpvulnerability-admin.php:1222 -#: wpvulnerability-adminms.php:1495 +#: wpvulnerability-admin.php:1236 +#: wpvulnerability-adminms.php:1418 msgid "Send a test email notification with current vulnerability data to verify your email configuration is working correctly." msgstr "" -#: wpvulnerability-admin.php:1232 -#: wpvulnerability-adminms.php:1505 +#: wpvulnerability-admin.php:1246 +#: wpvulnerability-adminms.php:1428 msgid "From address (configured via constant):" msgstr "" -#: wpvulnerability-admin.php:1244 -#: wpvulnerability-adminms.php:1517 +#: wpvulnerability-admin.php:1258 +#: wpvulnerability-adminms.php:1440 msgid "From address (default):" msgstr "" -#: wpvulnerability-admin.php:1253 -#: wpvulnerability-adminms.php:1526 +#: wpvulnerability-admin.php:1267 +#: wpvulnerability-adminms.php:1449 msgid "Learn how to customize the email sender address →" msgstr "" -#: wpvulnerability-admin.php:1259 -#: wpvulnerability-adminms.php:1532 +#: wpvulnerability-admin.php:1273 +#: wpvulnerability-adminms.php:1455 msgid "Send Test Email" msgstr "" -#: wpvulnerability-admin.php:1268 -#: wpvulnerability-adminms.php:1541 +#: wpvulnerability-admin.php:1282 +#: wpvulnerability-adminms.php:1464 msgid "WP-Cron Status" msgstr "" -#: wpvulnerability-admin.php:1271 -#: wpvulnerability-adminms.php:1544 +#: wpvulnerability-admin.php:1285 +#: wpvulnerability-adminms.php:1467 msgid "View and manage WP-Cron scheduled events. Compare expected schedules with actual cron jobs to ensure automated tasks are running correctly." msgstr "" -#: wpvulnerability-admin.php:1276 -#: wpvulnerability-adminms.php:1549 +#: wpvulnerability-admin.php:1290 +#: wpvulnerability-adminms.php:1472 msgid "No cron data available." msgstr "" -#: wpvulnerability-admin.php:1282 -#: wpvulnerability-adminms.php:1555 +#: wpvulnerability-admin.php:1296 +#: wpvulnerability-adminms.php:1478 msgid "Hook" msgstr "" -#: wpvulnerability-admin.php:1283 -#: wpvulnerability-adminms.php:1556 +#: wpvulnerability-admin.php:1297 +#: wpvulnerability-adminms.php:1479 msgid "Expected" msgstr "" -#: wpvulnerability-admin.php:1284 -#: wpvulnerability-adminms.php:1557 +#: wpvulnerability-admin.php:1298 +#: wpvulnerability-adminms.php:1480 msgid "Found" msgstr "" -#: wpvulnerability-admin.php:1285 -#: wpvulnerability-adminms.php:1558 +#: wpvulnerability-admin.php:1299 +#: wpvulnerability-adminms.php:1481 msgid "Next Run" msgstr "" -#: wpvulnerability-admin.php:1286 -#: wpvulnerability-admin.php:3243 -#: wpvulnerability-admin.php:3566 -#: wpvulnerability-adminms.php:1559 -#: wpvulnerability-adminms.php:3422 -#: wpvulnerability-adminms.php:3762 +#: wpvulnerability-admin.php:1300 +#: wpvulnerability-admin.php:3274 +#: wpvulnerability-admin.php:3600 +#: wpvulnerability-adminms.php:1482 +#: wpvulnerability-adminms.php:3331 +#: wpvulnerability-adminms.php:3674 msgid "Status" msgstr "" -#: wpvulnerability-admin.php:1300 -#: wpvulnerability-adminms.php:1573 +#: wpvulnerability-admin.php:1314 +#: wpvulnerability-adminms.php:1496 msgid "Not expected" msgstr "" -#: wpvulnerability-admin.php:1322 -#: wpvulnerability-adminms.php:1595 -#: wpvulnerability-general.php:1873 -#: wpvulnerability-general.php:1874 +#: wpvulnerability-admin.php:1336 +#: wpvulnerability-adminms.php:1518 +#: wpvulnerability-general.php:1891 +#: wpvulnerability-general.php:1892 msgid "None" msgstr "" -#: wpvulnerability-admin.php:1357 -#: wpvulnerability-adminms.php:1630 +#: wpvulnerability-admin.php:1371 +#: wpvulnerability-adminms.php:1553 msgid "OK" msgstr "" -#: wpvulnerability-admin.php:1367 -#: wpvulnerability-adminms.php:1640 +#: wpvulnerability-admin.php:1381 +#: wpvulnerability-adminms.php:1563 msgid "⚠️ Unexpected WPVulnerability Events" msgstr "" -#: wpvulnerability-admin.php:1384 -#: wpvulnerability-adminms.php:1657 +#: wpvulnerability-admin.php:1398 +#: wpvulnerability-adminms.php:1580 msgid "No interval" msgstr "" -#: wpvulnerability-admin.php:1391 -#: wpvulnerability-adminms.php:1664 +#: wpvulnerability-admin.php:1405 +#: wpvulnerability-adminms.php:1587 msgid "Next:" msgstr "" -#: wpvulnerability-admin.php:1401 -#: wpvulnerability-adminms.php:1674 +#: wpvulnerability-admin.php:1415 +#: wpvulnerability-adminms.php:1597 msgid "Repair Cron Events" msgstr "" -#: wpvulnerability-admin.php:1409 -#: wpvulnerability-adminms.php:1682 +#: wpvulnerability-admin.php:1423 +#: wpvulnerability-adminms.php:1605 msgid "Uninstall Data" msgstr "" -#: wpvulnerability-admin.php:1411 -#: wpvulnerability-adminms.php:1684 +#: wpvulnerability-admin.php:1425 +#: wpvulnerability-adminms.php:1607 msgid "By default, all plugin data is preserved when WPVulnerability is deleted. Enable the option below only if you want all options, logs, and cached data permanently removed on uninstall." msgstr "" -#: wpvulnerability-admin.php:1421 -#: wpvulnerability-adminms.php:1694 +#: wpvulnerability-admin.php:1435 +#: wpvulnerability-adminms.php:1617 msgid "Delete all plugin data on uninstall" msgstr "" -#: wpvulnerability-admin.php:1424 -#: wpvulnerability-adminms.php:1697 +#: wpvulnerability-admin.php:1438 +#: wpvulnerability-adminms.php:1620 msgid "Default: unchecked (data preserved). Check only if you want a clean removal." msgstr "" -#: wpvulnerability-admin.php:1426 -#: wpvulnerability-adminms.php:1699 +#: wpvulnerability-admin.php:1440 +#: wpvulnerability-adminms.php:1622 msgid "Save preference" msgstr "" -#: wpvulnerability-admin.php:1433 -#: wpvulnerability-adminms.php:1706 +#: wpvulnerability-admin.php:1447 +#: wpvulnerability-adminms.php:1629 msgid "Reset WPVulnerability" msgstr "" -#: wpvulnerability-admin.php:1435 -#: wpvulnerability-adminms.php:1708 +#: wpvulnerability-admin.php:1449 +#: wpvulnerability-adminms.php:1631 msgid "Delete all WPVulnerability settings, cached data, logs, and scheduled events, then restore defaults and reload data from the API." msgstr "" -#: wpvulnerability-admin.php:1437 -#: wpvulnerability-adminms.php:1710 +#: wpvulnerability-admin.php:1451 +#: wpvulnerability-adminms.php:1633 msgid "This action cannot be undone." msgstr "" -#: wpvulnerability-admin.php:1439 -#: wpvulnerability-adminms.php:1712 +#: wpvulnerability-admin.php:1453 +#: wpvulnerability-adminms.php:1635 msgid "Are you sure you want to reset all WPVulnerability data? This action cannot be undone." msgstr "" -#: wpvulnerability-admin.php:1442 -#: wpvulnerability-adminms.php:1714 +#: wpvulnerability-admin.php:1455 +#: wpvulnerability-adminms.php:1637 msgid "Reset Plugin" msgstr "" -#: wpvulnerability-admin.php:1469 -#: wpvulnerability-adminms.php:1741 +#: wpvulnerability-admin.php:1482 +#: wpvulnerability-adminms.php:1664 msgid "Vulnerability Database Statistics" msgstr "" #. translators: number of vulnerabilities. -#: wpvulnerability-admin.php:1511 -#: wpvulnerability-adminms.php:1783 +#: wpvulnerability-admin.php:1524 +#: wpvulnerability-adminms.php:1706 #, php-format msgid "%s vulnerability" msgid_plural "%s vulnerabilities" @@ -1063,161 +1066,161 @@ msgstr[0] "" msgstr[1] "" #. translators: number of products. -#: wpvulnerability-admin.php:1521 -#: wpvulnerability-adminms.php:1793 +#: wpvulnerability-admin.php:1534 +#: wpvulnerability-adminms.php:1716 #, php-format msgid "(%s product)" msgid_plural "(%s products)" msgstr[0] "" msgstr[1] "" -#: wpvulnerability-admin.php:1528 -#: wpvulnerability-adminms.php:1800 +#: wpvulnerability-admin.php:1541 +#: wpvulnerability-adminms.php:1723 msgid "No data" msgstr "" #. translators: date of last update. -#: wpvulnerability-admin.php:1545 -#: wpvulnerability-adminms.php:1817 +#: wpvulnerability-admin.php:1558 +#: wpvulnerability-adminms.php:1740 #, php-format msgid "Database last updated: %s" msgstr "" -#: wpvulnerability-admin.php:1567 -#: wpvulnerability-adminms.php:1839 +#: wpvulnerability-admin.php:1580 +#: wpvulnerability-adminms.php:1762 msgid "Intelligence Sources" msgstr "" -#: wpvulnerability-admin.php:1568 -#: wpvulnerability-adminms.php:1840 +#: wpvulnerability-admin.php:1581 +#: wpvulnerability-adminms.php:1763 msgid "Number of vulnerabilities contributed by each intelligence source." msgstr "" -#: wpvulnerability-admin.php:1572 -#: wpvulnerability-adminms.php:1844 +#: wpvulnerability-admin.php:1585 +#: wpvulnerability-adminms.php:1767 msgid "Source" msgstr "" -#: wpvulnerability-admin.php:1573 -#: wpvulnerability-admin.php:2105 -#: wpvulnerability-adminms.php:1845 -#: wpvulnerability-adminms.php:2264 +#: wpvulnerability-admin.php:1586 +#: wpvulnerability-admin.php:2118 +#: wpvulnerability-adminms.php:1768 +#: wpvulnerability-adminms.php:2187 msgid "Core" msgstr "" -#: wpvulnerability-admin.php:1604 -#: wpvulnerability-adminms.php:1876 +#: wpvulnerability-admin.php:1617 +#: wpvulnerability-adminms.php:1799 msgid "Sponsors" msgstr "" -#: wpvulnerability-admin.php:1627 -#: wpvulnerability-adminms.php:1899 +#: wpvulnerability-admin.php:1640 +#: wpvulnerability-adminms.php:1822 msgid "No sponsor data available." msgstr "" -#: wpvulnerability-admin.php:1634 -#: wpvulnerability-adminms.php:1906 +#: wpvulnerability-admin.php:1647 +#: wpvulnerability-adminms.php:1829 msgid "Contributors" msgstr "" -#: wpvulnerability-admin.php:1657 -#: wpvulnerability-adminms.php:1929 +#: wpvulnerability-admin.php:1670 +#: wpvulnerability-adminms.php:1852 msgid "No contributor data available." msgstr "" -#: wpvulnerability-admin.php:1675 -#: wpvulnerability-adminms.php:432 +#: wpvulnerability-admin.php:1688 +#: wpvulnerability-adminms.php:358 msgid "You do not have sufficient permissions to access this page." msgstr "" -#: wpvulnerability-admin.php:1685 -#: wpvulnerability-adminms.php:441 +#: wpvulnerability-admin.php:1698 +#: wpvulnerability-adminms.php:367 msgid "WPVulnerability settings" msgstr "" -#: wpvulnerability-admin.php:1802 -#: wpvulnerability-adminms.php:1966 +#: wpvulnerability-admin.php:1815 +#: wpvulnerability-adminms.php:1889 msgid "Configure and save these settings to receive notifications." msgstr "" -#: wpvulnerability-admin.php:1815 -#: wpvulnerability-adminms.php:2074 +#: wpvulnerability-admin.php:1828 +#: wpvulnerability-adminms.php:1997 msgid "Configure and save these settings to hide vulnerabilities." msgstr "" -#: wpvulnerability-admin.php:1846 -#: wpvulnerability-adminms.php:1996 +#: wpvulnerability-admin.php:1859 +#: wpvulnerability-adminms.php:1919 msgid "Default administrator email" msgstr "" -#: wpvulnerability-admin.php:1910 -#: wpvulnerability-adminms.php:2060 +#: wpvulnerability-admin.php:1923 +#: wpvulnerability-adminms.php:1983 msgid "Read more if you want to force the cache time." msgstr "" -#: wpvulnerability-admin.php:1940 -#: wpvulnerability-adminms.php:2102 +#: wpvulnerability-admin.php:1953 +#: wpvulnerability-adminms.php:2025 msgid "Never" msgstr "" -#: wpvulnerability-admin.php:1945 -#: wpvulnerability-adminms.php:2107 +#: wpvulnerability-admin.php:1958 +#: wpvulnerability-adminms.php:2030 msgid "Daily" msgstr "" -#: wpvulnerability-admin.php:1950 -#: wpvulnerability-adminms.php:2112 +#: wpvulnerability-admin.php:1963 +#: wpvulnerability-adminms.php:2035 msgid "Weekly" msgstr "" -#: wpvulnerability-admin.php:1954 -#: wpvulnerability-adminms.php:2116 +#: wpvulnerability-admin.php:1967 +#: wpvulnerability-adminms.php:2039 msgid "Day" msgstr "" -#: wpvulnerability-admin.php:1967 -#: wpvulnerability-adminms.php:2129 +#: wpvulnerability-admin.php:1980 +#: wpvulnerability-adminms.php:2052 msgid "Hour" msgstr "" -#: wpvulnerability-admin.php:1969 -#: wpvulnerability-adminms.php:2131 +#: wpvulnerability-admin.php:1982 +#: wpvulnerability-adminms.php:2054 msgid "Minute" msgstr "" -#: wpvulnerability-admin.php:2167 -#: wpvulnerability-adminms.php:2326 +#: wpvulnerability-admin.php:2180 +#: wpvulnerability-adminms.php:2249 msgid "Read more about how to force the deactivation of an item." msgstr "" -#: wpvulnerability-admin.php:2293 -#: wpvulnerability-adminms.php:2873 +#: wpvulnerability-admin.php:2310 +#: wpvulnerability-adminms.php:2800 msgid "Invalid Slack webhook URL. Must be a valid HTTPS URL from hooks.slack.com" msgstr "" -#: wpvulnerability-admin.php:2316 -#: wpvulnerability-adminms.php:2896 +#: wpvulnerability-admin.php:2337 +#: wpvulnerability-adminms.php:2827 msgid "Invalid Microsoft Teams webhook URL. Must be a valid HTTPS URL from office.com, office365.com, or api.hooks.microsoft.com" msgstr "" -#: wpvulnerability-admin.php:2339 -#: wpvulnerability-adminms.php:2919 +#: wpvulnerability-admin.php:2364 +#: wpvulnerability-adminms.php:2854 msgid "Invalid Discord webhook URL. Must be a valid HTTPS URL from discord.com or discordapp.com" msgstr "" -#: wpvulnerability-admin.php:2358 -#: wpvulnerability-adminms.php:2938 +#: wpvulnerability-admin.php:2387 +#: wpvulnerability-adminms.php:2877 msgid "Invalid Telegram bot token format. Must be like: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" msgstr "" -#: wpvulnerability-admin.php:2494 -#: wpvulnerability-adminms.php:2403 +#: wpvulnerability-admin.php:2523 +#: wpvulnerability-adminms.php:2326 msgid "All Clear" msgstr "" #. translators: %d: number of vulnerabilities -#: wpvulnerability-admin.php:2504 -#: wpvulnerability-adminms.php:2413 +#: wpvulnerability-admin.php:2533 +#: wpvulnerability-adminms.php:2336 #, php-format msgid "%d Critical Issue Found" msgid_plural "%d Critical Issues Found" @@ -1225,22 +1228,22 @@ msgstr[0] "" msgstr[1] "" #. translators: %d: number of vulnerabilities -#: wpvulnerability-admin.php:2509 -#: wpvulnerability-adminms.php:2418 +#: wpvulnerability-admin.php:2538 +#: wpvulnerability-adminms.php:2341 #, php-format msgid "%d Issue Found" msgid_plural "%d Issues Found" msgstr[0] "" msgstr[1] "" -#: wpvulnerability-admin.php:2516 -#: wpvulnerability-adminms.php:2425 +#: wpvulnerability-admin.php:2545 +#: wpvulnerability-adminms.php:2348 msgid "Never checked" msgstr "" #. translators: %d: number of minutes -#: wpvulnerability-admin.php:2524 -#: wpvulnerability-adminms.php:2433 +#: wpvulnerability-admin.php:2553 +#: wpvulnerability-adminms.php:2356 #, php-format msgid "%d minute ago" msgid_plural "%d minutes ago" @@ -1248,8 +1251,8 @@ msgstr[0] "" msgstr[1] "" #. translators: %d: number of hours -#: wpvulnerability-admin.php:2528 -#: wpvulnerability-adminms.php:2437 +#: wpvulnerability-admin.php:2557 +#: wpvulnerability-adminms.php:2360 #, php-format msgid "%d hour ago" msgid_plural "%d hours ago" @@ -1257,710 +1260,712 @@ msgstr[0] "" msgstr[1] "" #. translators: %s: time since last check -#: wpvulnerability-admin.php:2540 -#: wpvulnerability-adminms.php:2449 +#: wpvulnerability-admin.php:2569 +#: wpvulnerability-adminms.php:2372 #, php-format msgid "Last checked: %s" msgstr "" -#: wpvulnerability-admin.php:2541 -#: wpvulnerability-adminms.php:2450 +#: wpvulnerability-admin.php:2570 +#: wpvulnerability-adminms.php:2373 msgid "Refresh Now" msgstr "" -#: wpvulnerability-admin.php:2548 -#: wpvulnerability-adminms.php:2457 +#: wpvulnerability-admin.php:2577 +#: wpvulnerability-adminms.php:2380 msgid "No vulnerabilities detected" msgstr "" -#: wpvulnerability-admin.php:2549 -#: wpvulnerability-adminms.php:2458 +#: wpvulnerability-admin.php:2578 +#: wpvulnerability-adminms.php:2381 msgid "Your site is up to date and secure" msgstr "" -#: wpvulnerability-admin.php:2617 -#: wpvulnerability-adminms.php:2526 +#: wpvulnerability-admin.php:2646 +#: wpvulnerability-adminms.php:2449 msgid "Server Software" msgstr "" -#: wpvulnerability-admin.php:2830 -#: wpvulnerability-adminms.php:2739 +#: wpvulnerability-admin.php:2859 +#: wpvulnerability-adminms.php:2662 msgid "Site Health" msgstr "" -#: wpvulnerability-admin.php:2832 -#: wpvulnerability-adminms.php:2741 +#: wpvulnerability-admin.php:2861 +#: wpvulnerability-adminms.php:2664 #: wpvulnerability-run.php:33 msgid "Settings" msgstr "" -#: wpvulnerability-admin.php:2847 -#: wpvulnerability-adminms.php:2757 +#: wpvulnerability-admin.php:2876 +#: wpvulnerability-adminms.php:2680 msgid "WPVulnerability Status" msgstr "" -#: wpvulnerability-admin.php:2879 -#: wpvulnerability-adminms.php:3055 +#: wpvulnerability-admin.php:2909 +#: wpvulnerability-adminms.php:2964 msgid "Receive vulnerability notifications" msgstr "" -#: wpvulnerability-admin.php:2887 -#: wpvulnerability-adminms.php:3063 +#: wpvulnerability-admin.php:2917 +#: wpvulnerability-adminms.php:2972 msgid "Cache expiration time" msgstr "" -#: wpvulnerability-admin.php:2896 -#: wpvulnerability-adminms.php:3072 +#: wpvulnerability-admin.php:2926 +#: wpvulnerability-adminms.php:2981 msgid "How often you want to receive notifications" msgstr "" -#: wpvulnerability-admin.php:2905 -#: wpvulnerability-adminms.php:3081 +#: wpvulnerability-admin.php:2935 +#: wpvulnerability-adminms.php:2990 msgid "Where do you want to receive notifications?" msgstr "" -#: wpvulnerability-admin.php:2914 -#: wpvulnerability-adminms.php:3090 +#: wpvulnerability-admin.php:2944 +#: wpvulnerability-adminms.php:2999 msgid "Email addresses to notify (separated by commas)" msgstr "" -#: wpvulnerability-admin.php:2923 -#: wpvulnerability-adminms.php:3099 +#: wpvulnerability-admin.php:2953 +#: wpvulnerability-adminms.php:3008 msgid "Slack webhook URL" msgstr "" -#: wpvulnerability-admin.php:2932 -#: wpvulnerability-adminms.php:3108 +#: wpvulnerability-admin.php:2962 +#: wpvulnerability-adminms.php:3017 msgid "Teams webhook URL" msgstr "" -#: wpvulnerability-admin.php:2951 -#: wpvulnerability-adminms.php:3129 +#: wpvulnerability-admin.php:2982 +#: wpvulnerability-adminms.php:3038 msgid "Vulnerabilities to hide" msgstr "" -#: wpvulnerability-admin.php:2959 -#: wpvulnerability-adminms.php:3137 +#: wpvulnerability-admin.php:2990 +#: wpvulnerability-adminms.php:3046 msgid "What do you want to hide?" msgstr "" -#: wpvulnerability-admin.php:3009 -#: wpvulnerability-adminms.php:3188 +#: wpvulnerability-admin.php:3040 +#: wpvulnerability-adminms.php:3097 msgid "Standard (Hybrid Detection)" msgstr "" -#: wpvulnerability-admin.php:3010 -#: wpvulnerability-adminms.php:3189 +#: wpvulnerability-admin.php:3041 +#: wpvulnerability-adminms.php:3098 msgid "Strict (Extensions Only)" msgstr "" -#: wpvulnerability-admin.php:3011 -#: wpvulnerability-adminms.php:3190 +#: wpvulnerability-admin.php:3042 +#: wpvulnerability-adminms.php:3099 msgid "Disabled (No Detection)" msgstr "" -#: wpvulnerability-admin.php:3017 -#: wpvulnerability-adminms.php:3196 +#: wpvulnerability-admin.php:3048 +#: wpvulnerability-adminms.php:3105 msgid "Security Status" msgstr "" -#: wpvulnerability-admin.php:3021 -#: wpvulnerability-adminms.php:3200 +#: wpvulnerability-admin.php:3052 +#: wpvulnerability-adminms.php:3109 msgid "Security Mode" msgstr "" -#: wpvulnerability-admin.php:3026 -#: wpvulnerability-adminms.php:3205 +#: wpvulnerability-admin.php:3057 +#: wpvulnerability-adminms.php:3114 msgid "Shell Execution" msgstr "" -#: wpvulnerability-admin.php:3028 -#: wpvulnerability-admin.php:3482 -#: wpvulnerability-admin.php:3490 -#: wpvulnerability-adminms.php:3207 -#: wpvulnerability-adminms.php:3678 -#: wpvulnerability-adminms.php:3686 +#: wpvulnerability-admin.php:3059 +#: wpvulnerability-admin.php:3516 +#: wpvulnerability-admin.php:3524 +#: wpvulnerability-adminms.php:3116 +#: wpvulnerability-adminms.php:3590 +#: wpvulnerability-adminms.php:3598 msgid "Enabled" msgstr "" -#: wpvulnerability-admin.php:3035 -#: wpvulnerability-adminms.php:3214 +#: wpvulnerability-admin.php:3066 +#: wpvulnerability-adminms.php:3123 msgid "Audit Logging" msgstr "" #. translators: %d: Number of days. -#: wpvulnerability-admin.php:3040 -#: wpvulnerability-adminms.php:3219 +#: wpvulnerability-admin.php:3071 +#: wpvulnerability-adminms.php:3128 #, php-format msgid "Enabled (%d days)" msgstr "" -#: wpvulnerability-admin.php:3049 -#: wpvulnerability-adminms.php:3228 +#: wpvulnerability-admin.php:3080 +#: wpvulnerability-adminms.php:3137 msgid "Total Log Entries" msgstr "" -#: wpvulnerability-admin.php:3056 -#: wpvulnerability-adminms.php:3235 +#: wpvulnerability-admin.php:3087 +#: wpvulnerability-adminms.php:3144 msgid "Last Shell Execution" msgstr "" #. translators: %s: time ago -#: wpvulnerability-admin.php:3062 -#: wpvulnerability-admin.php:3273 -#: wpvulnerability-adminms.php:3241 -#: wpvulnerability-adminms.php:3450 +#: wpvulnerability-admin.php:3093 +#: wpvulnerability-admin.php:3304 +#: wpvulnerability-adminms.php:3150 +#: wpvulnerability-adminms.php:3359 #, php-format msgid "%s ago" msgstr "" -#: wpvulnerability-admin.php:3074 -#: wpvulnerability-adminms.php:3253 +#: wpvulnerability-admin.php:3105 +#: wpvulnerability-adminms.php:3162 msgid "Configure security settings via wp-config.php constants: WPVULNERABILITY_DISABLE_SHELL_EXEC, WPVULNERABILITY_SECURITY_MODE, WPVULNERABILITY_SHELL_EXEC_WHITELIST." msgstr "" #. translators: %s: URL to security documentation -#: wpvulnerability-admin.php:3080 -#: wpvulnerability-adminms.php:3259 +#: wpvulnerability-admin.php:3111 +#: wpvulnerability-adminms.php:3168 #, php-format msgid "See security configuration documentation for details." msgstr "" -#: wpvulnerability-admin.php:3104 -#: wpvulnerability-adminms.php:3283 +#: wpvulnerability-admin.php:3135 +#: wpvulnerability-adminms.php:3192 msgid "Memcached" msgstr "" -#: wpvulnerability-admin.php:3138 -#: wpvulnerability-adminms.php:3317 +#: wpvulnerability-admin.php:3169 +#: wpvulnerability-adminms.php:3226 msgid "Imagick Extension" msgstr "" -#: wpvulnerability-admin.php:3139 -#: wpvulnerability-adminms.php:3318 +#: wpvulnerability-admin.php:3170 +#: wpvulnerability-adminms.php:3227 msgid "Redis Extension" msgstr "" -#: wpvulnerability-admin.php:3140 -#: wpvulnerability-adminms.php:3319 +#: wpvulnerability-admin.php:3171 +#: wpvulnerability-adminms.php:3228 msgid "Memcached Extension" msgstr "" -#: wpvulnerability-admin.php:3141 -#: wpvulnerability-adminms.php:3320 +#: wpvulnerability-admin.php:3172 +#: wpvulnerability-adminms.php:3229 msgid "Memcache Extension" msgstr "" -#: wpvulnerability-admin.php:3142 -#: wpvulnerability-adminms.php:3321 +#: wpvulnerability-admin.php:3173 +#: wpvulnerability-adminms.php:3230 msgid "SQLite3 Extension" msgstr "" -#: wpvulnerability-admin.php:3143 -#: wpvulnerability-adminms.php:3322 +#: wpvulnerability-admin.php:3174 +#: wpvulnerability-adminms.php:3231 msgid "PDO SQLite" msgstr "" -#: wpvulnerability-admin.php:3144 -#: wpvulnerability-adminms.php:3323 +#: wpvulnerability-admin.php:3175 +#: wpvulnerability-adminms.php:3232 msgid "Shell Command" msgstr "" -#: wpvulnerability-admin.php:3145 -#: wpvulnerability-adminms.php:3324 +#: wpvulnerability-admin.php:3176 +#: wpvulnerability-adminms.php:3233 msgid "Binary Check" msgstr "" -#: wpvulnerability-admin.php:3146 -#: wpvulnerability-adminms.php:3325 +#: wpvulnerability-admin.php:3177 +#: wpvulnerability-adminms.php:3234 msgid "Not Detected" msgstr "" -#: wpvulnerability-admin.php:3150 -#: wpvulnerability-adminms.php:3329 +#: wpvulnerability-admin.php:3181 +#: wpvulnerability-adminms.php:3238 msgid "Software Detection Methods" msgstr "" -#: wpvulnerability-admin.php:3151 -#: wpvulnerability-adminms.php:3330 +#: wpvulnerability-admin.php:3182 +#: wpvulnerability-adminms.php:3239 msgid "Shows which detection method was used for each software component and the reliability score." msgstr "" -#: wpvulnerability-admin.php:3156 -#: wpvulnerability-admin.php:3241 -#: wpvulnerability-admin.php:3565 -#: wpvulnerability-adminms.php:3335 -#: wpvulnerability-adminms.php:3420 -#: wpvulnerability-adminms.php:3761 +#: wpvulnerability-admin.php:3187 +#: wpvulnerability-admin.php:3272 +#: wpvulnerability-admin.php:3599 +#: wpvulnerability-adminms.php:3244 +#: wpvulnerability-adminms.php:3329 +#: wpvulnerability-adminms.php:3673 msgid "Component" msgstr "" -#: wpvulnerability-admin.php:3157 -#: wpvulnerability-adminms.php:3336 +#: wpvulnerability-admin.php:3188 +#: wpvulnerability-adminms.php:3245 msgid "Version" msgstr "" -#: wpvulnerability-admin.php:3158 -#: wpvulnerability-adminms.php:3337 +#: wpvulnerability-admin.php:3189 +#: wpvulnerability-adminms.php:3246 msgid "Detection Method" msgstr "" -#: wpvulnerability-admin.php:3159 -#: wpvulnerability-adminms.php:3338 +#: wpvulnerability-admin.php:3190 +#: wpvulnerability-adminms.php:3247 msgid "Reliability" msgstr "" -#: wpvulnerability-admin.php:3171 -#: wpvulnerability-adminms.php:3350 +#: wpvulnerability-admin.php:3202 +#: wpvulnerability-adminms.php:3259 +#: wpvulnerability-debug.php:1056 msgid "Not detected" msgstr "" -#: wpvulnerability-admin.php:3227 -#: wpvulnerability-adminms.php:3406 +#: wpvulnerability-admin.php:3258 +#: wpvulnerability-adminms.php:3315 msgid "Shell Execution Audit Logs" msgstr "" -#: wpvulnerability-admin.php:3228 -#: wpvulnerability-adminms.php:3407 +#: wpvulnerability-admin.php:3259 +#: wpvulnerability-adminms.php:3316 msgid "Complete audit trail of shell command executions for security monitoring." msgstr "" -#: wpvulnerability-admin.php:3233 -#: wpvulnerability-adminms.php:3412 +#: wpvulnerability-admin.php:3264 +#: wpvulnerability-adminms.php:3321 msgid "No shell execution logs found." msgstr "" -#: wpvulnerability-admin.php:3234 -#: wpvulnerability-adminms.php:3413 +#: wpvulnerability-admin.php:3265 +#: wpvulnerability-adminms.php:3322 msgid "Logs will appear here when shell commands are executed for software detection." msgstr "" -#: wpvulnerability-admin.php:3240 -#: wpvulnerability-adminms.php:3419 +#: wpvulnerability-admin.php:3271 +#: wpvulnerability-adminms.php:3328 msgid "Time" msgstr "" -#: wpvulnerability-admin.php:3242 -#: wpvulnerability-adminms.php:3421 +#: wpvulnerability-admin.php:3273 +#: wpvulnerability-adminms.php:3330 msgid "Command" msgstr "" -#: wpvulnerability-admin.php:3244 -#: wpvulnerability-adminms.php:3423 +#: wpvulnerability-admin.php:3275 +#: wpvulnerability-adminms.php:3332 msgid "User" msgstr "" -#: wpvulnerability-admin.php:3245 -#: wpvulnerability-adminms.php:3424 +#: wpvulnerability-admin.php:3276 +#: wpvulnerability-adminms.php:3333 msgid "Output" msgstr "" #. translators: %d: number of logs shown -#: wpvulnerability-admin.php:3309 -#: wpvulnerability-adminms.php:3486 +#: wpvulnerability-admin.php:3340 +#: wpvulnerability-adminms.php:3395 #, php-format msgid "Showing the %d most recent log entries." msgstr "" -#: wpvulnerability-admin.php:3327 -#: wpvulnerability-adminms.php:3574 +#: wpvulnerability-admin.php:3358 +#: wpvulnerability-adminms.php:3486 msgid "Invalid nonce." msgstr "" -#: wpvulnerability-admin.php:3332 -#: wpvulnerability-adminms.php:3579 +#: wpvulnerability-admin.php:3363 +#: wpvulnerability-adminms.php:3491 msgid "Insufficient permissions." msgstr "" -#: wpvulnerability-admin.php:3338 -#: wpvulnerability-adminms.php:3585 +#: wpvulnerability-admin.php:3369 +#: wpvulnerability-adminms.php:3497 msgid "No component specified." msgstr "" -#: wpvulnerability-admin.php:3373 -#: wpvulnerability-adminms.php:3533 +#: wpvulnerability-admin.php:3404 +#: wpvulnerability-adminms.php:3442 msgid "Debug Information" msgstr "" -#: wpvulnerability-admin.php:3375 -#: wpvulnerability-adminms.php:3535 +#: wpvulnerability-admin.php:3406 +#: wpvulnerability-adminms.php:3444 msgid "This tab is only visible when WP_DEBUG is enabled. Use with caution." msgstr "" -#: wpvulnerability-admin.php:3436 -#: wpvulnerability-adminms.php:3632 +#: wpvulnerability-admin.php:3470 +#: wpvulnerability-adminms.php:3544 msgid "System Information" msgstr "" -#: wpvulnerability-admin.php:3440 -#: wpvulnerability-adminms.php:3636 +#: wpvulnerability-admin.php:3474 +#: wpvulnerability-adminms.php:3548 msgid "WordPress Version" msgstr "" -#: wpvulnerability-admin.php:3444 -#: wpvulnerability-adminms.php:3640 +#: wpvulnerability-admin.php:3478 +#: wpvulnerability-adminms.php:3552 msgid "Multisite" msgstr "" -#: wpvulnerability-admin.php:3445 -#: wpvulnerability-admin.php:3591 -#: wpvulnerability-adminms.php:3641 -#: wpvulnerability-adminms.php:3787 +#: wpvulnerability-admin.php:3479 +#: wpvulnerability-admin.php:3625 +#: wpvulnerability-adminms.php:3553 +#: wpvulnerability-adminms.php:3699 msgid "Yes" msgstr "" -#: wpvulnerability-admin.php:3445 -#: wpvulnerability-admin.php:3591 -#: wpvulnerability-adminms.php:3641 -#: wpvulnerability-adminms.php:3787 +#: wpvulnerability-admin.php:3479 +#: wpvulnerability-admin.php:3625 +#: wpvulnerability-adminms.php:3553 +#: wpvulnerability-adminms.php:3699 msgid "No" msgstr "" -#: wpvulnerability-admin.php:3448 -#: wpvulnerability-adminms.php:3644 +#: wpvulnerability-admin.php:3482 +#: wpvulnerability-adminms.php:3556 msgid "Language" msgstr "" -#: wpvulnerability-admin.php:3452 -#: wpvulnerability-adminms.php:3648 +#: wpvulnerability-admin.php:3486 +#: wpvulnerability-adminms.php:3560 msgid "PHP Version" msgstr "" -#: wpvulnerability-admin.php:3456 -#: wpvulnerability-adminms.php:3652 +#: wpvulnerability-admin.php:3490 +#: wpvulnerability-adminms.php:3564 +#: wpvulnerability-debug.php:1015 msgid "PHP Extensions" msgstr "" -#: wpvulnerability-admin.php:3467 -#: wpvulnerability-adminms.php:3663 +#: wpvulnerability-admin.php:3501 +#: wpvulnerability-adminms.php:3575 msgid "PHP Memory Limit" msgstr "" -#: wpvulnerability-admin.php:3471 -#: wpvulnerability-adminms.php:3667 +#: wpvulnerability-admin.php:3505 +#: wpvulnerability-adminms.php:3579 msgid "Database" msgstr "" -#: wpvulnerability-admin.php:3475 -#: wpvulnerability-adminms.php:3671 +#: wpvulnerability-admin.php:3509 +#: wpvulnerability-adminms.php:3583 msgid "Web Server" msgstr "" -#: wpvulnerability-admin.php:3479 -#: wpvulnerability-adminms.php:3675 +#: wpvulnerability-admin.php:3513 +#: wpvulnerability-adminms.php:3587 msgid "WP_DEBUG" msgstr "" -#: wpvulnerability-admin.php:3487 -#: wpvulnerability-adminms.php:3683 +#: wpvulnerability-admin.php:3521 +#: wpvulnerability-adminms.php:3595 msgid "WP_DEBUG_LOG" msgstr "" -#: wpvulnerability-admin.php:3497 -#: wpvulnerability-adminms.php:3693 +#: wpvulnerability-admin.php:3531 +#: wpvulnerability-adminms.php:3605 msgid "Path:" msgstr "" #. translators: %s: file size -#: wpvulnerability-admin.php:3506 -#: wpvulnerability-adminms.php:3707 +#: wpvulnerability-admin.php:3540 +#: wpvulnerability-adminms.php:3619 #, php-format msgid "Size: %s" msgstr "" -#: wpvulnerability-admin.php:3517 -#: wpvulnerability-adminms.php:3713 +#: wpvulnerability-admin.php:3551 +#: wpvulnerability-adminms.php:3625 msgid "View Log File" msgstr "" -#: wpvulnerability-admin.php:3523 -#: wpvulnerability-adminms.php:3719 +#: wpvulnerability-admin.php:3557 +#: wpvulnerability-adminms.php:3631 msgid "Log file is not web-accessible (outside wp-content)" msgstr "" -#: wpvulnerability-admin.php:3529 -#: wpvulnerability-adminms.php:3725 +#: wpvulnerability-admin.php:3563 +#: wpvulnerability-adminms.php:3637 msgid "Log file does not exist yet" msgstr "" -#: wpvulnerability-admin.php:3537 -#: wpvulnerability-adminms.php:3733 +#: wpvulnerability-admin.php:3571 +#: wpvulnerability-adminms.php:3645 msgid "Plugin Version" msgstr "" -#: wpvulnerability-admin.php:3561 -#: wpvulnerability-adminms.php:3757 +#: wpvulnerability-admin.php:3595 +#: wpvulnerability-adminms.php:3669 msgid "Component Detection" msgstr "" -#: wpvulnerability-admin.php:3567 -#: wpvulnerability-adminms.php:3763 +#: wpvulnerability-admin.php:3601 +#: wpvulnerability-adminms.php:3675 msgid "Version Detected" msgstr "" -#: wpvulnerability-admin.php:3568 -#: wpvulnerability-adminms.php:3764 +#: wpvulnerability-admin.php:3602 +#: wpvulnerability-adminms.php:3676 msgid "Analyzed" msgstr "" -#: wpvulnerability-admin.php:3569 -#: wpvulnerability-adminms.php:3765 +#: wpvulnerability-admin.php:3603 +#: wpvulnerability-adminms.php:3677 msgid "Cache Status" msgstr "" -#: wpvulnerability-admin.php:3629 -#: wpvulnerability-adminms.php:3825 +#: wpvulnerability-admin.php:3663 +#: wpvulnerability-adminms.php:3737 msgid "Configuration Summary" msgstr "" -#: wpvulnerability-admin.php:3633 -#: wpvulnerability-adminms.php:3829 +#: wpvulnerability-admin.php:3667 +#: wpvulnerability-adminms.php:3741 msgid "Cache Duration" msgstr "" -#: wpvulnerability-admin.php:3643 -#: wpvulnerability-adminms.php:3839 +#: wpvulnerability-admin.php:3677 +#: wpvulnerability-adminms.php:3751 msgid "Log Retention" msgstr "" -#: wpvulnerability-admin.php:3653 -#: wpvulnerability-adminms.php:3849 +#: wpvulnerability-admin.php:3687 +#: wpvulnerability-adminms.php:3761 msgid "Components Analyzed" msgstr "" -#: wpvulnerability-admin.php:3668 -#: wpvulnerability-adminms.php:3864 +#: wpvulnerability-admin.php:3702 +#: wpvulnerability-adminms.php:3776 msgid "Notification Email" msgstr "" -#: wpvulnerability-admin.php:3689 -#: wpvulnerability-adminms.php:3885 +#: wpvulnerability-admin.php:3723 +#: wpvulnerability-adminms.php:3797 msgid "Slack Webhook" msgstr "" -#: wpvulnerability-admin.php:3699 -#: wpvulnerability-admin.php:3719 -#: wpvulnerability-admin.php:3739 -#: wpvulnerability-admin.php:3759 -#: wpvulnerability-adminms.php:3895 -#: wpvulnerability-adminms.php:3915 -#: wpvulnerability-adminms.php:3935 -#: wpvulnerability-adminms.php:3955 +#: wpvulnerability-admin.php:3733 +#: wpvulnerability-admin.php:3753 +#: wpvulnerability-admin.php:3773 +#: wpvulnerability-admin.php:3793 +#: wpvulnerability-adminms.php:3807 +#: wpvulnerability-adminms.php:3827 +#: wpvulnerability-adminms.php:3847 +#: wpvulnerability-adminms.php:3867 msgid "Configured & Enabled" msgstr "" -#: wpvulnerability-admin.php:3701 -#: wpvulnerability-admin.php:3721 -#: wpvulnerability-admin.php:3741 -#: wpvulnerability-admin.php:3761 -#: wpvulnerability-adminms.php:3897 -#: wpvulnerability-adminms.php:3917 -#: wpvulnerability-adminms.php:3937 -#: wpvulnerability-adminms.php:3957 +#: wpvulnerability-admin.php:3735 +#: wpvulnerability-admin.php:3755 +#: wpvulnerability-admin.php:3775 +#: wpvulnerability-admin.php:3795 +#: wpvulnerability-adminms.php:3809 +#: wpvulnerability-adminms.php:3829 +#: wpvulnerability-adminms.php:3849 +#: wpvulnerability-adminms.php:3869 msgid "Configured (Disabled)" msgstr "" -#: wpvulnerability-admin.php:3703 -#: wpvulnerability-admin.php:3723 -#: wpvulnerability-admin.php:3743 -#: wpvulnerability-admin.php:3763 -#: wpvulnerability-adminms.php:3899 -#: wpvulnerability-adminms.php:3919 -#: wpvulnerability-adminms.php:3939 -#: wpvulnerability-adminms.php:3959 +#: wpvulnerability-admin.php:3737 +#: wpvulnerability-admin.php:3757 +#: wpvulnerability-admin.php:3777 +#: wpvulnerability-admin.php:3797 +#: wpvulnerability-adminms.php:3811 +#: wpvulnerability-adminms.php:3831 +#: wpvulnerability-adminms.php:3851 +#: wpvulnerability-adminms.php:3871 msgid "Not configured" msgstr "" -#: wpvulnerability-admin.php:3710 -#: wpvulnerability-adminms.php:3906 +#: wpvulnerability-admin.php:3744 +#: wpvulnerability-adminms.php:3818 msgid "Teams Webhook" msgstr "" -#: wpvulnerability-admin.php:3730 -#: wpvulnerability-adminms.php:3926 +#: wpvulnerability-admin.php:3764 +#: wpvulnerability-adminms.php:3838 msgid "Discord Webhook" msgstr "" -#: wpvulnerability-admin.php:3750 -#: wpvulnerability-adminms.php:3946 +#: wpvulnerability-admin.php:3784 +#: wpvulnerability-adminms.php:3858 msgid "Telegram Bot" msgstr "" -#: wpvulnerability-admin.php:3795 -#: wpvulnerability-adminms.php:3991 +#: wpvulnerability-admin.php:3829 +#: wpvulnerability-adminms.php:3903 msgid "Cron Status" msgstr "" -#: wpvulnerability-admin.php:3799 -#: wpvulnerability-adminms.php:3995 +#: wpvulnerability-admin.php:3833 +#: wpvulnerability-adminms.php:3907 msgid "Update Database" msgstr "" #. translators: %s: date and time -#: wpvulnerability-admin.php:3804 -#: wpvulnerability-admin.php:3817 -#: wpvulnerability-adminms.php:4000 -#: wpvulnerability-adminms.php:4013 +#: wpvulnerability-admin.php:3838 +#: wpvulnerability-admin.php:3851 +#: wpvulnerability-adminms.php:3912 +#: wpvulnerability-adminms.php:3925 #, php-format msgid "Next run: %s" msgstr "" -#: wpvulnerability-admin.php:3807 -#: wpvulnerability-admin.php:3820 -#: wpvulnerability-adminms.php:4003 -#: wpvulnerability-adminms.php:4016 +#: wpvulnerability-admin.php:3841 +#: wpvulnerability-admin.php:3854 +#: wpvulnerability-adminms.php:3915 +#: wpvulnerability-adminms.php:3928 msgid "Not scheduled" msgstr "" -#: wpvulnerability-admin.php:3812 -#: wpvulnerability-adminms.php:4008 +#: wpvulnerability-admin.php:3846 +#: wpvulnerability-adminms.php:3920 msgid "Send Notification" msgstr "" -#: wpvulnerability-admin.php:3833 -#: wpvulnerability-adminms.php:4029 +#: wpvulnerability-admin.php:3867 +#: wpvulnerability-adminms.php:3941 msgid "Run Update Now" msgstr "" -#: wpvulnerability-admin.php:3841 -#: wpvulnerability-adminms.php:4037 +#: wpvulnerability-admin.php:3875 +#: wpvulnerability-adminms.php:3949 msgid "Run Notification Now" msgstr "" -#: wpvulnerability-admin.php:3847 -#: wpvulnerability-adminms.php:4043 +#: wpvulnerability-admin.php:3881 +#: wpvulnerability-adminms.php:3955 msgid "You do not have permission to run these actions." msgstr "" -#: wpvulnerability-admin.php:3866 -#: wpvulnerability-adminms.php:4062 +#: wpvulnerability-admin.php:3900 +#: wpvulnerability-adminms.php:3974 msgid "API Testing" msgstr "" -#: wpvulnerability-admin.php:3869 -#: wpvulnerability-adminms.php:4065 +#: wpvulnerability-admin.php:3903 +#: wpvulnerability-adminms.php:3977 msgid "Test API connectivity for each component. Click a button to send a test request." msgstr "" #. translators: %s: component name -#: wpvulnerability-admin.php:3876 -#: wpvulnerability-adminms.php:4072 +#: wpvulnerability-admin.php:3910 +#: wpvulnerability-adminms.php:3984 #, php-format msgid "Test %s" msgstr "" -#: wpvulnerability-admin.php:3891 -#: wpvulnerability-adminms.php:4087 +#: wpvulnerability-admin.php:3925 +#: wpvulnerability-adminms.php:3999 msgid "Testing..." msgstr "" -#: wpvulnerability-admin.php:3907 -#: wpvulnerability-adminms.php:4103 +#: wpvulnerability-admin.php:3949 +#: wpvulnerability-adminms.php:4023 msgid "HTTP Code:" msgstr "" -#: wpvulnerability-admin.php:3908 -#: wpvulnerability-adminms.php:4104 +#: wpvulnerability-admin.php:3950 +#: wpvulnerability-adminms.php:4024 msgid "Response Time:" msgstr "" -#: wpvulnerability-admin.php:3910 -#: wpvulnerability-adminms.php:4106 +#: wpvulnerability-admin.php:3952 +#: wpvulnerability-adminms.php:4026 msgid "Response Preview" msgstr "" -#: wpvulnerability-admin.php:3917 -#: wpvulnerability-admin.php:3921 -#: wpvulnerability-adminms.php:4113 -#: wpvulnerability-adminms.php:4117 +#: wpvulnerability-admin.php:3959 +#: wpvulnerability-admin.php:3963 +#: wpvulnerability-adminms.php:4033 +#: wpvulnerability-adminms.php:4037 msgid "Test" msgstr "" -#: wpvulnerability-admin.php:3920 -#: wpvulnerability-adminms.php:4116 +#: wpvulnerability-admin.php:3962 +#: wpvulnerability-adminms.php:4036 msgid "Error:" msgstr "" -#: wpvulnerability-admin.php:3920 -#: wpvulnerability-adminms.php:4116 +#: wpvulnerability-admin.php:3962 +#: wpvulnerability-adminms.php:4036 msgid "AJAX request failed." msgstr "" -#: wpvulnerability-admin.php:3929 -#: wpvulnerability-adminms.php:4125 +#: wpvulnerability-admin.php:3971 +#: wpvulnerability-adminms.php:4045 msgid "You do not have permission to test API connectivity." msgstr "" -#: wpvulnerability-admin.php:3950 -#: wpvulnerability-adminms.php:4146 +#: wpvulnerability-admin.php:3992 +#: wpvulnerability-adminms.php:4066 msgid "Database Options Viewer" msgstr "" -#: wpvulnerability-admin.php:3951 -#: wpvulnerability-adminms.php:4147 +#: wpvulnerability-admin.php:3993 +#: wpvulnerability-adminms.php:4067 msgid "View the raw data stored in WordPress options." msgstr "" -#: wpvulnerability-admin.php:3957 -#: wpvulnerability-adminms.php:4153 +#: wpvulnerability-admin.php:3999 +#: wpvulnerability-adminms.php:4073 msgid "Select an option..." msgstr "" -#: wpvulnerability-admin.php:3976 -#: wpvulnerability-adminms.php:4172 +#: wpvulnerability-admin.php:4018 +#: wpvulnerability-adminms.php:4092 msgid "Option not found or empty." msgstr "" -#: wpvulnerability-admin.php:3994 -#: wpvulnerability-adminms.php:4191 +#: wpvulnerability-admin.php:4036 +#: wpvulnerability-adminms.php:4111 msgid "Quick Actions" msgstr "" -#: wpvulnerability-admin.php:3995 -#: wpvulnerability-adminms.php:4192 +#: wpvulnerability-admin.php:4037 +#: wpvulnerability-adminms.php:4112 msgid "Perform debugging actions. Use with caution." msgstr "" -#: wpvulnerability-admin.php:3999 -#: wpvulnerability-adminms.php:4196 +#: wpvulnerability-admin.php:4041 +#: wpvulnerability-adminms.php:4116 msgid "Are you sure you want to clear all caches?" msgstr "" -#: wpvulnerability-admin.php:4003 -#: wpvulnerability-adminms.php:4200 +#: wpvulnerability-admin.php:4045 +#: wpvulnerability-adminms.php:4120 msgid "Clear All Caches" msgstr "" -#: wpvulnerability-admin.php:4007 -#: wpvulnerability-adminms.php:4204 +#: wpvulnerability-admin.php:4049 +#: wpvulnerability-adminms.php:4124 msgid "Are you sure you want to reset signatures?" msgstr "" -#: wpvulnerability-admin.php:4011 -#: wpvulnerability-adminms.php:4208 +#: wpvulnerability-admin.php:4053 +#: wpvulnerability-adminms.php:4128 msgid "Reset Signatures" msgstr "" -#: wpvulnerability-admin.php:4019 -#: wpvulnerability-adminms.php:4216 +#: wpvulnerability-admin.php:4061 +#: wpvulnerability-adminms.php:4136 msgid "Export Debug Info" msgstr "" -#: wpvulnerability-admin.php:4025 -#: wpvulnerability-adminms.php:4222 +#: wpvulnerability-admin.php:4067 +#: wpvulnerability-adminms.php:4142 msgid "You do not have permission to perform these actions." msgstr "" -#: wpvulnerability-adminms.php:101 -#: wpvulnerability-adminms.php:199 +#: wpvulnerability-adminms.php:108 +#: wpvulnerability-adminms.php:125 msgid "Settings saved." msgstr "" -#: wpvulnerability-adminms.php:277 +#: wpvulnerability-adminms.php:203 msgid "WPVulnerability cron events have been repaired across the network." msgstr "" -#: wpvulnerability-adminms.php:3521 +#: wpvulnerability-adminms.php:3430 msgid "You do not have permission to access this page." msgstr "" @@ -1972,9 +1977,9 @@ msgstr "" #: wpvulnerability-core.php:115 #: wpvulnerability-plugins.php:206 -#: wpvulnerability-process.php:335 -#: wpvulnerability-process.php:443 -#: wpvulnerability-process.php:529 +#: wpvulnerability-process.php:345 +#: wpvulnerability-process.php:453 +#: wpvulnerability-process.php:539 #: wpvulnerability-themes.php:173 #: wpvulnerability-themes.php:469 msgid "Actively exploited" @@ -1982,8 +1987,8 @@ msgstr "" #: wpvulnerability-core.php:122 #: wpvulnerability-plugins.php:213 -#: wpvulnerability-process.php:342 -#: wpvulnerability-process.php:450 +#: wpvulnerability-process.php:352 +#: wpvulnerability-process.php:460 #: wpvulnerability-themes.php:180 #: wpvulnerability-themes.php:476 msgid "Public exploit" @@ -1991,8 +1996,8 @@ msgstr "" #: wpvulnerability-core.php:125 #: wpvulnerability-plugins.php:216 -#: wpvulnerability-process.php:345 -#: wpvulnerability-process.php:453 +#: wpvulnerability-process.php:355 +#: wpvulnerability-process.php:463 #: wpvulnerability-themes.php:183 #: wpvulnerability-themes.php:479 msgid "Automatable" @@ -2000,163 +2005,354 @@ msgstr "" #: wpvulnerability-core.php:139 #: wpvulnerability-plugins.php:244 -#: wpvulnerability-process.php:371 -#: wpvulnerability-process.php:469 -#: wpvulnerability-process.php:544 +#: wpvulnerability-process.php:381 +#: wpvulnerability-process.php:479 +#: wpvulnerability-process.php:554 #: wpvulnerability-themes.php:211 #: wpvulnerability-themes.php:503 msgid "References:" msgstr "" -#: wpvulnerability-debug.php:402 +#: wpvulnerability-debug.php:451 msgid "Invalid component specified." msgstr "" -#: wpvulnerability-debug.php:426 -msgid "Version not detected for this component." +#: wpvulnerability-debug.php:466 +msgid "WordPress version not detected for this component." msgstr "" -#: wpvulnerability-debug.php:456 +#: wpvulnerability-debug.php:476 +msgid "No plugins detected to test the API endpoint." +msgstr "" + +#: wpvulnerability-debug.php:477 +msgid "No themes detected to test the API endpoint." +msgstr "" + +#: wpvulnerability-debug.php:487 +msgid "No local version detected; the API endpoint was not called." +msgstr "" + +#: wpvulnerability-debug.php:517 msgid "API request successful." msgstr "" #. translators: %d: HTTP status code -#: wpvulnerability-debug.php:473 +#: wpvulnerability-debug.php:534 #, php-format msgid "API returned HTTP code %d." msgstr "" -#: wpvulnerability-general.php:355 +#: wpvulnerability-debug.php:796 +msgid "Built-in (no hosting action required)" +msgstr "" + +#: wpvulnerability-debug.php:798 +msgid "Regular expression engine used throughout PHP and WordPress." +msgstr "" + +#: wpvulnerability-debug.php:802 +msgid "Required" +msgstr "" + +#: wpvulnerability-debug.php:804 +msgid "Hashing, including passwords and update packages." +msgstr "" + +#: wpvulnerability-debug.php:805 +msgid "Communications with other servers and JSON data processing." +msgstr "" + +#: wpvulnerability-debug.php:806 +msgid "Connects to MySQL/MariaDB for database interactions." +msgstr "" + +#: wpvulnerability-debug.php:810 +msgid "Highly recommended" +msgstr "" + +#: wpvulnerability-debug.php:812 +msgid "Performs remote request operations." +msgstr "" + +#: wpvulnerability-debug.php:813 +msgid "Validates Text Widget content and configures IIS7+ automatically." +msgstr "" + +#: wpvulnerability-debug.php:814 +msgid "Works with metadata stored in images." +msgstr "" + +#: wpvulnerability-debug.php:815 +msgid "Detects MIME types of file uploads." +msgstr "" + +#: wpvulnerability-debug.php:816 +msgid "Drop-in replacement for the standard PHP serializer; improves performance." +msgstr "" + +#: wpvulnerability-debug.php:817 +msgid "Better image quality for media uploads and PDF thumbnail support." +msgstr "" + +#: wpvulnerability-debug.php:818 +msgid "Locale-aware operations: formatting, transliteration, collation, timezones." +msgstr "" + +#: wpvulnerability-debug.php:819 +msgid "Properly handles UTF-8 text." +msgstr "" + +#: wpvulnerability-debug.php:820 +msgid "SSL-based connections to other hosts." +msgstr "" + +#: wpvulnerability-debug.php:821 +msgid "XML parsing, such as from a third-party site." +msgstr "" + +#: wpvulnerability-debug.php:822 +msgid "Decompresses Plugin, Theme, and WordPress update packages." +msgstr "" + +#: wpvulnerability-debug.php:826 +msgid "Object cache (only one is needed)" +msgstr "" + +#: wpvulnerability-debug.php:828 +msgid "In-memory key-value store for PHP." +msgstr "" + +#: wpvulnerability-debug.php:829 +msgid "Distributed memory object caching system." +msgstr "" + +#: wpvulnerability-debug.php:830 +msgid "Stores precompiled PHP bytecode to reduce repeated parsing and compilation." +msgstr "" + +#: wpvulnerability-debug.php:831 +msgid "PHP extension for interfacing with Redis." +msgstr "" + +#: wpvulnerability-debug.php:835 +msgid "Optional improvements" +msgstr "" + +#: wpvulnerability-debug.php:837 +msgid "Timezone database used by the PHP date and time functions." +msgstr "" + +#: wpvulnerability-debug.php:841 +msgid "Fallbacks / situational use" +msgstr "" + +#: wpvulnerability-debug.php:843 +msgid "Arbitrary precision mathematics." +msgstr "" + +#: wpvulnerability-debug.php:844 +msgid "Securely filters user input." +msgstr "" + +#: wpvulnerability-debug.php:845 +msgid "Functionally limited fallback for image manipulation when Imagick is unavailable." +msgstr "" + +#: wpvulnerability-debug.php:846 +msgid "Converts between character sets." +msgstr "" + +#: wpvulnerability-debug.php:847 +msgid "Reads, writes, creates and deletes Unix shared memory segments." +msgstr "" + +#: wpvulnerability-debug.php:848 +#: wpvulnerability-debug.php:850 +msgid "XML parsing." +msgstr "" + +#: wpvulnerability-debug.php:849 +msgid "Signature validation and securely random bytes." +msgstr "" + +#: wpvulnerability-debug.php:851 +msgid "Gzip compression and decompression." +msgstr "" + +#: wpvulnerability-debug.php:855 +msgid "File changes when files are not writeable (transports)" +msgstr "" + +#: wpvulnerability-debug.php:857 +msgid "Shell, remote execution, tunneling and file transfer over SSH." +msgstr "" + +#: wpvulnerability-debug.php:858 +msgid "FTP client access for updates and installations." +msgstr "" + +#: wpvulnerability-debug.php:859 +msgid "Low-level socket communication functions." +msgstr "" + +#: wpvulnerability-debug.php:1017 +msgid "WordPress core makes use of various PHP extensions when they are available. This list is informational only: a missing extension is neither good nor bad." +msgstr "" + +#: wpvulnerability-debug.php:1031 +msgid "Loaded" +msgstr "" + +#: wpvulnerability-debug.php:1031 +msgid "Not loaded" +msgstr "" + +#: wpvulnerability-debug.php:1044 +msgid "System Packages" +msgstr "" + +#: wpvulnerability-debug.php:1046 +msgid "System software WordPress can leverage. Informational only." +msgstr "" + +#: wpvulnerability-debug.php:1056 +msgid "Available" +msgstr "" + +#. translators: %s: package version number +#: wpvulnerability-debug.php:1066 +#, php-format +msgid "Version %s" +msgstr "" + +#: wpvulnerability-debug.php:1071 +msgid "No local version detected." +msgstr "" + +#: wpvulnerability-general.php:363 msgid "WPVulnerability Logs" msgstr "" -#: wpvulnerability-general.php:356 +#: wpvulnerability-general.php:364 msgid "WPVulnerability Log" msgstr "" -#: wpvulnerability-general.php:1875 -#: wpvulnerability-general.php:1876 +#: wpvulnerability-general.php:1893 +#: wpvulnerability-general.php:1894 msgid "Low" msgstr "" -#: wpvulnerability-general.php:1877 -#: wpvulnerability-general.php:1878 +#: wpvulnerability-general.php:1895 +#: wpvulnerability-general.php:1896 msgid "Medium" msgstr "" -#: wpvulnerability-general.php:1879 -#: wpvulnerability-general.php:1880 +#: wpvulnerability-general.php:1897 +#: wpvulnerability-general.php:1898 msgid "High" msgstr "" -#: wpvulnerability-general.php:1881 -#: wpvulnerability-general.php:1882 +#: wpvulnerability-general.php:1899 +#: wpvulnerability-general.php:1900 msgid "Critical" msgstr "" -#: wpvulnerability-general.php:2681 +#: wpvulnerability-general.php:2733 msgid "WPVulnerability Shell Logs" msgstr "" -#: wpvulnerability-general.php:2682 +#: wpvulnerability-general.php:2734 msgid "WPVulnerability Shell Log" msgstr "" #: wpvulnerability-notifications.php:30 -msgid "Every week" -msgstr "" - -#: wpvulnerability-notifications.php:57 -msgid "Every day" -msgstr "" - -#: wpvulnerability-notifications.php:84 msgid "Invalid request." msgstr "" -#: wpvulnerability-notifications.php:104 +#: wpvulnerability-notifications.php:50 msgid "You have unsubscribed from WPVulnerability notifications." msgstr "" #. translators: %1$s the website of Database, %2$s database site name. -#: wpvulnerability-notifications.php:289 +#: wpvulnerability-notifications.php:257 #, php-format msgid "Learn more about the WordPress Vulnerability Database API at %2$s" msgstr "" #. translators: %1$s unsubscribe URL. -#: wpvulnerability-notifications.php:300 +#: wpvulnerability-notifications.php:268 #, php-format msgid "Want to stop receiving these alerts? Unsubscribe or change the email frequency to Never in the WPVulnerability settings." msgstr "" -#: wpvulnerability-notifications.php:599 +#: wpvulnerability-notifications.php:567 msgid "No vulnerabilities found" msgstr "" -#: wpvulnerability-notifications.php:600 +#: wpvulnerability-notifications.php:568 msgid "This is likely a test. The site does not have vulnerabilities." msgstr "" -#: wpvulnerability-notifications.php:605 +#: wpvulnerability-notifications.php:573 msgid "Core vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:611 +#: wpvulnerability-notifications.php:579 msgid "Plugins vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:617 +#: wpvulnerability-notifications.php:585 msgid "Themes vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:623 +#: wpvulnerability-notifications.php:591 msgid "PHP vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:629 +#: wpvulnerability-notifications.php:597 msgid "Apache HTTPD vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:635 +#: wpvulnerability-notifications.php:603 msgid "Nginx vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:641 +#: wpvulnerability-notifications.php:609 msgid "MariaDB vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:647 +#: wpvulnerability-notifications.php:615 msgid "MySQL vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:653 +#: wpvulnerability-notifications.php:621 msgid "ImageMagick vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:659 +#: wpvulnerability-notifications.php:627 msgid "curl vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:665 +#: wpvulnerability-notifications.php:633 msgid "memcached vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:671 +#: wpvulnerability-notifications.php:639 msgid "Redis vulnerabilities" msgstr "" -#: wpvulnerability-notifications.php:677 +#: wpvulnerability-notifications.php:645 msgid "SQLite vulnerabilities" msgstr "" #. translators: Site name. -#: wpvulnerability-notifications.php:700 +#: wpvulnerability-notifications.php:668 #, php-format msgid "Vulnerability found: %s" msgstr "" -#: wpvulnerability-notifications.php:704 +#: wpvulnerability-notifications.php:672 msgid "Vulnerability found" msgstr "" @@ -2168,70 +2364,76 @@ msgid "%1$s has a known vulnerability that may be affecting your installed versi msgstr "" #: wpvulnerability-plugins.php:229 -#: wpvulnerability-process.php:358 +#: wpvulnerability-process.php:368 msgid "This plugin is closed. Please replace it with another." msgstr "" #: wpvulnerability-plugins.php:232 -#: wpvulnerability-process.php:361 +#: wpvulnerability-process.php:371 msgid "This vulnerability appears to be unpatched. Stay tuned for upcoming plugin updates." msgstr "" -#: wpvulnerability-plugins.php:569 +#: wpvulnerability-plugins.php:570 msgid "It hasn't been updated in over a year." msgstr "" -#: wpvulnerability-plugins.php:575 +#: wpvulnerability-plugins.php:576 msgid "It may no longer be available (closed?)." msgstr "" -#: wpvulnerability-plugins.php:613 -#: wpvulnerability-plugins.php:620 +#: wpvulnerability-plugins.php:614 +#: wpvulnerability-plugins.php:621 msgid "Last updated on" msgstr "" #. translators: the number of vulnerabilities. -#: wpvulnerability-plugins.php:763 -#: wpvulnerability-themes.php:704 +#: wpvulnerability-plugins.php:764 +#: wpvulnerability-themes.php:705 #, php-format msgid "Vulnerabilities (%d)" msgstr "" +#. translators: %s: component type (plugin, theme, core, php, ...) +#: wpvulnerability-process.php:256 +#, php-format +msgid "%s icon" +msgstr "" + #. translators: %s: software name. -#: wpvulnerability-process.php:601 +#: wpvulnerability-process.php:611 #, php-format msgid "%s running" msgstr "" -#: wpvulnerability-process.php:611 -#: wpvulnerability-process.php:792 +#: wpvulnerability-process.php:621 +#: wpvulnerability-process.php:802 msgid "End of Life" msgstr "" -#: wpvulnerability-process.php:613 -#: wpvulnerability-process.php:618 +#: wpvulnerability-process.php:623 +#: wpvulnerability-process.php:628 msgid "End of life:" msgstr "" -#: wpvulnerability-process.php:616 +#: wpvulnerability-process.php:626 msgid "Supported" msgstr "" -#: wpvulnerability-process.php:652 +#: wpvulnerability-process.php:662 msgid "Plugin" msgstr "" -#: wpvulnerability-process.php:721 +#: wpvulnerability-process.php:731 msgid "Theme" msgstr "" #. translators: %s: end-of-life date -#: wpvulnerability-process.php:790 +#: wpvulnerability-process.php:800 #, php-format msgid "End of life: %s" msgstr "" -#: wpvulnerability-process.php:795 +#: wpvulnerability-process.php:805 msgid "EOL" msgstr "" @@ -2239,10 +2441,10 @@ msgstr "" msgid "Network Settings" msgstr "" -#: wpvulnerability-run.php:737 -#: wpvulnerability-run.php:787 -#: wpvulnerability-run.php:797 -#: wpvulnerability-run.php:842 +#: wpvulnerability-run.php:760 +#: wpvulnerability-run.php:810 +#: wpvulnerability-run.php:820 +#: wpvulnerability-run.php:865 msgid "Vulnerabilities" msgstr "" @@ -2250,39 +2452,47 @@ msgstr "" msgid "Every 6 hours" msgstr "" -#: wpvulnerability-schedule.php:303 +#: wpvulnerability-schedule.php:57 +msgid "Every week" +msgstr "" + +#: wpvulnerability-schedule.php:74 +msgid "Every day" +msgstr "" + +#: wpvulnerability-schedule.php:357 msgid "Database updates" msgstr "" -#: wpvulnerability-schedule.php:309 +#: wpvulnerability-schedule.php:363 msgid "Log cleanup" msgstr "" -#: wpvulnerability-schedule.php:439 +#: wpvulnerability-schedule.php:493 msgid "Notifications are scheduled, but settings currently disable them. Please review the notifications tab." msgstr "" -#: wpvulnerability-schedule.php:442 +#: wpvulnerability-schedule.php:496 msgid "This event should not be scheduled for this site." msgstr "" -#: wpvulnerability-schedule.php:446 +#: wpvulnerability-schedule.php:500 msgid "Not expected for this site." msgstr "" -#: wpvulnerability-schedule.php:450 +#: wpvulnerability-schedule.php:504 msgid "No instances found." msgstr "" -#: wpvulnerability-schedule.php:456 +#: wpvulnerability-schedule.php:510 msgid "Scheduled with an unexpected interval." msgstr "" -#: wpvulnerability-schedule.php:460 +#: wpvulnerability-schedule.php:514 msgid "Multiple instances detected." msgstr "" -#: wpvulnerability-schedule.php:465 +#: wpvulnerability-schedule.php:519 msgid "Scheduled as expected." msgstr "" @@ -2362,32 +2572,32 @@ msgstr "" msgid "Nginx" msgstr "" -#: wpvulnerability-sitehealth.php:189 +#: wpvulnerability-sitehealth.php:192 msgid "Invalid software type" msgstr "" -#: wpvulnerability-sitehealth.php:192 +#: wpvulnerability-sitehealth.php:195 msgid "Error" msgstr "" -#: wpvulnerability-sitehealth.php:197 +#: wpvulnerability-sitehealth.php:200 msgid "The specified software type is not valid." msgstr "" #. translators: name of the software. -#: wpvulnerability-sitehealth.php:207 +#: wpvulnerability-sitehealth.php:210 #, php-format msgid "There aren't %s vulnerabilities" msgstr "" #. translators: software with vulnerabilities. -#: wpvulnerability-sitehealth.php:216 +#: wpvulnerability-sitehealth.php:219 #, php-format msgid "Shows possible vulnerabilities existing in %s." msgstr "" #. translators: Software and number of vulnerabilities. -#: wpvulnerability-sitehealth.php:229 +#: wpvulnerability-sitehealth.php:232 #, php-format msgid "There is %1$d %2$s vulnerability" msgid_plural "There are %1$d %2$s vulnerabilities" @@ -2395,64 +2605,64 @@ msgstr[0] "" msgstr[1] "" #. translators: software with vulnerabilities. -#: wpvulnerability-sitehealth.php:237 +#: wpvulnerability-sitehealth.php:240 #, php-format msgid "We've detected potential vulnerabilities in %s. Please check them and keep your installation updated." msgstr "" -#: wpvulnerability-sitehealth.php:246 +#: wpvulnerability-sitehealth.php:249 msgid "How to update PHP" msgstr "" -#: wpvulnerability-sitehealth.php:386 +#: wpvulnerability-sitehealth.php:389 msgid "WPVulnerability Core" msgstr "" -#: wpvulnerability-sitehealth.php:394 +#: wpvulnerability-sitehealth.php:397 msgid "WPVulnerability Themes" msgstr "" -#: wpvulnerability-sitehealth.php:402 +#: wpvulnerability-sitehealth.php:405 msgid "WPVulnerability Plugins" msgstr "" -#: wpvulnerability-sitehealth.php:410 +#: wpvulnerability-sitehealth.php:413 msgid "WPVulnerability PHP" msgstr "" -#: wpvulnerability-sitehealth.php:418 +#: wpvulnerability-sitehealth.php:421 msgid "WPVulnerability Apache HTTPD" msgstr "" -#: wpvulnerability-sitehealth.php:426 +#: wpvulnerability-sitehealth.php:429 msgid "WPVulnerability Nginx" msgstr "" -#: wpvulnerability-sitehealth.php:434 +#: wpvulnerability-sitehealth.php:437 msgid "WPVulnerability MariaDB" msgstr "" -#: wpvulnerability-sitehealth.php:442 +#: wpvulnerability-sitehealth.php:445 msgid "WPVulnerability MySQL" msgstr "" -#: wpvulnerability-sitehealth.php:450 +#: wpvulnerability-sitehealth.php:453 msgid "WPVulnerability ImageMagick" msgstr "" -#: wpvulnerability-sitehealth.php:458 +#: wpvulnerability-sitehealth.php:461 msgid "WPVulnerability curl" msgstr "" -#: wpvulnerability-sitehealth.php:466 +#: wpvulnerability-sitehealth.php:469 msgid "WPVulnerability memcached" msgstr "" -#: wpvulnerability-sitehealth.php:474 +#: wpvulnerability-sitehealth.php:477 msgid "WPVulnerability redis" msgstr "" -#: wpvulnerability-sitehealth.php:482 +#: wpvulnerability-sitehealth.php:485 msgid "WPVulnerability sqlite" msgstr "" diff --git a/readme.txt b/readme.txt index 97ef9bb..d79647e 100644 --- a/readme.txt +++ b/readme.txt @@ -1,11 +1,11 @@ === WPVulnerability === -Contributors: javiercasares, davidperez, lbonomo, alexclassroom +Contributors: robotstxt, javiercasares, davidperez, lbonomo, alexclassroom Tags: security, vulnerability, site-health -Requires at least: 5.6 +Requires at least: 4.7 Tested up to: 7.1 -Stable tag: 5.1.2 +Stable tag: 5.1.6 Requires PHP: 7.0 -Version: 5.1.2 +Version: 5.1.6 License: GPL-3.0-or-later License URI: https://spdx.org/licenses/GPL-3.0-or-later.html @@ -195,107 +195,100 @@ First of all, peace of mind. Investigate what the vulnerability is and, above al == Compatibility == -* WordPress: 5.6 - 7.1 +* WordPress: 4.7 - 7.1 * PHP: 7.0 - 8.5 * WP-CLI: 2.3.0 - 2.12.0 == Changelog == -= [5.1.2] - 2026-08-07 = += [5.1.6] - 2026-08-22 = + **Fixed** -* Missing `load_plugin_textdomain()` call caused a "Translation loading for the `wpvulnerability` domain was triggered too early" `_doing_it_wrong()` notice on WordPress 6.7+. The textdomain is now explicitly loaded on the `init` hook. - -**Added** - -* WordPress Playground blueprint (`blueprint.json`) with pre-installed vulnerable plugins and a theme for quick testing. +* Saving the "Delete all plugin data on uninstall" preference no longer triggers a fatal error (`add_settings_error()` was called before the WordPress admin API was loaded). +* Notification channels can now be disabled entirely: unchecking every channel and saving previously kept the old channels active, because unchecked checkboxes are never submitted. +* "Reset Plugin" and "Delete all logs" now purge both log stores (API request logs and shell execution audit logs). Previously the shell audit store was never cleaned, and both deletions silently did nothing because the log post types were not registered yet when the actions ran. +* The Debug tab API connectivity tests now use the real API routes: WordPress core collapses pre-release versions (e.g. 7.1-alpha) to their stable milestone, and plugins/themes are tested against their actual slug routes instead of nonexistent plural ones (HTTP 404). +* Component icons now carry explicit width/height attributes and a meaningful alt text. The same icons are embedded in notification emails and Site Health, where the plugin CSS is not loaded and unsized SVGs rendered at their intrinsic 800x800 size. **Changed** -* Security vulnerability reporting link updated to [ROBOTSTXT](https://www.robotstxt.es/contacto/). -* Contributor repository link updated to [git.robotstxt.es/ROBOTSTXT/wpvulnerability](https://git.robotstxt.es/ROBOTSTXT/wpvulnerability). -* The `readme.txt` Changelog section now shows only the latest 3 versions; the full history remains in `changelog.txt`. +* `languages/wpvulnerability.pot` regenerated with the new strings. **Compatibility** -* WordPress: 5.6 - 7.1 +* WordPress: 4.7 - 7.1 * PHP: 7.0 - 8.5 * WP-CLI: 2.3.0 - 2.12.0 **Tests** -* PHP Coding Standards: 3.13.5 -* WordPress Coding Standards: 3.3.0 -* PHPStan: 2.1.55 (level 9, 0 errors) -* PHPUnit: 9.6.34 (25 tests) +* PHP Coding Standards: 3.13.6 (0 errors) +* WordPress Coding Standards: 3.4.1 +* PHPStan: 2.2.9 (level 9, 0 errors) +* PHPUnit: 9.6.36 (54 tests) -= [5.1.1] - 2026-07-09 = += [5.1.5] - 2026-08-22 = -**Fixed** - -* Site Health: the memcached, Redis, and SQLite vulnerability tests always returned "Invalid software type" because those components were missing from the software list. They now run correctly. -* "Send test email" failed when no email recipients were configured: the forced-test path added the admin email but the send gate used a flag computed before the override. The test email now sends. -* "Run notification now" reported failure when only webhook channels (Slack/Teams/Discord/Telegram) were enabled and email was disabled. The notification result now reflects whether any channel delivered successfully. -* Weekly notification cron events were never auto-scheduled, because the `weekly` cron schedule was registered only at `init` while the on-load scheduling runs earlier. The weekly/daily schedule registration moved to the always-loaded scheduler so weekly notifications schedule correctly. -* Deactivation deleted the per-component analysis settings (`wpvulnerability-analyze`), so deactivating and reactivating reset which components were hidden. Analysis settings are now preserved on deactivation (only uninstall removes them). -* The debug "Cron Status" panel always showed the notification event as "not scheduled" because it checked the wrong hook name (`wpvulnerability_send_notification` instead of `wpvulnerability_notification`). -* The debug "last run" timestamp was always empty because it read a `wpvulnerability-logs` option that is never written; it now reads the most recent API log entry. -* Redis detection called `close()` twice on the success path (once in the `try` block, once in `finally`); the redundant close was removed. -* The single-site vulnerable-themes count was hardcoded to `0` instead of reading the stored option. **Changed** -* Single-site email recipient sanitization now uses `is_email()` for strict validation, matching the multisite behaviour. -* Removed a redundant nonce field from the single-site "Reset Plugin" form (the form only submits the full-reset action). -* Removed dead code: the unused `wpvulnerability_sanitize_messages` callback and its setting registration, and an unused `$tools_action` variable in the multisite admin. +* The contributor list now leads with the ROBOTSTXT organization account, followed by Javier Casares; the remaining contributors are unchanged. +* Regenerated `languages/wpvulnerability.pot` so its source references match the current code. The string set is unchanged (450 strings); all shipped translations were verified against it and remain fully up to date. **Compatibility** -* WordPress: 5.6 - 7.1 +* WordPress: 4.7 - 7.1 * PHP: 7.0 - 8.5 * WP-CLI: 2.3.0 - 2.12.0 **Tests** -* PHP Coding Standards: 3.13.5 -* WordPress Coding Standards: 3.3.0 -* PHPStan: 2.1.55 (level 9, 0 errors) -* PHPUnit: 9.6.34 (25 tests) +* PHP Coding Standards: 3.13.6 +* WordPress Coding Standards: 3.4.1 +* PHPStan: 2.2.9 (level 9, 0 errors) +* PHPUnit: 9.6.36 (45 tests) += [5.1.4] - 2026-08-22 = -= [5.1.0] - 2026-07-08 = +**Highlights** + +* The Site Health vulnerability tests work again on every WordPress version: they were silently disabled everywhere by an availability gate that could never pass. Secrets are now masked in the admin forms, and compatibility was verified against real PHP interpreters from 5.6 to 8.5. + +**Fixed** + +* The Site Health integration registered its tests behind `class_exists( 'WP_Site_Health' )`, but core loads that class lazily, after `init`, where the plugin loads its modules — so the guard always evaluated to false and none of the 13 vulnerability tests ever appeared in Site Health. The filter is now registered unconditionally; below WordPress 5.2 it simply never fires. +* "Clear all caches" in the debug tools now also deletes the `wpvulnerability-plugins-cache-data` timestamp option, so the plugins dataset is fully refreshed afterwards. +* The notification email "From:" address is now sanitized with `sanitize_email()` on all code paths. +* The plugins list table "Last updated" column decoded the whole plugins dataset for every table row; it is now decoded once per request. +* Removed a duplicated `json_decode()` call when reading the vulnerable-themes count. +* The `wp_date()` compatibility polyfill no longer fatals on WordPress 4.7–5.3: core calls `wp_maybe_decline_date()` unconditionally, but that function only exists since 5.4; the call is now availability-guarded. +* The core and software JSON getters return `'[]'` instead of an empty string when encoding fails, matching the plugins and themes getters. **Security** -* `wpvulnerability_validate_shell_command()` now uses exact `in_array()` match instead of `stripos()` substring matching for the shell-command allowlist (defense-in-depth). -* `wpvulnerability_detect_php()`, `wpvulnerability_detect_curl()`, and `wpvulnerability_detect_webserver()` now route through `wpvulnerability_safe_shell_exec()`, so every software-detection shell call is validated and recorded in the Shell Execution Audit Log. Previously these called `shell_exec()` directly, bypassing the wrapper and the audit log. As a side effect this also fixes nginx/angie version detection: `escapeshellcmd()` was escaping the `2>&1` redirect, so stderr (where nginx prints its version) was never captured. - -**Fixed** - -* Multisite uninstall fatal error: `Uncaught Error: Undefined constant "WPVULNERABILITY_PLUGIN_BASE"` when "Delete all plugin data on uninstall" was enabled. `uninstall.php` now defines the constant before loading `wpvulnerability-run.php`. -* `WPVULNERABILITY_HIDE_*` constants now stop `shell_exec` detection for hidden components during scheduled scans and in the admin "Software Detection Methods" panel. Previously they only hid the results from the UI, so the audit log kept filling with "command not found" entries for components the administrator had explicitly deactivated. -* `wpvulnerability_detect_webserver()` no longer shell-probes a hidden web server via the sibling path: `WPVULNERABILITY_HIDE_NGINX` and `WPVULNERABILITY_HIDE_APACHE` now fully isolate the hidden server. -* Multisite cron requests on non-main subsites no longer load all plugin module files. No WPVulnerability cron events are scheduled on subsites, so the extra loading was wasted work. -* LiteSpeed / OpenLiteSpeed / Caddy shell detection in the WP_DEBUG diagnostic panel now works: the commands no longer use `2>/dev/null` (which the shell-command validator rejected), `caddy` was added to the allowlist, and `which` output is validated as a real path so "command not found" messages are not mistaken for a detection. +* Webhook URLs (Slack, Teams, Discord) and the Telegram bot token are masked in the admin settings forms (single-site and network), showing only their last 4 characters — previously they were rendered in full into the page HTML. The inputs are plain text fields, so masked values never trigger browser URL validation. Saving a form with unchanged masked values keeps the stored secret; clearing a field still removes it. +* Removed the dead `register_uninstall_hook()` registration: `uninstall.php` supersedes it and the registration added a stray autoloaded option row. +* Single-site settings registrations now explicitly set `show_in_rest => false`, matching the multisite configuration. **Changed** -* `Network: true` added to the plugin header to declare network-aware multisite behaviour. -* Removed the unused `$plugin_status` parameter from `wpvulnerability_plugin_info_after()`; the PHPCS suite now passes with zero warnings. -* Multisite network dashboard: the "Site Health" footer link pointed to `wp-admin/network/site-health.php`, which does not exist (Site Health is a per-site screen). It now links to the main site's `wp-admin/site-health.php`. +* Admin CSS/JS assets load only on the WPVulnerability settings pages, the dashboard and the plugins/themes list tables instead of on every wp-admin screen. +* The shell availability probe (`wpvulnerability_can_shell_exec()`) result is cached per request instead of spawning a process on every call. **Compatibility** -* WordPress: 5.6 - 7.1 +* WordPress: 4.7 - 7.1 * PHP: 7.0 - 8.5 * WP-CLI: 2.3.0 - 2.12.0 +* Verified against real PHP interpreters 5.6.40, 7.0.33, 7.1.33, 7.2.34, 7.3.33, 7.4.33, 8.0.30, 8.1.34, 8.2.33, 8.3.33, 8.4.24 and 8.5.9: every plugin file lints clean from 7.0 up and fails on 5.6, confirming the declared floor. PHPCompatibility gate 7.0-8.5 clean; wp-compat WordPress floor 4.7 clean. **Tests** -* PHP Coding Standards: 3.13.5 -* WordPress Coding Standards: 3.3.0 -* PHPStan: 2.1.55 (level 9, 0 errors) -* PHPUnit: 9.6.34 (18 tests) +* PHP Coding Standards: 3.13.6 +* WordPress Coding Standards: 3.4.1 +* PHPStan: 2.2.9 (level 9, 0 errors) +* PHPUnit: 9.6.36 (45 tests) = Previous versions = diff --git a/uninstall.php b/uninstall.php index 920c50b..221c78c 100644 --- a/uninstall.php +++ b/uninstall.php @@ -39,6 +39,7 @@ if ( ! defined( 'WPVULNERABILITY_PLUGIN_BASE' ) ) { } require_once WPVULNERABILITY_PLUGIN_PATH . 'wpvulnerability-run.php'; +require_once WPVULNERABILITY_PLUGIN_PATH . 'wpvulnerability-general.php'; if ( function_exists( 'wpvulnerability_uninstall' ) ) { wpvulnerability_uninstall(); diff --git a/wpvulnerability-admin.php b/wpvulnerability-admin.php index 1159d05..29e307c 100644 --- a/wpvulnerability-admin.php +++ b/wpvulnerability-admin.php @@ -19,14 +19,23 @@ $wpvulnerability_settings = get_option( 'wpvulnerability-config' ); $wpvulnerability_analyze = get_option( 'wpvulnerability-analyze' ); - /** - * Enqueues the WPVulnerability admin CSS file on WPVulnerability admin pages. - * - * @since 2.0.0 - * - * @return void - */ -function wpvulnerability_admin_enqueue_scripts() { + /** + * Enqueues the WPVulnerability admin CSS file on WPVulnerability admin pages. + * + * @since 2.0.0 + * @since 5.1.4 Assets load only on the plugin pages and the dashboard. + * + * @param string $hook The current admin page hook. + * + * @return void + */ +function wpvulnerability_admin_enqueue_scripts( $hook ) { + // Load on the settings page, the dashboard (widget) and the list tables + // where vulnerability columns are rendered. + if ( ! in_array( $hook, array( 'index.php', 'settings_page_wpvulnerability-options', 'plugins.php', 'themes.php' ), true ) ) { + return; + } + wp_enqueue_style( 'wpvulnerability-admin', WPVULNERABILITY_PLUGIN_URL . 'assets/admin.css', @@ -143,7 +152,7 @@ if ( isset( $_POST['wpvulnerability_delete_on_uninstall'] ) && check_admin_refer } $wpvulnerability_settings['delete_on_uninstall'] = isset( $_POST['delete_on_uninstall'] ) ? 1 : 0; update_option( 'wpvulnerability-config', $wpvulnerability_settings ); - add_settings_error( 'wpvulnerability-tools', 'uninstall-saved', __( 'Uninstall preference saved.', 'wpvulnerability' ), 'success' ); + set_transient( 'wpvulnerability_message_manual_success', __( 'Uninstall preference saved.', 'wpvulnerability' ), 10 ); } } @@ -532,6 +541,7 @@ function wpvulnerability_render_admin_tab_notifications() {
@@ -542,12 +552,13 @@ function wpvulnerability_render_admin_tab_notifications() {
- +
- +
- +
- + @@ -644,7 +658,7 @@ function wpvulnerability_render_admin_tab_notifications() { } function wpvToggleChannelInput(channel) { - var checkbox = document.querySelector('input[name="wpvulnerability-config[notify][' + channel + ']"]'); + var checkbox = document.querySelector('input[type="checkbox"][name="wpvulnerability-config[notify][' + channel + ']"]'); var inputs = document.getElementById('wpvulnerability-' + channel + '-inputs'); if (checkbox.checked) { @@ -2040,8 +2054,8 @@ function wpvulnerability_admin_slack_callback() { $slack_webhook = isset( $wpvulnerability_settings['slack_webhook'] ) ? $wpvulnerability_settings['slack_webhook'] : ''; ?> - - " value="" /> + - - " value="" /> + 'wpvulnerability_admin_sanitize', 'default' => array(), + 'show_in_rest' => false, ) ); @@ -2941,6 +2972,7 @@ function wpvulnerability_admin_init() { array( 'sanitize_callback' => 'wpvulnerability_analyze_sanitize', 'default' => array(), + 'show_in_rest' => false, ) ); @@ -3381,6 +3413,9 @@ function wpvulnerability_render_admin_tab_debug() { // Section 2: Component Detection. wpvulnerability_render_debug_section_components(); + // Section 2b: PHP extensions and system packages. + wpvulnerability_render_debug_section_php_extensions(); + // Section 4: Configuration Summary. wpvulnerability_render_debug_section_config(); @@ -3897,22 +3932,30 @@ function wpvulnerability_render_debug_section_api_testing() { component: component, nonce: '' }, - success: function(response) { - if (response.success) { - var result = response.data; - var statusColor = result.success ? '#00a32a' : '#d63638'; - var resultHtml = '
'; - resultHtml += '

' + component.toUpperCase() + ' - ' + result.message + '

'; - resultHtml += '

' + result.http_code + '

'; - resultHtml += '

' + result.response_time + ' ms

'; - if (result.data_preview) { - resultHtml += '
'; - resultHtml += '
' + result.data_preview + '
'; - resultHtml += '
'; - } - resultHtml += '
'; - $results.prepend(resultHtml); + success: function(response) { + if (response.success) { + var result = response.data; + var escHtml = function(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }; + var statusColor = result.success ? '#00a32a' : '#d63638'; + var resultHtml = '
'; + resultHtml += '

' + escHtml(component.toUpperCase()) + ' - ' + escHtml(result.message) + '

'; + resultHtml += '

' + escHtml(result.http_code) + '

'; + resultHtml += '

' + escHtml(result.response_time) + ' ms

'; + if (result.data_preview) { + resultHtml += '
'; + resultHtml += '
' + escHtml(result.data_preview) + '
'; + resultHtml += '
'; } + resultHtml += '
'; + $results.prepend(resultHtml); + } $btn.prop('disabled', false).text(' ' + component.charAt(0).toUpperCase() + component.slice(1)); }, error: function() { diff --git a/wpvulnerability-adminms.php b/wpvulnerability-adminms.php index 0dc81f9..4de9513 100644 --- a/wpvulnerability-adminms.php +++ b/wpvulnerability-adminms.php @@ -23,10 +23,19 @@ $wpvulnerability_analyze = get_site_option( 'wpvulnerability-analyze' ); * Enqueues the WPVulnerability admin CSS file on WPVulnerability admin pages. * * @since 2.0.0 + * @since 5.1.4 Assets load only on the plugin pages and the dashboard. + * + * @param string $hook The current admin page hook. * * @return void */ -function wpvulnerability_admin_enqueue_scripts() { +function wpvulnerability_admin_enqueue_scripts( $hook ) { + // Load on the network settings page, the dashboard (widget) and the list + // tables where vulnerability columns are rendered. + if ( ! in_array( $hook, array( 'index.php', 'settings_page_wpvulnerability-options', 'plugins.php', 'themes.php' ), true ) ) { + return; + } + // Enqueue the admin stylesheet. wp_enqueue_style( 'wpvulnerability-admin', @@ -53,8 +62,6 @@ function wpvulnerability_admin_enqueue_scripts() { ); } add_action( 'admin_enqueue_scripts', 'wpvulnerability_admin_enqueue_scripts' ); - - /** * Processes the form submission for the WPVulnerability plugin settings in a multisite network admin context. * @@ -106,92 +113,11 @@ function wpvulnerability_process_network_config_forms() { if ( isset( $_POST['wpvulnerability-analyze'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing - $wpvulnerability_sanitized_values = array( - 'core' => 0, - 'plugins' => 0, - 'themes' => 0, - 'php' => 0, - 'apache' => 0, - 'nginx' => 0, - 'mariadb' => 0, - 'mysql' => 0, - 'imagemagick' => 0, - 'curl' => 0, - 'memcached' => 0, - 'redis' => 0, - 'sqlite' => 0, - ); - $wpvulnerability_analyze_raw = (array) wp_unslash( $_POST['wpvulnerability-analyze'] ); - $wpvulnerability_values = array(); - foreach ( $wpvulnerability_analyze_raw as $v ) { - $wpvulnerability_values[] = sanitize_text_field( is_scalar( $v ) ? (string) $v : '' ); - } - foreach ( $wpvulnerability_values as $data ) { - switch ( $data ) { - case 'core': - $wpvulnerability_sanitized_values['core'] = 1; - break; - case 'plugins': - $wpvulnerability_sanitized_values['plugins'] = 1; - break; - case 'themes': - $wpvulnerability_sanitized_values['themes'] = 1; - break; - case 'php': - $wpvulnerability_sanitized_values['php'] = 1; - break; - case 'apache': - $wpvulnerability_sanitized_values['apache'] = 1; - break; - case 'nginx': - $wpvulnerability_sanitized_values['nginx'] = 1; - break; - case 'mariadb': - $wpvulnerability_sanitized_values['mariadb'] = 1; - break; - case 'mysql': - $wpvulnerability_sanitized_values['mysql'] = 1; - break; - case 'imagemagick': - $wpvulnerability_sanitized_values['imagemagick'] = 1; - break; - case 'curl': - $wpvulnerability_sanitized_values['curl'] = 1; - break; - case 'memcached': - $wpvulnerability_sanitized_values['memcached'] = 1; - break; - case 'redis': - $wpvulnerability_sanitized_values['redis'] = 1; - break; - case 'sqlite': - $wpvulnerability_sanitized_values['sqlite'] = 1; - break; - } - } - - update_site_option( - 'wpvulnerability-analyze', - array( - 'core' => $wpvulnerability_sanitized_values['core'], - 'plugins' => $wpvulnerability_sanitized_values['plugins'], - 'themes' => $wpvulnerability_sanitized_values['themes'], - 'php' => $wpvulnerability_sanitized_values['php'], - 'apache' => $wpvulnerability_sanitized_values['apache'], - 'nginx' => $wpvulnerability_sanitized_values['nginx'], - 'mariadb' => $wpvulnerability_sanitized_values['mariadb'], - 'mysql' => $wpvulnerability_sanitized_values['mysql'], - 'imagemagick' => $wpvulnerability_sanitized_values['imagemagick'], - 'curl' => $wpvulnerability_sanitized_values['curl'], - 'memcached' => $wpvulnerability_sanitized_values['memcached'], - 'redis' => $wpvulnerability_sanitized_values['redis'], - 'sqlite' => $wpvulnerability_sanitized_values['sqlite'], - ) - ); - - unset( $wpvulnerability_sanitized_values ); + // Reuse the strict sanitizer so WPVULNERABILITY_HIDE_* constants + // stay enforced on the network settings save path. + update_site_option( 'wpvulnerability-analyze', wpvulnerability_sanitize_analyze( $wpvulnerability_analyze_raw ) ); add_settings_error( 'wpvulnerability-messages', @@ -805,6 +731,7 @@ function wpvulnerability_render_network_admin_tab_notifications() {
@@ -815,12 +742,13 @@ function wpvulnerability_render_network_admin_tab_notifications() {
- +
- +
- +
- + @@ -917,7 +848,7 @@ function wpvulnerability_render_network_admin_tab_notifications() { } function wpvToggleChannelInput(channel) { - var checkbox = document.querySelector('input[name="wpvulnerability-config[notify][' + channel + ']"]'); + var checkbox = document.querySelector('input[type="checkbox"][name="wpvulnerability-config[notify][' + channel + ']"]'); var inputs = document.getElementById('wpvulnerability-' + channel + '-inputs'); if (checkbox.checked) { @@ -2193,7 +2124,7 @@ function wpvulnerability_admin_slack_callback() { $slack_webhook = isset( $wpvulnerability_settings['slack_webhook'] ) ? $wpvulnerability_settings['slack_webhook'] : ''; ?> - + - + $input Input values. + * @param array $input Input values. * @return array Sanitized values. */ function wpvulnerability_sanitize_analyze( $input ) { @@ -3504,6 +3451,9 @@ function wpvulnerability_render_network_admin_tab_debug() { // Section 2: Component Detection. wpvulnerability_render_debug_section_components(); + // Section 2b: PHP extensions and system packages. + wpvulnerability_render_debug_section_php_extensions(); + // Section 4: Configuration Summary. wpvulnerability_render_debug_section_config(); @@ -4056,22 +4006,30 @@ function wpvulnerability_render_debug_section_api_testing() { component: component, nonce: '' }, - success: function(response) { - if (response.success) { - var result = response.data; - var statusColor = result.success ? '#00a32a' : '#d63638'; - var resultHtml = '
'; - resultHtml += '

' + component.toUpperCase() + ' - ' + result.message + '

'; - resultHtml += '

' + result.http_code + '

'; - resultHtml += '

' + result.response_time + ' ms

'; - if (result.data_preview) { - resultHtml += '
'; - resultHtml += '
' + result.data_preview + '
'; - resultHtml += '
'; - } - resultHtml += '
'; - $results.prepend(resultHtml); + success: function(response) { + if (response.success) { + var result = response.data; + var escHtml = function(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }; + var statusColor = result.success ? '#00a32a' : '#d63638'; + var resultHtml = '
'; + resultHtml += '

' + escHtml(component.toUpperCase()) + ' - ' + escHtml(result.message) + '

'; + resultHtml += '

' + escHtml(result.http_code) + '

'; + resultHtml += '

' + escHtml(result.response_time) + ' ms

'; + if (result.data_preview) { + resultHtml += '
'; + resultHtml += '
' + escHtml(result.data_preview) + '
'; + resultHtml += '
'; } + resultHtml += '
'; + $results.prepend(resultHtml); + } $btn.prop('disabled', false).text(' ' + component.charAt(0).toUpperCase() + component.slice(1)); }, error: function() { diff --git a/wpvulnerability-api.php b/wpvulnerability-api.php index ade8baa..b5f17de 100644 --- a/wpvulnerability-api.php +++ b/wpvulnerability-api.php @@ -557,8 +557,10 @@ function wpvulnerability_permission_check( WP_REST_Request $request ) { return true; } - // Check if application passwords are available. - if ( wp_is_application_passwords_available() ) { + // Check if application passwords are available (WordPress 5.6+). + // On older WordPress versions this authentication method is skipped and + // only cookie-authenticated sessions with admin capabilities are accepted. + if ( function_exists( 'wp_is_application_passwords_available' ) && function_exists( 'wp_authenticate_application_password' ) && wp_is_application_passwords_available() ) { $authorization_header = $request->get_header( 'authorization' ); // Check if the authorization header is present and properly formatted. diff --git a/wpvulnerability-compat.php b/wpvulnerability-compat.php new file mode 100644 index 0000000..da40d96 --- /dev/null +++ b/wpvulnerability-compat.php @@ -0,0 +1,372 @@ +site_id; + } +} + +if ( ! function_exists( 'wp_is_json_request' ) ) { + /** + * Checks whether current request is a JSON request, or is expecting a JSON response. + * + * @since 5.0.0 + * + * @return bool True if Accepts or Content-Type headers contain application/json. + * False otherwise. + */ + function wp_is_json_request() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && false !== strpos( $_SERVER['HTTP_CONTENT_TYPE'], 'application/json' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verbatim WordPress core polyfill; header used only for a strpos comparison. + return true; + } + + if ( isset( $_SERVER['HTTP_ACCEPT'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT'], 'application/json' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verbatim WordPress core polyfill; header used only for a strpos comparison. + return true; + } + + return false; + } +} + +if ( ! function_exists( 'sanitize_locale_name' ) ) { + /** + * Strips out all characters not allowed in a locale name. + * + * @since 6.2.1 + * + * @param string $locale_name The locale name to be sanitized. + * @return string The sanitized value. + */ + function sanitize_locale_name( $locale_name ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + // Limit to A-Z, a-z, 0-9, '_', '-'. + $sanitized = preg_replace( '/[^A-Za-z0-9_-]/', '', $locale_name ); + + /** + * Filters a sanitized locale name string. + * + * @since 6.2.1 + * + * @param string $sanitized The sanitized locale name. + * @param string $locale_name The locale name before sanitization. + */ + return apply_filters( 'sanitize_locale_name', $sanitized, $locale_name ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved. + } +} + +if ( ! function_exists( 'determine_locale' ) ) { + /** + * Determines the current locale desired for the request. + * + * @since 5.0.0 + * + * @global string $pagenow The filename of the current screen. + * + * @return string The determined locale. + */ + function determine_locale() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + /** + * Filters the locale for the current request prior to the default determination process. + * + * Using this filter allows to override the default logic, effectively short-circuiting the function. + * + * @since 5.0.0 + * + * @param string|null $locale The locale to return and short-circuit. Default null. + */ + $determined_locale = apply_filters( 'pre_determine_locale', null ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved. + + if ( $determined_locale && is_string( $determined_locale ) ) { + return $determined_locale; + } + + $determined_locale = get_locale(); + + if ( is_admin() ) { + $determined_locale = get_user_locale(); + } + + if ( isset( $_GET['_locale'] ) && 'user' === $_GET['_locale'] && wp_is_json_request() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verbatim WordPress core polyfill; the parameter is only compared, never stored or displayed. + $determined_locale = get_user_locale(); + } + + if ( ! empty( $_GET['wp_lang'] ) && isset( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verbatim WordPress core polyfill; the parameter is sanitized below and never stored or displayed. + $determined_locale = sanitize_locale_name( wp_unslash( $_GET['wp_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verbatim WordPress core polyfill; the parameter is sanitized before use and never stored or displayed. + } + + /** + * Filters the locale for the current request. + * + * @since 5.0.0 + * + * @param string $determined_locale The locale. + */ + return apply_filters( 'determine_locale', $determined_locale ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved. + } +} + +if ( ! function_exists( 'wp_timezone_string' ) ) { + /** + * Retrieves the timezone of the site as a string. + * + * @since 5.3.0 + * + * @return string PHP timezone name or a ±HH:MM offset. + */ + function wp_timezone_string() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + $timezone_string = get_option( 'timezone_string' ); + + if ( $timezone_string ) { + return $timezone_string; + } + + $offset = (float) get_option( 'gmt_offset' ); + $hours = (int) $offset; + $minutes = ( $offset - $hours ); + + $sign = ( $offset < 0 ) ? '-' : '+'; + $abs_hour = abs( $hours ); + $abs_mins = abs( $minutes * 60 ); + $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); + + return $tz_offset; + } +} + +if ( ! function_exists( 'wp_timezone' ) ) { + /** + * Retrieves the timezone of the site as a DateTimeZone object. + * + * @since 5.3.0 + * + * @return DateTimeZone Timezone object. + */ + function wp_timezone() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + return new DateTimeZone( wp_timezone_string() ); + } +} + +if ( ! function_exists( 'wp_date' ) ) { + /** + * Retrieves the date, in localized format. + * + * @since 5.3.0 + * + * @param string $format PHP date format. + * @param int|null $timestamp Optional. Unix timestamp. Defaults to current time. + * @param DateTimeZone|null $timezone Optional. Timezone to output result in. + * Defaults to timezone from site settings. + * @return string|false The date, translated if locale specifies it. + * False on invalid timestamp input. + */ + function wp_date( $format, $timestamp = null, $timezone = null ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + global $wp_locale; + + if ( null === $timestamp ) { + $timestamp = time(); + } elseif ( ! is_numeric( $timestamp ) ) { + return false; + } + + if ( ! $timezone ) { + $timezone = wp_timezone(); + } + + $datetime = date_create( '@' . $timestamp ); + $datetime->setTimezone( $timezone ); + + if ( empty( $wp_locale->month ) || empty( $wp_locale->weekday ) ) { + $date = $datetime->format( $format ); + } else { + // We need to unpack shorthand `r` format because it has parts that might be localized. + $format = preg_replace( '/(?get_month( $datetime->format( 'm' ) ); + $weekday = $wp_locale->get_weekday( $datetime->format( 'w' ) ); + + for ( $i = 0; $i < $format_length; $i++ ) { + switch ( $format[ $i ] ) { + case 'D': + $new_format .= addcslashes( $wp_locale->get_weekday_abbrev( $weekday ), '\\A..Za..z' ); + break; + case 'F': + $new_format .= addcslashes( $month, '\\A..Za..z' ); + break; + case 'l': + $new_format .= addcslashes( $weekday, '\\A..Za..z' ); + break; + case 'M': + $new_format .= addcslashes( $wp_locale->get_month_abbrev( $month ), '\\A..Za..z' ); + break; + case 'a': + $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'a' ) ), '\\A..Za..z' ); + break; + case 'A': + $new_format .= addcslashes( $wp_locale->get_meridiem( $datetime->format( 'A' ) ), '\\A..Za..z' ); + break; + case '\\': + $new_format .= $format[ $i ]; + + // If character follows a slash, we add it without translating. + if ( $i < $format_length ) { + $new_format .= $format[ ++$i ]; + } + break; + default: + $new_format .= $format[ $i ]; + break; + } + } + + $date = $datetime->format( $new_format ); + + // Core's wp_date() calls wp_maybe_decline_date() unconditionally, but + // that function only exists since WP 5.4. Guarded here so the polyfill + // is safe on the declared 4.7 floor (dates are then not declined). + if ( function_exists( 'wp_maybe_decline_date' ) ) { + $date = wp_maybe_decline_date( $date ); + } + } + + /** + * Filters the date formatted based on the locale. + * + * @since 5.3.0 + * + * @param string $date Formatted date string. + * @param string $format Format to display the date. + * @param int $timestamp Unix timestamp. + * @param DateTimeZone $timezone Timezone. + */ + $date = apply_filters( 'wp_date', $date, $format, $timestamp, $timezone ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Verbatim WordPress core polyfill; core hook names must be preserved. + + return $date; + } +} + +if ( ! function_exists( 'get_post_datetime' ) ) { + /** + * Retrieves post published or modified time as a DateTimeImmutable object instance. + * + * The object will be set to the timezone from WordPress settings. + * + * @since 5.3.0 + * + * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post object. + * @param string $field Optional. Published or modified time to use from database. + * Accepts 'date' or 'modified'. Default 'date'. + * @param string $source Optional. Local or UTC time to use from database. + * Accepts 'local' or 'gmt'. Default 'local'. + * @return DateTimeImmutable|false Time object on success, false on failure. + */ + function get_post_datetime( $post = null, $field = 'date', $source = 'local' ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + $post = get_post( $post ); + + if ( ! $post ) { + return false; + } + + $wp_timezone = wp_timezone(); + + if ( 'gmt' === $source ) { + $time = ( 'modified' === $field ) ? $post->post_modified_gmt : $post->post_date_gmt; + $timezone = new DateTimeZone( 'UTC' ); + } else { + $time = ( 'modified' === $field ) ? $post->post_modified : $post->post_date; + $timezone = $wp_timezone; + } + + if ( empty( $time ) || '0000-00-00 00:00:00' === $time ) { + return false; + } + + $datetime = date_create_immutable_from_format( 'Y-m-d H:i:s', $time, $timezone ); + + if ( false === $datetime ) { + return false; + } + + return $datetime->setTimezone( $wp_timezone ); + } +} + +if ( ! function_exists( 'get_post_timestamp' ) ) { + /** + * Retrieves post published or modified time as a Unix timestamp. + * + * @since 5.3.0 + * + * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post object. + * @param string $field Optional. Published or modified time to use from database. + * Accepts 'date' or 'modified'. Default 'date'. + * @return int|false Unix timestamp on success, false on failure. + */ + function get_post_timestamp( $post = null, $field = 'date' ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Verbatim WordPress core polyfill. + $datetime = get_post_datetime( $post, $field ); + + if ( false === $datetime ) { + return false; + } + + return $datetime->getTimestamp(); + } +} diff --git a/wpvulnerability-core.php b/wpvulnerability-core.php index af1d466..a30c289 100644 --- a/wpvulnerability-core.php +++ b/wpvulnerability-core.php @@ -224,7 +224,7 @@ function wpvulnerability_core_get_installed() { // Return the JSON-encoded array of core vulnerabilities. $encoded = wp_json_encode( $core ); - return false !== $encoded ? $encoded : ''; + return false !== $encoded ? $encoded : '[]'; } /** diff --git a/wpvulnerability-debug.php b/wpvulnerability-debug.php index 459ebe9..d0d9d54 100644 --- a/wpvulnerability-debug.php +++ b/wpvulnerability-debug.php @@ -377,10 +377,59 @@ function wpvulnerability_debug_get_component_status() { return $status; } +/** + * Returns the slug of the first installed plugin or theme. + * + * Mirrors the slug derivation used by the vulnerability data loaders: the + * folder name, falling back to the text domain for single-file plugins. + * + * @since 5.1.6 + * + * @param string $type Either 'plugin' or 'theme'. + * + * @return string Slug, or an empty string when nothing of that type is installed. + */ +function wpvulnerability_debug_first_installed_slug( $type ) { + + if ( 'theme' === $type ) { + foreach ( wp_get_themes() as $theme ) { + return $theme->get_stylesheet(); + } + return ''; + } + + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + if ( ! function_exists( 'get_plugins' ) ) { + return ''; + } + + foreach ( get_plugins() as $plugin_file => $plugin_data ) { + $folder = explode( '/', $plugin_file ); + $slug = trim( (string) $folder[0] ); + if ( '' !== $slug && false === strpos( $plugin_file, '/' ) ) { + // Single-file plugin at the plugins root: use the file name. + $slug = basename( $slug, '.php' ); + } + if ( '' === $slug && isset( $plugin_data['TextDomain'] ) ) { + $td_raw = $plugin_data['TextDomain']; + $slug = trim( is_scalar( $td_raw ) ? (string) $td_raw : '' ); + } + if ( '' !== $slug ) { + return $slug; + } + } + + return ''; +} + /** * Tests API connectivity for a specific component. * * @since 4.3.0 + * @since 5.1.6 Core collapses pre-release versions to their stable milestone, + * and plugins/themes use the real slug-based API routes. * * @param string $component The component to test (e.g., 'core', 'plugins', 'php'). * @@ -403,33 +452,45 @@ function wpvulnerability_debug_test_api_component( $component ) { return $result; } - // Get version for the component. + // Build the API URL for the component. Core and software routes take a + // version; the plugin/theme routes take a slug only. + $url = ''; $version = null; switch ( $component ) { case 'core': global $wp_version; - $version = $wp_version; + // The API only serves stable milestones: collapse pre-release + // suffixes (e.g. 7.1-alpha-62421) so the check keeps working. + $version = preg_replace( '/-(?:alpha|beta|rc).*$/i', '', trim( (string) $wp_version ) ); + if ( ! is_string( $version ) || '' === $version ) { + $result['message'] = __( 'WordPress version not detected for this component.', 'wpvulnerability' ); + return $result; + } + $url = WPVULNERABILITY_API_HOST . 'core/' . wpvulnerability_sanitize_version( $version ) . '/'; break; case 'plugins': case 'themes': - // Use a generic version for testing. - $version = '1.0.0'; + $slug = wpvulnerability_debug_first_installed_slug( 'plugins' === $component ? 'plugin' : 'theme' ); + if ( '' === $slug ) { + $result['message'] = 'plugins' === $component + ? __( 'No plugins detected to test the API endpoint.', 'wpvulnerability' ) + : __( 'No themes detected to test the API endpoint.', 'wpvulnerability' ); + return $result; + } + $url = WPVULNERABILITY_API_HOST . ( 'plugins' === $component ? 'plugin/' : 'theme/' ) . sanitize_title( $slug ) . '/'; break; default: if ( function_exists( 'wpvulnerability_get_software_version' ) ) { $version = wpvulnerability_get_software_version( $component ); } + if ( ! $version ) { + $result['message'] = __( 'No local version detected; the API endpoint was not called.', 'wpvulnerability' ); + return $result; + } + $url = WPVULNERABILITY_API_HOST . $component . '/' . wpvulnerability_sanitize_version( $version ) . '/'; break; } - if ( ! $version ) { - $result['message'] = __( 'Version not detected for this component.', 'wpvulnerability' ); - return $result; - } - - // Build API URL. - $url = WPVULNERABILITY_API_HOST . $component . '/' . $version . '/'; - // Execute request with timing. $start_time = microtime( true ); $response = wp_remote_get( @@ -537,6 +598,47 @@ function wpvulnerability_debug_get_cron_status() { return $cron_status; } +/** + * Masks sensitive configuration values (webhook URLs, tokens, emails). + * + * Used whenever configuration data is displayed or exported for debugging, + * so shared debug files never contain usable secrets or recipient addresses. + * + * @since 5.1.3 + * + * @param mixed $config Plugin configuration. + * @return array Configuration with sensitive values masked. + */ +function wpvulnerability_debug_mask_config( $config ) { + if ( ! is_array( $config ) ) { + return array(); + } + + $secret_keys = array( 'slack_webhook', 'teams_webhook', 'discord_webhook', 'telegram_bot_token', 'telegram_chat_id' ); + + foreach ( $secret_keys as $key ) { + if ( isset( $config[ $key ] ) && is_scalar( $config[ $key ] ) && '' !== (string) $config[ $key ] ) { + $value = (string) $config[ $key ]; + $config[ $key ] = strlen( $value ) > 8 ? substr( $value, 0, 4 ) . '...' . substr( $value, -4 ) : '...'; + } + } + + if ( isset( $config['emails'] ) && is_scalar( $config['emails'] ) && '' !== (string) $config['emails'] ) { + $masked = array(); + foreach ( explode( ',', (string) $config['emails'] ) as $email ) { + $parts = explode( '@', trim( $email ) ); + if ( count( $parts ) < 2 || '' === $parts[1] ) { + $masked[] = '...'; + continue; + } + $masked[] = ( '' !== $parts[0] ? substr( $parts[0], 0, 1 ) . '***' : '***' ) . '@' . $parts[1]; + } + $config['emails'] = implode( ',', $masked ); + } + + return $config; +} + /** * Exports comprehensive debug information as JSON. * @@ -553,7 +655,7 @@ function wpvulnerability_debug_export_info() { 'timestamp' => current_time( 'mysql' ), 'system_info' => wpvulnerability_debug_get_system_info(), 'components' => wpvulnerability_debug_get_component_status(), - 'configuration' => $config, + 'configuration' => wpvulnerability_debug_mask_config( $config ), 'cron_status' => wpvulnerability_debug_get_cron_status(), 'vulnerability_counts' => array(), ); @@ -594,6 +696,13 @@ function wpvulnerability_debug_clear_all_caches() { } } + // Plugin data timestamp option, written by wpvulnerability_plugin_get_data(). + if ( is_multisite() ) { + delete_site_option( 'wpvulnerability-plugins-cache-data' ); + } else { + delete_option( 'wpvulnerability-plugins-cache-data' ); + } + return true; } @@ -657,8 +766,318 @@ function wpvulnerability_debug_get_option_names() { */ function wpvulnerability_debug_get_option_value( $option_name ) { if ( is_multisite() ) { - return get_site_option( $option_name, null ); + $value = get_site_option( $option_name, null ); } else { - return get_option( $option_name, null ); + $value = get_option( $option_name, null ); } + + // Mask secrets when displaying the configuration in the options viewer. + if ( 'wpvulnerability-config' === $option_name && is_array( $value ) ) { + $value = wpvulnerability_debug_mask_config( $value ); + } + + return $value; +} + +/** + * Returns the PHP extensions WordPress makes use of, grouped by relevance. + * + * Purely informational: presence or absence of an extension is neither good + * nor bad. Grouping and notes follow the WordPress core recommendations. + * + * @since 5.1.6 + * + * @return array}> Each group has a + * label and a map of extension name to description. + */ +function wpvulnerability_debug_get_php_extensions() { + return array( + array( + 'label' => __( 'Built-in (no hosting action required)', 'wpvulnerability' ), + 'extensions' => array( + 'pcre' => __( 'Regular expression engine used throughout PHP and WordPress.', 'wpvulnerability' ), + ), + ), + array( + 'label' => __( 'Required', 'wpvulnerability' ), + 'extensions' => array( + 'hash' => __( 'Hashing, including passwords and update packages.', 'wpvulnerability' ), + 'json' => __( 'Communications with other servers and JSON data processing.', 'wpvulnerability' ), + 'mysqli' => __( 'Connects to MySQL/MariaDB for database interactions.', 'wpvulnerability' ), + ), + ), + array( + 'label' => __( 'Highly recommended', 'wpvulnerability' ), + 'extensions' => array( + 'curl' => __( 'Performs remote request operations.', 'wpvulnerability' ), + 'dom' => __( 'Validates Text Widget content and configures IIS7+ automatically.', 'wpvulnerability' ), + 'exif' => __( 'Works with metadata stored in images.', 'wpvulnerability' ), + 'fileinfo' => __( 'Detects MIME types of file uploads.', 'wpvulnerability' ), + 'igbinary' => __( 'Drop-in replacement for the standard PHP serializer; improves performance.', 'wpvulnerability' ), + 'imagick' => __( 'Better image quality for media uploads and PDF thumbnail support.', 'wpvulnerability' ), + 'intl' => __( 'Locale-aware operations: formatting, transliteration, collation, timezones.', 'wpvulnerability' ), + 'mbstring' => __( 'Properly handles UTF-8 text.', 'wpvulnerability' ), + 'openssl' => __( 'SSL-based connections to other hosts.', 'wpvulnerability' ), + 'xml' => __( 'XML parsing, such as from a third-party site.', 'wpvulnerability' ), + 'zip' => __( 'Decompresses Plugin, Theme, and WordPress update packages.', 'wpvulnerability' ), + ), + ), + array( + 'label' => __( 'Object cache (only one is needed)', 'wpvulnerability' ), + 'extensions' => array( + 'apcu' => __( 'In-memory key-value store for PHP.', 'wpvulnerability' ), + 'memcached' => __( 'Distributed memory object caching system.', 'wpvulnerability' ), + 'opcache' => __( 'Stores precompiled PHP bytecode to reduce repeated parsing and compilation.', 'wpvulnerability' ), + 'redis' => __( 'PHP extension for interfacing with Redis.', 'wpvulnerability' ), + ), + ), + array( + 'label' => __( 'Optional improvements', 'wpvulnerability' ), + 'extensions' => array( + 'timezonedb' => __( 'Timezone database used by the PHP date and time functions.', 'wpvulnerability' ), + ), + ), + array( + 'label' => __( 'Fallbacks / situational use', 'wpvulnerability' ), + 'extensions' => array( + 'bcmath' => __( 'Arbitrary precision mathematics.', 'wpvulnerability' ), + 'filter' => __( 'Securely filters user input.', 'wpvulnerability' ), + 'gd' => __( 'Functionally limited fallback for image manipulation when Imagick is unavailable.', 'wpvulnerability' ), + 'iconv' => __( 'Converts between character sets.', 'wpvulnerability' ), + 'shmop' => __( 'Reads, writes, creates and deletes Unix shared memory segments.', 'wpvulnerability' ), + 'simplexml' => __( 'XML parsing.', 'wpvulnerability' ), + 'sodium' => __( 'Signature validation and securely random bytes.', 'wpvulnerability' ), + 'xmlreader' => __( 'XML parsing.', 'wpvulnerability' ), + 'zlib' => __( 'Gzip compression and decompression.', 'wpvulnerability' ), + ), + ), + array( + 'label' => __( 'File changes when files are not writeable (transports)', 'wpvulnerability' ), + 'extensions' => array( + 'ssh2' => __( 'Shell, remote execution, tunneling and file transfer over SSH.', 'wpvulnerability' ), + 'ftp' => __( 'FTP client access for updates and installations.', 'wpvulnerability' ), + 'sockets' => __( 'Low-level socket communication functions.', 'wpvulnerability' ), + ), + ), + ); +} + +/** + * Detects whether a PHP extension is loaded. + * + * Handles special cases where the internal extension name differs from the + * common name or where availability depends on functions instead. + * + * @since 5.1.6 + * + * @param string $extension Extension name as listed in the recommendations. + * + * @return bool True when the extension is loaded. + */ +function wpvulnerability_debug_extension_loaded( $extension ) { + if ( 'opcache' === $extension ) { + return extension_loaded( 'opcache' ) + || extension_loaded( 'Zend OPcache' ) + || function_exists( 'opcache_get_status' ); + } + + return extension_loaded( $extension ); +} + +/** + * Gathers system package information relevant to WordPress. + * + * Informational only. Shell probes honour the plugin shell-exec policy + * (security mode, disable constant) through the safe wrapper; when probing + * is not possible the version is reported as unknown. + * + * @since 5.1.6 + * + * @return array Package rows. + */ +function wpvulnerability_debug_get_system_packages() { + $packages = array(); + + // cURL: reuse the shared detector (PHP extension first, then CLI probe). + $curl_version = function_exists( 'wpvulnerability_detect_curl' ) ? wpvulnerability_detect_curl() : null; + $packages[] = array( + 'name' => 'curl', + 'available' => null !== $curl_version, + 'version' => $curl_version, + ); + + // ImageMagick: reuse the shared detector. + $imagemagick = function_exists( 'wpvulnerability_detect_imagemagick' ) ? wpvulnerability_detect_imagemagick() : array(); + $im_raw = isset( $imagemagick['version'] ) ? $imagemagick['version'] : null; + $im_version = is_string( $im_raw ) ? $im_raw : null; + $packages[] = array( + 'name' => 'ImageMagick', + 'available' => null !== $im_version, + 'version' => $im_version, + ); + + // Ghost Script: enables Imagick/ImageMagick PDF thumbnail generation. + $gs_version = null; + if ( wpvulnerability_can_shell_exec() ) { + $gs_output = wpvulnerability_safe_shell_exec( 'gs', 'gs --version' ); + $gs_matches = array(); + if ( is_string( $gs_output ) && preg_match( '/(\d+\.\d+(?:\.\d+)?)/', trim( $gs_output ), $gs_matches ) ) { + $gs_version = $gs_matches[1]; + } + } + $packages[] = array( + 'name' => 'Ghost Script', + 'available' => null !== $gs_version, + 'version' => $gs_version, + ); + + // OpenSSL: report the OpenSSL linked into the PHP build. + $openssl_version = null; + if ( defined( 'OPENSSL_VERSION_TEXT' ) && preg_match( '/(?:OpenSSL|LibreSSL|BoringSSL)\s+(\d[\w.]*)/', (string) OPENSSL_VERSION_TEXT, $o_matches ) ) { + $openssl_version = $o_matches[1]; + } + $packages[] = array( + 'name' => 'OpenSSL', + 'available' => defined( 'OPENSSL_VERSION_TEXT' ), + 'version' => $openssl_version, + ); + + // WebP and AVIF support: check Imagick formats, GD, then CLI tools. + foreach ( + array( + 'WebP' => array( + 'format' => 'WEBP', + 'gd_key' => 'WebP Support', + 'tool' => 'cwebp', + 'command' => 'cwebp -version', + ), + 'AVIF' => array( + 'format' => 'AVIF', + 'gd_key' => 'AVIF Support', + 'tool' => 'avifenc', + 'command' => 'avifenc --version', + ), + ) + as $format_name => $format_check + ) { + $supported = false; + $version = null; + + if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { + try { + $imagick = new Imagick(); + $supported = in_array( strtoupper( $format_check['format'] ), $imagick->queryFormats( $format_check['format'] ), true ); + } catch ( Exception $e ) { + $supported = false; + } + } + + if ( ! $supported && extension_loaded( 'gd' ) && function_exists( 'gd_info' ) ) { + $gd_info = gd_info(); + $supported = ! empty( $gd_info[ $format_check['gd_key'] ] ); + } + + if ( ! $supported && wpvulnerability_can_shell_exec() ) { + $tool_output = wpvulnerability_safe_shell_exec( $format_check['tool'], $format_check['command'] ); + if ( is_string( $tool_output ) && '' !== trim( $tool_output ) ) { + $supported = true; + $v_matches = array(); + if ( preg_match( '/(\d+\.\d+(?:\.\d+)?)/', $tool_output, $v_matches ) ) { + $version = $v_matches[1]; + } + } + } + + $packages[] = array( + 'name' => $format_name, + 'available' => $supported, + 'version' => $version, + ); + } + + return $packages; +} + +/** + * Renders the Debug tab section: PHP extensions and system packages. + * + * Informational listing only: nothing here is good or bad. + * + * @since 5.1.6 + * + * @return void + */ +function wpvulnerability_render_debug_section_php_extensions() { + $groups = wpvulnerability_debug_get_php_extensions(); + $packages = wpvulnerability_debug_get_system_packages(); + ?> +
+

+

+ +

+ +

+ + + $description ) : + $loaded = wpvulnerability_debug_extension_loaded( $extension ); + ?> + + + + + + + +
+ +
+ +

+

+ +

+ + + + + + + + + + +
+ + + + + +
+
+ 'wpvulnerability_log', - 'post_status' => 'publish', - 'fields' => 'ids', - 'posts_per_page' => 100, - 'orderby' => 'date', - 'order' => 'ASC', - 'no_found_rows' => true, - 'cache_results' => false, - 'update_post_term_cache' => false, - 'update_post_meta_cache' => false, - ) - ); - if ( empty( $logs ) ) { - break; - } + // The "delete logs" action runs at file scope during init, potentially + // before this file's init callbacks register the post types: queries for + // unregistered post types silently return nothing. + if ( ! post_type_exists( 'wpvulnerability_log' ) ) { + wpvulnerability_register_log_post_type(); + } + if ( ! post_type_exists( 'wpv_shell_log' ) ) { + wpvulnerability_register_shell_log_post_type(); + } - foreach ( $logs as $log_id ) { - wp_delete_post( $log_id, true ); - } - $logs_count = count( $logs ); - } while ( $logs_count >= 100 ); + // Purge both log stores: API request logs and shell audit logs. + foreach ( array( 'wpvulnerability_log', 'wpv_shell_log' ) as $log_post_type ) { + do { + $logs = get_posts( + array( + 'post_type' => $log_post_type, + 'post_status' => 'any', + 'fields' => 'ids', + 'posts_per_page' => 100, + 'orderby' => 'date', + 'order' => 'ASC', + 'no_found_rows' => true, + 'cache_results' => false, + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + ) + ); + + if ( empty( $logs ) ) { + break; + } + + foreach ( $logs as $log_id ) { + wp_delete_post( $log_id, true ); + } + $logs_count = count( $logs ); + } while ( $logs_count >= 100 ); + } } /** @@ -735,7 +757,11 @@ function wpvulnerability_normalize_notify_settings( $notify ) { if ( is_array( $notify ) ) { foreach ( $notify as $channel => $value ) { - $normalized[ $channel ] = wpvulnerability_normalize_yes_no( $value ); + // Only the five known channels are stored; anything else from + // a crafted request is discarded. + if ( isset( $defaults[ (string) $channel ] ) ) { + $normalized[ (string) $channel ] = wpvulnerability_normalize_yes_no( $value ); + } } } @@ -1905,14 +1931,28 @@ function wpvulnerability_get( $type, $slug = '', $cache = 1 ) { wp_die( 'Unknown vulnerability type sent.' ); } - // Validate slug for plugin or theme. - if ( ( 'plugin' === $type || 'theme' === $type ) && empty( sanitize_title( $slug ) ) ) { - return false; + // Validate and normalize slug for plugin or theme. + if ( 'plugin' === $type || 'theme' === $type ) { + $slug = sanitize_title( (string) $slug ); + if ( '' === $slug ) { + return false; + } } - // Validate slug for core. - if ( 'core' === $type && ! wpvulnerability_sanitize_version( $slug ) ) { - return false; + // Validate and normalize slug for core. + if ( 'core' === $type ) { + $slug = wpvulnerability_sanitize_version( (string) $slug ); + if ( ! $slug ) { + return false; + } + + // The API only serves stable milestones: collapse pre-release suffixes + // (e.g. 7.1-alpha-62421 or 6.9-beta1) to the milestone (7.1 / 6.9) so + // core lookups keep working on development installations. + $milestone = preg_replace( '/-(?:alpha|beta|rc).*$/i', '', $slug ); + if ( is_string( $milestone ) && '' !== $milestone ) { + $slug = $milestone; + } } // Cache key. @@ -1927,14 +1967,16 @@ function wpvulnerability_get( $type, $slug = '', $cache = 1 ) { $response = wp_remote_get( $url, array( 'timeout' => 2.5 ) ); wpvulnerability_maybe_log_api_response( $url, $response ); - if ( ! is_wp_error( $response ) ) { + if ( ! is_wp_error( $response ) && 200 === (int) wp_remote_retrieve_response_code( $response ) ) { $body = wp_remote_retrieve_body( $response ); - // Cache the response data. - if ( is_multisite() ) { - set_site_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); - } else { - set_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); + // Cache only valid JSON responses so error pages never poison the cache. + if ( is_array( json_decode( $body, true ) ) && $cache ) { + if ( is_multisite() ) { + set_site_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); + } else { + set_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); + } } $vulnerability_data = $body; // Use the fresh data. @@ -2248,15 +2290,18 @@ function wpvulnerability_get_statistics( $cache = 1 ) { $response = wp_remote_get( $url, array( 'timeout' => 2.5 ) ); wpvulnerability_maybe_log_api_response( $url, $response ); - if ( ! is_wp_error( $response ) ) { + if ( ! is_wp_error( $response ) && 200 === (int) wp_remote_retrieve_response_code( $response ) ) { $body = wp_remote_retrieve_body( $response ); - // Cache the response data. - if ( is_multisite() ) { - set_site_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); - } else { - set_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); + + // Cache only valid JSON responses so error pages never poison the cache. + if ( is_array( json_decode( $body, true ) ) ) { + if ( is_multisite() ) { + set_site_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); + } else { + set_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); + } + $vulnerability = $body; // Use the fresh data. } - $vulnerability = $body; // Use the fresh data. } } @@ -2433,6 +2478,16 @@ function wpvulnerability_statistics_get() { * @return list>|false Returns an array of vulnerabilities, or false if there are none. */ function wpvulnerability_get_vulnerabilities( $type, $version, $cache = 1 ) { + // Validate vulnerability type and version before building the request URL. + $type = strtolower( trim( (string) $type ) ); + $version = wpvulnerability_sanitize_version( (string) $version ); + + $valid_types = array( 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' ); + + if ( ! in_array( $type, $valid_types, true ) || ! $version ) { + return false; + } + $key = 'wpvulnerability_' . $type; $vulnerability_data = null; $vulnerability = array(); @@ -2448,9 +2503,11 @@ function wpvulnerability_get_vulnerabilities( $type, $version, $cache = 1 ) { $response = wp_remote_get( $url, array( 'timeout' => 2.5 ) ); wpvulnerability_maybe_log_api_response( $url, $response ); - if ( ! is_wp_error( $response ) ) { + if ( ! is_wp_error( $response ) && 200 === (int) wp_remote_retrieve_response_code( $response ) ) { $body = wp_remote_retrieve_body( $response ); - if ( $cache ) { + + // Cache only valid JSON responses so error pages never poison the cache. + if ( is_array( json_decode( $body, true ) ) && $cache ) { if ( is_multisite() ) { set_site_transient( $key, $body, HOUR_IN_SECONDS * wpvulnerability_cache_hours() ); } else { @@ -2612,6 +2669,9 @@ function wpvulnerability_validate_shell_command( $command ) { 'caddy', 'php', 'curl', + 'gs', + 'cwebp', + 'avifenc', ); // Extract the base command (first word). diff --git a/wpvulnerability-notifications.php b/wpvulnerability-notifications.php index 981e5fa..d42694a 100644 --- a/wpvulnerability-notifications.php +++ b/wpvulnerability-notifications.php @@ -120,6 +120,28 @@ function wpvulnerability_validate_webhook_url( $webhook_url, $allowed_hosts ) { return $webhook_url; } +/** + * Masks a secret value for display in the admin UI. + * + * Per the project security checklist, secrets (webhook URLs, bot tokens) are + * never rendered in full: only their last 4 characters are shown. + * + * @since 5.1.4 + * + * @param string $value Secret value to mask. + * + * @return string Masked value, or an empty string when the input is empty. + */ +function wpvulnerability_mask_secret( $value ) { + $value = (string) $value; + + if ( '' === $value ) { + return ''; + } + + return '••••••••' . mb_substr( $value, -4 ); +} + /** * Retrieves the unsubscribe URL for WPVulnerability email notifications. * @@ -629,7 +651,7 @@ function wpvulnerability_execute_notification( $forced = false ) { // Get the admin email. $admin_email = is_multisite() ? get_site_option( 'admin_email' ) : get_bloginfo( 'admin_email' ); - $from_email = $admin_email; + $from_email = is_scalar( $admin_email ) ? sanitize_email( (string) $admin_email ) : ''; // Check if WPVULNERABILITY_MAIL is defined and valid, and use it if available. if ( defined( 'WPVULNERABILITY_MAIL' ) ) { @@ -651,7 +673,7 @@ function wpvulnerability_execute_notification( $forced = false ) { // Prepare email headers. $email_headers = array(); - $email_headers[] = 'From: WPVulnerability <' . ( is_scalar( $from_email ) ? (string) $from_email : '' ) . '>'; + $email_headers[] = 'From: WPVulnerability <' . $from_email . '>'; $email_headers[] = 'Content-Type: text/html; charset=UTF-8'; if ( $forced && ( empty( $wpvulnerability_settings['emails'] ) ) ) { diff --git a/wpvulnerability-plugins.php b/wpvulnerability-plugins.php index d7c60f7..417991d 100644 --- a/wpvulnerability-plugins.php +++ b/wpvulnerability-plugins.php @@ -511,6 +511,8 @@ function wpvulnerability_plugin_get_vulnerabilities_clean() { */ function wpvulnerability_plugin_show_lastupdated( $column_name, $plugin_file, $plugin_data ) { + static $plugins_data = null; + $now = time(); $year = strtotime( '-1 year', $now ); @@ -533,11 +535,14 @@ function wpvulnerability_plugin_show_lastupdated( $column_name, $plugin_file, $p if ( '' !== $plugin_slug ) { - // Retrieve the vulnerabilities for all plugins from the options table and decode the JSON. - $raw_plugins_data = is_multisite() ? get_site_option( 'wpvulnerability-plugins-data', '' ) : get_option( 'wpvulnerability-plugins-data', '' ); - $plugins_data = json_decode( is_string( $raw_plugins_data ) ? $raw_plugins_data : '', true ); - if ( ! is_array( $plugins_data ) ) { - $plugins_data = array(); + // Retrieve the vulnerabilities for all plugins once per request; this + // callback runs for every row of the plugins list table. + if ( null === $plugins_data ) { + $raw_plugins_data = is_multisite() ? get_site_option( 'wpvulnerability-plugins-data', '' ) : get_option( 'wpvulnerability-plugins-data', '' ); + $plugins_data = json_decode( is_string( $raw_plugins_data ) ? $raw_plugins_data : '', true ); + if ( ! is_array( $plugins_data ) ) { + $plugins_data = array(); + } } // Get the plugin data from the stored data. @@ -548,15 +553,11 @@ function wpvulnerability_plugin_show_lastupdated( $column_name, $plugin_file, $p $pd_latest = is_scalar( $pd_latest_raw ) ? intval( $pd_latest_raw ) : 0; if ( $pd_latest > 0 ) { - $timestamp = $pd_latest; - $df_raw = get_option( 'date_format', 'Y-m-d' ); - $date_format = is_scalar( $df_raw ) ? (string) $df_raw : 'Y-m-d'; - if ( function_exists( 'wp_date' ) ) { - $plugin_data_updated = (string) wp_date( $date_format, $timestamp ); - } else { - $plugin_data_updated = gmdate( $date_format, $timestamp ); - } - $plugin_data_ago = human_time_diff( $timestamp ); + $timestamp = $pd_latest; + $df_raw = get_option( 'date_format', 'Y-m-d' ); + $date_format = is_scalar( $df_raw ) ? (string) $df_raw : 'Y-m-d'; + $plugin_data_updated = (string) wp_date( $date_format, $timestamp ); + $plugin_data_ago = human_time_diff( $timestamp ); $warning_date = $pd_latest < $year; $pd_closed_raw = $pd['closed'] ?? 0; @@ -587,7 +588,7 @@ function wpvulnerability_plugin_show_lastupdated( $column_name, $plugin_file, $p * Adds a 'Last Updated' column to the plugins table list in the WordPress admin area. * * This function iterates over the existing columns in the plugins table and inserts a new column titled 'Last Updated' - * just before the 'auto-updates' column if it exists. If the 'auto-updates' column is not found, the 'Last Updated' + * just after the 'description' column if it exists. If the 'description' column is not found, the 'Last Updated' * column is appended at the end. The function is typically hooked to the 'manage_plugins_columns' filter in WordPress * to modify the columns of the plugins table. * diff --git a/wpvulnerability-process.php b/wpvulnerability-process.php index 103d702..8554771 100644 --- a/wpvulnerability-process.php +++ b/wpvulnerability-process.php @@ -247,7 +247,17 @@ function wpvulnerability_component_icon_html( $type ) { if ( ! isset( $icon_map[ $type ] ) ) { return ''; } - return ''; + + // Explicit dimensions: this HTML is also embedded where the plugin CSS is + // not loaded (notification emails, Site Health), and without width/height + // the SVG renders at its intrinsic 800x800 size. + $alt = sprintf( + /* translators: %s: component type (plugin, theme, core, php, ...) */ + __( '%s icon', 'wpvulnerability' ), + (string) $type + ); + + return '' . esc_attr( $alt ) . ''; } /** diff --git a/wpvulnerability-run.php b/wpvulnerability-run.php index 147b2fa..1420a74 100644 --- a/wpvulnerability-run.php +++ b/wpvulnerability-run.php @@ -27,10 +27,10 @@ function wpvulnerability_add_settings_link( $links ) { // Determine the correct settings link based on the environment. if ( is_multisite() && is_network_admin() ) { // Network admin settings link for multisite. - $links[] = '' . __( 'Network Settings', 'wpvulnerability' ) . ''; + $links[] = '' . esc_html__( 'Network Settings', 'wpvulnerability' ) . ''; } elseif ( ! is_multisite() && is_admin() ) { // Standard settings link for single site. - $links[] = '' . __( 'Settings', 'wpvulnerability' ) . ''; + $links[] = '' . esc_html__( 'Settings', 'wpvulnerability' ) . ''; } } return $links; @@ -271,7 +271,10 @@ function wpvulnerability_initialize_plugin_data( $is_real_activation = false ) { // add_site_option does not support autoload control). $no_autoload_keys = array( 'wpvulnerability-plugins', + 'wpvulnerability-plugins-cache', + 'wpvulnerability-plugins-vulnerable', 'wpvulnerability-plugins-data', + 'wpvulnerability-plugins-data-cache', 'wpvulnerability-themes', 'wpvulnerability-core', 'wpvulnerability-php', @@ -351,6 +354,10 @@ function wpvulnerability_initialize_plugin_data( $is_real_activation = false ) { */ function wpvulnerability_activation() { wpvulnerability_initialize_plugin_data( true ); + + // init has already fired during the activation request, so the deferred + // scheduling in wpvulnerability-schedule.php cannot run here; schedule directly. + wpvulnerability_schedule_core_events(); } /** @@ -606,29 +613,46 @@ function wpvulnerability_uninstall() { } } - // Delete all stored log entries in batches to avoid timeouts. - $log_query_args = array( - 'post_type' => 'wpvulnerability_log', - 'fields' => 'ids', - 'post_status' => 'any', - 'posts_per_page' => 100, - 'orderby' => 'ID', - 'order' => 'ASC', - 'no_found_rows' => true, - 'update_post_meta_cache' => false, - 'update_post_term_cache' => false, - 'suppress_filters' => false, - ); - - while ( true ) { - $log_ids = get_posts( $log_query_args ); - - if ( empty( $log_ids ) ) { - break; + // Delete all stored log entries - both API request logs and shell audit + // logs - in batches to avoid timeouts. The post types may not be + // registered yet when this routine runs early (the admin reset executes + // during init, and uninstall.php does not process init): queries for + // unregistered post types silently return nothing, so registration is + // forced here first. + foreach ( + array( + 'wpvulnerability_log' => 'wpvulnerability_register_log_post_type', + 'wpv_shell_log' => 'wpvulnerability_register_shell_log_post_type', + ) + as $log_post_type => $log_register_callback + ) { + if ( ! post_type_exists( $log_post_type ) && function_exists( $log_register_callback ) ) { + call_user_func( $log_register_callback ); } - foreach ( $log_ids as $log_id ) { - wp_delete_post( (int) $log_id, true ); + $log_query_args = array( + 'post_type' => $log_post_type, + 'fields' => 'ids', + 'post_status' => 'any', + 'posts_per_page' => 100, + 'orderby' => 'ID', + 'order' => 'ASC', + 'no_found_rows' => true, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'suppress_filters' => false, + ); + + while ( true ) { + $log_ids = get_posts( $log_query_args ); + + if ( empty( $log_ids ) ) { + break; + } + + foreach ( $log_ids as $log_id ) { + wp_delete_post( (int) $log_id, true ); + } } } @@ -733,7 +757,7 @@ function wpvulnerability_counter_plugins() { global $menu; foreach ( $menu as $key => $value ) { if ( 'plugins.php' === $menu[ $key ][2] ) { - $menu[ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_plugins_total ) . ''; // phpcs:ignore + $menu[ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_plugins_total ) . ''; // phpcs:ignore break; } } @@ -838,7 +862,7 @@ function wpvulnerability_counter_core() { if ( isset( $submenu['index.php'] ) ) { foreach ( $submenu['index.php'] as $key => $value ) { if ( 'update-core.php' === $submenu['index.php'][ $key ][2] ) { - $submenu['index.php'][ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_core_total ) . ''; // phpcs:ignore + $submenu['index.php'][ $key ][0] .= ' ' . esc_html( (string) $wpvulnerability_core_total ) . ''; // phpcs:ignore break; } } diff --git a/wpvulnerability-schedule.php b/wpvulnerability-schedule.php index c409f92..5d62eb3 100644 --- a/wpvulnerability-schedule.php +++ b/wpvulnerability-schedule.php @@ -38,9 +38,8 @@ function wpvulnerability_add_every_six_hours( $schedules ) { } // Add weekly and daily schedules used by the notification cron. -// These must be registered in this file (always loaded pre-init) so that the -// on-load notification scheduling at the bottom of this file can resolve the -// 'weekly' schedule before notifications.php is loaded at init. +// Registered here because this file is always loaded, while the notification +// scheduling that resolves them runs on init for every request type. add_filter( 'cron_schedules', 'wpvulnerability_add_every_week' ); add_filter( 'cron_schedules', 'wpvulnerability_add_every_day' ); @@ -83,15 +82,10 @@ if ( is_multisite() && ! is_main_site() ) { wpvulnerability_clear_plugin_cron_hooks(); } -/** - * Schedule Automatic Vulnerability Database Update. - * If the 'wpvulnerability_update_database' event is not already scheduled, schedule it to run twice daily. - * - * @since 2.0.0 - * - * @return void - */ -wpvulnerability_schedule_core_events(); +// Schedule the automatic vulnerability database updates on init. Scheduling at +// file scope called wp_get_schedules() before init, firing other plugins' +// cron_schedules callbacks (and their __() calls) too early on WordPress 6.7+. +add_action( 'init', 'wpvulnerability_schedule_core_events', 20 ); // Hook the event to the function that updates the database. add_action( 'wpvulnerability_update_database', 'wpvulnerability_update_database_data' ); @@ -179,16 +173,35 @@ function wpvulnerability_schedule_notification_event( $config, $force = true ) { if ( ! is_multisite() || is_main_site() ) { $period_val = $config['period']; $period_str = is_scalar( $period_val ) ? (string) $period_val : ''; - $timestamp = wpvulnerability_get_next_notification_timestamp( $config ); + + // Whitelist the period: a corrupted value would make the event unschedulable. + if ( ! in_array( $period_str, array( 'daily', 'weekly' ), true ) ) { + return; + } + + $timestamp = wpvulnerability_get_next_notification_timestamp( $config ); wp_schedule_event( $timestamp, $period_str, 'wpvulnerability_notification' ); } } -$wpvulnerability_s_raw = is_multisite() ? get_site_option( 'wpvulnerability-config' ) : get_option( 'wpvulnerability-config' ); -$wpvulnerability_s = is_array( $wpvulnerability_s_raw ) ? $wpvulnerability_s_raw : array(); -wpvulnerability_schedule_notification_event( $wpvulnerability_s, false ); add_action( 'wpvulnerability_notification', 'wpvulnerability_execute_notification' ); // @phpstan-ignore return.void (function returns bool but action callbacks are not required to be void) -unset( $wpvulnerability_s ); + +/** + * Schedules the notification event on init. + * + * Runs at init so wp_get_schedules() is never called before translations can load. + * + * @since 5.1.3 + * + * @return void + */ +function wpvulnerability_schedule_notification_boot() { + $config_raw = is_multisite() ? get_site_option( 'wpvulnerability-config' ) : get_option( 'wpvulnerability-config' ); + $config = is_array( $config_raw ) ? $config_raw : array(); + + wpvulnerability_schedule_notification_event( $config, false ); +} +add_action( 'init', 'wpvulnerability_schedule_notification_boot', 20 ); /** * Returns the WPVulnerability cron hooks. diff --git a/wpvulnerability-sitehealth.php b/wpvulnerability-sitehealth.php index 1834667..84573fc 100644 --- a/wpvulnerability-sitehealth.php +++ b/wpvulnerability-sitehealth.php @@ -491,4 +491,10 @@ function wpvulnerability_tests( $tests ) { } // Adds the vulnerability tests to the site status tests. +// Site Health exists since WordPress 5.2. Core loads the WP_Site_Health class +// lazily (only inside site-health.php, health-check AJAX, and the dashboard +// widget), long after init, so class_exists() cannot be used here: it would +// always be false and the integration would never register. Registering +// unconditionally is safe on every version - below 5.2 the +// 'site_status_tests' filter simply never fires. add_filter( 'site_status_tests', 'wpvulnerability_tests' ); diff --git a/wpvulnerability-software.php b/wpvulnerability-software.php index 9242ca3..b57b54c 100644 --- a/wpvulnerability-software.php +++ b/wpvulnerability-software.php @@ -221,7 +221,7 @@ function wpvulnerability_get_installed( $software ) { // Return the JSON-encoded array of software data. $encoded = wp_json_encode( $data ); - return false !== $encoded ? $encoded : ''; + return false !== $encoded ? $encoded : '[]'; } /** diff --git a/wpvulnerability-themes.php b/wpvulnerability-themes.php index 540ad83..8610687 100644 --- a/wpvulnerability-themes.php +++ b/wpvulnerability-themes.php @@ -688,7 +688,8 @@ function wpvulnerability_themes_view( $views ) { } $raw_count = is_multisite() ? get_site_option( 'wpvulnerability-themes-vulnerable', '0' ) : get_option( 'wpvulnerability-themes-vulnerable', '0' ); - $wpvulnerability_themes_total = ( is_scalar( json_decode( is_string( $raw_count ) ? $raw_count : '0', true ) ) ? (int) json_decode( is_string( $raw_count ) ? $raw_count : '0', true ) : 0 ); + $decoded_count = json_decode( is_string( $raw_count ) ? $raw_count : '0', true ); + $wpvulnerability_themes_total = ( is_scalar( $decoded_count ) ? (int) $decoded_count : 0 ); if ( is_multisite() && is_network_admin() ) { $url = network_admin_url( 'themes.php?theme_status=vulnerable' ); diff --git a/wpvulnerability.php b/wpvulnerability.php index a69e90c..df03f02 100644 --- a/wpvulnerability.php +++ b/wpvulnerability.php @@ -3,9 +3,9 @@ * Plugin Name: WPVulnerability * Plugin URI: https://www.wpvulnerability.com/plugin/ * Description: Receive information about possible vulnerabilities in your WordPress from WordPress Vulnerability Database API. - * Requires at least: 5.6 + * Requires at least: 4.7 * Requires PHP: 7.0 - * Version: 5.1.2 + * Version: 5.1.6 * Author: ROBOTSTXT * Author URI: https://www.robotstxt.es/ * License: GPL-3.0-or-later @@ -16,7 +16,7 @@ * * @package WPVulnerability * - * @version 5.1.2 + * @version 5.1.6 */ defined( 'ABSPATH' ) || die( 'No script kiddies please!' ); @@ -24,7 +24,7 @@ defined( 'ABSPATH' ) || die( 'No script kiddies please!' ); /** * Set some constants that I can change in future versions. */ -define( 'WPVULNERABILITY_PLUGIN_VERSION', '5.1.2' ); +define( 'WPVULNERABILITY_PLUGIN_VERSION', '5.1.6' ); define( 'WPVULNERABILITY_API_HOST', 'https://www.wpvulnerability.net/' ); /** @@ -35,6 +35,14 @@ define( 'WPVULNERABILITY_PLUGIN_FILE', __FILE__ ); define( 'WPVULNERABILITY_PLUGIN_BASE', plugin_basename( __FILE__ ) ); define( 'WPVULNERABILITY_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); +/* + * WordPress core function polyfills (wp_date, wp_timezone, wp_doing_cron, ...). + * Loaded first so every module can call the core functions unconditionally. + * + * @since 5.1.3 + */ +require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-compat.php'; + // Handle front-end email opt-out requests early. if ( isset( $_GET['wpvulnerability_disable_email'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended // Load pluggable functions so wp_verify_nonce() is available. @@ -126,7 +134,8 @@ if ( require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-run.php'; register_activation_hook( WPVULNERABILITY_PLUGIN_FILE, 'wpvulnerability_activation' ); register_deactivation_hook( WPVULNERABILITY_PLUGIN_FILE, 'wpvulnerability_deactivation' ); - register_uninstall_hook( WPVULNERABILITY_PLUGIN_FILE, 'wpvulnerability_uninstall' ); + // Uninstall is handled by uninstall.php (it supersedes register_uninstall_hook + // and enforces the delete_on_uninstall opt-in); see docs/known-issues.md KI-3. add_action( 'init', 'wpvulnerability_plugin_init' ); }