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-admin.php

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

4,031 lines 164.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Process functions
4 *
5 * @package WPVulnerability
6 *
7 * @version 2.0.0
8 */
9
10 defined( 'ABSPATH' ) || die( 'No script kiddies please!' );
11
12 /**
13 * Load the settings to be available always.
14 *
15 * @since 2.0.0
16 *
17 * @return array|false An array containing the WPVulnerability settings if they exist, or false if they don't.
18 */
19 $wpvulnerability_settings = get_option( 'wpvulnerability-config' );
20 $wpvulnerability_analyze = get_option( 'wpvulnerability-analyze' );
21
22 /**
23 * Enqueues the WPVulnerability admin CSS file on WPVulnerability admin pages.
24 *
25 * @since 2.0.0
26 *
27 * @return void
28 */
29 function wpvulnerability_admin_enqueue_scripts() {
30 wp_enqueue_style(
31 'wpvulnerability-admin',
32 WPVULNERABILITY_PLUGIN_URL . 'assets/admin.css',
33 array(),
34 WPVULNERABILITY_PLUGIN_VERSION
35 );
36
37 wp_enqueue_script(
38 'wpvulnerability-admin-js',
39 WPVULNERABILITY_PLUGIN_URL . 'assets/admin.js',
40 array( 'jquery' ),
41 WPVULNERABILITY_PLUGIN_VERSION,
42 true
43 );
44 }
45 add_action( 'admin_enqueue_scripts', 'wpvulnerability_admin_enqueue_scripts' );
46
47 /**
48 * Reset the data.
49 *
50 * This function checks for a reset request and, if valid, calls the function to update the database data.
51 *
52 * @since 3.0.0
53 *
54 * @return void
55 */
56 if ( isset( $_POST['wpvulnerability_reset'] ) && check_admin_referer( 'wpvulnerability_reset_action', 'wpvulnerability_reset_nonce' ) ) {
57
58 if ( current_user_can( 'manage_options' ) ) {
59 // Calls the reset function.
60 wpvulnerability_update_database_data();
61
62 // Set a transient message for success.
63 set_transient( 'wpvulnerability_message_manual_success', __( 'Data from source has been reloaded.', 'wpvulnerability' ), 10 );
64 } else {
65 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to reload data.', 'wpvulnerability' ), 10 );
66 }
67 }
68
69 /**
70 * Send a test email.
71 *
72 * This code checks for a request to send a test email and executes the notification function.
73 *
74 * @since 3.0.0
75 *
76 * @return void
77 */
78 if ( isset( $_POST['wpvulnerability_email'] ) && check_admin_referer( 'wpvulnerability_email_action', 'wpvulnerability_email_nonce' ) ) {
79
80 // Include necessary files if the notification function is not already defined.
81 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-core.php';
82 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php';
83 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-themes.php';
84 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-software.php';
85 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-process.php';
86 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-notifications.php';
87
88 // Calls the notifications function, forced.
89 if ( current_user_can( 'manage_options' ) ) {
90 $wpvulnerability_wpmail = wpvulnerability_execute_notification( true );
91
92 if ( $wpvulnerability_wpmail ) {
93 set_transient( 'wpvulnerability_message_manual_success', __( 'Test email has been sent.', 'wpvulnerability' ), 10 );
94 } else {
95 set_transient( 'wpvulnerability_message_manual_error', __( 'Test email has failed. Please, check your email settings.', 'wpvulnerability' ), 10 );
96 }
97 } else {
98 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to send test emails.', 'wpvulnerability' ), 10 );
99 }
100 }
101
102 /**
103 * Repairs scheduled cron events for the current site.
104 *
105 * @since 4.3.0
106 *
107 * @return void
108 */
109 if ( isset( $_POST['wpvulnerability_repair_cron'] ) && check_admin_referer( 'wpvulnerability_cron_action', 'wpvulnerability_cron_nonce' ) ) {
110 if ( current_user_can( 'manage_options' ) ) {
111 $wpvulnerability_cron_config = is_array( $wpvulnerability_settings ) ? $wpvulnerability_settings : array();
112 wpvulnerability_repair_cron_events( $wpvulnerability_cron_config );
113 set_transient( 'wpvulnerability_message_manual_success', __( 'WPVulnerability cron events have been repaired.', 'wpvulnerability' ), 10 );
114 } else {
115 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to repair cron events.', 'wpvulnerability' ), 10 );
116 }
117 }
118
119 /**
120 * Deletes all stored API logs when requested.
121 *
122 * @since 4.3.0
123 */
124 if ( isset( $_POST['wpvulnerability_delete_logs'] ) && check_admin_referer( 'wpvulnerability_delete_logs_action', 'wpvulnerability_delete_logs_nonce' ) ) {
125 if ( current_user_can( 'manage_options' ) ) {
126 wpvulnerability_delete_all_logs();
127 set_transient( 'wpvulnerability_message_manual_success', __( 'All logs have been deleted.', 'wpvulnerability' ), 10 );
128 } else {
129 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to delete logs.', 'wpvulnerability' ), 10 );
130 }
131 }
132
133 /**
134 * Fully resets plugin data, settings, and cached API content.
135 *
136 * @since 4.3.0
137 */
138 if ( isset( $_POST['wpvulnerability_delete_on_uninstall'] ) && check_admin_referer( 'wpvulnerability_uninstall_action', 'wpvulnerability_uninstall_nonce' ) ) {
139 if ( current_user_can( 'manage_options' ) ) {
140 $wpvulnerability_settings = get_option( 'wpvulnerability-config', array() );
141 if ( ! is_array( $wpvulnerability_settings ) ) {
142 $wpvulnerability_settings = array();
143 }
144 $wpvulnerability_settings['delete_on_uninstall'] = isset( $_POST['delete_on_uninstall'] ) ? 1 : 0;
145 update_option( 'wpvulnerability-config', $wpvulnerability_settings );
146 add_settings_error( 'wpvulnerability-tools', 'uninstall-saved', __( 'Uninstall preference saved.', 'wpvulnerability' ), 'success' );
147 }
148 }
149
150 if ( isset( $_POST['wpvulnerability_full_reset'] ) && check_admin_referer( 'wpvulnerability_full_reset_action', 'wpvulnerability_full_reset_nonce' ) ) {
151 if ( current_user_can( 'manage_options' ) ) {
152 wpvulnerability_reset_plugin_data();
153 set_transient( 'wpvulnerability_message_manual_success', __( 'WPVulnerability has been reset to defaults and reloaded.', 'wpvulnerability' ), 10 );
154 } else {
155 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to reset WPVulnerability.', 'wpvulnerability' ), 10 );
156 }
157 }
158
159 /**
160 * Handles debug action: Clear all caches.
161 *
162 * @since 4.3.0
163 */
164 if ( isset( $_POST['wpvulnerability_debug_clear_caches'] ) && check_admin_referer( 'wpvulnerability_debug_action', 'wpvulnerability_debug_nonce' ) ) {
165 if ( current_user_can( 'manage_options' ) ) {
166 if ( ! function_exists( 'wpvulnerability_debug_clear_all_caches' ) ) {
167 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-debug.php';
168 }
169 wpvulnerability_debug_clear_all_caches();
170 set_transient( 'wpvulnerability_message_manual_success', __( 'All caches have been cleared.', 'wpvulnerability' ), 10 );
171 } else {
172 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to clear caches.', 'wpvulnerability' ), 10 );
173 }
174 }
175
176 /**
177 * Handles debug action: Reset signatures.
178 *
179 * @since 4.3.0
180 */
181 if ( isset( $_POST['wpvulnerability_debug_reset_signatures'] ) && check_admin_referer( 'wpvulnerability_debug_action', 'wpvulnerability_debug_nonce' ) ) {
182 if ( current_user_can( 'manage_options' ) ) {
183 if ( ! function_exists( 'wpvulnerability_debug_reset_signatures' ) ) {
184 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-debug.php';
185 }
186 wpvulnerability_debug_reset_signatures();
187 set_transient( 'wpvulnerability_message_manual_success', __( 'Plugin and theme signatures have been reset.', 'wpvulnerability' ), 10 );
188 } else {
189 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to reset signatures.', 'wpvulnerability' ), 10 );
190 }
191 }
192
193 /**
194 * Handles debug action: Export debug info.
195 *
196 * @since 4.3.0
197 */
198 if ( isset( $_POST['wpvulnerability_debug_export'] ) && check_admin_referer( 'wpvulnerability_debug_action', 'wpvulnerability_debug_nonce' ) ) {
199 if ( current_user_can( 'manage_options' ) ) {
200 if ( ! function_exists( 'wpvulnerability_debug_export_info' ) ) {
201 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-debug.php';
202 }
203 $wpvulnerability_debug_info = wpvulnerability_debug_export_info();
204 $wpvulnerability_filename = 'wpvulnerability-debug-' . gmdate( 'Y-m-d-His' ) . '.json';
205
206 header( 'Content-Type: application/json' );
207 header( 'Content-Disposition: attachment; filename="' . $wpvulnerability_filename . '"' );
208 header( 'Content-Length: ' . strlen( $wpvulnerability_debug_info ) );
209 echo $wpvulnerability_debug_info; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
210 exit;
211 } else {
212 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to export debug information.', 'wpvulnerability' ), 10 );
213 }
214 }
215
216 /**
217 * Handles debug action: Run update database now.
218 *
219 * @since 4.3.0
220 */
221 if ( isset( $_POST['wpvulnerability_run_update'] ) && check_admin_referer( 'wpvulnerability_cron_action', 'wpvulnerability_cron_nonce' ) ) {
222 if ( current_user_can( 'manage_options' ) ) {
223 wpvulnerability_update_database_data();
224 set_transient( 'wpvulnerability_message_manual_success', __( 'Database update has been executed.', 'wpvulnerability' ), 10 );
225 } else {
226 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to run database updates.', 'wpvulnerability' ), 10 );
227 }
228 }
229
230 /**
231 * Handles debug action: Run notification now.
232 *
233 * @since 4.3.0
234 */
235 if ( isset( $_POST['wpvulnerability_run_notification'] ) && check_admin_referer( 'wpvulnerability_cron_action', 'wpvulnerability_cron_nonce' ) ) {
236 if ( current_user_can( 'manage_options' ) ) {
237 if ( ! function_exists( 'wpvulnerability_execute_notification' ) ) {
238 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-core.php';
239 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-plugins.php';
240 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-themes.php';
241 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-software.php';
242 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-process.php';
243 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-notifications.php';
244 }
245 $wpvulnerability_result = wpvulnerability_execute_notification( true );
246 if ( $wpvulnerability_result ) {
247 set_transient( 'wpvulnerability_message_manual_success', __( 'Notification has been sent.', 'wpvulnerability' ), 10 );
248 } else {
249 set_transient( 'wpvulnerability_message_manual_error', __( 'Notification sending failed.', 'wpvulnerability' ), 10 );
250 }
251 } else {
252 set_transient( 'wpvulnerability_message_manual_error', __( 'You do not have permission to send notifications.', 'wpvulnerability' ), 10 );
253 }
254 }
255
256 /**
257 * Retrieves the available tabs for the WPVulnerability admin settings page.
258 *
259 * @since 4.1.2
260 *
261 * @return array<string, array<string, string>> An associative array of tab slugs and their labels.
262 */
263 function wpvulnerability_get_admin_tabs() {
264 $tabs = array(
265 'notifications' => array(
266 'label' => __( 'Notifications', 'wpvulnerability' ),
267 ),
268 'analysis' => array(
269 'label' => __( 'Analysis', 'wpvulnerability' ),
270 ),
271 'logs' => array(
272 'label' => __( 'Logs', 'wpvulnerability' ),
273 ),
274 'security' => array(
275 'label' => __( 'Security', 'wpvulnerability' ),
276 ),
277 'tools' => array(
278 'label' => __( 'Tools', 'wpvulnerability' ),
279 ),
280 );
281
282 // Add Debug tab only if WP_DEBUG is enabled.
283 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
284 $tabs['debug'] = array(
285 'label' => __( 'Debug', 'wpvulnerability' ),
286 );
287 }
288
289 $tabs['about'] = array(
290 'label' => __( 'About', 'wpvulnerability' ),
291 );
292
293 return $tabs;
294 }
295
296 /**
297 * Determines the active admin tab for the settings screen.
298 *
299 * @since 4.1.2
300 *
301 * @param array<string, array<string, string>> $tabs Registered admin tabs.
302 *
303 * @return string The active tab slug.
304 */
305 function wpvulnerability_get_current_admin_tab( $tabs ) {
306 $tab_keys = array_keys( $tabs );
307 $default = reset( $tab_keys );
308 $tab_filter = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_SPECIAL_CHARS );
309
310 if ( $tab_filter ) {
311 $tab_filter = sanitize_key( $tab_filter );
312 }
313
314 if ( $tab_filter && isset( $tabs[ $tab_filter ] ) ) {
315 return $tab_filter;
316 }
317
318 return $default ? $default : 'notifications';
319 }
320
321 /**
322 * Renders the requested admin tab content.
323 *
324 * @since 4.1.2
325 *
326 * @param string $tab Tab slug to render.
327 *
328 * @return void
329 */
330 function wpvulnerability_render_admin_tab( $tab ) {
331 switch ( $tab ) {
332 case 'analysis':
333 wpvulnerability_render_admin_tab_analysis();
334 break;
335 case 'logs':
336 wpvulnerability_render_admin_tab_logs();
337 break;
338 case 'security':
339 wpvulnerability_render_admin_tab_security();
340 break;
341 case 'tools':
342 wpvulnerability_render_admin_tab_tools();
343 break;
344 case 'debug':
345 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
346 wpvulnerability_render_admin_tab_debug();
347 }
348 break;
349 case 'about':
350 wpvulnerability_render_admin_tab_about();
351 break;
352 case 'notifications':
353 default:
354 wpvulnerability_render_admin_tab_notifications();
355 break;
356 }
357 }
358
359 /**
360 * Outputs the Notifications tab contents.
361 *
362 * @since 4.1.2
363 *
364 * @return void
365 */
366 function wpvulnerability_render_admin_tab_notifications() {
367 $wpvulnerability_settings = get_option( 'wpvulnerability-config', array() );
368 if ( ! is_array( $wpvulnerability_settings ) ) {
369 $wpvulnerability_settings = array();
370 }
371 $defaults = array(
372 'cache' => 12,
373 'period' => 'weekly',
374 'day' => 'monday',
375 'hour' => 0,
376 'minute' => 0,
377 'emails' => '',
378 'slack_webhook' => '',
379 'teams_webhook' => '',
380 'discord_webhook' => '',
381 'telegram_bot_token' => '',
382 'telegram_chat_id' => '',
383 'notify' => array(
384 'email' => 'y',
385 'slack' => 'n',
386 'teams' => 'n',
387 'discord' => 'n',
388 'telegram' => 'n',
389 ),
390 );
391 $wpvulnerability_settings = wp_parse_args( $wpvulnerability_settings, $defaults );
392
393 // Normalize notify settings.
394 if ( ! is_array( $wpvulnerability_settings['notify'] ) ) {
395 $wpvulnerability_settings['notify'] = $defaults['notify'];
396 } else {
397 $wpvulnerability_settings['notify'] = wp_parse_args( $wpvulnerability_settings['notify'], $defaults['notify'] );
398 }
399 $wpvulnerability_settings['notify'] = wpvulnerability_normalize_notify_settings( $wpvulnerability_settings['notify'] );
400
401 $email_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['email'] );
402 $slack_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['slack'] );
403 $teams_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['teams'] );
404 $discord_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['discord'] );
405 $telegram_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['telegram'] );
406
407 // Check if cache is forced.
408 $forced_cache = null;
409 $cache_options = array( 1, 6, 12, 24 );
410 if ( defined( 'WPVULNERABILITY_CACHE_HOURS' ) ) {
411 $forced_cache = (int) WPVULNERABILITY_CACHE_HOURS;
412 if ( ! in_array( $forced_cache, $cache_options, true ) ) {
413 $cache_options[] = $forced_cache;
414 sort( $cache_options, SORT_NUMERIC );
415 }
416 }
417 $current_cache = null !== $forced_cache ? $forced_cache : (int) $wpvulnerability_settings['cache'];
418
419 $admin_email = get_bloginfo( 'admin_email' );
420 ?>
421 <section class="section wpvulnerability-notifications-panel">
422
423 <form method="post" action="options.php">
424 <?php settings_fields( 'admin_wpvulnerability_settings' ); ?>
425
426 <div class="wpvulnerability-security-section">
427 <h3><?php esc_html_e( 'Notification Settings', 'wpvulnerability' ); ?></h3>
428
429 <div class="wpvulnerability-intro">
430 <p><strong><?php esc_html_e( 'Configure how and when you want to receive vulnerability notifications.', 'wpvulnerability' ); ?></strong></p>
431 <p><?php esc_html_e( 'Stay informed about security vulnerabilities in your WordPress installation, plugins, themes, and server software.', 'wpvulnerability' ); ?></p>
432 </div>
433
434 <!-- Cache Settings -->
435 <div class="wpvulnerability-setting-group">
436 <div class="wpvulnerability-setting-label">
437 <span class="wpvulnerability-setting-icon">⏱️</span>
438 <?php esc_html_e( 'Cache Expiration Time', 'wpvulnerability' ); ?>
439 <?php if ( null !== $forced_cache ) : ?>
440 <span class="wpvulnerability-forced-badge"><?php esc_html_e( 'FORCED', 'wpvulnerability' ); ?></span>
441 <?php endif; ?>
442 </div>
443 <div class="wpvulnerability-setting-description">
444 <?php esc_html_e( 'How long to cache vulnerability data before refreshing from the API.', 'wpvulnerability' ); ?>
445 </div>
446 <select name="wpvulnerability-config[cache]" id="wpvulnerability_cache" <?php disabled( null !== $forced_cache ); ?>>
447 <?php foreach ( $cache_options as $hours ) : ?>
448 <option value="<?php echo esc_attr( (string) $hours ); ?>" <?php selected( $current_cache, $hours ); ?>>
449 <?php
450 echo esc_html(
451 sprintf(
452 /* translators: %d: number of hours */
453 _n( '%d hour', '%d hours', $hours, 'wpvulnerability' ),
454 $hours
455 )
456 );
457 ?>
458 </option>
459 <?php endforeach; ?>
460 </select>
461 <?php if ( null !== $forced_cache ) : ?>
462 <input type="hidden" name="wpvulnerability-config[cache]" value="<?php echo esc_attr( (string) $current_cache ); ?>" />
463 <?php endif; ?>
464 <div class="wpvulnerability-info-box">
465 <p>
466 <?php
467 printf(
468 /* translators: %s: documentation URL */
469 wp_kses_post( __( 'You can force the cache time via wp-config.php constant. <a href="%s" target="_blank" rel="noopener noreferrer">Learn more →</a>', 'wpvulnerability' ) ),
470 esc_url( 'https://www.wpvulnerability.com/plugin/#cache-duration' )
471 );
472 ?>
473 </p>
474 </div>
475 </div>
476
477 <!-- Notification Frequency -->
478 <div class="wpvulnerability-setting-group">
479 <div class="wpvulnerability-setting-label">
480 <span class="wpvulnerability-setting-icon">�
481 </span>
482 <?php esc_html_e( 'Notification Frequency', 'wpvulnerability' ); ?>
483 </div>
484 <div class="wpvulnerability-setting-description">
485 <?php esc_html_e( 'Choose how often you want to receive vulnerability notifications.', 'wpvulnerability' ); ?>
486 </div>
487 <div class="wpvulnerability-radio-group">
488 <label>
489 <input type="radio" name="wpvulnerability-config[period]" value="never" <?php checked( $wpvulnerability_settings['period'], 'never' ); ?> onchange="wpvUpdateScheduleVisibility()" />
490 <?php esc_html_e( 'Never - Disable automatic notifications', 'wpvulnerability' ); ?>
491 </label>
492 <label>
493 <input type="radio" name="wpvulnerability-config[period]" value="daily" <?php checked( $wpvulnerability_settings['period'], 'daily' ); ?> onchange="wpvUpdateScheduleVisibility()" />
494 <?php esc_html_e( 'Daily - Receive notifications every day', 'wpvulnerability' ); ?>
495 </label>
496 <label>
497 <input type="radio" name="wpvulnerability-config[period]" value="weekly" <?php checked( $wpvulnerability_settings['period'], 'weekly' ); ?> onchange="wpvUpdateScheduleVisibility()" />
498 <?php esc_html_e( 'Weekly - Receive notifications once a week', 'wpvulnerability' ); ?>
499 </label>
500 </div>
501
502 <div class="wpvulnerability-schedule-controls" id="wpvulnerability-schedule-controls">
503 <div class="wpvulnerability-schedule-row" id="wpvulnerability-day-selector">
504 <label for="wpvulnerability_day"><?php esc_html_e( 'Day:', 'wpvulnerability' ); ?></label>
505 <select name="wpvulnerability-config[day]" id="wpvulnerability_day">
506 <option value="monday" <?php selected( $wpvulnerability_settings['day'], 'monday' ); ?>><?php esc_html_e( 'Monday', 'wpvulnerability' ); ?></option>
507 <option value="tuesday" <?php selected( $wpvulnerability_settings['day'], 'tuesday' ); ?>><?php esc_html_e( 'Tuesday', 'wpvulnerability' ); ?></option>
508 <option value="wednesday" <?php selected( $wpvulnerability_settings['day'], 'wednesday' ); ?>><?php esc_html_e( 'Wednesday', 'wpvulnerability' ); ?></option>
509 <option value="thursday" <?php selected( $wpvulnerability_settings['day'], 'thursday' ); ?>><?php esc_html_e( 'Thursday', 'wpvulnerability' ); ?></option>
510 <option value="friday" <?php selected( $wpvulnerability_settings['day'], 'friday' ); ?>><?php esc_html_e( 'Friday', 'wpvulnerability' ); ?></option>
511 <option value="saturday" <?php selected( $wpvulnerability_settings['day'], 'saturday' ); ?>><?php esc_html_e( 'Saturday', 'wpvulnerability' ); ?></option>
512 <option value="sunday" <?php selected( $wpvulnerability_settings['day'], 'sunday' ); ?>><?php esc_html_e( 'Sunday', 'wpvulnerability' ); ?></option>
513 </select>
514 </div>
515 <div class="wpvulnerability-schedule-row">
516 <label for="wpvulnerability_hour"><?php esc_html_e( 'Time:', 'wpvulnerability' ); ?></label>
517 <input type="number" min="0" max="23" name="wpvulnerability-config[hour]" id="wpvulnerability_hour" value="<?php echo esc_attr( (string) $wpvulnerability_settings['hour'] ); ?>" />
518 <span>:</span>
519 <input type="number" min="0" max="59" name="wpvulnerability-config[minute]" id="wpvulnerability_minute" value="<?php echo esc_attr( (string) $wpvulnerability_settings['minute'] ); ?>" />
520 <span class="wpvulnerability-input-hint"><?php esc_html_e( '(24-hour format, server timezone)', 'wpvulnerability' ); ?></span>
521 </div>
522 </div>
523 </div>
524
525 <!-- Notification Channels -->
526 <div class="wpvulnerability-setting-group">
527 <div class="wpvulnerability-setting-label">
528 <span class="wpvulnerability-setting-icon">📢</span>
529 <?php esc_html_e( 'Notification Channels', 'wpvulnerability' ); ?>
530 </div>
531 <div class="wpvulnerability-setting-description">
532 <?php esc_html_e( 'Select where you want to receive notifications.', 'wpvulnerability' ); ?>
533 </div>
534 <div class="wpvulnerability-checkbox-group">
535 <label>
536 <input type="checkbox" name="wpvulnerability-config[notify][email]" value="y" <?php checked( $email_enabled ); ?> onchange="wpvToggleChannelInput('email')" />
537 <?php esc_html_e( 'Email', 'wpvulnerability' ); ?>
538 </label>
539 <div class="wpvulnerability-channel-inputs" id="wpvulnerability-email-inputs">
540 <label for="wpvulnerability_emails"><?php esc_html_e( 'Email Addresses (separated by commas):', 'wpvulnerability' ); ?></label>
541 <input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[emails]" id="wpvulnerability_emails" placeholder="<?php echo esc_attr( (string) $admin_email ); ?>" value="<?php echo esc_attr( (string) $wpvulnerability_settings['emails'] ); ?>" />
542 <span class="wpvulnerability-input-hint"><?php esc_html_e( 'Default:', 'wpvulnerability' ); ?> <?php echo esc_html( (string) $admin_email ); ?></span>
543 </div>
544
545 <label>
546 <input type="checkbox" name="wpvulnerability-config[notify][slack]" value="y" <?php checked( $slack_enabled ); ?> onchange="wpvToggleChannelInput('slack')" />
547 <?php esc_html_e( 'Slack', 'wpvulnerability' ); ?>
548 </label>
549 <div class="wpvulnerability-channel-inputs" id="wpvulnerability-slack-inputs">
550 <label for="wpvulnerability_slack_webhook"><?php esc_html_e( 'Slack Webhook URL:', 'wpvulnerability' ); ?></label>
551 <input class="wpvulnerability-input-full" type="url" name="wpvulnerability-config[slack_webhook]" id="wpvulnerability_slack_webhook" placeholder="https://hooks.slack.com/services/..." value="<?php echo esc_attr( (string) $wpvulnerability_settings['slack_webhook'] ); ?>" />
552 <span class="wpvulnerability-input-hint">
553 <?php
554 printf(
555 /* translators: %s: Slack documentation URL */
556 wp_kses_post( __( '<a href="%s" target="_blank" rel="noopener noreferrer">Learn how to create a Slack incoming webhook →</a>', 'wpvulnerability' ) ),
557 esc_url( 'https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/' )
558 );
559 ?>
560 </span>
561 </div>
562
563 <label>
564 <input type="checkbox" name="wpvulnerability-config[notify][teams]" value="y" <?php checked( $teams_enabled ); ?> onchange="wpvToggleChannelInput('teams')" />
565 <?php esc_html_e( 'Microsoft Teams', 'wpvulnerability' ); ?>
566 </label>
567 <div class="wpvulnerability-channel-inputs" id="wpvulnerability-teams-inputs">
568 <label for="wpvulnerability_teams_webhook"><?php esc_html_e( 'Teams Webhook URL:', 'wpvulnerability' ); ?></label>
569 <input class="wpvulnerability-input-full" type="url" name="wpvulnerability-config[teams_webhook]" id="wpvulnerability_teams_webhook" placeholder="https://outlook.office.com/webhook/..." value="<?php echo esc_attr( (string) $wpvulnerability_settings['teams_webhook'] ); ?>" />
570 <span class="wpvulnerability-input-hint">
571 <?php
572 printf(
573 /* translators: %s: Microsoft Teams documentation URL */
574 wp_kses_post( __( '<a href="%s" target="_blank" rel="noopener noreferrer">Learn how to create a Teams incoming webhook →</a>', 'wpvulnerability' ) ),
575 esc_url( 'https://learn.microsoft.com/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook' )
576 );
577 ?>
578 </span>
579 </div>
580
581 <label>
582 <input type="checkbox" name="wpvulnerability-config[notify][discord]" value="y" <?php checked( $discord_enabled ); ?> onchange="wpvToggleChannelInput('discord')" />
583 <?php esc_html_e( 'Discord', 'wpvulnerability' ); ?>
584 </label>
585 <div class="wpvulnerability-channel-inputs" id="wpvulnerability-discord-inputs">
586 <label for="wpvulnerability_discord_webhook"><?php esc_html_e( 'Discord Webhook URL:', 'wpvulnerability' ); ?></label>
587 <input class="wpvulnerability-input-full" type="url" name="wpvulnerability-config[discord_webhook]" id="wpvulnerability_discord_webhook" placeholder="https://discord.com/api/webhooks/..." value="<?php echo esc_attr( (string) $wpvulnerability_settings['discord_webhook'] ); ?>" />
588 <span class="wpvulnerability-input-hint">
589 <?php
590 printf(
591 /* translators: %s: Discord documentation URL */
592 wp_kses_post( __( '<a href="%s" target="_blank" rel="noopener noreferrer">Learn how to create a Discord webhook →</a>', 'wpvulnerability' ) ),
593 esc_url( 'https://support.discord.com/hc/articles/228383668' )
594 );
595 ?>
596 </span>
597 </div>
598
599 <label>
600 <input type="checkbox" name="wpvulnerability-config[notify][telegram]" value="y" <?php checked( $telegram_enabled ); ?> onchange="wpvToggleChannelInput('telegram')" />
601 <?php esc_html_e( 'Telegram', 'wpvulnerability' ); ?>
602 </label>
603 <div class="wpvulnerability-channel-inputs" id="wpvulnerability-telegram-inputs">
604 <label for="wpvulnerability_telegram_bot_token"><?php esc_html_e( 'Telegram Bot Token:', 'wpvulnerability' ); ?></label>
605 <input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[telegram_bot_token]" id="wpvulnerability_telegram_bot_token" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" value="<?php echo esc_attr( (string) $wpvulnerability_settings['telegram_bot_token'] ); ?>" />
606 <span class="wpvulnerability-input-hint"><?php esc_html_e( 'Format: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', 'wpvulnerability' ); ?></span>
607
608 <label for="wpvulnerability_telegram_chat_id" style="margin-top: 12px;"><?php esc_html_e( 'Telegram Chat ID:', 'wpvulnerability' ); ?></label>
609 <input class="wpvulnerability-input-full" type="text" name="wpvulnerability-config[telegram_chat_id]" id="wpvulnerability_telegram_chat_id" placeholder="-1001234567890" value="<?php echo esc_attr( (string) $wpvulnerability_settings['telegram_chat_id'] ); ?>" />
610 <span class="wpvulnerability-input-hint">
611 <?php
612 printf(
613 /* translators: %s: Telegram documentation URL */
614 wp_kses_post( __( '<a href="%s" target="_blank" rel="noopener noreferrer">Learn how to create a Telegram bot and get Chat ID →</a>', 'wpvulnerability' ) ),
615 esc_url( 'https://core.telegram.org/bots' )
616 );
617 ?>
618 </span>
619 </div>
620 </div>
621 </div>
622
623 <div class="wpvulnerability-save-section">
624 <?php submit_button( __( 'Save Notification Settings', 'wpvulnerability' ), 'primary', 'submit', false ); ?>
625 </div>
626 </div>
627 </form>
628
629 <script>
630 function wpvUpdateScheduleVisibility() {
631 var period = document.querySelector('input[name="wpvulnerability-config[period]"]:checked').value;
632 var scheduleControls = document.getElementById('wpvulnerability-schedule-controls');
633 var daySelector = document.getElementById('wpvulnerability-day-selector');
634
635 if (period === 'never') {
636 scheduleControls.classList.add('wpvulnerability-hidden');
637 } else {
638 scheduleControls.classList.remove('wpvulnerability-hidden');
639 if (period === 'weekly') {
640 daySelector.style.display = 'block';
641 } else {
642 daySelector.style.display = 'none';
643 }
644 }
645 }
646
647 function wpvToggleChannelInput(channel) {
648 var checkbox = document.querySelector('input[name="wpvulnerability-config[notify][' + channel + ']"]');
649 var inputs = document.getElementById('wpvulnerability-' + channel + '-inputs');
650
651 if (checkbox.checked) {
652 inputs.classList.remove('wpvulnerability-hidden');
653 } else {
654 inputs.classList.add('wpvulnerability-hidden');
655 }
656 }
657
658 // Initialize visibility on page load.
659 document.addEventListener('DOMContentLoaded', function() {
660 wpvUpdateScheduleVisibility();
661 wpvToggleChannelInput('email');
662 wpvToggleChannelInput('slack');
663 wpvToggleChannelInput('teams');
664 wpvToggleChannelInput('discord');
665 wpvToggleChannelInput('telegram');
666 });
667 </script>
668 </section>
669 <?php
670 }
671
672 /**
673 * Outputs the Analysis tab contents.
674 *
675 * @since 4.1.2
676 *
677 * @return void
678 */
679 function wpvulnerability_render_admin_tab_analysis() {
680 $wpvulnerability_analyze = get_option( 'wpvulnerability-analyze', array() );
681 if ( ! is_array( $wpvulnerability_analyze ) ) {
682 $wpvulnerability_analyze = array();
683 }
684 $components = array( 'core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
685 $forced = array();
686
687 foreach ( $components as $component ) {
688 if ( ! isset( $wpvulnerability_analyze[ $component ] ) ) {
689 $wpvulnerability_analyze[ $component ] = 0;
690 }
691 $constant = 'WPVULNERABILITY_HIDE_' . strtoupper( (string) $component );
692 $forced[ $component ] = defined( $constant ) && constant( $constant );
693 if ( $forced[ $component ] ) {
694 $wpvulnerability_analyze[ $component ] = 1;
695 }
696 }
697
698 // Component configuration with labels and icons.
699 $component_config = array(
700 'wordpress' => array(
701 'label' => __( 'WordPress Components', 'wpvulnerability' ),
702 'items' => array(
703 'core' => array(
704 'label' => __( 'WordPress Core', 'wpvulnerability' ),
705 'icon' => '🌐',
706 ),
707 'plugins' => array(
708 'label' => __( 'Plugins', 'wpvulnerability' ),
709 'icon' => '🧩',
710 ),
711 'themes' => array(
712 'label' => __( 'Themes', 'wpvulnerability' ),
713 'icon' => '🎨',
714 ),
715 ),
716 ),
717 'software' => array(
718 'label' => __( 'Software & Languages', 'wpvulnerability' ),
719 'items' => array(
720 'php' => array(
721 'label' => __( 'PHP', 'wpvulnerability' ),
722 'icon' => '🐘',
723 ),
724 ),
725 ),
726 'webservers' => array(
727 'label' => __( 'Web Servers', 'wpvulnerability' ),
728 'items' => array(
729 'apache' => array(
730 'label' => __( 'Apache HTTPD', 'wpvulnerability' ),
731 'icon' => '🪶',
732 ),
733 'nginx' => array(
734 'label' => __( 'nginx', 'wpvulnerability' ),
735 'icon' => '🟩',
736 ),
737 ),
738 ),
739 'databases' => array(
740 'label' => __( 'Databases', 'wpvulnerability' ),
741 'items' => array(
742 'mariadb' => array(
743 'label' => __( 'MariaDB', 'wpvulnerability' ),
744 'icon' => '🐬',
745 ),
746 'mysql' => array(
747 'label' => __( 'MySQL', 'wpvulnerability' ),
748 'icon' => '🐬',
749 ),
750 'sqlite' => array(
751 'label' => __( 'SQLite', 'wpvulnerability' ),
752 'icon' => '💾',
753 ),
754 ),
755 ),
756 'tools' => array(
757 'label' => __( 'Additional Tools', 'wpvulnerability' ),
758 'items' => array(
759 'imagemagick' => array(
760 'label' => __( 'ImageMagick', 'wpvulnerability' ),
761 'icon' => '🖼️',
762 ),
763 'curl' => array(
764 'label' => __( 'curl', 'wpvulnerability' ),
765 'icon' => '🌐',
766 ),
767 'memcached' => array(
768 'label' => __( 'memcached', 'wpvulnerability' ),
769 'icon' => '',
770 ),
771 'redis' => array(
772 'label' => __( 'Redis', 'wpvulnerability' ),
773 'icon' => '🔴',
774 ),
775 ),
776 ),
777 );
778 ?>
779 <section class="section wpvulnerability-analysis-panel">
780
781 <form method="post" action="options.php">
782 <?php settings_fields( 'admin_wpvulnerability_analyze' ); ?>
783
784 <div class="wpvulnerability-security-section">
785 <h3><?php esc_html_e( 'Component Analysis Settings', 'wpvulnerability' ); ?></h3>
786
787 <div class="wpvulnerability-intro">
788 <p><strong><?php esc_html_e( 'Enable or disable vulnerability analysis for specific components.', 'wpvulnerability' ); ?></strong></p>
789 <p><?php esc_html_e( '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.', 'wpvulnerability' ); ?></p>
790 </div>
791
792 <?php foreach ( $component_config as $category_key => $category_data ) : ?>
793 <div class="wpvulnerability-category">
794 <div class="wpvulnerability-category-title"><?php echo esc_html( $category_data['label'] ); ?></div>
795 <div class="wpvulnerability-components-grid">
796 <?php foreach ( $category_data['items'] as $component => $component_data ) : ?>
797 <?php
798 $is_checked = ! empty( $wpvulnerability_analyze[ $component ] );
799 $is_forced = ! empty( $forced[ $component ] );
800 $card_class = 'wpvulnerability-component-card';
801 if ( $is_checked ) {
802 $card_class .= ' wpvulnerability-checked';
803 }
804 if ( $is_forced ) {
805 $card_class .= ' wpvulnerability-disabled';
806 }
807 ?>
808 <div class="<?php echo esc_attr( $card_class ); ?>" onclick="if (!this.classList.contains('wpvulnerability-disabled')) { var cb = this.querySelector('input[type=checkbox]'); cb.checked = !cb.checked; this.classList.toggle('wpvulnerability-checked'); }">
809 <label>
810 <span class="wpvulnerability-component-icon"><?php echo esc_html( $component_data['icon'] ); ?></span>
811 <span class="wpvulnerability-component-content">
812 <span class="wpvulnerability-component-label">
813 <?php echo esc_html( $component_data['label'] ); ?>
814 <?php if ( $is_forced ) : ?>
815 <span class="wpvulnerability-forced-badge"><?php esc_html_e( 'FORCED', 'wpvulnerability' ); ?></span>
816 <?php endif; ?>
817 </span>
818 <span class="wpvulnerability-component-status">
819 <?php
820 if ( $is_forced ) {
821 echo '<span class="wpvulnerability-status-badge wpvulnerability-status-hidden">' . esc_html__( 'Hidden (forced by constant)', 'wpvulnerability' ) . '</span>';
822 } elseif ( $is_checked ) {
823 // Checked = value is 1 = analysis is DISABLED (hidden).
824 echo '<span class="wpvulnerability-status-badge wpvulnerability-status-hidden">' . esc_html__( '🔴 Analysis disabled', 'wpvulnerability' ) . '</span>';
825 } else {
826 // Unchecked = value is 0 or not set = analysis is ENABLED (active).
827 echo '<span class="wpvulnerability-status-badge wpvulnerability-status-active">' . esc_html__( '🟢 Analysis active', 'wpvulnerability' ) . '</span>';
828 }
829 ?>
830 </span>
831 </span>
832 <input
833 type="checkbox"
834 name="wpvulnerability-analyze[<?php echo esc_attr( $component ); ?>]"
835 value="<?php echo esc_attr( $component ); ?>"
836 <?php checked( $is_checked ); ?>
837 <?php disabled( $is_forced ); ?>
838 onclick="event.stopPropagation();"
839 />
840 </label>
841 </div>
842 <?php endforeach; ?>
843 </div>
844 </div>
845 <?php endforeach; ?>
846
847 <div class="wpvulnerability-info-box">
848 <p>
849 <strong><?php esc_html_e( 'Tip:', 'wpvulnerability' ); ?></strong>
850 <?php
851 printf(
852 /* translators: %s: documentation URL */
853 wp_kses_post( __( 'You can force-disable components via wp-config.php constants. <a href="%s" target="_blank" rel="noopener noreferrer">Learn more about force-hiding checks →</a>', 'wpvulnerability' ) ),
854 esc_url( 'https://www.wpvulnerability.com/plugin/#force-hiding-checks' )
855 );
856 ?>
857 </p>
858 </div>
859
860 <div class="wpvulnerability-save-section">
861 <?php submit_button( __( 'Save Analysis Settings', 'wpvulnerability' ), 'primary', 'submit', false ); ?>
862 </div>
863 </div>
864 </form>
865 </section>
866 <?php
867 }
868
869 /**
870 * Outputs the Logs tab contents.
871 *
872 * @since 4.2.0
873 *
874 * @return void
875 */
876 function wpvulnerability_render_admin_tab_logs() {
877 $choices = wpvulnerability_get_log_retention_values();
878 $current = wpvulnerability_log_retention_days();
879 $forced = wpvulnerability_forced_log_retention();
880 $per_page_options = wpvulnerability_get_log_per_page_options();
881 $logs_per_page = wpvulnerability_get_default_log_per_page();
882 $per_page_request = isset( $_GET['logs_per_page'] ) && is_string( $_GET['logs_per_page'] ) ? absint( (int) wp_unslash( $_GET['logs_per_page'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
883 if ( in_array( $per_page_request, $per_page_options, true ) ) {
884 $logs_per_page = $per_page_request;
885 }
886 $current_page = isset( $_GET['log_page'] ) && is_string( $_GET['log_page'] ) ? absint( (int) wp_unslash( $_GET['log_page'] ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
887 if ( $current_page < 1 ) {
888 $current_page = 1;
889 }
890 $total_logs = wpvulnerability_count_api_logs();
891 $total_pages = max( 1, (int) ceil( $total_logs / $logs_per_page ) );
892 if ( $current_page > $total_pages ) {
893 $current_page = $total_pages;
894 }
895 $logs = wpvulnerability_get_api_logs( $logs_per_page, $current_page );
896 $logs_page_url = add_query_arg(
897 array(
898 'page' => 'wpvulnerability-options',
899 'tab' => 'logs',
900 'logs_per_page' => $logs_per_page,
901 'log_page' => $current_page,
902 ),
903 admin_url( 'options-general.php' )
904 );
905 $requested_log = isset( $_GET['log'] ) && is_string( $_GET['log'] ) ? absint( (int) wp_unslash( $_GET['log'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
906 $view_log = null;
907 $log_error = '';
908 $pagination = '';
909
910 if ( $total_pages > 1 ) {
911 $pagination_base = remove_query_arg(
912 array( 'log', 'log_page' ),
913 $logs_page_url
914 );
915 $pagination = paginate_links(
916 array(
917 'base' => add_query_arg( 'log_page', '%#%', $pagination_base ),
918 'format' => '',
919 'current' => $current_page,
920 'total' => $total_pages,
921 'prev_text' => __( '&laquo; Previous', 'wpvulnerability' ),
922 'next_text' => __( 'Next &raquo;', 'wpvulnerability' ),
923 'type' => 'list',
924 )
925 );
926 }
927
928 if ( $requested_log > 0 ) {
929 $nonce = isset( $_GET['_wpnonce'] ) && is_string( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
930 if ( $nonce && wp_verify_nonce( $nonce, 'wpvulnerability_view_log_' . $requested_log ) ) {
931 $view_log = wpvulnerability_get_api_log( $requested_log );
932 if ( ! $view_log ) {
933 $log_error = __( 'Log entry not found or has been removed.', 'wpvulnerability' );
934 }
935 } else {
936 $log_error = __( 'Unable to load the requested log entry.', 'wpvulnerability' );
937 }
938 }
939
940 ?>
941 <section class="section wpvulnerability-logs-panel">
942
943 <?php if ( ! $view_log ) : ?>
944 <!-- Configuration Section -->
945 <div class="wpvulnerability-security-section">
946 <h3><?php esc_html_e( 'Log Configuration', 'wpvulnerability' ); ?></h3>
947
948 <!-- Stats Row -->
949 <div class="wpvulnerability-stats-row">
950 <div class="wpvulnerability-stat-card">
951 <div class="wpvulnerability-stat-label"><?php esc_html_e( 'Total Logs', 'wpvulnerability' ); ?></div>
952 <div class="wpvulnerability-stat-value"><?php echo esc_html( number_format_i18n( $total_logs ) ); ?></div>
953 </div>
954 <div class="wpvulnerability-stat-card">
955 <div class="wpvulnerability-stat-label"><?php esc_html_e( 'Retention Period', 'wpvulnerability' ); ?></div>
956 <div class="wpvulnerability-stat-value">
957 <?php
958 echo esc_html(
959 0 === $current
960 ? __( 'Disabled', 'wpvulnerability' )
961 : sprintf(
962 /* translators: %d: Number of days. */
963 _n( '%d day', '%d days', $current, 'wpvulnerability' ),
964 $current
965 )
966 );
967 ?>
968 </div>
969 </div>
970 </div>
971
972 <!-- Config Form -->
973 <form method="post" action="options.php" class="wpvulnerability-config-form">
974 <?php settings_fields( 'admin_wpvulnerability_settings' ); ?>
975 <label for="wpvulnerability_log_retention">
976 <?php esc_html_e( 'Log Retention Period', 'wpvulnerability' ); ?>
977 </label>
978 <select name="wpvulnerability-config[log_retention]" id="wpvulnerability_log_retention"<?php disabled( null !== $forced ); ?>>
979 <?php
980 foreach ( $choices as $days ) {
981 $label = 0 === $days
982 ? __( 'None (Disabled)', 'wpvulnerability' )
983 : sprintf(
984 /* translators: %d: Number of days. */
985 _n( '%d day', '%d days', $days, 'wpvulnerability' ),
986 $days
987 );
988 printf(
989 '<option value="%1$s"%2$s>%3$s</option>',
990 esc_attr( (string) $days ),
991 selected( $current, $days, false ),
992 esc_html( $label )
993 );
994 }
995 ?>
996 </select>
997 <?php
998 if ( null !== $forced ) {
999 printf(
1000 '<input type="hidden" name="wpvulnerability-config[log_retention]" value="%s" />',
1001 esc_attr( (string) $forced )
1002 );
1003 printf(
1004 '<p class="description">%s</p>',
1005 esc_html__( 'This value is enforced by the WPVULNERABILITY_LOG_RETENTION_DAYS constant.', 'wpvulnerability' )
1006 );
1007 } else {
1008 printf(
1009 '<p class="description">%s</p>',
1010 esc_html__( 'Choose how long WPVulnerability should keep API response logs. Older logs are automatically deleted.', 'wpvulnerability' )
1011 );
1012 }
1013 ?>
1014 <?php submit_button( __( 'Save Configuration', 'wpvulnerability' ), 'primary', 'submit', false ); ?>
1015 </form>
1016
1017 <?php if ( 0 === $current ) : ?>
1018 <div class="wpvulnerability-info-box" style="margin-top: 20px;">
1019 <p><strong><?php esc_html_e( 'Log retention is currently disabled.', 'wpvulnerability' ); ?></strong> <?php esc_html_e( 'API responses are not being saved.', 'wpvulnerability' ); ?></p>
1020 </div>
1021 <?php endif; ?>
1022 </div>
1023
1024 <?php if ( $log_error ) : ?>
1025 <div class="notice notice-error"><p><?php echo esc_html( $log_error ); ?></p></div>
1026 <?php endif; ?>
1027 <?php endif; ?>
1028
1029 <?php if ( $view_log ) : ?>
1030 <!-- Log Detail View -->
1031 <div class="wpvulnerability-log-detail">
1032 <h3><?php esc_html_e( 'Log Details', 'wpvulnerability' ); ?></h3>
1033
1034 <div class="wpvulnerability-log-meta">
1035 <div class="wpvulnerability-log-meta-item">
1036 <div class="wpvulnerability-log-meta-label"><?php esc_html_e( 'Date', 'wpvulnerability' ); ?></div>
1037 <div class="wpvulnerability-log-meta-value"><?php echo esc_html( wpvulnerability_format_log_date( $view_log ) ); ?></div>
1038 </div>
1039 <div class="wpvulnerability-log-meta-item">
1040 <div class="wpvulnerability-log-meta-label"><?php esc_html_e( 'API Endpoint', 'wpvulnerability' ); ?></div>
1041 <div class="wpvulnerability-log-meta-value"><?php echo esc_html( get_the_title( $view_log ) ); ?></div>
1042 </div>
1043 </div>
1044
1045 <h4 style="margin: 20px 0 12px 0; color: #1d2327; font-size: 15px;"><?php esc_html_e( 'API Response', 'wpvulnerability' ); ?></h4>
1046 <pre><code><?php echo esc_html( wpvulnerability_format_log_content( $view_log->post_content ) ); ?></code></pre>
1047
1048 <p style="margin-top: 20px;">
1049 <a class="button button-primary" href="<?php echo esc_url( $logs_page_url ); ?>">
1050 &larr; <?php esc_html_e( 'Back to Logs', 'wpvulnerability' ); ?>
1051 </a>
1052 </p>
1053 </div>
1054 <?php else : ?>
1055 <!-- Logs List Section -->
1056 <?php if ( empty( $logs ) ) : ?>
1057 <div class="wpvulnerability-empty-state">
1058 <div class="wpvulnerability-empty-state-icon">📋</div>
1059 <h3><?php esc_html_e( 'No logs available', 'wpvulnerability' ); ?></h3>
1060 <p><?php esc_html_e( 'API response logs will appear here once the vulnerability scanner runs.', 'wpvulnerability' ); ?></p>
1061 </div>
1062 <?php else : ?>
1063 <div class="wpvulnerability-security-section">
1064 <h3><?php esc_html_e( 'API Response Logs', 'wpvulnerability' ); ?></h3>
1065
1066 <!-- Toolbar -->
1067 <div class="wpvulnerability-toolbar">
1068 <div class="wpvulnerability-toolbar-left">
1069 <form method="get" action="<?php echo esc_url( admin_url( 'options-general.php' ) ); ?>" style="display: flex; align-items: center; gap: 8px; margin: 0;">
1070 <input type="hidden" name="page" value="wpvulnerability-options" />
1071 <input type="hidden" name="tab" value="logs" />
1072 <input type="hidden" name="log_page" value="1" />
1073 <label for="wpvulnerability_logs_per_page" style="margin: 0; font-size: 13px; color: #646970;">
1074 <?php esc_html_e( 'Show:', 'wpvulnerability' ); ?>
1075 </label>
1076 <select name="logs_per_page" id="wpvulnerability_logs_per_page" style="min-width: 80px;">
1077 <?php foreach ( $per_page_options as $per_page_option ) : ?>
1078 <option value="<?php echo esc_attr( (string) $per_page_option ); ?>"<?php selected( $logs_per_page, $per_page_option ); ?>>
1079 <?php echo esc_html( number_format_i18n( $per_page_option ) ); ?>
1080 </option>
1081 <?php endforeach; ?>
1082 </select>
1083 <?php submit_button( __( 'Apply', 'wpvulnerability' ), 'secondary', 'submit', false, array( 'style' => 'margin: 0; padding: 4px 12px; height: auto;' ) ); ?>
1084 </form>
1085 </div>
1086 <div class="wpvulnerability-toolbar-right">
1087 <span style="font-size: 13px; color: #646970;">
1088 <?php
1089 printf(
1090 /* translators: 1: first item, 2: last item, 3: total items */
1091 esc_html__( 'Showing %1$s–%2$s of %3$s', 'wpvulnerability' ),
1092 esc_html( number_format_i18n( ( ( $current_page - 1 ) * $logs_per_page ) + 1 ) ),
1093 esc_html( number_format_i18n( min( $current_page * $logs_per_page, $total_logs ) ) ),
1094 esc_html( number_format_i18n( $total_logs ) )
1095 );
1096 ?>
1097 </span>
1098 </div>
1099 </div>
1100
1101 <!-- Logs Table -->
1102 <table class="wpvulnerability-logs-table">
1103 <thead>
1104 <tr>
1105 <th style="width: 200px;"><?php esc_html_e( 'Date', 'wpvulnerability' ); ?></th>
1106 <th><?php esc_html_e( 'API Endpoint', 'wpvulnerability' ); ?></th>
1107 <th style="width: 100px; text-align: center;"><?php esc_html_e( 'Actions', 'wpvulnerability' ); ?></th>
1108 </tr>
1109 </thead>
1110 <tbody>
1111 <?php foreach ( $logs as $log ) : ?>
1112 <tr>
1113 <td>
1114 <span class="wpvulnerability-log-date"><?php echo esc_html( wpvulnerability_format_log_date( $log ) ); ?></span>
1115 </td>
1116 <td>
1117 <span class="wpvulnerability-log-url"><?php echo esc_html( get_the_title( $log ) ); ?></span>
1118 </td>
1119 <td style="text-align: center;">
1120 <?php
1121 $view_url = add_query_arg(
1122 array(
1123 'page' => 'wpvulnerability-options',
1124 'tab' => 'logs',
1125 'log' => $log->ID,
1126 'logs_per_page' => $logs_per_page,
1127 'log_page' => $current_page,
1128 ),
1129 admin_url( 'options-general.php' )
1130 );
1131 $view_url = wp_nonce_url( $view_url, 'wpvulnerability_view_log_' . $log->ID );
1132 ?>
1133 <a class="button button-small" href="<?php echo esc_url( $view_url ); ?>">
1134 <?php esc_html_e( 'View', 'wpvulnerability' ); ?>
1135 </a>
1136 </td>
1137 </tr>
1138 <?php endforeach; ?>
1139 </tbody>
1140 </table>
1141
1142 <!-- Pagination -->
1143 <?php if ( $pagination ) : ?>
1144 <div class="wpvulnerability-pagination">
1145 <?php echo wp_kses_post( $pagination ); ?>
1146 </div>
1147 <?php endif; ?>
1148
1149 <!-- Danger Zone -->
1150 <div class="wpvulnerability-danger-zone">
1151 <h4><?php esc_html_e( 'Danger Zone', 'wpvulnerability' ); ?></h4>
1152 <p style="margin: 0 0 12px 0; font-size: 13px; color: #646970;">
1153 <?php esc_html_e( 'Permanently delete all API response logs. This action cannot be undone.', 'wpvulnerability' ); ?>
1154 </p>
1155 <form method="post" action="" onsubmit="return confirm('<?php echo esc_js( __( 'Are you sure you want to delete all logs? This action cannot be undone.', 'wpvulnerability' ) ); ?>');">
1156 <?php wp_nonce_field( 'wpvulnerability_delete_logs_action', 'wpvulnerability_delete_logs_nonce' ); ?>
1157 <input type="hidden" name="wpvulnerability_delete_logs" value="1" />
1158 <?php submit_button( __( 'Delete All Logs', 'wpvulnerability' ), 'delete', 'submit', false ); ?>
1159 </form>
1160 </div>
1161 </div>
1162 <?php endif; ?>
1163 <?php endif; ?>
1164 </section>
1165 <?php
1166 }
1167
1168 /**
1169 * Outputs the Tools tab contents.
1170 *
1171 * @since 4.1.2
1172 *
1173 * @return void
1174 */
1175 function wpvulnerability_render_admin_tab_tools() {
1176 global $wpvulnerability_settings;
1177
1178 $tools_action = add_query_arg(
1179 array(
1180 'page' => 'wpvulnerability-options',
1181 'tab' => 'tools',
1182 ),
1183 admin_url( 'options-general.php' )
1184 );
1185
1186 $wpvulnerability_cron_config = is_multisite() ? get_site_option( 'wpvulnerability-config', array() ) : get_option( 'wpvulnerability-config', array() );
1187 if ( ! is_array( $wpvulnerability_cron_config ) ) {
1188 $wpvulnerability_cron_config = array();
1189 }
1190 $is_main_site = ( ! is_multisite() || is_main_site() );
1191 $cron_status = wpvulnerability_get_cron_status( $wpvulnerability_cron_config, $is_main_site );
1192 $cron_schedules = function_exists( 'wp_get_schedules' ) ? wp_get_schedules() : array();
1193 $_df = get_option( 'date_format' );
1194 $_tf = get_option( 'time_format' );
1195 $date_format = ( is_scalar( $_df ) ? (string) $_df : '' ) . ' ' . ( is_scalar( $_tf ) ? (string) $_tf : '' );
1196 ?>
1197 <section class="section wpvulnerability-tools-panel">
1198
1199 <!-- Reload Data Tool -->
1200 <div class="wpvulnerability-tool-card">
1201 <h3>
1202 <span class="wpvulnerability-tool-icon">🔄</span>
1203 <?php esc_html_e( 'Reload Data from API', 'wpvulnerability' ); ?>
1204 </h3>
1205 <p class="wpvulnerability-tool-description">
1206 <?php esc_html_e( '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.', 'wpvulnerability' ); ?>
1207 </p>
1208 <div class="wpvulnerability-tool-actions">
1209 <form method="post" action="<?php echo esc_url( $tools_action ); ?>" style="display: inline;">
1210 <?php wp_nonce_field( 'wpvulnerability_reset_action', 'wpvulnerability_reset_nonce' ); ?>
1211 <input type="submit" name="wpvulnerability_reset" value="<?php esc_attr_e( 'Reload Data', 'wpvulnerability' ); ?>" class="button button-primary">
1212 </form>
1213 </div>
1214 </div>
1215
1216 <!-- Email Test Tool -->
1217 <div class="wpvulnerability-tool-card">
1218 <h3>
1219 <span class="wpvulnerability-tool-icon">📧</span>
1220 <?php esc_html_e( 'Test Email Notifications', 'wpvulnerability' ); ?>
1221 </h3>
1222 <p class="wpvulnerability-tool-description">
1223 <?php esc_html_e( 'Send a test email notification with current vulnerability data to verify your email configuration is working correctly.', 'wpvulnerability' ); ?>
1224 </p>
1225 <?php
1226 $from_email = null;
1227 if ( defined( 'WPVULNERABILITY_MAIL' ) ) {
1228 $from_email = sanitize_email( trim( (string) WPVULNERABILITY_MAIL ) );
1229 if ( is_email( $from_email ) ) {
1230 ?>
1231 <div class="wpvulnerability-info-box">
1232 <p>
1233 <strong><?php esc_html_e( 'From address (configured via constant):', 'wpvulnerability' ); ?></strong>
1234 <code><?php echo esc_html( $from_email ); ?></code>
1235 </p>
1236 </div>
1237 <?php
1238 }
1239 }
1240 if ( ! $from_email ) {
1241 $from_email = get_bloginfo( 'admin_email' );
1242 ?>
1243 <div class="wpvulnerability-info-box">
1244 <p>
1245 <strong><?php esc_html_e( 'From address (default):', 'wpvulnerability' ); ?></strong>
1246 <code><?php echo esc_html( $from_email ); ?></code>
1247 </p>
1248 </div>
1249 <?php
1250 }
1251 ?>
1252 <p style="margin: 8px 0; font-size: 13px;">
1253 <a href="https://www.wpvulnerability.com/plugin/#from-mail" target="_blank" rel="noopener noreferrer">
1254 <?php esc_html_e( 'Learn how to customize the email sender address →', 'wpvulnerability' ); ?>
1255 </a>
1256 </p>
1257 <div class="wpvulnerability-tool-actions">
1258 <form method="post" action="<?php echo esc_url( $tools_action ); ?>" style="display: inline;">
1259 <?php wp_nonce_field( 'wpvulnerability_email_action', 'wpvulnerability_email_nonce' ); ?>
1260 <input type="submit" name="wpvulnerability_email" value="<?php esc_attr_e( 'Send Test Email', 'wpvulnerability' ); ?>" class="button button-primary">
1261 </form>
1262 </div>
1263 </div>
1264
1265 <!-- WP-Cron Status Tool -->
1266 <div class="wpvulnerability-tool-card">
1267 <h3>
1268 <span class="wpvulnerability-tool-icon"></span>
1269 <?php esc_html_e( 'WP-Cron Status', 'wpvulnerability' ); ?>
1270 </h3>
1271 <p class="wpvulnerability-tool-description">
1272 <?php esc_html_e( 'View and manage WP-Cron scheduled events. Compare expected schedules with actual cron jobs to ensure automated tasks are running correctly.', 'wpvulnerability' ); ?>
1273 </p>
1274
1275 <?php if ( empty( $cron_status['expected'] ) ) : ?>
1276 <div class="wpvulnerability-info-box">
1277 <p><?php esc_html_e( 'No cron data available.', 'wpvulnerability' ); ?></p>
1278 </div>
1279 <?php else : ?>
1280 <table class="wpvulnerability-cron-table">
1281 <thead>
1282 <tr>
1283 <th><?php esc_html_e( 'Hook', 'wpvulnerability' ); ?></th>
1284 <th><?php esc_html_e( 'Expected', 'wpvulnerability' ); ?></th>
1285 <th><?php esc_html_e( 'Found', 'wpvulnerability' ); ?></th>
1286 <th><?php esc_html_e( 'Next Run', 'wpvulnerability' ); ?></th>
1287 <th><?php esc_html_e( 'Status', 'wpvulnerability' ); ?></th>
1288 </tr>
1289 </thead>
1290 <tbody>
1291 <?php foreach ( $cron_status['expected'] as $row ) : ?>
1292 <?php
1293 $row_hook = isset( $row['hook'] ) && is_scalar( $row['hook'] ) ? (string) $row['hook'] : '';
1294 $row_label = isset( $row['label'] ) && is_scalar( $row['label'] ) ? (string) $row['label'] : $row_hook;
1295 $row_should_exist = ! empty( $row['should_exist'] );
1296 $row_count = isset( $row['count'] ) && is_scalar( $row['count'] ) ? (int) $row['count'] : 0;
1297 $row_next_run_raw = isset( $row['next_run'] ) ? $row['next_run'] : null;
1298 $expected_schedule = isset( $row['schedule'] ) && is_scalar( $row['schedule'] ) ? (string) $row['schedule'] : '';
1299 $schedule_label = '';
1300 if ( ! $row_should_exist ) {
1301 $schedule_label = __( 'Not expected', 'wpvulnerability' );
1302 } elseif ( '' === $expected_schedule ) {
1303 $schedule_label = __( 'Disabled', 'wpvulnerability' );
1304 } elseif ( isset( $cron_schedules[ $expected_schedule ]['display'] ) ) {
1305 $schedule_label = $cron_schedules[ $expected_schedule ]['display'];
1306 } else {
1307 $schedule_label = $expected_schedule;
1308 }
1309
1310 $found_schedules = array();
1311 $row_schedules_found = isset( $row['schedules_found'] ) && is_array( $row['schedules_found'] ) ? $row['schedules_found'] : array();
1312 if ( ! empty( $row_schedules_found ) ) {
1313 foreach ( $row_schedules_found as $schedule_id ) {
1314 $schedule_id = is_scalar( $schedule_id ) ? (string) $schedule_id : '';
1315 if ( isset( $cron_schedules[ $schedule_id ]['display'] ) ) {
1316 $found_schedules[] = $cron_schedules[ $schedule_id ]['display'];
1317 } else {
1318 $found_schedules[] = $schedule_id;
1319 }
1320 }
1321 }
1322
1323 $found_label = $row_count > 0 ? implode( ', ', $found_schedules ) : __( 'None', 'wpvulnerability' );
1324 $next_run = ( $row_next_run_raw && is_numeric( $row_next_run_raw ) ) ? date_i18n( $date_format, (int) $row_next_run_raw ) : '';
1325 $row_messages = isset( $row['messages'] ) && is_array( $row['messages'] ) ? $row['messages'] : array();
1326 $status_text = implode(
1327 ' ',
1328 array_map(
1329 static function ( $m ) {
1330 return is_scalar( $m ) ? (string) $m : '';
1331 },
1332 $row_messages
1333 )
1334 );
1335
1336 // Determine status badge class.
1337 $status_class = 'success';
1338 if ( strpos( $status_text, 'Missing' ) !== false || strpos( $status_text, 'Duplicate' ) !== false ) {
1339 $status_class = 'error';
1340 } elseif ( strpos( $status_text, 'legacy' ) !== false ) {
1341 $status_class = 'warning';
1342 }
1343 ?>
1344 <tr>
1345 <td>
1346 <strong><?php echo esc_html( $row_label ); ?></strong><br>
1347 <code><?php echo esc_html( $row_hook ); ?></code>
1348 </td>
1349 <td><?php echo esc_html( $schedule_label ); ?></td>
1350 <td><?php echo esc_html( $found_label ); ?></td>
1351 <td><?php echo esc_html( $next_run ); ?></td>
1352 <td>
1353 <?php if ( $status_text ) : ?>
1354 <span class="wpvulnerability-status-badge <?php echo esc_attr( $status_class ); ?>">
1355 <?php echo esc_html( $status_text ); ?>
1356 </span>
1357 <?php else : ?>
1358 <span class="wpvulnerability-status-badge success"> <?php esc_html_e( 'OK', 'wpvulnerability' ); ?></span>
1359 <?php endif; ?>
1360 </td>
1361 </tr>
1362 <?php endforeach; ?>
1363 </tbody>
1364 </table>
1365
1366 <?php if ( ! empty( $cron_status['unexpected'] ) ) : ?>
1367 <div class="wpvulnerability-unexpected-list">
1368 <h4><?php esc_html_e( '⚠️ Unexpected WPVulnerability Events', 'wpvulnerability' ); ?></h4>
1369 <ul>
1370 <?php foreach ( $cron_status['unexpected'] as $unexpected ) : ?>
1371 <?php
1372 $unexpected_hook = isset( $unexpected['hook'] ) && is_scalar( $unexpected['hook'] ) ? (string) $unexpected['hook'] : '';
1373 $unexpected_schedules = array();
1374 $unexpected_sched_raw = isset( $unexpected['schedules'] ) && is_array( $unexpected['schedules'] ) ? $unexpected['schedules'] : array();
1375 if ( ! empty( $unexpected_sched_raw ) ) {
1376 foreach ( $unexpected_sched_raw as $schedule_id ) {
1377 $schedule_id = is_scalar( $schedule_id ) ? (string) $schedule_id : '';
1378 if ( isset( $cron_schedules[ $schedule_id ]['display'] ) ) {
1379 $unexpected_schedules[] = $cron_schedules[ $schedule_id ]['display'];
1380 } else {
1381 $unexpected_schedules[] = $schedule_id;
1382 }
1383 }
1384 }
1385 $unexpected_label = empty( $unexpected_schedules ) ? __( 'No interval', 'wpvulnerability' ) : implode( ', ', $unexpected_schedules );
1386 $unexpected_next_raw = isset( $unexpected['next_run'] ) ? $unexpected['next_run'] : null;
1387 $unexpected_next = ( $unexpected_next_raw && is_numeric( $unexpected_next_raw ) ) ? date_i18n( $date_format, (int) $unexpected_next_raw ) : '';
1388 ?>
1389 <li>
1390 <code><?php echo esc_html( $unexpected_hook ); ?></code>
1391 <?php echo esc_html( $unexpected_label ); ?>
1392 <?php esc_html_e( 'Next:', 'wpvulnerability' ); ?> <?php echo esc_html( $unexpected_next ); ?>
1393 </li>
1394 <?php endforeach; ?>
1395 </ul>
1396 </div>
1397 <?php endif; ?>
1398
1399 <div class="wpvulnerability-tool-actions">
1400 <form method="post" action="<?php echo esc_url( $tools_action ); ?>" style="display: inline;">
1401 <?php wp_nonce_field( 'wpvulnerability_cron_action', 'wpvulnerability_cron_nonce' ); ?>
1402 <input type="submit" name="wpvulnerability_repair_cron" value="<?php esc_attr_e( 'Repair Cron Events', 'wpvulnerability' ); ?>" class="button button-secondary">
1403 </form>
1404 </div>
1405 <?php endif; ?>
1406 </div>
1407
1408 <!-- Uninstall Data Preference -->
1409 <div class="wpvulnerability-tool-card">
1410 <h4><?php esc_html_e( 'Uninstall Data', 'wpvulnerability' ); ?></h4>
1411 <p style="margin: 0 0 12px 0; color: #646970; font-size: 13px; line-height: 1.6;">
1412 <?php esc_html_e( '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.', 'wpvulnerability' ); ?>
1413 </p>
1414 <form method="post" action="">
1415 <?php wp_nonce_field( 'wpvulnerability_uninstall_action', 'wpvulnerability_uninstall_nonce' ); ?>
1416 <?php
1417 $current_config = get_option( 'wpvulnerability-config', array() );
1418 $delete_on_uninstall = is_array( $current_config ) && ! empty( $current_config['delete_on_uninstall'] );
1419 ?>
1420 <label>
1421 <input type="checkbox" name="delete_on_uninstall" value="1" <?php checked( $delete_on_uninstall ); ?> />
1422 <strong style="color: #b32d2e;"><?php esc_html_e( 'Delete all plugin data on uninstall', 'wpvulnerability' ); ?></strong>
1423 </label>
1424 <p style="margin: 8px 0 12px; color: #646970; font-size: 12px;">
1425 <?php esc_html_e( 'Default: unchecked (data preserved). Check only if you want a clean removal.', 'wpvulnerability' ); ?>
1426 </p>
1427 <input type="submit" name="wpvulnerability_delete_on_uninstall" value="<?php esc_attr_e( 'Save preference', 'wpvulnerability' ); ?>" class="button button-secondary">
1428 </form>
1429 </div>
1430
1431 <!-- Reset Plugin Tool -->
1432 <div class="wpvulnerability-tool-card">
1433 <div class="wpvulnerability-danger-zone">
1434 <h4>⚠️ <?php esc_html_e( 'Reset WPVulnerability', 'wpvulnerability' ); ?></h4>
1435 <p style="margin: 0 0 16px 0; color: #646970; font-size: 13px; line-height: 1.6;">
1436 <?php esc_html_e( 'Delete all WPVulnerability settings, cached data, logs, and scheduled events, then restore defaults and reload data from the API.', 'wpvulnerability' ); ?>
1437 <br>
1438 <strong style="color: #b32d2e;"><?php esc_html_e( 'This action cannot be undone.', 'wpvulnerability' ); ?></strong>
1439 </p>
1440 <form method="post" action="<?php echo esc_url( $tools_action ); ?>" onsubmit="return confirm('<?php echo esc_js( __( 'Are you sure you want to reset all WPVulnerability data? This action cannot be undone.', 'wpvulnerability' ) ); ?>');">
1441 <?php wp_nonce_field( 'wpvulnerability_full_reset_action', 'wpvulnerability_full_reset_nonce' ); ?>
1442 <input type="submit" name="wpvulnerability_full_reset" value="<?php esc_attr_e( 'Reset Plugin', 'wpvulnerability' ); ?>" class="button button-delete">
1443 </form>
1444 </div>
1445 </div>
1446 </section>
1447 <?php
1448 }
1449
1450 /**
1451 * Outputs the About tab contents.
1452 *
1453 * @since 4.1.2
1454 *
1455 * @return void
1456 */
1457 function wpvulnerability_render_admin_tab_about() {
1458 $raw_statistics = get_option( 'wpvulnerability-statistics', '' );
1459 $wpvulnerability_statistics = json_decode( is_string( $raw_statistics ) ? $raw_statistics : '', true );
1460 if ( ! is_array( $wpvulnerability_statistics ) ) {
1461 $wpvulnerability_statistics = array();
1462 }
1463
1464 ?>
1465 <section class="section wpvulnerability-about-panel">
1466
1467 <!-- Database Statistics Section -->
1468 <div class="wpvulnerability-security-section">
1469 <h3><?php esc_html_e( 'Vulnerability Database Statistics', 'wpvulnerability' ); ?></h3>
1470
1471 <?php
1472 $components = array(
1473 'plugins' => __( 'Plugins', 'wpvulnerability' ),
1474 'themes' => __( 'Themes', 'wpvulnerability' ),
1475 'php' => __( 'PHP', 'wpvulnerability' ),
1476 'apache' => __( 'Apache HTTPD', 'wpvulnerability' ),
1477 'nginx' => __( 'nginx', 'wpvulnerability' ),
1478 'mariadb' => __( 'MariaDB', 'wpvulnerability' ),
1479 'mysql' => __( 'MySQL', 'wpvulnerability' ),
1480 'imagemagick' => __( 'ImageMagick', 'wpvulnerability' ),
1481 'curl' => __( 'curl', 'wpvulnerability' ),
1482 'memcached' => __( 'memcached', 'wpvulnerability' ),
1483 'redis' => __( 'Redis', 'wpvulnerability' ),
1484 'sqlite' => __( 'SQLite', 'wpvulnerability' ),
1485 );
1486 ?>
1487
1488 <div class="wpvulnerability-stats-grid">
1489 <?php foreach ( $components as $component => $label ) : ?>
1490 <?php
1491 $component_data = isset( $wpvulnerability_statistics[ $component ] ) && is_array( $wpvulnerability_statistics[ $component ] ) ? $wpvulnerability_statistics[ $component ] : null;
1492 $has_data = null !== $component_data;
1493 $card_class = 'wpvulnerability-stat-card';
1494 if ( ! $has_data ) {
1495 $card_class .= ' wpvulnerability-no-data';
1496 }
1497 ?>
1498 <div class="<?php echo esc_attr( $card_class ); ?>">
1499 <div class="wpvulnerability-stat-label"><?php echo esc_html( $label ); ?></div>
1500 <?php if ( $has_data ) : ?>
1501 <?php
1502 $vuln_count = absint( isset( $component_data['vulnerabilities'] ) && is_scalar( $component_data['vulnerabilities'] ) ? (int) $component_data['vulnerabilities'] : 0 );
1503 ?>
1504 <div class="wpvulnerability-stat-value">
1505 <?php echo esc_html( number_format_i18n( $vuln_count ) ); ?>
1506 </div>
1507 <div class="wpvulnerability-stat-meta">
1508 <?php
1509 printf(
1510 // translators: number of vulnerabilities.
1511 esc_html( _n( '%s vulnerability', '%s vulnerabilities', $vuln_count, 'wpvulnerability' ) ),
1512 ''
1513 );
1514 ?>
1515 <?php if ( isset( $component_data['products'] ) ) : ?>
1516 <?php $prod_count = absint( is_scalar( $component_data['products'] ) ? (int) $component_data['products'] : 0 ); ?>
1517 <br>
1518 <?php
1519 printf(
1520 // translators: number of products.
1521 esc_html( _n( '(%s product)', '(%s products)', $prod_count, 'wpvulnerability' ) ),
1522 esc_html( number_format_i18n( $prod_count ) )
1523 );
1524 ?>
1525 <?php endif; ?>
1526 </div>
1527 <?php else : ?>
1528 <div class="wpvulnerability-stat-value"><?php esc_html_e( 'No data', 'wpvulnerability' ); ?></div>
1529 <?php endif; ?>
1530 </div>
1531 <?php endforeach; ?>
1532 </div>
1533
1534 <?php if ( isset( $wpvulnerability_statistics['updated'] ) ) : ?>
1535 <div class="wpvulnerability-info-box">
1536 <p>
1537 <?php
1538 switch_to_locale( determine_locale() );
1539 $updated_data = is_array( $wpvulnerability_statistics['updated'] ) ? $wpvulnerability_statistics['updated'] : array();
1540 $updated_epoch = isset( $updated_data['unixepoch'] ) && is_scalar( $updated_data['unixepoch'] ) ? (int) $updated_data['unixepoch'] : 0;
1541 $_upd_df = get_option( 'date_format' );
1542 $_upd_tf = get_option( 'time_format' );
1543 $formatted_datetime = date_i18n( ( is_scalar( $_upd_df ) ? (string) $_upd_df : '' ) . ' ' . ( is_scalar( $_upd_tf ) ? (string) $_upd_tf : '' ), $updated_epoch );
1544 // translators: date of last update.
1545 printf( esc_html__( 'Database last updated: %s', 'wpvulnerability' ), '<strong>' . esc_html( $formatted_datetime ) . '</strong>' );
1546 restore_previous_locale();
1547 ?>
1548 </p>
1549 </div>
1550 <?php endif; ?>
1551 </div>
1552
1553 <!-- Intelligence Sources Section -->
1554 <?php
1555 $sources_stat = isset( $wpvulnerability_statistics['sources'] ) && is_array( $wpvulnerability_statistics['sources'] ) ? $wpvulnerability_statistics['sources'] : array();
1556 $source_labels = array(
1557 'cve' => 'CVE',
1558 'euvd' => 'EUVD',
1559 'jvn' => 'JVN',
1560 'patchstack' => 'Patchstack',
1561 'wpscan' => 'WPScan',
1562 'wordfence' => 'Wordfence',
1563 );
1564 if ( ! empty( $sources_stat ) ) :
1565 ?>
1566 <div class="wpvulnerability-security-section">
1567 <h3><?php esc_html_e( 'Intelligence Sources', 'wpvulnerability' ); ?></h3>
1568 <p><?php esc_html_e( 'Number of vulnerabilities contributed by each intelligence source.', 'wpvulnerability' ); ?></p>
1569 <table class="wp-list-table widefat wpvulnerability">
1570 <thead>
1571 <tr>
1572 <th><?php esc_html_e( 'Source', 'wpvulnerability' ); ?></th>
1573 <th><?php esc_html_e( 'Core', 'wpvulnerability' ); ?></th>
1574 <th><?php esc_html_e( 'Plugins', 'wpvulnerability' ); ?></th>
1575 <th><?php esc_html_e( 'Themes', 'wpvulnerability' ); ?></th>
1576 </tr>
1577 </thead>
1578 <tbody>
1579 <?php foreach ( $source_labels as $src_key => $src_label ) : ?>
1580 <?php
1581 if ( ! isset( $sources_stat[ $src_key ] ) || ! is_array( $sources_stat[ $src_key ] ) ) {
1582 continue; }
1583 ?>
1584 <?php
1585 $src_row = $sources_stat[ $src_key ];
1586 $s_core = absint( is_scalar( $src_row['core'] ?? 0 ) ? (int) ( $src_row['core'] ?? 0 ) : 0 );
1587 $s_plugins = absint( is_scalar( $src_row['plugins'] ?? 0 ) ? (int) ( $src_row['plugins'] ?? 0 ) : 0 );
1588 $s_themes = absint( is_scalar( $src_row['themes'] ?? 0 ) ? (int) ( $src_row['themes'] ?? 0 ) : 0 );
1589 ?>
1590 <tr>
1591 <td><strong><?php echo esc_html( $src_label ); ?></strong></td>
1592 <td><?php echo esc_html( number_format_i18n( $s_core ) ); ?></td>
1593 <td><?php echo esc_html( number_format_i18n( $s_plugins ) ); ?></td>
1594 <td><?php echo esc_html( number_format_i18n( $s_themes ) ); ?></td>
1595 </tr>
1596 <?php endforeach; ?>
1597 </tbody>
1598 </table>
1599 </div>
1600 <?php endif; ?>
1601
1602 <!-- Sponsors Section -->
1603 <div class="wpvulnerability-security-section">
1604 <h3><?php esc_html_e( 'Sponsors', 'wpvulnerability' ); ?></h3>
1605
1606 <?php
1607 $sponsors_list = isset( $wpvulnerability_statistics['sponsors'] ) && is_array( $wpvulnerability_statistics['sponsors'] ) ? $wpvulnerability_statistics['sponsors'] : array();
1608 ?>
1609 <?php if ( ! empty( $sponsors_list ) ) : ?>
1610 <div class="wpvulnerability-people-grid">
1611 <?php foreach ( $sponsors_list as $sponsor ) : ?>
1612 <?php
1613 if ( ! is_array( $sponsor ) ) {
1614 continue; }
1615 ?>
1616 <div class="wpvulnerability-person-card">
1617 <img src="<?php echo esc_url( isset( $sponsor['image'] ) && is_scalar( $sponsor['image'] ) ? (string) $sponsor['image'] : '' ); ?>" alt="<?php echo esc_attr( isset( $sponsor['name'] ) && is_scalar( $sponsor['name'] ) ? (string) $sponsor['name'] : '' ); ?>">
1618 <a href="<?php echo esc_url( isset( $sponsor['url'] ) && is_scalar( $sponsor['url'] ) ? (string) $sponsor['url'] : '' ); ?>" target="_blank" rel="noreferrer noopener">
1619 <?php echo esc_html( isset( $sponsor['name'] ) && is_scalar( $sponsor['name'] ) ? (string) $sponsor['name'] : '' ); ?>
1620 </a>
1621 </div>
1622 <?php endforeach; ?>
1623 </div>
1624 <?php else : ?>
1625 <div class="wpvulnerability-empty-state">
1626 <div class="wpvulnerability-empty-state-icon">💎</div>
1627 <p><strong><?php esc_html_e( 'No sponsor data available.', 'wpvulnerability' ); ?></strong></p>
1628 </div>
1629 <?php endif; ?>
1630 </div>
1631
1632 <!-- Contributors Section -->
1633 <div class="wpvulnerability-security-section">
1634 <h3><?php esc_html_e( 'Contributors', 'wpvulnerability' ); ?></h3>
1635
1636 <?php
1637 $contributors_list = isset( $wpvulnerability_statistics['contributors'] ) && is_array( $wpvulnerability_statistics['contributors'] ) ? $wpvulnerability_statistics['contributors'] : array();
1638 ?>
1639 <?php if ( ! empty( $contributors_list ) ) : ?>
1640 <div class="wpvulnerability-people-grid">
1641 <?php foreach ( $contributors_list as $contributor ) : ?>
1642 <?php
1643 if ( ! is_array( $contributor ) ) {
1644 continue; }
1645 ?>
1646 <div class="wpvulnerability-person-card">
1647 <img src="<?php echo esc_url( isset( $contributor['image'] ) && is_scalar( $contributor['image'] ) ? (string) $contributor['image'] : '' ); ?>" alt="<?php echo esc_attr( isset( $contributor['name'] ) && is_scalar( $contributor['name'] ) ? (string) $contributor['name'] : '' ); ?>">
1648 <a href="<?php echo esc_url( isset( $contributor['url'] ) && is_scalar( $contributor['url'] ) ? (string) $contributor['url'] : '' ); ?>" target="_blank" rel="noreferrer noopener">
1649 <?php echo esc_html( isset( $contributor['name'] ) && is_scalar( $contributor['name'] ) ? (string) $contributor['name'] : '' ); ?>
1650 </a>
1651 </div>
1652 <?php endforeach; ?>
1653 </div>
1654 <?php else : ?>
1655 <div class="wpvulnerability-empty-state">
1656 <div class="wpvulnerability-empty-state-icon">👥</div>
1657 <p><strong><?php esc_html_e( 'No contributor data available.', 'wpvulnerability' ); ?></strong></p>
1658 </div>
1659 <?php endif; ?>
1660 </div>
1661 </section>
1662 <?php
1663 }
1664
1665 /**
1666 * Renders the plugin settings page in the WordPress admin area.
1667 *
1668 * @since 2.0.0
1669 *
1670 * @return void
1671 */
1672 function wpvulnerability_create_admin_page() {
1673
1674 if ( ! current_user_can( 'manage_options' ) ) {
1675 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wpvulnerability' ) );
1676 }
1677
1678 $wpvulnerability_settings = get_option( 'wpvulnerability-config' );
1679 ?>
1680 <div class="header-wrap">
1681 <div class="wrapper">
1682 <div class="header wpvulnerability-header">
1683 <div class="logo">
1684 <img src="<?php echo esc_url( WPVULNERABILITY_PLUGIN_URL ); ?>assets/icon.svg" style="height: 64px; vertical-align: text-top; width: 64px;" alt="" title="WPVulnerability">
1685 <h2><?php esc_html_e( 'WPVulnerability settings', 'wpvulnerability' ); ?></h2>
1686 </div>
1687 </div>
1688 </div>
1689 </div>
1690 <?php
1691 $wpvulnerability_message_manual_success = get_transient( 'wpvulnerability_message_manual_success' );
1692 if ( $wpvulnerability_message_manual_success ) {
1693 echo '<div class="notice notice-success"><p>' . esc_html( is_scalar( $wpvulnerability_message_manual_success ) ? (string) $wpvulnerability_message_manual_success : '' ) . '</p></div>';
1694 delete_transient( 'wpvulnerability_message_manual_success' );
1695 unset( $wpvulnerability_message_manual_success );
1696 }
1697 $wpvulnerability_message_manual_error = get_transient( 'wpvulnerability_message_manual_error' );
1698 if ( $wpvulnerability_message_manual_error ) {
1699 echo '<div class="notice notice-error"><p>' . esc_html( is_scalar( $wpvulnerability_message_manual_error ) ? (string) $wpvulnerability_message_manual_error : '' ) . '</p></div>';
1700 delete_transient( 'wpvulnerability_message_manual_error' );
1701 unset( $wpvulnerability_message_manual_error );
1702 }
1703 settings_errors( 'admin_wpvulnerability_settings' );
1704 settings_errors( 'admin_wpvulnerability_analyze' );
1705
1706 $tabs = wpvulnerability_get_admin_tabs();
1707
1708 if ( empty( $tabs ) ) {
1709 return;
1710 }
1711
1712 $current_tab = wpvulnerability_get_current_admin_tab( $tabs );
1713
1714 if ( ! isset( $tabs[ $current_tab ] ) ) {
1715 $tab_keys = array_keys( $tabs );
1716 $current_tab = reset( $tab_keys );
1717 }
1718
1719 ?>
1720 <div class="wrap">
1721 <div class="wpvulnerability-settings">
1722 <h2 class="nav-tab-wrapper wpvulnerability-tab-nav" role="tablist">
1723 <?php
1724 foreach ( $tabs as $tab_slug => $tab_data ) {
1725 $tab_label = isset( $tab_data['label'] ) ? $tab_data['label'] : '';
1726 $is_active = ( $tab_slug === $current_tab );
1727 $tab_url = add_query_arg(
1728 array(
1729 'page' => 'wpvulnerability-options',
1730 'tab' => $tab_slug,
1731 ),
1732 admin_url( 'options-general.php' )
1733 );
1734 $tab_class = 'nav-tab wpvulnerability-tab-link';
1735 if ( $is_active ) {
1736 $tab_class .= ' nav-tab-active';
1737 }
1738 ?>
1739 <a
1740 href="<?php echo esc_url( $tab_url ); ?>"
1741 class="<?php echo esc_attr( $tab_class ); ?>"
1742 id="<?php echo esc_attr( 'wpvulnerability-tab-link-' . $tab_slug ); ?>"
1743 role="tab"
1744 aria-controls="<?php echo esc_attr( 'wpvulnerability-tab-panel-' . $tab_slug ); ?>"
1745 aria-selected="<?php echo $is_active ? 'true' : 'false'; ?>"
1746 <?php
1747 if ( ! $is_active ) :
1748 ?>
1749 tabindex="-1"<?php endif; ?>
1750 >
1751 <?php echo esc_html( $tab_label ); ?>
1752 </a>
1753 <?php
1754 }
1755 ?>
1756 </h2>
1757 <div
1758 id="<?php echo esc_attr( 'wpvulnerability-tab-panel-' . $current_tab ); ?>"
1759 class="wpvulnerability-tab-panel is-active"
1760 role="tabpanel"
1761 aria-labelledby="<?php echo esc_attr( 'wpvulnerability-tab-link-' . $current_tab ); ?>"
1762 tabindex="0"
1763 >
1764 <?php wpvulnerability_render_admin_tab( $current_tab ); ?>
1765 </div>
1766 </div>
1767 </div>
1768 <?php
1769 }
1770
1771 /**
1772 * Registers the WPVulnerability settings page within the Settings menu.
1773 *
1774 * @since 2.0.0
1775 *
1776 * @return void
1777 */
1778 function wpvulnerability_admin_menu() {
1779
1780 // Adds a submenu page under the Settings menu.
1781 add_submenu_page(
1782 'options-general.php',
1783 __( 'WPVulnerability', 'wpvulnerability' ),
1784 __( 'WPVulnerability', 'wpvulnerability' ),
1785 'manage_options',
1786 'wpvulnerability-options',
1787 'wpvulnerability_create_admin_page'
1788 );
1789 }
1790 add_action( 'admin_menu', 'wpvulnerability_admin_menu' );
1791
1792 /**
1793 * Print the settings header information for the notifications section.
1794 *
1795 * @since 2.0.0
1796 *
1797 * @return void
1798 */
1799 function wpvulnerability_admin_section_notifications() {
1800
1801 // Output the header information for the notifications section.
1802 esc_html_e( 'Configure and save these settings to receive notifications.', 'wpvulnerability' );
1803 }
1804
1805 /**
1806 * Print the settings header information for the analyze section.
1807 *
1808 * @since 3.3.0
1809 *
1810 * @return void
1811 */
1812 function wpvulnerability_admin_section_analyze() {
1813
1814 // Output the header information for the analyze section.
1815 esc_html_e( 'Configure and save these settings to hide vulnerabilities.', 'wpvulnerability' );
1816 }
1817
1818 /**
1819 * Callback function to display the email input field in the admin settings page.
1820 * This function retrieves the current WPVulnerability plugin settings and displays the email input field
1821 * for users to enter their email addresses. If no email is saved in the settings, the admin email is displayed.
1822 *
1823 * @since 2.0.0
1824 *
1825 * @return void
1826 */
1827 function wpvulnerability_admin_emails_callback() {
1828
1829 // Retrieve the WPVulnerability plugin settings.
1830 $wpvulnerability_settings = get_option( 'wpvulnerability-config' );
1831 if ( ! is_array( $wpvulnerability_settings ) ) {
1832 $wpvulnerability_settings = array();
1833 }
1834
1835 // Set a default value for the email input field if no email is saved in the settings.
1836 if ( ! isset( $wpvulnerability_settings['emails'] ) ) {
1837 $wpvulnerability_settings['emails'] = '';
1838 }
1839
1840 // Output the email input field.
1841 $admin_email = get_bloginfo( 'admin_email' );
1842
1843 // Output the email input field. Use the network admin email as a placeholder in a multisite environment.
1844 ?>
1845 <input class="regular-text" type="text" name="wpvulnerability-config[emails]" id="wpvulnerability_emails" placeholder="<?php echo esc_attr( $admin_email ); ?>" value="<?php echo esc_attr( is_scalar( $wpvulnerability_settings['emails'] ) ? (string) $wpvulnerability_settings['emails'] : '' ); ?>">
1846 <br><small><?php esc_html_e( 'Default administrator email', 'wpvulnerability' ); ?>: <?php echo esc_attr( $admin_email ); ?></small>
1847 <?php
1848
1849 unset( $admin_email );
1850 }
1851
1852 /**
1853 * Print the cache expiration selector.
1854 *
1855 * @since 4.1.0
1856 *
1857 * @return void
1858 */
1859 function wpvulnerability_admin_cache_callback() {
1860
1861 $wpvulnerability_settings = get_option( 'wpvulnerability-config', array() );
1862 if ( ! is_array( $wpvulnerability_settings ) ) {
1863 $wpvulnerability_settings = array();
1864 }
1865 $options = array( 1, 6, 12, 24 );
1866 $forced_cache = null;
1867
1868 if ( defined( 'WPVULNERABILITY_CACHE_HOURS' ) ) {
1869 $forced_cache = (int) WPVULNERABILITY_CACHE_HOURS;
1870 if ( ! in_array( $forced_cache, $options, true ) ) {
1871 $options[] = $forced_cache;
1872 sort( $options, SORT_NUMERIC );
1873 }
1874 }
1875
1876 $current = isset( $wpvulnerability_settings['cache'] ) && is_scalar( $wpvulnerability_settings['cache'] ) ? (int) $wpvulnerability_settings['cache'] : 12;
1877 if ( null !== $forced_cache ) {
1878 $current = $forced_cache;
1879 }
1880
1881 echo '<select name="wpvulnerability-config[cache]" id="wpvulnerability_cache"';
1882 disabled( null !== $forced_cache );
1883 echo '>';
1884 foreach ( $options as $hours ) {
1885 printf(
1886 '<option value="%1$s"%2$s>%3$s</option>',
1887 esc_attr( (string) $hours ),
1888 selected( $current, $hours, false ),
1889 esc_html(
1890 sprintf(
1891 /* translators: %d: number of hours */
1892 _n( '%d hour', '%d hours', $hours, 'wpvulnerability' ),
1893 $hours
1894 )
1895 )
1896 );
1897 }
1898 echo '</select>';
1899
1900 if ( null !== $forced_cache ) {
1901 printf(
1902 '<input type="hidden" name="wpvulnerability-config[cache]" value="%s" />',
1903 esc_attr( (string) $current )
1904 );
1905 }
1906
1907 printf(
1908 '<p class="description"><a href="%1$s" target="_blank"><small><i>%2$s</i></small></a></p>',
1909 esc_url( 'https://www.wpvulnerability.com/plugin/#cache-duration' ),
1910 esc_html__( 'Read more if you want to force the cache time.', 'wpvulnerability' )
1911 );
1912 }
1913
1914 /**
1915 * Print when to send the vulnerability scan emails.
1916 *
1917 * @since 2.0.0
1918 *
1919 * @return void
1920 */
1921 function wpvulnerability_admin_period_callback() {
1922
1923 // Get the saved plugin settings.
1924 $wpvulnerability_settings = get_option( 'wpvulnerability-config', array() );
1925 if ( ! is_array( $wpvulnerability_settings ) ) {
1926 $wpvulnerability_settings = array();
1927 }
1928 $defaults = array(
1929 'period' => 'weekly',
1930 'day' => 'monday',
1931 'hour' => 0,
1932 'minute' => 0,
1933 );
1934 $wpvulnerability_settings = wp_parse_args( $wpvulnerability_settings, $defaults );
1935
1936 ?>
1937 <div id="wpvulnerability_period">
1938 <label>
1939 <input type="radio" name="wpvulnerability-config[period]" value="never" <?php checked( $wpvulnerability_settings['period'], 'never' ); ?> />
1940 <?php esc_html_e( 'Never', 'wpvulnerability' ); ?>
1941 </label>
1942 <br/>
1943 <label>
1944 <input type="radio" name="wpvulnerability-config[period]" value="daily" <?php checked( $wpvulnerability_settings['period'], 'daily' ); ?> />
1945 <?php esc_html_e( 'Daily', 'wpvulnerability' ); ?>
1946 </label>
1947 <br/>
1948 <label>
1949 <input type="radio" name="wpvulnerability-config[period]" value="weekly" <?php checked( $wpvulnerability_settings['period'], 'weekly' ); ?> />
1950 <?php esc_html_e( 'Weekly', 'wpvulnerability' ); ?>
1951 </label>
1952 <div id="wpvulnerability_day_wrap">
1953 <br/>
1954 <label for="wpvulnerability_day"><?php esc_html_e( 'Day', 'wpvulnerability' ); ?></label>
1955 <select name="wpvulnerability-config[day]" id="wpvulnerability_day">
1956 <option value="monday" <?php selected( $wpvulnerability_settings['day'], 'monday' ); ?>><?php esc_html_e( 'Monday', 'wpvulnerability' ); ?></option>
1957 <option value="tuesday" <?php selected( $wpvulnerability_settings['day'], 'tuesday' ); ?>><?php esc_html_e( 'Tuesday', 'wpvulnerability' ); ?></option>
1958 <option value="wednesday" <?php selected( $wpvulnerability_settings['day'], 'wednesday' ); ?>><?php esc_html_e( 'Wednesday', 'wpvulnerability' ); ?></option>
1959 <option value="thursday" <?php selected( $wpvulnerability_settings['day'], 'thursday' ); ?>><?php esc_html_e( 'Thursday', 'wpvulnerability' ); ?></option>
1960 <option value="friday" <?php selected( $wpvulnerability_settings['day'], 'friday' ); ?>><?php esc_html_e( 'Friday', 'wpvulnerability' ); ?></option>
1961 <option value="saturday" <?php selected( $wpvulnerability_settings['day'], 'saturday' ); ?>><?php esc_html_e( 'Saturday', 'wpvulnerability' ); ?></option>
1962 <option value="sunday" <?php selected( $wpvulnerability_settings['day'], 'sunday' ); ?>><?php esc_html_e( 'Sunday', 'wpvulnerability' ); ?></option>
1963 </select>
1964 </div>
1965 <div id="wpvulnerability_time_wrap">
1966 <br/>
1967 <label for="wpvulnerability_hour"><?php esc_html_e( 'Hour', 'wpvulnerability' ); ?></label>
1968 <input type="number" min="0" max="23" name="wpvulnerability-config[hour]" id="wpvulnerability_hour" value="<?php echo esc_attr( (string) $wpvulnerability_settings['hour'] ); ?>" />
1969 <label for="wpvulnerability_minute"><?php esc_html_e( 'Minute', 'wpvulnerability' ); ?></label>
1970 <input type="number" min="0" max="59" name="wpvulnerability-config[minute]" id="wpvulnerability_minute" value="<?php echo esc_attr( (string) $wpvulnerability_settings['minute'] ); ?>" />
1971 </div>
1972 </div>
1973 <?php
1974 }
1975
1976 /**
1977 * Print where to send the notifications.
1978 *
1979 * @since 3.6.0
1980 *
1981 * @return void
1982 */
1983 function wpvulnerability_admin_notify_callback() {
1984
1985 // Get the saved plugin settings.
1986 $wpvulnerability_settings = get_option( 'wpvulnerability-config', array() );
1987 if ( ! is_array( $wpvulnerability_settings ) ) {
1988 $wpvulnerability_settings = array();
1989 }
1990 $defaults = array(
1991 'email' => 'y',
1992 'slack' => 'n',
1993 'teams' => 'n',
1994 );
1995
1996 if ( ! isset( $wpvulnerability_settings['notify'] ) || ! is_array( $wpvulnerability_settings['notify'] ) ) {
1997 $wpvulnerability_settings['notify'] = $defaults;
1998 } else {
1999 $wpvulnerability_settings['notify'] = wp_parse_args( $wpvulnerability_settings['notify'], $defaults );
2000 }
2001
2002 $wpvulnerability_settings['notify'] = wpvulnerability_normalize_notify_settings( $wpvulnerability_settings['notify'] );
2003
2004 $email_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['email'] );
2005 $slack_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['slack'] );
2006 $teams_enabled = wpvulnerability_is_yes( $wpvulnerability_settings['notify']['teams'] );
2007
2008 ?>
2009 <div id="wpvulnerability_notify">
2010 <label>
2011 <input type="checkbox" name="wpvulnerability-config[notify][email]" value="y" <?php checked( $email_enabled ); ?> />
2012 <?php esc_html_e( 'Email', 'wpvulnerability' ); ?>
2013 </label>
2014 <br/>
2015 <label>
2016 <input type="checkbox" name="wpvulnerability-config[notify][slack]" value="y" <?php checked( $slack_enabled ); ?> />
2017 <?php esc_html_e( 'Slack', 'wpvulnerability' ); ?>
2018 </label>
2019 <br/>
2020 <label>
2021 <input type="checkbox" name="wpvulnerability-config[notify][teams]" value="y" <?php checked( $teams_enabled ); ?> />
2022 <?php esc_html_e( 'Microsoft Teams', 'wpvulnerability' ); ?>
2023 </label>
2024 </div>
2025 <?php
2026 }
2027
2028 /**
2029 * Print the Slack webhook input field.
2030 *
2031 * @since 3.6.0
2032 *
2033 * @return void
2034 */
2035 function wpvulnerability_admin_slack_callback() {
2036
2037 $wpvulnerability_settings = get_option( 'wpvulnerability-config', array() );
2038 if ( ! is_array( $wpvulnerability_settings ) ) {
2039 $wpvulnerability_settings = array();
2040 }
2041 $slack_webhook = isset( $wpvulnerability_settings['slack_webhook'] ) ? $wpvulnerability_settings['slack_webhook'] : '';
2042
2043 ?>
2044 <input class="regular-text" type="text" name="wpvulnerability-config[slack_webhook]" id="wpvulnerability_slack_webhook" placeholder="<?php echo esc_attr( 'https://hooks.slack.com/services/...' ); ?>" value="<?php echo esc_attr( is_scalar( $slack_webhook ) ? (string) $slack_webhook : '' ); ?>" />
2045 <?php
2046 }
2047
2048 /**
2049 * Print the Teams webhook input field.
2050 *
2051 * @since 3.6.0
2052 *
2053 * @return void
2054 */
2055 function wpvulnerability_admin_teams_callback() {
2056
2057 $wpvulnerability_settings = get_option( 'wpvulnerability-config', array() );
2058 if ( ! is_array( $wpvulnerability_settings ) ) {
2059 $wpvulnerability_settings = array();
2060 }
2061 $teams_webhook = isset( $wpvulnerability_settings['teams_webhook'] ) ? $wpvulnerability_settings['teams_webhook'] : '';
2062
2063 ?>
2064 <input class="regular-text" type="text" name="wpvulnerability-config[teams_webhook]" id="wpvulnerability_teams_webhook" placeholder="<?php echo esc_attr( 'https://outlook.office.com/webhook/...' ); ?>" value="<?php echo esc_attr( is_scalar( $teams_webhook ) ? (string) $teams_webhook : '' ); ?>" />
2065 <?php
2066 }
2067
2068 /**
2069 * Displays the WPVulnerability plugin analysis settings in the admin panel.
2070 *
2071 * This function retrieves the current WPVulnerability analysis settings and
2072 * ensures all necessary options are set. It then outputs a multiple-select
2073 * field allowing the user to select which components (core, plugins, themes,
2074 * php, apache, nginx) to analyze.
2075 *
2076 * @since 3.3.0
2077 *
2078 * @return void
2079 */
2080 function wpvulnerability_admin_analyze_callback() {
2081
2082 // Retrieve the WPVulnerability plugin settings.
2083 $wpvulnerability_analyze = get_option( 'wpvulnerability-analyze', array() );
2084 if ( ! is_array( $wpvulnerability_analyze ) ) {
2085 $wpvulnerability_analyze = array();
2086 }
2087 $components = array( 'core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mariadb', 'mysql', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
2088 $forced = array();
2089
2090 foreach ( $components as $component ) {
2091 if ( ! isset( $wpvulnerability_analyze[ $component ] ) ) {
2092 $wpvulnerability_analyze[ $component ] = 0;
2093 }
2094 $constant = 'WPVULNERABILITY_HIDE_' . strtoupper( (string) $component );
2095 $forced[ $component ] = defined( $constant ) && constant( $constant );
2096 if ( $forced[ $component ] ) {
2097 $wpvulnerability_analyze[ $component ] = 1;
2098 }
2099 }
2100
2101 ?>
2102 <div id="wpvulnerability_analyze">
2103 <label>
2104 <input type="checkbox" name="wpvulnerability-analyze[core]" value="core" <?php checked( $wpvulnerability_analyze['core'] ); ?> <?php disabled( $forced['core'] ); ?> />
2105 <?php esc_html_e( 'Core', 'wpvulnerability' ); ?>
2106 </label>
2107 <br/>
2108 <label>
2109 <input type="checkbox" name="wpvulnerability-analyze[plugins]" value="plugins" <?php checked( $wpvulnerability_analyze['plugins'] ); ?> <?php disabled( $forced['plugins'] ); ?> />
2110 <?php esc_html_e( 'Plugins', 'wpvulnerability' ); ?>
2111 </label>
2112 <br/>
2113 <label>
2114 <input type="checkbox" name="wpvulnerability-analyze[themes]" value="themes" <?php checked( $wpvulnerability_analyze['themes'] ); ?> <?php disabled( $forced['themes'] ); ?> />
2115 <?php esc_html_e( 'Themes', 'wpvulnerability' ); ?>
2116 </label>
2117 <br/>
2118 <label>
2119 <input type="checkbox" name="wpvulnerability-analyze[php]" value="php" <?php checked( $wpvulnerability_analyze['php'] ); ?> <?php disabled( $forced['php'] ); ?> />
2120 <?php esc_html_e( 'PHP', 'wpvulnerability' ); ?>
2121 </label>
2122 <br/>
2123 <label>
2124 <input type="checkbox" name="wpvulnerability-analyze[apache]" value="apache" <?php checked( $wpvulnerability_analyze['apache'] ); ?> <?php disabled( $forced['apache'] ); ?> />
2125 <?php esc_html_e( 'Apache HTTPD', 'wpvulnerability' ); ?>
2126 </label>
2127 <br/>
2128 <label>
2129 <input type="checkbox" name="wpvulnerability-analyze[nginx]" value="nginx" <?php checked( $wpvulnerability_analyze['nginx'] ); ?> <?php disabled( $forced['nginx'] ); ?> />
2130 <?php esc_html_e( 'nginx', 'wpvulnerability' ); ?>
2131 </label>
2132 <br/>
2133 <label>
2134 <input type="checkbox" name="wpvulnerability-analyze[mariadb]" value="mariadb" <?php checked( $wpvulnerability_analyze['mariadb'] ); ?> <?php disabled( $forced['mariadb'] ); ?> />
2135 <?php esc_html_e( 'MariaDB', 'wpvulnerability' ); ?>
2136 </label>
2137 <br/>
2138 <label>
2139 <input type="checkbox" name="wpvulnerability-analyze[mysql]" value="mysql" <?php checked( $wpvulnerability_analyze['mysql'] ); ?> <?php disabled( $forced['mysql'] ); ?> />
2140 <?php esc_html_e( 'MySQL', 'wpvulnerability' ); ?>
2141 </label>
2142 <br/>
2143 <label>
2144 <input type="checkbox" name="wpvulnerability-analyze[imagemagick]" value="imagemagick" <?php checked( $wpvulnerability_analyze['imagemagick'] ); ?> <?php disabled( $forced['imagemagick'] ); ?> />
2145 <?php esc_html_e( 'ImageMagick', 'wpvulnerability' ); ?>
2146 </label>
2147 <br/>
2148 <label>
2149 <input type="checkbox" name="wpvulnerability-analyze[curl]" value="curl" <?php checked( $wpvulnerability_analyze['curl'] ); ?> <?php disabled( $forced['curl'] ); ?> />
2150 <?php esc_html_e( 'curl', 'wpvulnerability' ); ?>
2151 </label>
2152 <br/>
2153 <label>
2154 <input type="checkbox" name="wpvulnerability-analyze[memcached]" value="memcached" <?php checked( $wpvulnerability_analyze['memcached'] ); ?> <?php disabled( $forced['memcached'] ); ?> />
2155 <?php esc_html_e( 'memcached', 'wpvulnerability' ); ?>
2156 </label>
2157 <br/>
2158 <label>
2159 <input type="checkbox" name="wpvulnerability-analyze[redis]" value="redis" <?php checked( $wpvulnerability_analyze['redis'] ); ?> <?php disabled( $forced['redis'] ); ?> />
2160 <?php esc_html_e( 'Redis', 'wpvulnerability' ); ?>
2161 </label>
2162 <br/>
2163 <label>
2164 <input type="checkbox" name="wpvulnerability-analyze[sqlite]" value="sqlite" <?php checked( $wpvulnerability_analyze['sqlite'] ); ?> <?php disabled( $forced['sqlite'] ); ?> />
2165 <?php esc_html_e( 'SQLite', 'wpvulnerability' ); ?>
2166 </label>
2167 <p><a href="https://www.wpvulnerability.com/plugin/#force-hiding-checks" target="_blank"><small><i><?php esc_html_e( 'Read more about how to force the deactivation of an item.', 'wpvulnerability' ); ?></i></small></a></p>
2168 </div>
2169 <?php
2170 }
2171
2172 /**
2173 * Sanitize fields before saving into the database
2174 *
2175 * @since 2.0.0
2176 *
2177 * @param array<string, mixed> $input The input fields to sanitize.
2178 *
2179 * @return array<string, mixed> The sanitized values.
2180 */
2181 function wpvulnerability_admin_sanitize( $input ) {
2182
2183 $input_emails = array();
2184 $defaults = array(
2185 'notify' => array(
2186 'email' => 'n',
2187 'slack' => 'n',
2188 'teams' => 'n',
2189 ),
2190 'slack_webhook' => '',
2191 'teams_webhook' => '',
2192 'day' => 'monday',
2193 'hour' => 0,
2194 'minute' => 0,
2195 'cache' => 12,
2196 'log_retention' => 0,
2197 'emails' => null,
2198 'period' => 'weekly',
2199 );
2200
2201 $current_values = is_multisite() ? get_site_option( 'wpvulnerability-config', array() ) : get_option( 'wpvulnerability-config', array() );
2202 if ( ! is_array( $current_values ) ) {
2203 $current_values = array();
2204 }
2205
2206 $sanitized_values = $current_values;
2207
2208 if ( isset( $input['emails'] ) ) {
2209 $input_email_text = explode( ',', is_scalar( $input['emails'] ) ? (string) $input['emails'] : '' );
2210
2211 foreach ( $input_email_text as $input_email ) {
2212 $input_email = sanitize_email( trim( (string) $input_email ) );
2213
2214 if ( is_email( $input_email ) ) {
2215 $input_emails[] = $input_email;
2216 }
2217 }
2218
2219 if ( count( $input_emails ) ) {
2220 $sanitized_values['emails'] = implode( ',', $input_emails );
2221 } else {
2222 $sanitized_values['emails'] = null;
2223 }
2224 }
2225
2226 if ( isset( $input['period'] ) ) {
2227
2228 // Check the value of the period field and sanitize it.
2229 switch ( $input['period'] ) {
2230 case 'never':
2231 $sanitized_values['period'] = 'never';
2232 break;
2233 case 'daily':
2234 $sanitized_values['period'] = 'daily';
2235 break;
2236 case 'weekly':
2237 $sanitized_values['period'] = 'weekly';
2238 break;
2239 default:
2240 $sanitized_values['period'] = 'weekly';
2241 break;
2242 }
2243 }
2244
2245 if ( isset( $input['day'] ) ) {
2246 $day = strtolower( sanitize_text_field( is_scalar( $input['day'] ) ? (string) $input['day'] : '' ) );
2247 $valid_days = array( 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' );
2248 $sanitized_values['day'] = in_array( $day, $valid_days, true ) ? $day : 'monday';
2249 }
2250
2251 if ( isset( $input['hour'] ) ) {
2252 $hour = is_scalar( $input['hour'] ) ? (int) $input['hour'] : 0;
2253 $sanitized_values['hour'] = max( 0, min( 23, $hour ) );
2254 }
2255
2256 if ( isset( $input['minute'] ) ) {
2257 $minute = is_scalar( $input['minute'] ) ? (int) $input['minute'] : 0;
2258 $sanitized_values['minute'] = max( 0, min( 59, $minute ) );
2259 }
2260
2261 if ( isset( $input['cache'] ) ) {
2262 $cache = is_scalar( $input['cache'] ) ? (int) $input['cache'] : 0;
2263 $sanitized_values['cache'] = in_array( $cache, array( 1, 6, 12, 24 ), true ) ? $cache : 12;
2264 }
2265
2266 if ( isset( $input['log_retention'] ) ) {
2267 $retention = is_scalar( $input['log_retention'] ) ? (int) $input['log_retention'] : 0;
2268 if ( in_array( $retention, wpvulnerability_get_log_retention_values(), true ) ) {
2269 $sanitized_values['log_retention'] = $retention;
2270 }
2271 }
2272
2273 if ( isset( $input['notify'] ) && is_array( $input['notify'] ) ) {
2274 $notify_raw = (array) wp_unslash( $input['notify'] );
2275 $notify_input = array();
2276 foreach ( $notify_raw as $k => $v ) {
2277 $notify_input[ (string) $k ] = sanitize_text_field( is_scalar( $v ) ? (string) $v : '' );
2278 }
2279 $sanitized_values['notify'] = wpvulnerability_normalize_notify_settings( $notify_input );
2280 }
2281
2282 if ( isset( $input['slack_webhook'] ) ) {
2283 $slack_url = trim( is_scalar( $input['slack_webhook'] ) ? (string) $input['slack_webhook'] : '' );
2284 if ( '' !== $slack_url ) {
2285 $validated_slack = wpvulnerability_validate_webhook_url(
2286 $slack_url,
2287 array( 'hooks.slack.com' )
2288 );
2289 if ( '' === $validated_slack ) {
2290 add_settings_error(
2291 'wpvulnerability-config',
2292 'invalid-slack-webhook',
2293 __( 'Invalid Slack webhook URL. Must be a valid HTTPS URL from hooks.slack.com', 'wpvulnerability' ),
2294 'error'
2295 );
2296 $sanitized_values['slack_webhook'] = '';
2297 } else {
2298 $sanitized_values['slack_webhook'] = $validated_slack;
2299 }
2300 } else {
2301 $sanitized_values['slack_webhook'] = '';
2302 }
2303 }
2304
2305 if ( isset( $input['teams_webhook'] ) ) {
2306 $teams_url = trim( is_scalar( $input['teams_webhook'] ) ? (string) $input['teams_webhook'] : '' );
2307 if ( '' !== $teams_url ) {
2308 $validated_teams = wpvulnerability_validate_webhook_url(
2309 $teams_url,
2310 array( 'office.com', 'office365.com', 'api.hooks.microsoft.com' )
2311 );
2312 if ( '' === $validated_teams ) {
2313 add_settings_error(
2314 'wpvulnerability-config',
2315 'invalid-teams-webhook',
2316 __( 'Invalid Microsoft Teams webhook URL. Must be a valid HTTPS URL from office.com, office365.com, or api.hooks.microsoft.com', 'wpvulnerability' ),
2317 'error'
2318 );
2319 $sanitized_values['teams_webhook'] = '';
2320 } else {
2321 $sanitized_values['teams_webhook'] = $validated_teams;
2322 }
2323 } else {
2324 $sanitized_values['teams_webhook'] = '';
2325 }
2326 }
2327
2328 if ( isset( $input['discord_webhook'] ) ) {
2329 $discord_url = trim( is_scalar( $input['discord_webhook'] ) ? (string) $input['discord_webhook'] : '' );
2330 if ( '' !== $discord_url ) {
2331 $validated_discord = wpvulnerability_validate_webhook_url(
2332 $discord_url,
2333 array( 'discord.com', 'discordapp.com' )
2334 );
2335 if ( '' === $validated_discord ) {
2336 add_settings_error(
2337 'wpvulnerability-config',
2338 'invalid-discord-webhook',
2339 __( 'Invalid Discord webhook URL. Must be a valid HTTPS URL from discord.com or discordapp.com', 'wpvulnerability' ),
2340 'error'
2341 );
2342 $sanitized_values['discord_webhook'] = '';
2343 } else {
2344 $sanitized_values['discord_webhook'] = $validated_discord;
2345 }
2346 } else {
2347 $sanitized_values['discord_webhook'] = '';
2348 }
2349 }
2350
2351 if ( isset( $input['telegram_bot_token'] ) ) {
2352 $telegram_bot_token = sanitize_text_field( trim( is_scalar( $input['telegram_bot_token'] ) ? (string) $input['telegram_bot_token'] : '' ) );
2353 if ( '' !== $telegram_bot_token ) {
2354 if ( ! preg_match( '/^\d+:[A-Za-z0-9_-]+$/', $telegram_bot_token ) ) {
2355 add_settings_error(
2356 'wpvulnerability-config',
2357 'invalid-telegram-token',
2358 __( 'Invalid Telegram bot token format. Must be like: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', 'wpvulnerability' ),
2359 'error'
2360 );
2361 $sanitized_values['telegram_bot_token'] = '';
2362 } else {
2363 $sanitized_values['telegram_bot_token'] = $telegram_bot_token;
2364 }
2365 } else {
2366 $sanitized_values['telegram_bot_token'] = '';
2367 }
2368 }
2369
2370 if ( isset( $input['telegram_chat_id'] ) ) {
2371 $sanitized_values['telegram_chat_id'] = sanitize_text_field( trim( is_scalar( $input['telegram_chat_id'] ) ? (string) $input['telegram_chat_id'] : '' ) );
2372 }
2373
2374 $sanitized_values = wp_parse_args( $sanitized_values, $defaults );
2375
2376 // Schedule notification based on sanitized values.
2377 wpvulnerability_schedule_notification_event( $sanitized_values );
2378 return $sanitized_values;
2379 }
2380
2381 /**
2382 * Sanitizes the input fields for vulnerability analysis.
2383 *
2384 * This function takes an array of input fields and sanitizes them by setting
2385 * the corresponding values in the output array to 1 if they are present in
2386 * the input. The possible fields are 'core', 'plugins', 'themes', 'php',
2387 * 'apache', and 'nginx'.
2388 *
2389 * @since 3.3.0
2390 *
2391 * @param array<string, mixed> $input The input fields to sanitize.
2392 *
2393 * @return array<string, int> The sanitized values with keys 'core', 'plugins', 'themes', 'php', 'apache', and 'nginx'.
2394 */
2395 function wpvulnerability_analyze_sanitize( $input ) {
2396 $sanitized_values = array(
2397 'core' => isset( $input['core'] ) ? 1 : 0,
2398 'plugins' => isset( $input['plugins'] ) ? 1 : 0,
2399 'themes' => isset( $input['themes'] ) ? 1 : 0,
2400 'php' => isset( $input['php'] ) ? 1 : 0,
2401 'apache' => isset( $input['apache'] ) ? 1 : 0,
2402 'nginx' => isset( $input['nginx'] ) ? 1 : 0,
2403 'mariadb' => isset( $input['mariadb'] ) ? 1 : 0,
2404 'mysql' => isset( $input['mysql'] ) ? 1 : 0,
2405 'imagemagick' => isset( $input['imagemagick'] ) ? 1 : 0,
2406 'curl' => isset( $input['curl'] ) ? 1 : 0,
2407 'memcached' => isset( $input['memcached'] ) ? 1 : 0,
2408 'redis' => isset( $input['redis'] ) ? 1 : 0,
2409 'sqlite' => isset( $input['sqlite'] ) ? 1 : 0,
2410 );
2411
2412 foreach ( $sanitized_values as $component => $value ) {
2413 $constant = 'WPVULNERABILITY_HIDE_' . strtoupper( (string) $component );
2414 if ( defined( $constant ) && constant( $constant ) ) {
2415 $sanitized_values[ $component ] = 1;
2416 }
2417 }
2418
2419 return $sanitized_values;
2420 }
2421
2422 /**
2423 * Content for the Dashboard Widget
2424 *
2425 * @since 2.2.0
2426 *
2427 * @return void
2428 */
2429 function wpvulnerability_admin_dashboard_content() {
2430
2431 if ( ! wpvulnerability_capabilities() ) {
2432 return;
2433 }
2434
2435 // Get vulnerability counts for all components.
2436 $core_count = wpvulnerability_get_component_count( 'core' );
2437 $plugins_count = wpvulnerability_get_component_count( 'plugins' );
2438 $themes_count = wpvulnerability_get_component_count( 'themes' );
2439 $php_count = wpvulnerability_get_component_count( 'php' );
2440 $apache_count = wpvulnerability_get_component_count( 'apache' );
2441 $nginx_count = wpvulnerability_get_component_count( 'nginx' );
2442 $mariadb_count = wpvulnerability_get_component_count( 'mariadb' );
2443 $mysql_count = wpvulnerability_get_component_count( 'mysql' );
2444 $imagemagick_count = wpvulnerability_get_component_count( 'imagemagick' );
2445 $curl_count = wpvulnerability_get_component_count( 'curl' );
2446 $memcached_count = wpvulnerability_get_component_count( 'memcached' );
2447 $redis_count = wpvulnerability_get_component_count( 'redis' );
2448 $sqlite_count = wpvulnerability_get_component_count( 'sqlite' );
2449
2450 // Calculate total vulnerabilities (only for enabled components).
2451 $total_vulnerabilities = 0;
2452 if ( wpvulnerability_analyze_filter( 'core' ) ) {
2453 $total_vulnerabilities += $core_count;
2454 }
2455 if ( wpvulnerability_analyze_filter( 'plugins' ) ) {
2456 $total_vulnerabilities += $plugins_count;
2457 }
2458 if ( wpvulnerability_analyze_filter( 'themes' ) ) {
2459 $total_vulnerabilities += $themes_count;
2460 }
2461 if ( wpvulnerability_analyze_filter( 'php' ) ) {
2462 $total_vulnerabilities += $php_count;
2463 }
2464 if ( wpvulnerability_analyze_filter( 'apache' ) ) {
2465 $total_vulnerabilities += $apache_count;
2466 }
2467 if ( wpvulnerability_analyze_filter( 'nginx' ) ) {
2468 $total_vulnerabilities += $nginx_count;
2469 }
2470 if ( wpvulnerability_analyze_filter( 'mariadb' ) ) {
2471 $total_vulnerabilities += $mariadb_count;
2472 }
2473 if ( wpvulnerability_analyze_filter( 'mysql' ) ) {
2474 $total_vulnerabilities += $mysql_count;
2475 }
2476 if ( wpvulnerability_analyze_filter( 'imagemagick' ) ) {
2477 $total_vulnerabilities += $imagemagick_count;
2478 }
2479 if ( wpvulnerability_analyze_filter( 'curl' ) ) {
2480 $total_vulnerabilities += $curl_count;
2481 }
2482 if ( wpvulnerability_analyze_filter( 'memcached' ) ) {
2483 $total_vulnerabilities += $memcached_count;
2484 }
2485 if ( wpvulnerability_analyze_filter( 'redis' ) ) {
2486 $total_vulnerabilities += $redis_count;
2487 }
2488 if ( wpvulnerability_analyze_filter( 'sqlite' ) ) {
2489 $total_vulnerabilities += $sqlite_count;
2490 }
2491
2492 // Determine status badge.
2493 $status_class = 'wpvuln-status-secure';
2494 $status_text = '' . __( 'All Clear', 'wpvulnerability' );
2495 $status_icon = '';
2496
2497 if ( $total_vulnerabilities > 0 ) {
2498 if ( ( wpvulnerability_analyze_filter( 'core' ) && $core_count > 0 ) ||
2499 ( wpvulnerability_analyze_filter( 'php' ) && $php_count > 0 ) ||
2500 $total_vulnerabilities > 5 ) {
2501 $status_class = 'wpvuln-status-critical';
2502 $status_icon = '';
2503 /* translators: %d: number of vulnerabilities */
2504 $status_text = sprintf( _n( '%d Critical Issue Found', '%d Critical Issues Found', $total_vulnerabilities, 'wpvulnerability' ), $total_vulnerabilities );
2505 } else {
2506 $status_class = 'wpvuln-status-warning';
2507 $status_icon = '';
2508 /* translators: %d: number of vulnerabilities */
2509 $status_text = sprintf( _n( '%d Issue Found', '%d Issues Found', $total_vulnerabilities, 'wpvulnerability' ), $total_vulnerabilities );
2510 }
2511 }
2512
2513 // Get last check time.
2514 $raw_core_cache = get_option( 'wpvulnerability-core-cache', '' );
2515 $core_cache = json_decode( is_string( $raw_core_cache ) ? $raw_core_cache : '', true );
2516 $last_check_text = __( 'Never checked', 'wpvulnerability' );
2517 if ( $core_cache && is_numeric( $core_cache ) ) {
2518 $cache_hours = wpvulnerability_cache_hours();
2519 $last_check_time = $core_cache - ( $cache_hours * 3600 );
2520 $time_diff = time() - $last_check_time;
2521 if ( $time_diff < 3600 ) {
2522 $minutes_ago = (int) floor( $time_diff / 60 );
2523 /* translators: %d: number of minutes */
2524 $last_check_text = sprintf( _n( '%d minute ago', '%d minutes ago', $minutes_ago, 'wpvulnerability' ), $minutes_ago );
2525 } else {
2526 $hours_ago = (int) floor( $time_diff / 3600 );
2527 /* translators: %d: number of hours */
2528 $last_check_text = sprintf( _n( '%d hour ago', '%d hours ago', $hours_ago, 'wpvulnerability' ), $hours_ago );
2529 }
2530 }
2531
2532 // Status badge.
2533 echo '<div class="wpvuln-status-badge ' . esc_attr( $status_class ) . '">';
2534 echo esc_html( $status_text );
2535 echo '</div>';
2536
2537 // Meta information.
2538 echo '<div class="wpvuln-meta">';
2539 /* translators: %s: time since last check */
2540 echo esc_html( sprintf( __( 'Last checked: %s', 'wpvulnerability' ), $last_check_text ) );
2541 echo ' | <a href="' . esc_url( admin_url( 'options-general.php?page=wpvulnerability-options&tab=tools' ) ) . '" class="wpvuln-refresh-btn">↻ ' . esc_html( __( 'Refresh Now', 'wpvulnerability' ) ) . '</a>';
2542 echo '</div>';
2543
2544 // If no vulnerabilities, show empty state.
2545 if ( 0 === $total_vulnerabilities ) {
2546 echo '<div class="wpvuln-empty-state">';
2547 echo '<div class="wpvuln-empty-state-icon">✓</div>';
2548 echo '<div class="wpvuln-empty-state-title">' . esc_html( __( 'No vulnerabilities detected', 'wpvulnerability' ) ) . '</div>';
2549 echo '<div class="wpvuln-empty-state-text">' . esc_html( __( 'Your site is up to date and secure', 'wpvulnerability' ) ) . '</div>';
2550 echo '</div>';
2551 } else {
2552 // WordPress Components section.
2553 echo '<div class="wpvuln-section-title">' . esc_html( __( 'WordPress Components', 'wpvulnerability' ) ) . '</div>';
2554
2555 // Core.
2556 if ( wpvulnerability_analyze_filter( 'core' ) ) {
2557 $badge_class = $core_count > 0 ? 'wpvuln-badge-critical' : 'wpvuln-badge-secure';
2558 $badge_icon = $core_count > 0 ? '' : '';
2559 echo '<div class="wpvuln-component">';
2560 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-wordpress.svg" width="16" height="16" alt="">';
2561 echo '<span class="wpvuln-component-name">' . esc_html( __( 'WordPress Core', 'wpvulnerability' ) ) . '</span>';
2562 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $core_count ) . '</span>';
2563 echo '</div>';
2564 }
2565
2566 // Plugins.
2567 if ( wpvulnerability_analyze_filter( 'plugins' ) ) {
2568 $badge_class = $plugins_count > 0 ? 'wpvuln-badge-critical' : 'wpvuln-badge-secure';
2569 $badge_icon = $plugins_count > 0 ? '' : '';
2570 echo '<div class="wpvuln-component">';
2571 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-plugin.svg" width="16" height="16" alt="">';
2572 echo '<span class="wpvuln-component-name">' . esc_html( __( 'Plugins', 'wpvulnerability' ) ) . '</span>';
2573 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $plugins_count ) . '</span>';
2574 echo '</div>';
2575 if ( $plugins_count > 0 ) {
2576 echo '<div class="wpvuln-plugin-list">';
2577 echo wpvulnerability_list_plugins(); // phpcs:ignore
2578 echo '</div>';
2579 }
2580 }
2581
2582 // Themes.
2583 if ( wpvulnerability_analyze_filter( 'themes' ) ) {
2584 $badge_class = $themes_count > 0 ? 'wpvuln-badge-critical' : 'wpvuln-badge-secure';
2585 $badge_icon = $themes_count > 0 ? '' : '';
2586 echo '<div class="wpvuln-component">';
2587 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-theme.svg" width="16" height="16" alt="">';
2588 echo '<span class="wpvuln-component-name">' . esc_html( __( 'Themes', 'wpvulnerability' ) ) . '</span>';
2589 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $themes_count ) . '</span>';
2590 echo '</div>';
2591 if ( $themes_count > 0 ) {
2592 echo '<div class="wpvuln-plugin-list">';
2593 echo wpvulnerability_list_themes(); // phpcs:ignore
2594 echo '</div>';
2595 }
2596 }
2597
2598 // Server Software section.
2599 $php_version = wpvulnerability_detect_php();
2600 $webserver = wpvulnerability_detect_webserver();
2601 $sqlserver = wpvulnerability_detect_sqlserver();
2602 $show_server_section = false;
2603
2604 // Check if any server software is detected.
2605 if ( ( wpvulnerability_analyze_filter( 'php' ) && $php_version ) ||
2606 ( isset( $webserver['id'] ) && isset( $webserver['version'] ) ) ||
2607 ( isset( $sqlserver['id'] ) && isset( $sqlserver['version'] ) ) ||
2608 wpvulnerability_get_software_version( 'imagemagick' ) ||
2609 wpvulnerability_get_software_version( 'curl' ) ||
2610 wpvulnerability_get_software_version( 'memcached' ) ||
2611 wpvulnerability_get_software_version( 'redis' ) ||
2612 wpvulnerability_get_software_version( 'sqlite' ) ) {
2613 $show_server_section = true;
2614 }
2615
2616 if ( $show_server_section ) {
2617 echo '<div class="wpvuln-section-title">' . esc_html( __( 'Server Software', 'wpvulnerability' ) ) . '</div>';
2618 echo '<div class="wpvuln-grid">';
2619
2620 // PHP.
2621 if ( wpvulnerability_analyze_filter( 'php' ) && $php_version ) {
2622 $badge_class = $php_count > 0 ? 'wpvuln-badge-critical' : 'wpvuln-badge-secure';
2623 $badge_icon = $php_count > 0 ? '' : '';
2624 echo '<div class="wpvuln-component">';
2625 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-php.svg" width="16" height="16" alt="">';
2626 echo '<span class="wpvuln-component-name">PHP ' . esc_html( $php_version ) . '</span>';
2627 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $php_count ) . '</span>';
2628 echo wp_kses(
2629 wpvulnerability_eol_badge_html( 'php' ),
2630 array(
2631 'span' => array(
2632 'class' => array(),
2633 'title' => array(),
2634 ),
2635 )
2636 );
2637 echo '</div>';
2638 }
2639
2640 // Web server (Apache/nginx).
2641 if ( isset( $webserver['id'] ) && isset( $webserver['version'] ) ) {
2642 if ( 'apache' === $webserver['id'] && wpvulnerability_analyze_filter( 'apache' ) ) {
2643 $badge_class = $apache_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2644 $badge_icon = $apache_count > 0 ? '' : '';
2645 echo '<div class="wpvuln-component">';
2646 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-apache.svg" width="16" height="16" alt="">';
2647 echo '<span class="wpvuln-component-name">Apache ' . esc_html( $webserver['version'] ) . '</span>';
2648 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $apache_count ) . '</span>';
2649 echo wp_kses(
2650 wpvulnerability_eol_badge_html( 'apache' ),
2651 array(
2652 'span' => array(
2653 'class' => array(),
2654 'title' => array(),
2655 ),
2656 )
2657 );
2658 echo '</div>';
2659 } elseif ( 'nginx' === $webserver['id'] && wpvulnerability_analyze_filter( 'nginx' ) ) {
2660 $badge_class = $nginx_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2661 $badge_icon = $nginx_count > 0 ? '' : '';
2662 echo '<div class="wpvuln-component">';
2663 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-nginx.svg" width="16" height="16" alt="">';
2664 echo '<span class="wpvuln-component-name">nginx ' . esc_html( $webserver['version'] ) . '</span>';
2665 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $nginx_count ) . '</span>';
2666 echo wp_kses(
2667 wpvulnerability_eol_badge_html( 'nginx' ),
2668 array(
2669 'span' => array(
2670 'class' => array(),
2671 'title' => array(),
2672 ),
2673 )
2674 );
2675 echo '</div>';
2676 }
2677 }
2678
2679 // Database (MariaDB/MySQL).
2680 if ( isset( $sqlserver['id'] ) && isset( $sqlserver['version'] ) ) {
2681 if ( 'mariadb' === $sqlserver['id'] && wpvulnerability_analyze_filter( 'mariadb' ) ) {
2682 $badge_class = $mariadb_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2683 $badge_icon = $mariadb_count > 0 ? '' : '';
2684 echo '<div class="wpvuln-component">';
2685 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-mariadb.svg" width="16" height="16" alt="">';
2686 echo '<span class="wpvuln-component-name">MariaDB ' . esc_html( $sqlserver['version'] ) . '</span>';
2687 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $mariadb_count ) . '</span>';
2688 echo wp_kses(
2689 wpvulnerability_eol_badge_html( 'mariadb' ),
2690 array(
2691 'span' => array(
2692 'class' => array(),
2693 'title' => array(),
2694 ),
2695 )
2696 );
2697 echo '</div>';
2698 } elseif ( 'mysql' === $sqlserver['id'] && wpvulnerability_analyze_filter( 'mysql' ) ) {
2699 $badge_class = $mysql_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2700 $badge_icon = $mysql_count > 0 ? '' : '';
2701 echo '<div class="wpvuln-component">';
2702 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-mysql.svg" width="16" height="16" alt="">';
2703 echo '<span class="wpvuln-component-name">MySQL ' . esc_html( $sqlserver['version'] ) . '</span>';
2704 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $mysql_count ) . '</span>';
2705 echo wp_kses(
2706 wpvulnerability_eol_badge_html( 'mysql' ),
2707 array(
2708 'span' => array(
2709 'class' => array(),
2710 'title' => array(),
2711 ),
2712 )
2713 );
2714 echo '</div>';
2715 }
2716 }
2717
2718 // ImageMagick.
2719 $imagemagick_version = wpvulnerability_get_software_version( 'imagemagick' );
2720 if ( $imagemagick_version && wpvulnerability_analyze_filter( 'imagemagick' ) ) {
2721 $badge_class = $imagemagick_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2722 $badge_icon = $imagemagick_count > 0 ? '' : '';
2723 echo '<div class="wpvuln-component">';
2724 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-imagemagick.svg" width="16" height="16" alt="">';
2725 echo '<span class="wpvuln-component-name">ImageMagick ' . esc_html( $imagemagick_version ) . '</span>';
2726 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $imagemagick_count ) . '</span>';
2727 echo wp_kses(
2728 wpvulnerability_eol_badge_html( 'imagemagick' ),
2729 array(
2730 'span' => array(
2731 'class' => array(),
2732 'title' => array(),
2733 ),
2734 )
2735 );
2736 echo '</div>';
2737 }
2738
2739 // curl.
2740 $curl_version = wpvulnerability_get_software_version( 'curl' );
2741 if ( $curl_version && wpvulnerability_analyze_filter( 'curl' ) ) {
2742 $badge_class = $curl_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2743 $badge_icon = $curl_count > 0 ? '' : '';
2744 echo '<div class="wpvuln-component">';
2745 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-curl.svg" width="16" height="16" alt="">';
2746 echo '<span class="wpvuln-component-name">curl ' . esc_html( $curl_version ) . '</span>';
2747 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $curl_count ) . '</span>';
2748 echo wp_kses(
2749 wpvulnerability_eol_badge_html( 'curl' ),
2750 array(
2751 'span' => array(
2752 'class' => array(),
2753 'title' => array(),
2754 ),
2755 )
2756 );
2757 echo '</div>';
2758 }
2759
2760 // memcached.
2761 $memcached_version = wpvulnerability_get_software_version( 'memcached' );
2762 if ( $memcached_version && wpvulnerability_analyze_filter( 'memcached' ) ) {
2763 $badge_class = $memcached_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2764 $badge_icon = $memcached_count > 0 ? '' : '';
2765 echo '<div class="wpvuln-component">';
2766 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-memcached.svg" width="16" height="16" alt="">';
2767 echo '<span class="wpvuln-component-name">memcached ' . esc_html( $memcached_version ) . '</span>';
2768 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $memcached_count ) . '</span>';
2769 echo wp_kses(
2770 wpvulnerability_eol_badge_html( 'memcached' ),
2771 array(
2772 'span' => array(
2773 'class' => array(),
2774 'title' => array(),
2775 ),
2776 )
2777 );
2778 echo '</div>';
2779 }
2780
2781 // Redis.
2782 $redis_version = wpvulnerability_get_software_version( 'redis' );
2783 if ( $redis_version && wpvulnerability_analyze_filter( 'redis' ) ) {
2784 $badge_class = $redis_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2785 $badge_icon = $redis_count > 0 ? '' : '';
2786 echo '<div class="wpvuln-component">';
2787 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-redis.svg" width="16" height="16" alt="">';
2788 echo '<span class="wpvuln-component-name">Redis ' . esc_html( $redis_version ) . '</span>';
2789 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $redis_count ) . '</span>';
2790 echo wp_kses(
2791 wpvulnerability_eol_badge_html( 'redis' ),
2792 array(
2793 'span' => array(
2794 'class' => array(),
2795 'title' => array(),
2796 ),
2797 )
2798 );
2799 echo '</div>';
2800 }
2801
2802 // SQLite.
2803 $sqlite_version = wpvulnerability_get_software_version( 'sqlite' );
2804 if ( $sqlite_version && wpvulnerability_analyze_filter( 'sqlite' ) ) {
2805 $badge_class = $sqlite_count > 0 ? 'wpvuln-badge-warning' : 'wpvuln-badge-secure';
2806 $badge_icon = $sqlite_count > 0 ? '' : '';
2807 echo '<div class="wpvuln-component">';
2808 echo '<img src="' . esc_url( WPVULNERABILITY_PLUGIN_URL ) . 'assets/icon-sqlite.svg" width="16" height="16" alt="">';
2809 echo '<span class="wpvuln-component-name">SQLite ' . esc_html( $sqlite_version ) . '</span>';
2810 echo '<span class="wpvuln-badge ' . esc_attr( $badge_class ) . '">' . esc_html( $badge_icon ) . ' ' . esc_html( (string) $sqlite_count ) . '</span>';
2811 echo wp_kses(
2812 wpvulnerability_eol_badge_html( 'sqlite' ),
2813 array(
2814 'span' => array(
2815 'class' => array(),
2816 'title' => array(),
2817 ),
2818 )
2819 );
2820 echo '</div>';
2821 }
2822
2823 echo '</div>';
2824 }
2825 }
2826
2827 // Footer links.
2828 echo '<div class="wpvuln-footer">';
2829 if ( version_compare( get_bloginfo( 'version' ), '5.2', '>=' ) ) {
2830 echo '<a href="' . esc_url( get_admin_url( null, 'site-health.php' ) ) . '">' . esc_html( __( 'Site Health', 'wpvulnerability' ) ) . '</a> | ';
2831 }
2832 echo '<a href="' . esc_url( admin_url( 'options-general.php?page=wpvulnerability-options' ) ) . '">' . esc_html( __( 'Settings', 'wpvulnerability' ) ) . '</a>';
2833 echo '</div>';
2834 }
2835
2836 /**
2837 * Created a widget in the WordPress dashboard with vulnerability info.
2838 *
2839 * @since 2.2.0
2840 *
2841 * @return void
2842 */
2843 function wpvulnerability_admin_dashboard() {
2844 if ( wpvulnerability_capabilities() ) {
2845 wp_add_dashboard_widget(
2846 'wpvulnerability',
2847 __( 'WPVulnerability Status', 'wpvulnerability' ),
2848 'wpvulnerability_admin_dashboard_content',
2849 null,
2850 null,
2851 'side',
2852 'high'
2853 );
2854 }
2855 }
2856 add_action( 'wp_dashboard_setup', 'wpvulnerability_admin_dashboard' );
2857
2858 /**
2859 * Initializes the WP-Admin settings page for the WP Vulnerability plugin
2860 *
2861 * @since 2.0.0
2862 *
2863 * @return void
2864 */
2865 function wpvulnerability_admin_init() {
2866 // Register the plugin settings to be saved in the database.
2867 register_setting(
2868 'admin_wpvulnerability_settings',
2869 'wpvulnerability-config',
2870 array(
2871 'sanitize_callback' => 'wpvulnerability_admin_sanitize',
2872 'default' => array(),
2873 )
2874 );
2875
2876 // Add a section to the settings page.
2877 add_settings_section(
2878 'admin_wpvulnerability_settings',
2879 __( 'Receive vulnerability notifications', 'wpvulnerability' ),
2880 'wpvulnerability_admin_section_notifications',
2881 'wpvulnerability-config'
2882 );
2883
2884 // Add a field for the cache expiration time.
2885 add_settings_field(
2886 'wpvulnerability_cache',
2887 __( 'Cache expiration time', 'wpvulnerability' ),
2888 'wpvulnerability_admin_cache_callback',
2889 'wpvulnerability-config',
2890 'admin_wpvulnerability_settings'
2891 );
2892
2893 // Add a field for the notification period.
2894 add_settings_field(
2895 'wpvulnerability_period',
2896 __( 'How often you want to receive notifications', 'wpvulnerability' ),
2897 'wpvulnerability_admin_period_callback',
2898 'wpvulnerability-config',
2899 'admin_wpvulnerability_settings'
2900 );
2901
2902 // Add a field for notification methods.
2903 add_settings_field(
2904 'wpvulnerability_notify',
2905 __( 'Where do you want to receive notifications?', 'wpvulnerability' ),
2906 'wpvulnerability_admin_notify_callback',
2907 'wpvulnerability-config',
2908 'admin_wpvulnerability_settings'
2909 );
2910
2911 // Add a field for the email addresses.
2912 add_settings_field(
2913 'wpvulnerability_emails',
2914 __( 'Email addresses to notify (separated by commas)', 'wpvulnerability' ),
2915 'wpvulnerability_admin_emails_callback',
2916 'wpvulnerability-config',
2917 'admin_wpvulnerability_settings'
2918 );
2919
2920 // Add a field for the Slack webhook.
2921 add_settings_field(
2922 'wpvulnerability_slack_webhook',
2923 __( 'Slack webhook URL', 'wpvulnerability' ),
2924 'wpvulnerability_admin_slack_callback',
2925 'wpvulnerability-config',
2926 'admin_wpvulnerability_settings'
2927 );
2928
2929 // Add a field for the Teams webhook.
2930 add_settings_field(
2931 'wpvulnerability_teams_webhook',
2932 __( 'Teams webhook URL', 'wpvulnerability' ),
2933 'wpvulnerability_admin_teams_callback',
2934 'wpvulnerability-config',
2935 'admin_wpvulnerability_settings'
2936 );
2937
2938 // Register the settings for analysis.
2939 register_setting(
2940 'admin_wpvulnerability_analyze',
2941 'wpvulnerability-analyze',
2942 array(
2943 'sanitize_callback' => 'wpvulnerability_analyze_sanitize',
2944 'default' => array(),
2945 )
2946 );
2947
2948 // Add a section for analyzing settings.
2949 add_settings_section(
2950 'admin_wpvulnerability_analyze',
2951 __( 'Vulnerabilities to hide', 'wpvulnerability' ),
2952 'wpvulnerability_admin_section_analyze',
2953 'wpvulnerability-analyze'
2954 );
2955
2956 // Add a field for analysis options.
2957 add_settings_field(
2958 'wpvulnerability_analyze',
2959 __( 'What do you want to hide?', 'wpvulnerability' ),
2960 'wpvulnerability_admin_analyze_callback',
2961 'wpvulnerability-analyze',
2962 'admin_wpvulnerability_analyze'
2963 );
2964 }
2965 add_action( 'admin_init', 'wpvulnerability_admin_init' );
2966
2967 /**
2968 * Outputs the Security tab contents.
2969 *
2970 * Displays shell_exec security status, detection methods, and audit logs.
2971 *
2972 * @since 4.3.0
2973 *
2974 * @return void
2975 */
2976 function wpvulnerability_render_admin_tab_security() {
2977 ?>
2978 <section class="section wpvulnerability-security-panel">
2979
2980 <?php wpvulnerability_display_security_status(); ?>
2981 <?php wpvulnerability_display_detection_methods(); ?>
2982 <?php wpvulnerability_display_security_logs(); ?>
2983 </section>
2984 <?php
2985 }
2986
2987 /**
2988 * Displays the security status section.
2989 *
2990 * Shows current security mode, shell_exec availability, and logging status.
2991 *
2992 * @since 4.3.0
2993 *
2994 * @return void
2995 */
2996 function wpvulnerability_display_security_status() {
2997 $security_mode = wpvulnerability_get_security_mode();
2998 $shell_exec_enabled = wpvulnerability_can_shell_exec();
2999 $log_retention = wpvulnerability_log_retention_days();
3000 $total_logs = wpvulnerability_count_shell_exec_logs();
3001 $last_log = null;
3002
3003 $recent_logs = wpvulnerability_get_shell_exec_logs( 1, 1 );
3004 if ( ! empty( $recent_logs ) ) {
3005 $last_log = $recent_logs[0];
3006 }
3007
3008 $mode_labels = array(
3009 'standard' => __( 'Standard (Hybrid Detection)', 'wpvulnerability' ),
3010 'strict' => __( 'Strict (Extensions Only)', 'wpvulnerability' ),
3011 'disabled' => __( 'Disabled (No Detection)', 'wpvulnerability' ),
3012 );
3013
3014 $mode_label = isset( $mode_labels[ $security_mode ] ) ? $mode_labels[ $security_mode ] : $security_mode;
3015 ?>
3016 <div class="wpvulnerability-security-section">
3017 <h3><?php esc_html_e( 'Security Status', 'wpvulnerability' ); ?></h3>
3018
3019 <div class="wpvulnerability-status-grid">
3020 <div class="wpvulnerability-status-item">
3021 <strong><?php esc_html_e( 'Security Mode', 'wpvulnerability' ); ?></strong>
3022 <span><?php echo esc_html( $mode_label ); ?></span>
3023 </div>
3024
3025 <div class="wpvulnerability-status-item">
3026 <strong><?php esc_html_e( 'Shell Execution', 'wpvulnerability' ); ?></strong>
3027 <?php if ( $shell_exec_enabled ) : ?>
3028 <span class="wpvulnerability-status-badge success"><?php esc_html_e( 'Enabled', 'wpvulnerability' ); ?></span>
3029 <?php else : ?>
3030 <span class="wpvulnerability-status-badge disabled"><?php esc_html_e( 'Disabled', 'wpvulnerability' ); ?></span>
3031 <?php endif; ?>
3032 </div>
3033
3034 <div class="wpvulnerability-status-item">
3035 <strong><?php esc_html_e( 'Audit Logging', 'wpvulnerability' ); ?></strong>
3036 <?php if ( $log_retention > 0 ) : ?>
3037 <span class="wpvulnerability-status-badge success">
3038 <?php
3039 /* translators: %d: Number of days. */
3040 echo esc_html( sprintf( __( 'Enabled (%d days)', 'wpvulnerability' ), $log_retention ) );
3041 ?>
3042 </span>
3043 <?php else : ?>
3044 <span class="wpvulnerability-status-badge disabled"><?php esc_html_e( 'Disabled', 'wpvulnerability' ); ?></span>
3045 <?php endif; ?>
3046 </div>
3047
3048 <div class="wpvulnerability-status-item">
3049 <strong><?php esc_html_e( 'Total Log Entries', 'wpvulnerability' ); ?></strong>
3050 <span><?php echo esc_html( number_format_i18n( $total_logs ) ); ?></span>
3051 </div>
3052 </div>
3053
3054 <?php if ( $last_log ) : ?>
3055 <div class="wpvulnerability-status-item" style="margin-top: 10px;">
3056 <strong><?php esc_html_e( 'Last Shell Execution', 'wpvulnerability' ); ?></strong>
3057 <span>
3058 <?php
3059 echo esc_html(
3060 sprintf(
3061 /* translators: %s: time ago */
3062 __( '%s ago', 'wpvulnerability' ),
3063 human_time_diff( absint( strtotime( $last_log->post_date ) ), time() )
3064 )
3065 );
3066 ?>
3067 </span>
3068 </div>
3069 <?php endif; ?>
3070
3071 <div class="wpvulnerability-info-box">
3072 <p>
3073 <?php
3074 esc_html_e( 'Configure security settings via wp-config.php constants: WPVULNERABILITY_DISABLE_SHELL_EXEC, WPVULNERABILITY_SECURITY_MODE, WPVULNERABILITY_SHELL_EXEC_WHITELIST.', 'wpvulnerability' );
3075 ?>
3076 <br>
3077 <?php
3078 printf(
3079 /* translators: %s: URL to security documentation */
3080 wp_kses_post( __( 'See <a href="%s" target="_blank" rel="noopener noreferrer">security configuration documentation</a> for details.', 'wpvulnerability' ) ),
3081 esc_url( 'https://www.wpvulnerability.com/plugin/#security-configuration' )
3082 );
3083 ?>
3084 </p>
3085 </div>
3086 </div>
3087 <?php
3088 }
3089
3090 /**
3091 * Displays the detection methods section.
3092 *
3093 * Shows which detection method was used for each software component
3094 * with reliability scoring.
3095 *
3096 * @since 4.3.0
3097 *
3098 * @return void
3099 */
3100 function wpvulnerability_display_detection_methods() {
3101 $components = array(
3102 'imagemagick' => __( 'ImageMagick', 'wpvulnerability' ),
3103 'redis' => __( 'Redis', 'wpvulnerability' ),
3104 'memcached' => __( 'Memcached', 'wpvulnerability' ),
3105 'sqlite' => __( 'SQLite', 'wpvulnerability' ),
3106 );
3107
3108 $detections = array();
3109
3110 foreach ( array_keys( $components ) as $component ) {
3111 $detection = null;
3112
3113 // Do not run detection for components hidden via the analysis settings or
3114 // a WPVULNERABILITY_HIDE_* constant: they must not trigger shell_exec.
3115 if ( ! wpvulnerability_analyze_filter( $component ) ) {
3116 continue;
3117 }
3118
3119 switch ( $component ) {
3120 case 'imagemagick':
3121 $detection = wpvulnerability_detect_imagemagick();
3122 break;
3123 case 'redis':
3124 $detection = wpvulnerability_detect_redis();
3125 break;
3126 case 'memcached':
3127 $detection = wpvulnerability_detect_memcached();
3128 break;
3129 case 'sqlite':
3130 $detection = wpvulnerability_detect_sqlite();
3131 break;
3132 }
3133
3134 $detections[ $component ] = $detection;
3135 }
3136
3137 $method_labels = array(
3138 'imagick_extension' => __( 'Imagick Extension', 'wpvulnerability' ),
3139 'redis_extension' => __( 'Redis Extension', 'wpvulnerability' ),
3140 'memcached_extension' => __( 'Memcached Extension', 'wpvulnerability' ),
3141 'memcache_extension' => __( 'Memcache Extension', 'wpvulnerability' ),
3142 'sqlite3_extension' => __( 'SQLite3 Extension', 'wpvulnerability' ),
3143 'pdo_sqlite' => __( 'PDO SQLite', 'wpvulnerability' ),
3144 'shell_exec' => __( 'Shell Command', 'wpvulnerability' ),
3145 'binary_exists' => __( 'Binary Check', 'wpvulnerability' ),
3146 'none' => __( 'Not Detected', 'wpvulnerability' ),
3147 );
3148 ?>
3149 <div class="wpvulnerability-security-section">
3150 <h3><?php esc_html_e( 'Software Detection Methods', 'wpvulnerability' ); ?></h3>
3151 <p><?php esc_html_e( 'Shows which detection method was used for each software component and the reliability score.', 'wpvulnerability' ); ?></p>
3152
3153 <table class="wpvulnerability-detection-table">
3154 <thead>
3155 <tr>
3156 <th><?php esc_html_e( 'Component', 'wpvulnerability' ); ?></th>
3157 <th><?php esc_html_e( 'Version', 'wpvulnerability' ); ?></th>
3158 <th><?php esc_html_e( 'Detection Method', 'wpvulnerability' ); ?></th>
3159 <th><?php esc_html_e( 'Reliability', 'wpvulnerability' ); ?></th>
3160 </tr>
3161 </thead>
3162 <tbody>
3163 <?php foreach ( $detections as $component => $detection ) : ?>
3164 <tr>
3165 <td><strong><?php echo esc_html( $components[ $component ] ); ?></strong></td>
3166 <td>
3167 <?php
3168 if ( ! empty( $detection['version'] ) && 'unknown' !== $detection['version'] ) {
3169 echo esc_html( $detection['version'] );
3170 } else {
3171 echo '<span style="color: #646970;">' . esc_html__( 'Not detected', 'wpvulnerability' ) . '</span>';
3172 }
3173 ?>
3174 </td>
3175 <td>
3176 <?php
3177 $method = isset( $method_labels[ $detection['method'] ] ) ? $method_labels[ $detection['method'] ] : $detection['method'];
3178 echo esc_html( $method );
3179 ?>
3180 </td>
3181 <td>
3182 <?php
3183 $reliability = (int) $detection['reliability'];
3184
3185 if ( $reliability > 0 ) {
3186 $reliability_class = 'low';
3187 if ( $reliability >= 80 ) {
3188 $reliability_class = '';
3189 } elseif ( $reliability >= 50 ) {
3190 $reliability_class = 'medium';
3191 }
3192 ?>
3193 <span class="wpvulnerability-reliability-bar">
3194 <span class="wpvulnerability-reliability-fill <?php echo esc_attr( $reliability_class ); ?>" style="width: <?php echo esc_attr( (string) $reliability ); ?>%;"></span>
3195 </span>
3196 <span><?php echo esc_html( (string) $reliability ); ?>%</span>
3197 <?php
3198 } else {
3199 ?>
3200 <span style="color: #646970;"></span>
3201 <?php
3202 }
3203 ?>
3204 </td>
3205 </tr>
3206 <?php endforeach; ?>
3207 </tbody>
3208 </table>
3209 </div>
3210 <?php
3211 }
3212
3213 /**
3214 * Displays the shell execution audit logs section.
3215 *
3216 * Shows recent shell_exec calls with full details for security auditing.
3217 *
3218 * @since 4.3.0
3219 *
3220 * @return void
3221 */
3222 function wpvulnerability_display_security_logs() {
3223 $per_page = 20;
3224 $logs = wpvulnerability_get_shell_exec_logs( $per_page, 1 );
3225 ?>
3226 <div class="wpvulnerability-security-section">
3227 <h3><?php esc_html_e( 'Shell Execution Audit Logs', 'wpvulnerability' ); ?></h3>
3228 <p><?php esc_html_e( 'Complete audit trail of shell command executions for security monitoring.', 'wpvulnerability' ); ?></p>
3229
3230 <?php if ( empty( $logs ) ) : ?>
3231 <div class="wpvulnerability-empty-state">
3232 <div class="wpvulnerability-empty-state-icon">🔒</div>
3233 <p><strong><?php esc_html_e( 'No shell execution logs found.', 'wpvulnerability' ); ?></strong></p>
3234 <p><?php esc_html_e( 'Logs will appear here when shell commands are executed for software detection.', 'wpvulnerability' ); ?></p>
3235 </div>
3236 <?php else : ?>
3237 <table class="wpvulnerability-logs-table">
3238 <thead>
3239 <tr>
3240 <th><?php esc_html_e( 'Time', 'wpvulnerability' ); ?></th>
3241 <th><?php esc_html_e( 'Component', 'wpvulnerability' ); ?></th>
3242 <th><?php esc_html_e( 'Command', 'wpvulnerability' ); ?></th>
3243 <th><?php esc_html_e( 'Status', 'wpvulnerability' ); ?></th>
3244 <th><?php esc_html_e( 'User', 'wpvulnerability' ); ?></th>
3245 <th><?php esc_html_e( 'Output', 'wpvulnerability' ); ?></th>
3246 </tr>
3247 </thead>
3248 <tbody>
3249 <?php foreach ( $logs as $log ) : ?>
3250 <?php
3251 $log_data = json_decode( $log->post_content, true );
3252 if ( ! is_array( $log_data ) ) {
3253 $log_data = array();
3254 }
3255 if ( empty( $log_data ) ) {
3256 continue;
3257 }
3258 ?>
3259 <?php
3260 $log_component = isset( $log_data['component'] ) && is_string( $log_data['component'] ) ? $log_data['component'] : '';
3261 $log_command = isset( $log_data['command'] ) && is_string( $log_data['command'] ) ? $log_data['command'] : '';
3262 $log_reason = isset( $log_data['reason'] ) && is_string( $log_data['reason'] ) ? $log_data['reason'] : '';
3263 $log_user = isset( $log_data['user'] ) && is_string( $log_data['user'] ) ? $log_data['user'] : '';
3264 $log_output = isset( $log_data['output'] ) && is_string( $log_data['output'] ) ? $log_data['output'] : '';
3265 $log_success = ! empty( $log_data['success'] );
3266 ?>
3267 <tr>
3268 <td>
3269 <?php
3270 echo esc_html(
3271 sprintf(
3272 /* translators: %s: time ago */
3273 __( '%s ago', 'wpvulnerability' ),
3274 human_time_diff( absint( strtotime( $log->post_date ) ), time() )
3275 )
3276 );
3277 ?>
3278 </td>
3279 <td><code><?php echo esc_html( $log_component ); ?></code></td>
3280 <td><code><?php echo esc_html( $log_command ); ?></code></td>
3281 <td>
3282 <?php if ( $log_success ) : ?>
3283 <span class="wpvulnerability-status-badge success"><?php echo esc_html( ucfirst( $log_reason ) ); ?></span>
3284 <?php else : ?>
3285 <span class="wpvulnerability-status-badge warning"><?php echo esc_html( ucfirst( $log_reason ) ); ?></span>
3286 <?php endif; ?>
3287 </td>
3288 <td><?php echo esc_html( $log_user ); ?></td>
3289 <td>
3290 <?php if ( '' !== $log_output ) : ?>
3291 <div class="wpvulnerability-log-output" title="<?php echo esc_attr( $log_output ); ?>">
3292 <?php echo esc_html( substr( $log_output, 0, 50 ) ); ?>
3293 <?php if ( strlen( $log_output ) > 50 ) : ?>
3294 <span>...</span>
3295 <?php endif; ?>
3296 </div>
3297 <?php else : ?>
3298 <span style="color: #646970;"></span>
3299 <?php endif; ?>
3300 </td>
3301 </tr>
3302 <?php endforeach; ?>
3303 </tbody>
3304 </table>
3305
3306 <p style="margin-top: 15px; color: #646970; font-size: 13px;">
3307 <?php
3308 /* translators: %d: number of logs shown */
3309 echo esc_html( sprintf( __( 'Showing the %d most recent log entries.', 'wpvulnerability' ), $per_page ) );
3310 ?>
3311 </p>
3312 <?php endif; ?>
3313 </div>
3314 <?php
3315 }
3316
3317 /**
3318 * AJAX handler for testing API connectivity.
3319 *
3320 * @since 4.3.0
3321 *
3322 * @return void
3323 */
3324 function wpvulnerability_ajax_test_api() {
3325 // Verify nonce.
3326 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( is_string( $_POST['nonce'] ) ? $_POST['nonce'] : '' ), 'wpvulnerability_test_api' ) ) {
3327 wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'wpvulnerability' ) ) );
3328 }
3329
3330 // Check permissions.
3331 if ( ! current_user_can( 'manage_options' ) ) {
3332 wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'wpvulnerability' ) ) );
3333 }
3334
3335 // Get component.
3336 $component = isset( $_POST['component'] ) ? sanitize_key( is_string( $_POST['component'] ) ? $_POST['component'] : '' ) : '';
3337 if ( empty( $component ) ) {
3338 wp_send_json_error( array( 'message' => __( 'No component specified.', 'wpvulnerability' ) ) );
3339 }
3340
3341 // Load debug functions.
3342 if ( ! function_exists( 'wpvulnerability_debug_test_api_component' ) ) {
3343 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-debug.php';
3344 }
3345
3346 // Test API.
3347 $wpvulnerability_result = wpvulnerability_debug_test_api_component( $component );
3348
3349 wp_send_json_success( $wpvulnerability_result );
3350 }
3351 add_action( 'wp_ajax_wpvulnerability_test_api', 'wpvulnerability_ajax_test_api' );
3352
3353 /**
3354 * Outputs the Debug tab contents.
3355 *
3356 * @since 4.3.0
3357 *
3358 * @return void
3359 */
3360 function wpvulnerability_render_admin_tab_debug() {
3361 // Security check: only show in debug mode.
3362 if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
3363 return;
3364 }
3365
3366 // Load debug functions if not already loaded.
3367 if ( ! function_exists( 'wpvulnerability_debug_get_system_info' ) ) {
3368 require_once WPVULNERABILITY_PLUGIN_PATH . '/wpvulnerability-debug.php';
3369 }
3370
3371 ?>
3372 <div class="wpvulnerability-section">
3373 <h2><?php esc_html_e( 'Debug Information', 'wpvulnerability' ); ?></h2>
3374 <p style="color: #d63638; font-weight: 500;">
3375 <?php esc_html_e( 'This tab is only visible when WP_DEBUG is enabled. Use with caution.', 'wpvulnerability' ); ?>
3376 </p>
3377
3378 <?php
3379 // Section 1: System Information.
3380 wpvulnerability_render_debug_section_system_info();
3381
3382 // Section 2: Component Detection.
3383 wpvulnerability_render_debug_section_components();
3384
3385 // Section 4: Configuration Summary.
3386 wpvulnerability_render_debug_section_config();
3387
3388 // Section 5: Cron Status.
3389 wpvulnerability_render_debug_section_cron();
3390
3391 // Section 3: API Testing.
3392 wpvulnerability_render_debug_section_api_testing();
3393
3394 // Section 6: Database Options Viewer.
3395 wpvulnerability_render_debug_section_database_options();
3396
3397 // Section 7: Quick Actions.
3398 wpvulnerability_render_debug_section_quick_actions();
3399 ?>
3400 </div>
3401 <?php
3402 }
3403
3404 /**
3405 * Renders Section 1: System Information.
3406 *
3407 * @since 4.3.0
3408 *
3409 * @return void
3410 */
3411 function wpvulnerability_render_debug_section_system_info() {
3412 $system_info = wpvulnerability_debug_get_system_info();
3413
3414 // Extract typed sub-arrays from the mixed return value.
3415 $si_wordpress = isset( $system_info['wordpress'] ) && is_array( $system_info['wordpress'] ) ? $system_info['wordpress'] : array();
3416 $si_php = isset( $system_info['php'] ) && is_array( $system_info['php'] ) ? $system_info['php'] : array();
3417 $si_database = isset( $system_info['database'] ) && is_array( $system_info['database'] ) ? $system_info['database'] : array();
3418 $si_webserver = isset( $system_info['webserver'] ) && is_array( $system_info['webserver'] ) ? $system_info['webserver'] : array();
3419 $si_debug = isset( $system_info['debug'] ) && is_array( $system_info['debug'] ) ? $system_info['debug'] : array();
3420
3421 $si_wp_version = isset( $si_wordpress['version'] ) && is_scalar( $si_wordpress['version'] ) ? (string) $si_wordpress['version'] : '';
3422 $si_multisite = ! empty( $si_wordpress['multisite'] );
3423 $si_language = isset( $si_wordpress['language'] ) && is_scalar( $si_wordpress['language'] ) ? (string) $si_wordpress['language'] : '';
3424 $si_php_version = isset( $si_php['version'] ) && is_scalar( $si_php['version'] ) ? (string) $si_php['version'] : '';
3425 $si_extensions = isset( $si_php['extensions'] ) && is_array( $si_php['extensions'] ) ? $si_php['extensions'] : array();
3426 $si_memory = isset( $si_php['memory'] ) && is_array( $si_php['memory'] ) ? $si_php['memory'] : array();
3427 $si_mem_limit = isset( $si_memory['limit'] ) && is_scalar( $si_memory['limit'] ) ? (string) $si_memory['limit'] : '';
3428 $si_db_type = isset( $si_database['type'] ) && is_scalar( $si_database['type'] ) ? (string) $si_database['type'] : '';
3429 $si_db_version = isset( $si_database['version'] ) && is_scalar( $si_database['version'] ) ? (string) $si_database['version'] : '';
3430 $si_webserver_s = isset( $si_webserver['software'] ) && is_scalar( $si_webserver['software'] ) ? (string) $si_webserver['software'] : '';
3431 $si_wp_debug = ! empty( $si_debug['wp_debug'] );
3432 $si_debug_log = ! empty( $si_debug['wp_debug_log'] );
3433 $si_log_file = isset( $si_debug['log_file'] ) && is_array( $si_debug['log_file'] ) ? $si_debug['log_file'] : array();
3434 ?>
3435 <div class="wpvulnerability-subsection" style="margin-top: 20px;">
3436 <h3><?php esc_html_e( 'System Information', 'wpvulnerability' ); ?></h3>
3437 <table class="widefat" style="margin-top: 10px;">
3438 <tbody>
3439 <tr>
3440 <td style="width: 200px; font-weight: 600;"><?php esc_html_e( 'WordPress Version', 'wpvulnerability' ); ?></td>
3441 <td><?php echo esc_html( $si_wp_version ); ?></td>
3442 </tr>
3443 <tr>
3444 <td style="font-weight: 600;"><?php esc_html_e( 'Multisite', 'wpvulnerability' ); ?></td>
3445 <td><?php echo $si_multisite ? esc_html__( 'Yes', 'wpvulnerability' ) : esc_html__( 'No', 'wpvulnerability' ); ?></td>
3446 </tr>
3447 <tr>
3448 <td style="font-weight: 600;"><?php esc_html_e( 'Language', 'wpvulnerability' ); ?></td>
3449 <td><?php echo esc_html( $si_language ); ?></td>
3450 </tr>
3451 <tr>
3452 <td style="font-weight: 600;"><?php esc_html_e( 'PHP Version', 'wpvulnerability' ); ?></td>
3453 <td><?php echo esc_html( $si_php_version ); ?></td>
3454 </tr>
3455 <tr>
3456 <td style="font-weight: 600;"><?php esc_html_e( 'PHP Extensions', 'wpvulnerability' ); ?></td>
3457 <td>
3458 <?php foreach ( $si_extensions as $ext => $loaded ) : ?>
3459 <span style="margin-right: 10px;">
3460 <span style="color: <?php echo $loaded ? '#00a32a' : '#d63638'; ?>;"></span>
3461 <?php echo esc_html( (string) $ext ); ?>
3462 </span>
3463 <?php endforeach; ?>
3464 </td>
3465 </tr>
3466 <tr>
3467 <td style="font-weight: 600;"><?php esc_html_e( 'PHP Memory Limit', 'wpvulnerability' ); ?></td>
3468 <td><?php echo esc_html( $si_mem_limit ); ?></td>
3469 </tr>
3470 <tr>
3471 <td style="font-weight: 600;"><?php esc_html_e( 'Database', 'wpvulnerability' ); ?></td>
3472 <td><?php echo esc_html( $si_db_type . ' ' . $si_db_version ); ?></td>
3473 </tr>
3474 <tr>
3475 <td style="font-weight: 600;"><?php esc_html_e( 'Web Server', 'wpvulnerability' ); ?></td>
3476 <td><?php echo esc_html( $si_webserver_s ); ?></td>
3477 </tr>
3478 <tr>
3479 <td style="font-weight: 600;"><?php esc_html_e( 'WP_DEBUG', 'wpvulnerability' ); ?></td>
3480 <td>
3481 <span style="color: <?php echo $si_wp_debug ? '#00a32a' : '#d63638'; ?>; font-weight: 600;">
3482 <?php echo $si_wp_debug ? esc_html__( 'Enabled', 'wpvulnerability' ) : esc_html__( 'Disabled', 'wpvulnerability' ); ?>
3483 </span>
3484 </td>
3485 </tr>
3486 <tr>
3487 <td style="font-weight: 600;"><?php esc_html_e( 'WP_DEBUG_LOG', 'wpvulnerability' ); ?></td>
3488 <td>
3489 <span style="color: <?php echo $si_debug_log ? '#00a32a' : '#d63638'; ?>; font-weight: 600;">
3490 <?php echo $si_debug_log ? esc_html__( 'Enabled', 'wpvulnerability' ) : esc_html__( 'Disabled', 'wpvulnerability' ); ?>
3491 </span>
3492 <?php if ( $si_debug_log && ! empty( $si_log_file ) ) : ?>
3493 <?php $log_file = $si_log_file; ?>
3494 <?php if ( ! empty( $log_file['path'] ) ) : ?>
3495 <br>
3496 <span style="color: #646970; font-size: 12px;">
3497 <?php esc_html_e( 'Path:', 'wpvulnerability' ); ?>
3498 <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;"><?php echo esc_html( is_scalar( $log_file['path'] ) ? (string) $log_file['path'] : '' ); ?></code>
3499 </span>
3500 <?php if ( $log_file['exists'] ) : ?>
3501 <br>
3502 <span style="color: #646970; font-size: 12px;">
3503 <?php
3504 $lf_size = isset( $log_file['size'] ) && is_scalar( $log_file['size'] ) ? (int) $log_file['size'] : 0;
3505 /* translators: %s: file size */
3506 echo esc_html( sprintf( __( 'Size: %s', 'wpvulnerability' ), size_format( $lf_size ) ) );
3507 ?>
3508 </span>
3509 <?php
3510 $lf_accessible = ! empty( $log_file['accessible'] );
3511 $lf_url = isset( $log_file['url'] ) && is_scalar( $log_file['url'] ) ? (string) $log_file['url'] : '';
3512 $lf_exists = (bool) $log_file['exists'];
3513 ?>
3514 <?php if ( $lf_accessible && '' !== $lf_url ) : ?>
3515 <br>
3516 <a href="<?php echo esc_url( $lf_url ); ?>" target="_blank" rel="noopener noreferrer" class="button button-small" style="margin-top: 5px;">
3517 <?php esc_html_e( 'View Log File', 'wpvulnerability' ); ?>
3518 <span class="dashicons dashicons-external" style="font-size: 13px; margin-top: 3px;"></span>
3519 </a>
3520 <?php elseif ( $lf_exists ) : ?>
3521 <br>
3522 <span style="color: #d63638; font-size: 12px;">
3523 <?php esc_html_e( 'Log file is not web-accessible (outside wp-content)', 'wpvulnerability' ); ?>
3524 </span>
3525 <?php endif; ?>
3526 <?php else : ?>
3527 <br>
3528 <span style="color: #646970; font-size: 12px;">
3529 <?php esc_html_e( 'Log file does not exist yet', 'wpvulnerability' ); ?>
3530 </span>
3531 <?php endif; ?>
3532 <?php endif; ?>
3533 <?php endif; ?>
3534 </td>
3535 </tr>
3536 <tr>
3537 <td style="font-weight: 600;"><?php esc_html_e( 'Plugin Version', 'wpvulnerability' ); ?></td>
3538 <?php
3539 $si_plugin = isset( $system_info['plugin'] ) && is_array( $system_info['plugin'] ) ? $system_info['plugin'] : array();
3540 $si_plug_ver = isset( $si_plugin['version'] ) && is_scalar( $si_plugin['version'] ) ? (string) $si_plugin['version'] : '';
3541 ?>
3542 <td><?php echo esc_html( $si_plug_ver ); ?></td>
3543 </tr>
3544 </tbody>
3545 </table>
3546 </div>
3547 <?php
3548 }
3549
3550 /**
3551 * Renders Section 2: Component Detection.
3552 *
3553 * @since 4.3.0
3554 *
3555 * @return void
3556 */
3557 function wpvulnerability_render_debug_section_components() {
3558 $components = wpvulnerability_debug_get_component_status();
3559 ?>
3560 <div class="wpvulnerability-subsection" style="margin-top: 30px;">
3561 <h3><?php esc_html_e( 'Component Detection', 'wpvulnerability' ); ?></h3>
3562 <table class="widefat striped" style="margin-top: 10px;">
3563 <thead>
3564 <tr>
3565 <th><?php esc_html_e( 'Component', 'wpvulnerability' ); ?></th>
3566 <th><?php esc_html_e( 'Status', 'wpvulnerability' ); ?></th>
3567 <th><?php esc_html_e( 'Version Detected', 'wpvulnerability' ); ?></th>
3568 <th><?php esc_html_e( 'Analyzed', 'wpvulnerability' ); ?></th>
3569 <th><?php esc_html_e( 'Cache Status', 'wpvulnerability' ); ?></th>
3570 </tr>
3571 </thead>
3572 <tbody>
3573 <?php foreach ( $components as $comp ) : ?>
3574 <?php
3575 $comp_name = isset( $comp['component'] ) && is_scalar( $comp['component'] ) ? (string) $comp['component'] : '';
3576 $comp_detected = ! empty( $comp['detected'] );
3577 $comp_version = isset( $comp['version'] ) && is_scalar( $comp['version'] ) ? (string) $comp['version'] : '';
3578 $comp_analyzed = ! empty( $comp['analyzed'] );
3579 $comp_cache_status = isset( $comp['cache_status'] ) && is_scalar( $comp['cache_status'] ) ? (string) $comp['cache_status'] : '';
3580 ?>
3581 <tr>
3582 <td style="font-weight: 600;"><?php echo esc_html( ucfirst( $comp_name ) ); ?></td>
3583 <td>
3584 <span style="color: <?php echo $comp_detected ? '#00a32a' : '#d63638'; ?>; font-size: 16px;">
3585 <?php echo $comp_detected ? '' : ''; ?>
3586 </span>
3587 </td>
3588 <td><?php echo esc_html( $comp_version ); ?></td>
3589 <td>
3590 <span style="color: <?php echo $comp_analyzed ? '#00a32a' : '#d63638'; ?>;">
3591 <?php echo $comp_analyzed ? esc_html__( 'Yes', 'wpvulnerability' ) : esc_html__( 'No', 'wpvulnerability' ); ?>
3592 </span>
3593 </td>
3594 <td>
3595 <?php
3596 $badge_color = '#646970';
3597 if ( strpos( $comp_cache_status, 'Fresh' ) !== false ) {
3598 $badge_color = '#00a32a';
3599 } elseif ( strpos( $comp_cache_status, 'Expired' ) !== false ) {
3600 $badge_color = '#d63638';
3601 }
3602 ?>
3603 <span style="background: <?php echo esc_attr( $badge_color ); ?>; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px; font-weight: 600;">
3604 <?php echo esc_html( $comp_cache_status ); ?>
3605 </span>
3606 </td>
3607 </tr>
3608 <?php endforeach; ?>
3609 </tbody>
3610 </table>
3611 </div>
3612 <?php
3613 }
3614
3615 /**
3616 * Renders Section 4: Configuration Summary.
3617 *
3618 * @since 4.3.0
3619 *
3620 * @return void
3621 */
3622 function wpvulnerability_render_debug_section_config() {
3623 $config_raw = is_multisite()
3624 ? get_site_option( 'wpvulnerability-config', array() )
3625 : get_option( 'wpvulnerability-config', array() );
3626 $config = is_array( $config_raw ) ? $config_raw : array();
3627 ?>
3628 <div class="wpvulnerability-subsection" style="margin-top: 30px;">
3629 <h3><?php esc_html_e( 'Configuration Summary', 'wpvulnerability' ); ?></h3>
3630 <table class="widefat" style="margin-top: 10px;">
3631 <tbody>
3632 <tr>
3633 <td style="width: 250px; font-weight: 600;"><?php esc_html_e( 'Cache Duration', 'wpvulnerability' ); ?></td>
3634 <td>
3635 <?php
3636 $cache_hours = isset( $config['cache'] ) && is_scalar( $config['cache'] ) ? absint( (int) $config['cache'] ) : 12;
3637 /* translators: %d: number of hours */
3638 echo esc_html( sprintf( _n( '%d hour', '%d hours', $cache_hours, 'wpvulnerability' ), $cache_hours ) );
3639 ?>
3640 </td>
3641 </tr>
3642 <tr>
3643 <td style="font-weight: 600;"><?php esc_html_e( 'Log Retention', 'wpvulnerability' ); ?></td>
3644 <td>
3645 <?php
3646 $retention_days = isset( $config['log_retention_days'] ) && is_scalar( $config['log_retention_days'] ) ? absint( (int) $config['log_retention_days'] ) : 14;
3647 /* translators: %d: Number of days. */
3648 echo esc_html( sprintf( _n( '%d day', '%d days', $retention_days, 'wpvulnerability' ), $retention_days ) );
3649 ?>
3650 </td>
3651 </tr>
3652 <tr>
3653 <td style="font-weight: 600;"><?php esc_html_e( 'Components Analyzed', 'wpvulnerability' ); ?></td>
3654 <td>
3655 <?php
3656 $components = array( 'core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mysql', 'mariadb', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
3657 $analyzed_count = 0;
3658 foreach ( $components as $component ) {
3659 if ( wpvulnerability_analyze_filter( $component ) ) {
3660 ++$analyzed_count;
3661 }
3662 }
3663 echo esc_html( sprintf( '%d / %d', $analyzed_count, count( $components ) ) );
3664 ?>
3665 </td>
3666 </tr>
3667 <tr>
3668 <td style="font-weight: 600;"><?php esc_html_e( 'Notification Email', 'wpvulnerability' ); ?></td>
3669 <td><?php echo isset( $config['emails'] ) && is_scalar( $config['emails'] ) ? esc_html( (string) $config['emails'] ) : ''; ?></td>
3670 </tr>
3671 <tr>
3672 <td style="font-weight: 600;"><?php esc_html_e( 'Notification Frequency', 'wpvulnerability' ); ?></td>
3673 <td>
3674 <?php
3675 $period = isset( $config['period'] ) && is_scalar( $config['period'] ) ? (string) $config['period'] : 'weekly';
3676 echo esc_html( ucfirst( $period ) );
3677 if ( 'weekly' === $period && isset( $config['day'] ) ) {
3678 $config_day_raw = $config['day'];
3679 $config_day = is_scalar( $config_day_raw ) ? (string) $config_day_raw : 'monday';
3680 echo ' (' . esc_html( ucfirst( $config_day ) ) . ')';
3681 }
3682 if ( isset( $config['hour'] ) && isset( $config['minute'] ) ) {
3683 echo ' ' . esc_html( sprintf( '%02d:%02d', absint( is_scalar( $config['hour'] ) ? (int) $config['hour'] : 0 ), absint( is_scalar( $config['minute'] ) ? (int) $config['minute'] : 0 ) ) );
3684 }
3685 ?>
3686 </td>
3687 </tr>
3688 <tr>
3689 <td style="font-weight: 600;"><?php esc_html_e( 'Slack Webhook', 'wpvulnerability' ); ?></td>
3690 <td>
3691 <?php
3692 $config_notify = isset( $config['notify'] ) && is_array( $config['notify'] ) ? $config['notify'] : array();
3693 $slack_configured = ! empty( $config['slack_webhook'] );
3694 $slack_enabled = isset( $config_notify['slack'] ) && 'y' === $config_notify['slack'];
3695 ?>
3696 <span style="color: <?php echo ( $slack_configured && $slack_enabled ) ? '#00a32a' : '#646970'; ?>;">
3697 <?php
3698 if ( $slack_configured && $slack_enabled ) {
3699 esc_html_e( 'Configured & Enabled', 'wpvulnerability' );
3700 } elseif ( $slack_configured ) {
3701 esc_html_e( 'Configured (Disabled)', 'wpvulnerability' );
3702 } else {
3703 esc_html_e( 'Not configured', 'wpvulnerability' );
3704 }
3705 ?>
3706 </span>
3707 </td>
3708 </tr>
3709 <tr>
3710 <td style="font-weight: 600;"><?php esc_html_e( 'Teams Webhook', 'wpvulnerability' ); ?></td>
3711 <td>
3712 <?php
3713 $teams_configured = ! empty( $config['teams_webhook'] );
3714 $teams_enabled = isset( $config_notify['teams'] ) && 'y' === $config_notify['teams'];
3715 ?>
3716 <span style="color: <?php echo ( $teams_configured && $teams_enabled ) ? '#00a32a' : '#646970'; ?>;">
3717 <?php
3718 if ( $teams_configured && $teams_enabled ) {
3719 esc_html_e( 'Configured & Enabled', 'wpvulnerability' );
3720 } elseif ( $teams_configured ) {
3721 esc_html_e( 'Configured (Disabled)', 'wpvulnerability' );
3722 } else {
3723 esc_html_e( 'Not configured', 'wpvulnerability' );
3724 }
3725 ?>
3726 </span>
3727 </td>
3728 </tr>
3729 <tr>
3730 <td style="font-weight: 600;"><?php esc_html_e( 'Discord Webhook', 'wpvulnerability' ); ?></td>
3731 <td>
3732 <?php
3733 $discord_configured = ! empty( $config['discord_webhook'] );
3734 $discord_enabled = isset( $config_notify['discord'] ) && 'y' === $config_notify['discord'];
3735 ?>
3736 <span style="color: <?php echo ( $discord_configured && $discord_enabled ) ? '#00a32a' : '#646970'; ?>;">
3737 <?php
3738 if ( $discord_configured && $discord_enabled ) {
3739 esc_html_e( 'Configured & Enabled', 'wpvulnerability' );
3740 } elseif ( $discord_configured ) {
3741 esc_html_e( 'Configured (Disabled)', 'wpvulnerability' );
3742 } else {
3743 esc_html_e( 'Not configured', 'wpvulnerability' );
3744 }
3745 ?>
3746 </span>
3747 </td>
3748 </tr>
3749 <tr>
3750 <td style="font-weight: 600;"><?php esc_html_e( 'Telegram Bot', 'wpvulnerability' ); ?></td>
3751 <td>
3752 <?php
3753 $telegram_configured = ! empty( $config['telegram_bot_token'] ) && ! empty( $config['telegram_chat_id'] );
3754 $telegram_enabled = isset( $config_notify['telegram'] ) && 'y' === $config_notify['telegram'];
3755 ?>
3756 <span style="color: <?php echo ( $telegram_configured && $telegram_enabled ) ? '#00a32a' : '#646970'; ?>;">
3757 <?php
3758 if ( $telegram_configured && $telegram_enabled ) {
3759 esc_html_e( 'Configured & Enabled', 'wpvulnerability' );
3760 } elseif ( $telegram_configured ) {
3761 esc_html_e( 'Configured (Disabled)', 'wpvulnerability' );
3762 } else {
3763 esc_html_e( 'Not configured', 'wpvulnerability' );
3764 }
3765 ?>
3766 </span>
3767 </td>
3768 </tr>
3769 </tbody>
3770 </table>
3771 </div>
3772 <?php
3773 }
3774
3775 /**
3776 * Renders Section 5: Cron Status.
3777 *
3778 * @since 4.3.0
3779 *
3780 * @return void
3781 */
3782 function wpvulnerability_render_debug_section_cron() {
3783 $cron_status = wpvulnerability_debug_get_cron_status();
3784 $can_manage = current_user_can( 'manage_options' );
3785
3786 // Extract typed values from mixed cron status arrays.
3787 $cs_update = isset( $cron_status['update_database'] ) ? (array) $cron_status['update_database'] : array();
3788 $cs_notify = isset( $cron_status['send_notification'] ) ? (array) $cron_status['send_notification'] : array();
3789 $upd_scheduled = ! empty( $cs_update['scheduled'] );
3790 $upd_next_run = isset( $cs_update['next_run'] ) && is_numeric( $cs_update['next_run'] ) ? (int) $cs_update['next_run'] : null;
3791 $notif_scheduled = ! empty( $cs_notify['scheduled'] );
3792 $notif_next_run = isset( $cs_notify['next_run'] ) && is_numeric( $cs_notify['next_run'] ) ? (int) $cs_notify['next_run'] : null;
3793 ?>
3794 <div class="wpvulnerability-subsection" style="margin-top: 30px;">
3795 <h3><?php esc_html_e( 'Cron Status', 'wpvulnerability' ); ?></h3>
3796 <table class="widefat" style="margin-top: 10px;">
3797 <tbody>
3798 <tr>
3799 <td style="width: 250px; font-weight: 600;"><?php esc_html_e( 'Update Database', 'wpvulnerability' ); ?></td>
3800 <td>
3801 <?php if ( $upd_scheduled && null !== $upd_next_run ) : ?>
3802 <?php
3803 /* translators: %s: date and time */
3804 echo esc_html( sprintf( __( 'Next run: %s', 'wpvulnerability' ), wp_date( 'Y-m-d H:i:s', $upd_next_run ) ) );
3805 ?>
3806 <?php else : ?>
3807 <span style="color: #d63638;"><?php esc_html_e( 'Not scheduled', 'wpvulnerability' ); ?></span>
3808 <?php endif; ?>
3809 </td>
3810 </tr>
3811 <tr>
3812 <td style="font-weight: 600;"><?php esc_html_e( 'Send Notification', 'wpvulnerability' ); ?></td>
3813 <td>
3814 <?php if ( $notif_scheduled && null !== $notif_next_run ) : ?>
3815 <?php
3816 /* translators: %s: date and time */
3817 echo esc_html( sprintf( __( 'Next run: %s', 'wpvulnerability' ), wp_date( 'Y-m-d H:i:s', $notif_next_run ) ) );
3818 ?>
3819 <?php else : ?>
3820 <span style="color: #646970;"><?php esc_html_e( 'Not scheduled', 'wpvulnerability' ); ?></span>
3821 <?php endif; ?>
3822 </td>
3823 </tr>
3824 </tbody>
3825 </table>
3826
3827 <?php if ( $can_manage ) : ?>
3828 <div style="margin-top: 15px;">
3829 <form method="post" style="display: inline-block; margin-right: 10px;">
3830 <?php wp_nonce_field( 'wpvulnerability_cron_action', 'wpvulnerability_cron_nonce' ); ?>
3831 <input type="hidden" name="wpvulnerability_run_update" value="1">
3832 <button type="submit" class="button button-secondary">
3833 <?php esc_html_e( 'Run Update Now', 'wpvulnerability' ); ?>
3834 </button>
3835 </form>
3836
3837 <form method="post" style="display: inline-block;">
3838 <?php wp_nonce_field( 'wpvulnerability_cron_action', 'wpvulnerability_cron_nonce' ); ?>
3839 <input type="hidden" name="wpvulnerability_run_notification" value="1">
3840 <button type="submit" class="button button-secondary">
3841 <?php esc_html_e( 'Run Notification Now', 'wpvulnerability' ); ?>
3842 </button>
3843 </form>
3844 </div>
3845 <?php else : ?>
3846 <p style="color: #d63638; margin-top: 10px;">
3847 <?php esc_html_e( 'You do not have permission to run these actions.', 'wpvulnerability' ); ?>
3848 </p>
3849 <?php endif; ?>
3850 </div>
3851 <?php
3852 }
3853
3854 /**
3855 * Renders Section 3: API Testing.
3856 *
3857 * @since 4.3.0
3858 *
3859 * @return void
3860 */
3861 function wpvulnerability_render_debug_section_api_testing() {
3862 $can_manage = current_user_can( 'manage_options' );
3863 $components = array( 'core', 'plugins', 'themes', 'php', 'apache', 'nginx', 'mysql', 'mariadb', 'imagemagick', 'curl', 'memcached', 'redis', 'sqlite' );
3864 ?>
3865 <div class="wpvulnerability-subsection" style="margin-top: 30px;">
3866 <h3><?php esc_html_e( 'API Testing', 'wpvulnerability' ); ?></h3>
3867
3868 <?php if ( $can_manage ) : ?>
3869 <p><?php esc_html_e( 'Test API connectivity for each component. Click a button to send a test request.', 'wpvulnerability' ); ?></p>
3870
3871 <div id="wpvulnerability-api-test-buttons" style="margin-top: 15px;">
3872 <?php foreach ( $components as $component ) : ?>
3873 <button type="button" class="button button-secondary wpvulnerability-test-api-btn" data-component="<?php echo esc_attr( $component ); ?>" style="margin: 5px;">
3874 <?php
3875 /* translators: %s: component name */
3876 echo esc_html( sprintf( __( 'Test %s', 'wpvulnerability' ), ucfirst( $component ) ) );
3877 ?>
3878 </button>
3879 <?php endforeach; ?>
3880 </div>
3881
3882 <div id="wpvulnerability-api-test-results" style="margin-top: 20px;"></div>
3883
3884 <script type="text/javascript">
3885 jQuery(document).ready(function($) {
3886 $('.wpvulnerability-test-api-btn').on('click', function() {
3887 var $btn = $(this);
3888 var component = $btn.data('component');
3889 var $results = $('#wpvulnerability-api-test-results');
3890
3891 $btn.prop('disabled', true).text('<?php echo esc_js( __( 'Testing...', 'wpvulnerability' ) ); ?>');
3892
3893 $.ajax({
3894 url: ajaxurl,
3895 type: 'POST',
3896 data: {
3897 action: 'wpvulnerability_test_api',
3898 component: component,
3899 nonce: '<?php echo esc_js( wp_create_nonce( 'wpvulnerability_test_api' ) ); ?>'
3900 },
3901 success: function(response) {
3902 if (response.success) {
3903 var result = response.data;
3904 var statusColor = result.success ? '#00a32a' : '#d63638';
3905 var resultHtml = '<div style="border: 1px solid ' + statusColor + '; padding: 15px; margin-top: 10px; border-radius: 4px;">';
3906 resultHtml += '<h4 style="margin-top: 0; color: ' + statusColor + ';">' + component.toUpperCase() + ' - ' + result.message + '</h4>';
3907 resultHtml += '<p><strong><?php echo esc_js( __( 'HTTP Code:', 'wpvulnerability' ) ); ?></strong> ' + result.http_code + '</p>';
3908 resultHtml += '<p><strong><?php echo esc_js( __( 'Response Time:', 'wpvulnerability' ) ); ?></strong> ' + result.response_time + ' ms</p>';
3909 if (result.data_preview) {
3910 resultHtml += '<details style="margin-top: 10px;"><summary style="cursor: pointer; font-weight: 600;"><?php echo esc_js( __( 'Response Preview', 'wpvulnerability' ) ); ?></summary>';
3911 resultHtml += '<pre style="background: #f0f0f0; padding: 10px; overflow-x: auto; margin-top: 10px;">' + result.data_preview + '</pre>';
3912 resultHtml += '</details>';
3913 }
3914 resultHtml += '</div>';
3915 $results.prepend(resultHtml);
3916 }
3917 $btn.prop('disabled', false).text('<?php echo esc_js( __( 'Test', 'wpvulnerability' ) ); ?> ' + component.charAt(0).toUpperCase() + component.slice(1));
3918 },
3919 error: function() {
3920 $results.prepend('<div style="border: 1px solid #d63638; padding: 15px; margin-top: 10px; border-radius: 4px; color: #d63638;"><strong><?php echo esc_js( __( 'Error:', 'wpvulnerability' ) ); ?></strong> <?php echo esc_js( __( 'AJAX request failed.', 'wpvulnerability' ) ); ?></div>');
3921 $btn.prop('disabled', false).text('<?php echo esc_js( __( 'Test', 'wpvulnerability' ) ); ?> ' + component.charAt(0).toUpperCase() + component.slice(1));
3922 }
3923 });
3924 });
3925 });
3926 </script>
3927 <?php else : ?>
3928 <p style="color: #d63638;">
3929 <?php esc_html_e( 'You do not have permission to test API connectivity.', 'wpvulnerability' ); ?>
3930 </p>
3931 <?php endif; ?>
3932 </div>
3933 <?php
3934 }
3935
3936 /**
3937 * Renders Section 6: Database Options Viewer.
3938 *
3939 * @since 4.3.0
3940 *
3941 * @return void
3942 */
3943 function wpvulnerability_render_debug_section_database_options() {
3944 $option_names = wpvulnerability_debug_get_option_names();
3945 $selected_option = filter_input( INPUT_GET, 'debug_option', FILTER_SANITIZE_SPECIAL_CHARS );
3946 $selected_option = $selected_option ? sanitize_key( $selected_option ) : '';
3947 $page_value = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS );
3948 ?>
3949 <div class="wpvulnerability-subsection" style="margin-top: 30px;">
3950 <h3><?php esc_html_e( 'Database Options Viewer', 'wpvulnerability' ); ?></h3>
3951 <p><?php esc_html_e( 'View the raw data stored in WordPress options.', 'wpvulnerability' ); ?></p>
3952
3953 <form method="get" style="margin-top: 15px;">
3954 <input type="hidden" name="page" value="<?php echo $page_value ? esc_attr( $page_value ) : ''; ?>">
3955 <input type="hidden" name="tab" value="debug">
3956 <select name="debug_option" style="min-width: 300px;">
3957 <option value=""><?php esc_html_e( 'Select an option...', 'wpvulnerability' ); ?></option>
3958 <?php foreach ( $option_names as $option ) : ?>
3959 <option value="<?php echo esc_attr( $option ); ?>" <?php selected( $selected_option, $option ); ?>>
3960 <?php echo esc_html( $option ); ?>
3961 </option>
3962 <?php endforeach; ?>
3963 </select>
3964 <button type="submit" class="button button-secondary"><?php esc_html_e( 'View', 'wpvulnerability' ); ?></button>
3965 </form>
3966
3967 <?php if ( $selected_option && in_array( $selected_option, $option_names, true ) ) : ?>
3968 <?php
3969 $option_value = wpvulnerability_debug_get_option_value( $selected_option );
3970 ?>
3971 <div style="margin-top: 20px;">
3972 <h4><?php echo esc_html( $selected_option ); ?></h4>
3973 <?php if ( null !== $option_value ) : ?>
3974 <pre style="background: #f0f0f0; padding: 15px; overflow-x: auto; border: 1px solid #ddd; border-radius: 4px;"><?php echo esc_html( strval( wp_json_encode( $option_value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ) ); ?></pre>
3975 <?php else : ?>
3976 <p style="color: #646970;"><?php esc_html_e( 'Option not found or empty.', 'wpvulnerability' ); ?></p>
3977 <?php endif; ?>
3978 </div>
3979 <?php endif; ?>
3980 </div>
3981 <?php
3982 }
3983 /**
3984 * Renders Section 7: Quick Actions.
3985 *
3986 * @since 4.3.0
3987 *
3988 * @return void
3989 */
3990 function wpvulnerability_render_debug_section_quick_actions() {
3991 $can_manage = current_user_can( 'manage_options' );
3992 ?>
3993 <div class="wpvulnerability-subsection" style="margin-top: 30px;">
3994 <h3><?php esc_html_e( 'Quick Actions', 'wpvulnerability' ); ?></h3>
3995 <p><?php esc_html_e( 'Perform debugging actions. Use with caution.', 'wpvulnerability' ); ?></p>
3996
3997 <?php if ( $can_manage ) : ?>
3998 <div style="margin-top: 15px;">
3999 <form method="post" style="display: inline-block; margin-right: 10px;" onsubmit="return confirm('<?php echo esc_js( __( 'Are you sure you want to clear all caches?', 'wpvulnerability' ) ); ?>');">
4000 <?php wp_nonce_field( 'wpvulnerability_debug_action', 'wpvulnerability_debug_nonce' ); ?>
4001 <input type="hidden" name="wpvulnerability_debug_clear_caches" value="1">
4002 <button type="submit" class="button button-secondary">
4003 <?php esc_html_e( 'Clear All Caches', 'wpvulnerability' ); ?>
4004 </button>
4005 </form>
4006
4007 <form method="post" style="display: inline-block; margin-right: 10px;" onsubmit="return confirm('<?php echo esc_js( __( 'Are you sure you want to reset signatures?', 'wpvulnerability' ) ); ?>');">
4008 <?php wp_nonce_field( 'wpvulnerability_debug_action', 'wpvulnerability_debug_nonce' ); ?>
4009 <input type="hidden" name="wpvulnerability_debug_reset_signatures" value="1">
4010 <button type="submit" class="button button-secondary">
4011 <?php esc_html_e( 'Reset Signatures', 'wpvulnerability' ); ?>
4012 </button>
4013 </form>
4014
4015 <form method="post" style="display: inline-block;">
4016 <?php wp_nonce_field( 'wpvulnerability_debug_action', 'wpvulnerability_debug_nonce' ); ?>
4017 <input type="hidden" name="wpvulnerability_debug_export" value="1">
4018 <button type="submit" class="button button-primary">
4019 <?php esc_html_e( 'Export Debug Info', 'wpvulnerability' ); ?>
4020 </button>
4021 </form>
4022 </div>
4023 <?php else : ?>
4024 <p style="color: #d63638;">
4025 <?php esc_html_e( 'You do not have permission to perform these actions.', 'wpvulnerability' ); ?>
4026 </p>
4027 <?php endif; ?>
4028 </div>
4029 <?php
4030 }
4031