PluginProbe
WPVulnerability / 5.1.2
WPVulnerability v5.1.2
5.1.6 5.1.2 5.1.1 5.0.1 5.0.0 trunk 0.1 0.2 1.0 1.0.1 1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 All 57 releases
wpvulnerability / wpvulnerability-run.php

wpvulnerability-run.php in WPVulnerability 5.1.2, at wpvulnerability-run.php

855 lines 29.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Running functions
4 *
5 * @package WPVulnerability
6 *
7 * @version 2.0.0
8 */
9
10 defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
11
12 /**
13 * Adds a settings link to the plugin row in the plugins list.
14 *
15 * This function conditionally adds either a network admin settings link for multisite
16 * or a standard settings link for single site installations.
17 *
18 * @since 3.5.0
19 *
20 * @param array<int|string, mixed> $links The links that appear in the plugin row.
21 *
22 * @return array<int|string, mixed> The modified array of links.
23 */
24 function wpvulnerability_add_settings_link( $links ) {
25 // Check if the user has the required capabilities to view the settings link.
26 if ( wpvulnerability_capabilities() ) {
27 // Determine the correct settings link based on the environment.
28 if ( is_multisite() && is_network_admin() ) {
29 // Network admin settings link for multisite.
30 $links[] = '<a href="' . network_admin_url( 'settings.php?page=wpvulnerability-options' ) . '">' . __( 'Network Settings', 'wpvulnerability' ) . '</a>';
31 } elseif ( ! is_multisite() && is_admin() ) {
32 // Standard settings link for single site.
33 $links[] = '<a href="' . get_admin_url( null, 'options-general.php?page=wpvulnerability-options' ) . '">' . __( 'Settings', 'wpvulnerability' ) . '</a>';
34 }
35 }
36 return $links;
37 }
38
39 // Hook the function to the appropriate filters.
40 if ( is_multisite() ) {
41 add_filter( 'network_admin_plugin_action_links_' . WPVULNERABILITY_PLUGIN_BASE, 'wpvulnerability_add_settings_link' );
42 } else {
43 add_filter( 'plugin_action_links_' . WPVULNERABILITY_PLUGIN_BASE, 'wpvulnerability_add_settings_link' );
44 }
45
46 /**
47 * Updates the plugin's vulnerability data.
48 *
49 * This function updates the vulnerability data for WordPress core, plugins, themes, PHP, Apache, nginx, MariaDB, and MySQL.
50 * It ensures that the required functions are available by including the necessary files.
51 * After updating the vulnerabilities, it flushes the WordPress cache.
52 *
53 * @since 2.0.0
54 *
55 * @return void
56 */
57 function wpvulnerability_update_database_data() {
58
59 // Ensure necessary files are included for core, plugins, and themes.
60 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-core.php';
61 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php';
62 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-themes.php';
63 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-software.php';
64
65 wpvulnerability_delete_transients();
66
67 // Update core, plugins, and themes vulnerabilities.
68 wpvulnerability_core_get_vulnerabilities_clean();
69 wpvulnerability_plugin_get_vulnerabilities_clean();
70 wpvulnerability_theme_get_vulnerabilities_clean();
71
72 // Array of software types to update.
73 $software_types = array( 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
74
75 // Update vulnerabilities for each software type.
76 foreach ( $software_types as $software ) {
77 wpvulnerability_get_vulnerabilities_clean( $software );
78 }
79
80 wpvulnerability_statistics_get();
81 }
82
83 /**
84 * Updates the plugin's vulnerability data if the cache has expired.
85 *
86 * This function checks if the cached vulnerability data for various components (core, plugins, themes, PHP, Apache, nginx, MariaDB, MySQL) has expired and updates it accordingly.
87 * It ensures that the required functions are available by including the necessary files.
88 * The function handles both multisite and single site installations.
89 *
90 * @since 3.0.0
91 *
92 * @return void
93 */
94 function wpvulnerability_expired_database_data() {
95
96 // Ensure necessary files are included for core, plugins, and themes.
97 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-core.php';
98 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php';
99 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-themes.php';
100 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-software.php';
101
102 // Current time for cache expiration comparison.
103 $cache_time = time();
104
105 // Check and update core, plugins, and themes vulnerabilities if cache has expired.
106 $components = array(
107 'core' => 'wpvulnerability-core-cache',
108 'plugin' => 'wpvulnerability-plugins-cache',
109 'theme' => 'wpvulnerability-themes-cache',
110 );
111
112 foreach ( $components as $component => $cache_key ) {
113 $cache_value = is_multisite() ? get_site_option( $cache_key ) : get_option( $cache_key );
114
115 if ( json_decode( is_string( $cache_value ) ? $cache_value : '' ) < $cache_time ) {
116 call_user_func( "wpvulnerability_{$component}_get_vulnerabilities_clean" );
117 }
118 }
119
120 // Array of software types to update.
121 $software_types = array( 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
122
123 // Ensure necessary files are included and update vulnerabilities for each software type.
124 foreach ( $software_types as $software ) {
125 if ( is_multisite() ) {
126 $site_opt = get_site_option( 'wpvulnerability-' . $software . '-cache' );
127 if ( json_decode( is_string( $site_opt ) ? $site_opt : '' ) < $cache_time ) {
128 wpvulnerability_get_vulnerabilities_clean( $software );
129 }
130 } else {
131 $opt = get_option( 'wpvulnerability-' . $software . '-cache' );
132 if ( json_decode( is_string( $opt ) ? $opt : '' ) < $cache_time ) {
133 wpvulnerability_get_vulnerabilities_clean( $software );
134 }
135 }
136 }
137
138 $statistics_cache_raw = is_multisite() ? get_site_option( 'wpvulnerability-statistics-cache' ) : get_option( 'wpvulnerability-statistics-cache' );
139
140 if ( is_numeric( $statistics_cache_raw ) ) {
141 $statistics_cache = (int) $statistics_cache_raw;
142 } elseif ( is_string( $statistics_cache_raw ) ) {
143 $decoded_cache = json_decode( $statistics_cache_raw );
144 $statistics_cache = is_numeric( $decoded_cache ) ? (int) $decoded_cache : 0;
145 } else {
146 $statistics_cache = 0;
147 }
148
149 if ( $statistics_cache < $cache_time ) {
150 wpvulnerability_statistics_get();
151 }
152
153 unset( $cache_time, $statistics_cache, $statistics_cache_raw );
154 }
155
156 /**
157 * Initializes persistent plugin data, ensures required options exist, and tracks
158 * initialization metadata so upgrades provision new defaults when needed.
159 *
160 * @since 4.1.2
161 *
162 * @param bool $is_real_activation Optional. Whether the routine runs during the activation hook. Default false.
163 *
164 * @return void
165 */
166 function wpvulnerability_initialize_plugin_data( $is_real_activation = false ) {
167 $is_multisite = is_multisite();
168 $config_key = $is_multisite ? 'get_site_option' : 'get_option';
169 $add_option = $is_multisite ? 'add_site_option' : 'add_option';
170 $update_option = $is_multisite ? 'update_site_option' : 'update_option';
171
172 $initialized_raw = $config_key( 'wpvulnerability_initialized' );
173 $initialized = array(
174 'timestamp' => 0,
175 'version' => '',
176 );
177
178 if ( is_array( $initialized_raw ) ) {
179 if ( isset( $initialized_raw['timestamp'] ) && is_numeric( $initialized_raw['timestamp'] ) ) {
180 $initialized['timestamp'] = (int) $initialized_raw['timestamp'];
181 }
182
183 if ( isset( $initialized_raw['version'] ) && is_string( $initialized_raw['version'] ) ) {
184 $initialized['version'] = $initialized_raw['version'];
185 }
186 } elseif ( is_numeric( $initialized_raw ) ) {
187 $initialized['timestamp'] = (int) $initialized_raw;
188 }
189
190 $needs_upgrade = version_compare( (string) $initialized['version'], WPVULNERABILITY_PLUGIN_VERSION, '<' );
191
192 if ( ! $is_real_activation && ! empty( $initialized_raw ) && ! $needs_upgrade ) {
193 return;
194 }
195
196 if ( $is_real_activation ) {
197 wpvulnerability_delete_transients();
198 }
199
200 // Add wpvulnerability-config option if it does not exist.
201 if ( ! $config_key( 'wpvulnerability-config' ) ) {
202 $default_config = array(
203 'emails' => get_bloginfo( 'admin_email' ),
204 'period' => 'weekly',
205 'day' => 'monday',
206 'hour' => 0,
207 'minute' => 0,
208 'cache' => 12,
209 'log_retention' => 0,
210 'delete_on_uninstall' => 0,
211 'notify' => array(
212 'email' => 'y',
213 'slack' => 'n',
214 'teams' => 'n',
215 ),
216 'slack_webhook' => '',
217 'teams_webhook' => '',
218 );
219 $add_option( 'wpvulnerability-config', $default_config );
220 }
221
222 // Add other options if they do not exist.
223 $options = array(
224 'wpvulnerability-plugins' => '',
225 'wpvulnerability-plugins-cache' => 0,
226 'wpvulnerability-plugins-vulnerable' => 0,
227 'wpvulnerability-plugins-data' => '',
228 'wpvulnerability-plugins-data-cache' => 0,
229 'wpvulnerability-themes' => '',
230 'wpvulnerability-themes-cache' => 0,
231 'wpvulnerability-themes-vulnerable' => 0,
232 'wpvulnerability-core' => '',
233 'wpvulnerability-core-cache' => 0,
234 'wpvulnerability-core-vulnerable' => 0,
235 'wpvulnerability-php' => '',
236 'wpvulnerability-php-cache' => 0,
237 'wpvulnerability-php-vulnerable' => 0,
238 'wpvulnerability-apache' => '',
239 'wpvulnerability-apache-cache' => 0,
240 'wpvulnerability-apache-vulnerable' => 0,
241 'wpvulnerability-nginx' => '',
242 'wpvulnerability-nginx-cache' => 0,
243 'wpvulnerability-nginx-vulnerable' => 0,
244 'wpvulnerability-mariadb' => '',
245 'wpvulnerability-mariadb-cache' => 0,
246 'wpvulnerability-mariadb-vulnerable' => 0,
247 'wpvulnerability-mysql' => '',
248 'wpvulnerability-mysql-cache' => 0,
249 'wpvulnerability-mysql-vulnerable' => 0,
250 'wpvulnerability-imagemagick' => '',
251 'wpvulnerability-imagemagick-cache' => 0,
252 'wpvulnerability-imagemagick-vulnerable' => 0,
253 'wpvulnerability-curl' => '',
254 'wpvulnerability-curl-cache' => 0,
255 'wpvulnerability-curl-vulnerable' => 0,
256 'wpvulnerability-memcached' => '',
257 'wpvulnerability-memcached-cache' => 0,
258 'wpvulnerability-memcached-vulnerable' => 0,
259 'wpvulnerability-redis' => '',
260 'wpvulnerability-redis-cache' => 0,
261 'wpvulnerability-redis-vulnerable' => 0,
262 'wpvulnerability-sqlite' => '',
263 'wpvulnerability-sqlite-cache' => 0,
264 'wpvulnerability-sqlite-vulnerable' => 0,
265 'wpvulnerability-statistics' => '',
266 'wpvulnerability-statistics-cache' => 0,
267 'wpvulnerability_initialized' => 0,
268 );
269
270 // Large data blobs should not autoload on every WP request (single-site only;
271 // add_site_option does not support autoload control).
272 $no_autoload_keys = array(
273 'wpvulnerability-plugins',
274 'wpvulnerability-plugins-data',
275 'wpvulnerability-themes',
276 'wpvulnerability-core',
277 'wpvulnerability-php',
278 'wpvulnerability-apache',
279 'wpvulnerability-nginx',
280 'wpvulnerability-mariadb',
281 'wpvulnerability-mysql',
282 'wpvulnerability-imagemagick',
283 'wpvulnerability-curl',
284 'wpvulnerability-memcached',
285 'wpvulnerability-redis',
286 'wpvulnerability-sqlite',
287 'wpvulnerability-statistics',
288 );
289
290 foreach ( $options as $key => $value ) {
291 if ( ! $config_key( $key ) ) {
292 if ( ! $is_multisite && in_array( $key, $no_autoload_keys, true ) ) {
293 add_option( $key, $value, '', false );
294 } else {
295 $add_option( $key, $value );
296 }
297 }
298 }
299
300 // Add wpvulnerability-analyze option if it does not exist.
301 if ( ! $config_key( 'wpvulnerability-analyze' ) ) {
302 $default_analyze = array(
303 'core' => 0,
304 'plugins' => 0,
305 'themes' => 0,
306 'php' => 0,
307 'apache' => 0,
308 'nginx' => 0,
309 'mariadb' => 0,
310 'mysql' => 0,
311 'imagemagick' => 0,
312 'curl' => 0,
313 'memcached' => 0,
314 'redis' => 0,
315 'sqlite' => 0,
316 );
317
318 foreach ( array_keys( $default_analyze ) as $component ) {
319 $constant = 'WPVULNERABILITY_HIDE_' . strtoupper( (string) $component );
320 if ( defined( $constant ) && constant( $constant ) ) {
321 $default_analyze[ $component ] = 1;
322 }
323 }
324 $current_option_raw = $config_key( 'wpvulnerability-analyze' );
325 $current_option = is_array( $current_option_raw ) ? $current_option_raw : false;
326
327 if ( false === $current_option ) {
328 $add_option( 'wpvulnerability-analyze', $default_analyze );
329 } else {
330 $updated_option = array_merge( $default_analyze, $current_option );
331 $update_option( 'wpvulnerability-analyze', $updated_option );
332 }
333 }
334
335 $update_option(
336 'wpvulnerability_initialized',
337 array(
338 'timestamp' => (int) time(),
339 'version' => WPVULNERABILITY_PLUGIN_VERSION,
340 )
341 );
342 }
343
344 /**
345 * Callback function for when the plugin is activated.
346 * Adds plugin data options if they are not already created.
347 *
348 * @since 2.0.0
349 *
350 * @return void
351 */
352 function wpvulnerability_activation() {
353 wpvulnerability_initialize_plugin_data( true );
354 }
355
356 /**
357 * Callback function to run when the plugin is deactivated.
358 * Deletes options and removes scheduled wp-cron jobs.
359 *
360 * @since 2.0.0
361 *
362 * @return void
363 */
364 function wpvulnerability_deactivation() {
365 $options = array(
366 'wpvulnerability_settings',
367 'wpvulnerability-data',
368 'wpvulnerability-themes',
369 'wpvulnerability-themes-cache',
370 'wpvulnerability-themes-vulnerable',
371 'wpvulnerability-plugins',
372 'wpvulnerability-plugins-cache',
373 'wpvulnerability-plugins-vulnerable',
374 'wpvulnerability-core',
375 'wpvulnerability-core-cache',
376 'wpvulnerability-core-vulnerable',
377 'wpvulnerability-php',
378 'wpvulnerability-php-cache',
379 'wpvulnerability-php-vulnerable',
380 'wpvulnerability-apache',
381 'wpvulnerability-apache-cache',
382 'wpvulnerability-apache-vulnerable',
383 'wpvulnerability-nginx',
384 'wpvulnerability-nginx-cache',
385 'wpvulnerability-nginx-vulnerable',
386 'wpvulnerability-mariadb',
387 'wpvulnerability-mariadb-cache',
388 'wpvulnerability-mariadb-vulnerable',
389 'wpvulnerability-mysql',
390 'wpvulnerability-mysql-cache',
391 'wpvulnerability-mysql-vulnerable',
392 'wpvulnerability-imagemagick',
393 'wpvulnerability-imagemagick-cache',
394 'wpvulnerability-imagemagick-vulnerable',
395 'wpvulnerability-curl',
396 'wpvulnerability-curl-cache',
397 'wpvulnerability-curl-vulnerable',
398 'wpvulnerability-memcached',
399 'wpvulnerability-memcached-cache',
400 'wpvulnerability-memcached-vulnerable',
401 'wpvulnerability-redis',
402 'wpvulnerability-redis-cache',
403 'wpvulnerability-redis-vulnerable',
404 'wpvulnerability-sqlite',
405 'wpvulnerability-sqlite-cache',
406 'wpvulnerability-sqlite-vulnerable',
407 'wpvulnerability-statistics',
408 'wpvulnerability-statistics-cache',
409 'wpvulnerability_initialized',
410 );
411
412 // Delete options based on the installation type.
413 $delete_option_func = is_multisite() ? 'delete_site_option' : 'delete_option';
414 foreach ( $options as $option ) {
415 $delete_option_func( $option );
416 }
417
418 wpvulnerability_delete_transients();
419
420 // Unschedule and remove scheduled wp-cron jobs.
421 $cron_jobs = array(
422 'wpvulnerability_notification',
423 'wpvulnerability_update_database',
424 'wpvulnerability_pull_db_data_event',
425 'wpvulnerability_cleanup_logs',
426 );
427 foreach ( $cron_jobs as $job ) {
428 $next_ts = wp_next_scheduled( $job );
429 if ( false !== $next_ts ) {
430 wp_unschedule_event( $next_ts, $job );
431 }
432 wp_clear_scheduled_hook( $job );
433 }
434 }
435
436 /**
437 * Deletes all transients that start with 'wpvulnerability_'.
438 *
439 * @since 3.5.0
440 *
441 * @return void
442 */
443 function wpvulnerability_delete_transients() {
444 global $wpdb;
445
446 // Determine if the site is multisite.
447 $is_multisite = is_multisite();
448
449 // Define the prefix according to whether it is multisite or not.
450 $transient_prefix = $is_multisite ? '_site_transient_wpvulnerability_' : '_transient_wpvulnerability_';
451
452 // Prepare the LIKE pattern securely.
453 $like_pattern = $wpdb->esc_like( $transient_prefix ) . '%';
454
455 // Try to get transients from cache first.
456 $cache_key = 'wpvulnerability_transients_list';
457 $transients = wp_cache_get( $cache_key );
458
459 if ( false === $transients ) {
460 // If cache is empty, query the database for matching transients.
461 if ( $is_multisite ) {
462 $transients = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
463 $wpdb->prepare(
464 "SELECT meta_key FROM {$wpdb->sitemeta} WHERE meta_key LIKE %s AND site_id = %d",
465 $like_pattern,
466 get_current_network_id()
467 )
468 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
469 } else {
470 $transients = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
471 $wpdb->prepare(
472 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
473 $like_pattern
474 )
475 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
476 }
477
478 // Store the result in cache for future use.
479 wp_cache_set( $cache_key, $transients, '', HOUR_IN_SECONDS );
480 }
481
482 // If no transients are found, exit early.
483 if ( empty( $transients ) ) {
484 return;
485 }
486
487 foreach ( $transients as $transient ) {
488 if ( $is_multisite ) {
489 // For multisite, delete using delete_site_transient.
490 $transient_name = str_replace( '_site_transient_', '', $transient );
491 delete_site_transient( $transient_name );
492 } else {
493 // For single sites, delete using delete_transient.
494 $transient_name = str_replace( '_transient_', '', $transient );
495 delete_transient( $transient_name );
496 }
497 }
498
499 // Optionally clear the cache after deletion.
500 wp_cache_delete( $cache_key );
501 }
502
503 /**
504 * Reset the plugin data to defaults and repopulate from the API.
505 *
506 * This routine mimics a full uninstall by deleting options, logs, and cron jobs,
507 * then provisions default settings, reschedules events, and reloads data.
508 *
509 * @since 4.3.0
510 *
511 * @return void
512 */
513 function wpvulnerability_reset_plugin_data() {
514 wpvulnerability_uninstall();
515
516 wpvulnerability_initialize_plugin_data( true );
517
518 if ( function_exists( 'wpvulnerability_schedule_core_events' ) ) {
519 wpvulnerability_schedule_core_events();
520 }
521
522 $config_raw = is_multisite() ? get_site_option( 'wpvulnerability-config', array() ) : get_option( 'wpvulnerability-config', array() );
523 $config = is_array( $config_raw ) ? $config_raw : array();
524
525 if ( function_exists( 'wpvulnerability_schedule_notification_event' ) ) {
526 wpvulnerability_schedule_notification_event( $config );
527 }
528
529 wpvulnerability_update_database_data();
530 }
531
532 /**
533 * Callback function to run when the plugin is uninstalled.
534 * Deletes options and removes scheduled wp-cron jobs.
535 *
536 * @since 3.0.0
537 *
538 * @return void
539 */
540 function wpvulnerability_uninstall() {
541 // Delete deprecated options.
542 delete_option( 'wpvulnerability_settings' );
543 delete_option( 'wpvulnerability-data' );
544 if ( function_exists( 'delete_site_option' ) ) {
545 delete_site_option( 'wpvulnerability_settings' );
546 delete_site_option( 'wpvulnerability-data' );
547 }
548
549 // Options to delete for both single site and multisite.
550 $options = array(
551 'wpvulnerability-themes',
552 'wpvulnerability-themes-cache',
553 'wpvulnerability-themes-vulnerable',
554 'wpvulnerability-themes-signature',
555 'wpvulnerability-plugins',
556 'wpvulnerability-plugins-cache',
557 'wpvulnerability-plugins-vulnerable',
558 'wpvulnerability-plugins-signature',
559 'wpvulnerability-plugins-data',
560 'wpvulnerability-plugins-cache-data',
561 'wpvulnerability-plugins-data-cache',
562 'wpvulnerability-core',
563 'wpvulnerability-core-cache',
564 'wpvulnerability-core-vulnerable',
565 'wpvulnerability-core-version',
566 'wpvulnerability-php',
567 'wpvulnerability-php-cache',
568 'wpvulnerability-php-vulnerable',
569 'wpvulnerability-apache',
570 'wpvulnerability-apache-cache',
571 'wpvulnerability-apache-vulnerable',
572 'wpvulnerability-nginx',
573 'wpvulnerability-nginx-cache',
574 'wpvulnerability-nginx-vulnerable',
575 'wpvulnerability-mariadb',
576 'wpvulnerability-mariadb-cache',
577 'wpvulnerability-mariadb-vulnerable',
578 'wpvulnerability-mysql',
579 'wpvulnerability-mysql-cache',
580 'wpvulnerability-mysql-vulnerable',
581 'wpvulnerability-imagemagick',
582 'wpvulnerability-imagemagick-cache',
583 'wpvulnerability-imagemagick-vulnerable',
584 'wpvulnerability-curl',
585 'wpvulnerability-curl-cache',
586 'wpvulnerability-curl-vulnerable',
587 'wpvulnerability-memcached',
588 'wpvulnerability-memcached-cache',
589 'wpvulnerability-memcached-vulnerable',
590 'wpvulnerability-redis',
591 'wpvulnerability-redis-cache',
592 'wpvulnerability-redis-vulnerable',
593 'wpvulnerability-sqlite',
594 'wpvulnerability-sqlite-cache',
595 'wpvulnerability-sqlite-vulnerable',
596 'wpvulnerability-statistics',
597 'wpvulnerability-statistics-cache',
598 'wpvulnerability_initialized',
599 'wpvulnerability-analyze',
600 );
601
602 foreach ( $options as $option ) {
603 delete_option( $option );
604 if ( function_exists( 'delete_site_option' ) ) {
605 delete_site_option( $option );
606 }
607 }
608
609 // Delete all stored log entries in batches to avoid timeouts.
610 $log_query_args = array(
611 'post_type' => 'wpvulnerability_log',
612 'fields' => 'ids',
613 'post_status' => 'any',
614 'posts_per_page' => 100,
615 'orderby' => 'ID',
616 'order' => 'ASC',
617 'no_found_rows' => true,
618 'update_post_meta_cache' => false,
619 'update_post_term_cache' => false,
620 'suppress_filters' => false,
621 );
622
623 while ( true ) {
624 $log_ids = get_posts( $log_query_args );
625
626 if ( empty( $log_ids ) ) {
627 break;
628 }
629
630 foreach ( $log_ids as $log_id ) {
631 wp_delete_post( (int) $log_id, true );
632 }
633 }
634
635 // Delete config data.
636 delete_option( 'wpvulnerability-config' );
637 if ( function_exists( 'delete_site_option' ) ) {
638 delete_site_option( 'wpvulnerability-config' );
639 }
640
641 wpvulnerability_delete_transients();
642
643 // Unschedule and remove scheduled wp-cron jobs.
644 $cron_jobs = array(
645 'wpvulnerability_notification',
646 'wpvulnerability_update_database',
647 'wpvulnerability_pull_db_data_event',
648 'wpvulnerability_cleanup_logs',
649 );
650 foreach ( $cron_jobs as $job ) {
651 $next_ts = wp_next_scheduled( $job );
652 if ( false !== $next_ts ) {
653 wp_unschedule_event( $next_ts, $job );
654 }
655 wp_clear_scheduled_hook( $job );
656 }
657 }
658
659 /**
660 * Filters and returns the WPVulnerability analysis setting for a given type.
661 *
662 * This function retrieves the WPVulnerability analysis settings, either from
663 * the single site or the multisite network, depending on the WordPress setup.
664 * It returns false if the specified type ('core', 'plugins', 'themes',
665 * 'php', 'apache', 'nginx', 'mariadb', 'mysql') is set. If the type is not set or is invalid, it returns true.
666 *
667 * @since 3.3.0
668 *
669 * @param string $type The type of analysis setting to retrieve ('core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mariadb', 'mysql').
670 *
671 * @return bool False if the specified type is set, true if not set or invalid.
672 */
673 function wpvulnerability_analyze_filter( $type ) {
674 // Retrieve the analysis settings based on the WordPress setup.
675 $raw_analyze = is_multisite() ? get_site_option( 'wpvulnerability-analyze', array() ) : get_option( 'wpvulnerability-analyze', array() );
676 $wpvulnerability_analyze = is_array( $raw_analyze ) ? $raw_analyze : array();
677
678 // Define the valid types for analysis.
679 $valid_types = array( 'core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
680
681 if ( in_array( $type, $valid_types, true ) ) {
682 $constant = 'WPVULNERABILITY_HIDE_' . strtoupper( (string) $type );
683 if ( defined( $constant ) && constant( $constant ) ) {
684 return false;
685 }
686
687 $type_val = $wpvulnerability_analyze[ $type ] ?? 0;
688 return ! ( is_numeric( $type_val ) && (int) $type_val );
689 }
690
691 return true; // Return true for invalid types.
692 }
693
694 /**
695 * Clean the cache after an update.
696 *
697 * This function is triggered after a plugin or theme update to clean the cache
698 * and refresh the vulnerability data.
699 *
700 * @since 2.0.0
701 *
702 * @return void
703 */
704 add_action( 'upgrader_process_complete', 'wpvulnerability_update_database_data', 10, 0 );
705
706 /**
707 * Adds a notification count to the Plugins menu item in the WordPress admin if there are vulnerable plugins.
708 *
709 * This function retrieves the number of vulnerable plugins from the cache, either from a single site
710 * or a multisite setup, and displays the count in the WordPress admin menu next to the Plugins menu item.
711 * The count is shown with a gold background (#FFD700) and black text.
712 *
713 * @since 3.3.5
714 *
715 * @return void
716 */
717 function wpvulnerability_counter_plugins() {
718
719 if ( ! wpvulnerability_analyze_filter( 'plugins' ) ) {
720 return; // Skip if plugin analysis is disabled.
721 }
722
723 // Retrieve the number of vulnerable plugins from cache.
724 $wpvulnerability_plugins_count = is_multisite() && is_network_admin()
725 ? get_site_option( 'wpvulnerability-plugins-vulnerable' )
726 : get_option( 'wpvulnerability-plugins-vulnerable' );
727
728 // Decode the count from JSON, default to 0 if not set.
729 $wpvulnerability_plugins_decoded = is_string( $wpvulnerability_plugins_count ) ? json_decode( $wpvulnerability_plugins_count ) : 0;
730 $wpvulnerability_plugins_total = is_numeric( $wpvulnerability_plugins_decoded ) ? (int) $wpvulnerability_plugins_decoded : 0;
731
732 if ( $wpvulnerability_plugins_total > 0 ) {
733 global $menu;
734 foreach ( $menu as $key => $value ) {
735 if ( 'plugins.php' === $menu[ $key ][2] ) {
736 $menu[ $key ][0] .= ' <span class="update-plugins" style="background-color: #FFD700; color: #000000;"><span class="update-count" title="' . __( 'Vulnerabilities', 'wpvulnerability' ) . '">' . esc_html( (string) $wpvulnerability_plugins_total ) . '</span></span>'; // phpcs:ignore
737 break;
738 }
739 }
740 }
741 }
742
743 // Hook into the appropriate admin menu action based on the site type.
744 if ( is_multisite() && is_network_admin() ) {
745 add_action( 'network_admin_menu', 'wpvulnerability_counter_plugins' );
746 } elseif ( ! is_multisite() ) {
747 add_action( 'admin_menu', 'wpvulnerability_counter_plugins' );
748 }
749
750 /**
751 * Adds a notification count to the Themes menu item in the WordPress admin if there are vulnerable themes.
752 *
753 * This function retrieves the number of vulnerable themes from the cache, either from a single site
754 * or a multisite setup, and displays the count in the WordPress admin menu next to the Themes menu item.
755 * The count is displayed with a gold background (#FFD700) and black text.
756 *
757 * @since 3.3.5
758 *
759 * @return void
760 */
761 function wpvulnerability_counter_themes() {
762
763 if ( ! wpvulnerability_analyze_filter( 'themes' ) ) {
764 return; // Skip if theme analysis is disabled.
765 }
766
767 // Retrieve the number of theme vulnerabilities from cache.
768 $wpvulnerability_themes_count = ( is_multisite() && is_network_admin() )
769 ? get_site_option( 'wpvulnerability-themes-vulnerable' )
770 : get_option( 'wpvulnerability-themes-vulnerable' );
771
772 // Decode the count from JSON, default to 0 if not set.
773 $wpvulnerability_themes_decoded = is_string( $wpvulnerability_themes_count ) ? json_decode( $wpvulnerability_themes_count ) : 0;
774 $wpvulnerability_themes_total = is_numeric( $wpvulnerability_themes_decoded ) ? (int) $wpvulnerability_themes_decoded : 0;
775
776 if ( $wpvulnerability_themes_total > 0 ) {
777
778 // Check if we are in a multisite setup or not.
779 if ( ! is_multisite() ) {
780 global $submenu;
781
782 // Check if the submenu for themes exists.
783 if ( isset( $submenu['themes.php'] ) ) {
784 foreach ( $submenu['themes.php'] as $key => $value ) {
785 if ( 'themes.php' === $submenu['themes.php'][ $key ][2] ) {
786 $submenu['themes.php'][ $key ][0] .= ' <span class="update-plugins" style="background-color: #FFD700; color: #000000;"><span class="update-count" title="' . esc_html__( 'Vulnerabilities', 'wpvulnerability' ) . '">' . esc_html( (string) $wpvulnerability_themes_total ) . '</span></span>'; // phpcs:ignore
787 break;
788 }
789 }
790 }
791 } elseif ( is_network_admin() ) {
792 global $menu;
793
794 foreach ( $menu as $key => $value ) {
795 if ( 'themes.php' === $menu[ $key ][2] ) {
796 $menu[ $key ][0] .= ' <span class="update-plugins" style="background-color: #FFD700; color: #000000;"><span class="update-count" title="' . esc_html__( 'Vulnerabilities', 'wpvulnerability' ) . '">' . esc_html( (string) $wpvulnerability_themes_total ) . '</span></span>'; // phpcs:ignore
797 break;
798 }
799 }
800 }
801 }
802 }
803
804 // Hook into the appropriate admin menu action based on the site type.
805 if ( is_multisite() && is_network_admin() ) {
806 add_action( 'network_admin_menu', 'wpvulnerability_counter_themes' );
807 } elseif ( ! is_multisite() ) {
808 add_action( 'admin_menu', 'wpvulnerability_counter_themes' );
809 }
810
811 /**
812 * Adds a notification count to the Updates submenu item under Dashboard in the WordPress admin if there are core updates.
813 *
814 * This function checks for core updates and then displays the count in the WordPress admin submenu
815 * next to the Updates menu item with a gold background (#FFD700) and black text.
816 *
817 * @since 3.3.5
818 *
819 * @return void
820 */
821 function wpvulnerability_counter_core() {
822
823 if ( ! wpvulnerability_analyze_filter( 'core' ) ) {
824 return; // Skip if core analysis is disabled.
825 }
826
827 // Retrieve the number of core vulnerabilities from cache.
828 $wpvulnerability_core_count = is_multisite() && is_network_admin()
829 ? get_site_option( 'wpvulnerability-core-vulnerable' )
830 : get_option( 'wpvulnerability-core-vulnerable' );
831
832 // Decode the count from JSON, default to 0 if not set.
833 $wpvulnerability_core_decoded = is_string( $wpvulnerability_core_count ) ? json_decode( $wpvulnerability_core_count ) : 0;
834 $wpvulnerability_core_total = is_numeric( $wpvulnerability_core_decoded ) ? (int) $wpvulnerability_core_decoded : 0;
835
836 if ( $wpvulnerability_core_total > 0 ) {
837 global $submenu;
838 if ( isset( $submenu['index.php'] ) ) {
839 foreach ( $submenu['index.php'] as $key => $value ) {
840 if ( 'update-core.php' === $submenu['index.php'][ $key ][2] ) {
841 $submenu['index.php'][ $key ][0] .= ' <span class="update-plugins" style="background-color: #FFD700; color: #000000;"><span class="update-count" title="' . __( 'Vulnerabilities', 'wpvulnerability' ) . '">' . esc_html( (string) $wpvulnerability_core_total ) . '</span></span>'; // phpcs:ignore
842 break;
843 }
844 }
845 }
846 }
847 }
848
849 // Hook into the appropriate admin menu action based on the site type.
850 if ( is_multisite() && is_network_admin() ) {
851 add_action( 'network_admin_menu', 'wpvulnerability_counter_core' );
852 } elseif ( ! is_multisite() ) {
853 add_action( 'admin_menu', 'wpvulnerability_counter_core' );
854 }
855