PluginProbe
WPVulnerability / 4.3.0
WPVulnerability v4.3.0
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 4.3.0, at wpvulnerability-admin.php

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