484 lines
16 KiB
PHP
484 lines
16 KiB
PHP
<?php
|
||
/**
|
||
* WPB Debug view.
|
||
*
|
||
* HIGH RISK – Admin-only debug UI. Outputs sensitive data (keys, paths, user info).
|
||
* Only load when is_admin(), manage_options, and WPBRIGADE_SDK__DEV_MODE are satisfied.
|
||
*
|
||
* @package Wp Headers and Footers
|
||
*/
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
|
||
wp_die(
|
||
esc_html__( 'You do not have permission to access this page.', 'wp-headers-and-footers' ),
|
||
'',
|
||
array( 'response' => 403 )
|
||
);
|
||
}
|
||
|
||
if ( ! defined( 'WPBRIGADE_SDK__DEV_MODE' ) || true !== WPBRIGADE_SDK__DEV_MODE ) {
|
||
wp_die(
|
||
esc_html__( 'Debug mode is not enabled.', 'wp-headers-and-footers' ),
|
||
'',
|
||
array( 'response' => 403 )
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Enqueue CSS file for admin debugging.
|
||
*/
|
||
function wpb_debug_enqueue_styles() {
|
||
wp_enqueue_style(
|
||
'custom-debug-style',
|
||
plugins_url( 'admin/css/debug.css', __FILE__ ),
|
||
array(),
|
||
defined( 'WP_WPBRIGADE_SDK_VERSION' ) ? WP_WPBRIGADE_SDK_VERSION : '1.0.0'
|
||
);
|
||
}
|
||
add_action( 'wp_enqueue_scripts', 'wpb_debug_enqueue_styles' );
|
||
|
||
/**
|
||
* Verify POST request: method, capability, and nonce for a given action.
|
||
*
|
||
* @param string $action Nonce action (e.g. 'wpb_debug_clear_cache').
|
||
* @return bool True if valid POST with valid nonce and capability.
|
||
*/
|
||
function wpb_debug_verify_request( $action ) {
|
||
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
|
||
return false;
|
||
}
|
||
if ( ! current_user_can( 'manage_options' ) ) {
|
||
return false;
|
||
}
|
||
if ( ! isset( $_POST['_wpnonce'] ) ) {
|
||
return false;
|
||
}
|
||
return (bool) wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), $action );
|
||
}
|
||
|
||
/**
|
||
* Mask a sensitive string (show last N chars, rest as bullets).
|
||
*
|
||
* @param string $value Raw value.
|
||
* @param int $visible Number of trailing characters to show.
|
||
* @return string Masked value.
|
||
*/
|
||
function wpb_debug_mask( $value, $visible = 4 ) {
|
||
if ( '' === (string) $value ) {
|
||
return '—';
|
||
}
|
||
$value = (string) $value;
|
||
$len = strlen( $value );
|
||
if ( $len <= $visible ) {
|
||
return str_repeat( '•', $len );
|
||
}
|
||
return str_repeat( '•', $len - $visible ) . substr( $value, - $visible );
|
||
}
|
||
|
||
/**
|
||
* Mask email for display (e.g. a***@***.com).
|
||
*
|
||
* @param string $email Email address.
|
||
* @return string Masked email.
|
||
*/
|
||
function wpb_debug_mask_email( $email ) {
|
||
if ( '' === (string) $email || ! is_email( $email ) ) {
|
||
return '—';
|
||
}
|
||
$parts = explode( '@', $email, 2 );
|
||
if ( 2 !== count( $parts ) ) {
|
||
return wpb_debug_mask( $email, 0 );
|
||
}
|
||
$local = $parts[0];
|
||
$domain = $parts[1];
|
||
$local_display = strlen( $local ) > 2 ? substr( $local, 0, 1 ) . str_repeat( '•', strlen( $local ) - 1 ) : '••';
|
||
$domain_display = strlen( $domain ) > 4 ? '•••' . substr( $domain, -4 ) : '••••';
|
||
return $local_display . '@' . $domain_display;
|
||
}
|
||
|
||
/**
|
||
* Shorten path for display (show last two segments to avoid exposing full server path).
|
||
*
|
||
* @param string $path Full path.
|
||
* @return string Shortened path.
|
||
*/
|
||
function wpb_debug_mask_path( $path ) {
|
||
if ( '' === (string) $path ) {
|
||
return '—';
|
||
}
|
||
$path = str_replace( array( '\\', '/' ), '/', (string) $path );
|
||
$parts = array_filter( explode( '/', $path ) );
|
||
$tail = array_slice( $parts, -2 );
|
||
return ( count( $parts ) > 2 ? '…/' : '' ) . implode( '/', $tail );
|
||
}
|
||
|
||
$slug = get_option( 'wpb_sdk_module_slug' );
|
||
$wpb_sdk_module_id = get_option( 'wpb_sdk_module_id' );
|
||
|
||
$all_plugins = array();
|
||
|
||
$wpb = WPBRIGADE_Logger::instance( $wpb_sdk_module_id, $slug, true );
|
||
$data = $wpb->get_logs_data( $slug );
|
||
|
||
$plugin_path = isset( $data['product_info']['path'] ) ? $data['product_info']['path'] : '';
|
||
$installed_plugin_slugs = array_keys( get_plugins() );
|
||
$active_plugins = get_option( 'active_plugins', array() );
|
||
$sdk_path = WPBRIGADE_SDK_DIR;
|
||
|
||
$this_sdk_path = strstr( $sdk_path, $slug );
|
||
if ( false !== $this_sdk_path ) {
|
||
$this_sdk_path = '\\' . ltrim( $this_sdk_path, '\\' );
|
||
}
|
||
|
||
// Clear API cache.
|
||
if ( isset( $_POST['wpb_clear_api_cache'] ) && 'true' === $_POST['wpb_clear_api_cache'] && wpb_debug_verify_request( 'wpb_debug_clear_cache' ) ) {
|
||
update_option( 'wpb_api_cache', null );
|
||
}
|
||
|
||
// Clear updates data.
|
||
if ( isset( $_POST['wpb_action'] ) && 'clear_updates_data' === $_POST['wpb_action'] && wpb_debug_verify_request( 'wpb_debug_clear_updates' ) ) {
|
||
set_site_transient( 'update_plugins', null );
|
||
set_site_transient( 'update_themes', null );
|
||
}
|
||
|
||
// Background sync.
|
||
if ( isset( $_POST['background_sync'] ) && 'true' === $_POST['background_sync'] && wpb_debug_verify_request( 'wpb_debug_background_sync' ) ) {
|
||
$response = wp_remote_post(
|
||
WPBRIGADE_SDK_API_ENDPOINT,
|
||
array(
|
||
'method' => 'POST',
|
||
'body' => $data,
|
||
'timeout' => 5,
|
||
'headers' => array(),
|
||
)
|
||
);
|
||
|
||
if ( is_wp_error( $response ) ) {
|
||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug view only.
|
||
error_log( 'Error sending data: ' . $response->get_error_message() );
|
||
} else {
|
||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug view only.
|
||
error_log( 'Log sent successfully' . wp_json_encode( $data ) );
|
||
}
|
||
}
|
||
|
||
/** Option name prefix allowed for Set DB Option (strict whitelist by prefix). */
|
||
define( 'WPB_DEBUG_OPTION_PREFIX', 'wpb_' );
|
||
|
||
/**
|
||
* Set an option value only if it is in the allowed prefix scope.
|
||
*
|
||
* @param string $option_name Option name (must start with WPB_DEBUG_OPTION_PREFIX).
|
||
* @param mixed $option_value Option value.
|
||
* @return bool True on success, false if not allowed.
|
||
*/
|
||
function wpb_debug_set_option( $option_name, $option_value ) {
|
||
if ( ! current_user_can( 'manage_options' ) ) {
|
||
return false;
|
||
}
|
||
$option_name = sanitize_text_field( $option_name );
|
||
if ( '' === $option_name || 0 !== strpos( $option_name, WPB_DEBUG_OPTION_PREFIX ) ) {
|
||
return false;
|
||
}
|
||
update_option( $option_name, $option_value );
|
||
return true;
|
||
}
|
||
|
||
$wpb_debug_set_option_success = false;
|
||
$wpb_debug_set_option_submitted = false;
|
||
if ( isset( $_POST['set_option_name'], $_POST['option_value'] ) && wpb_debug_verify_request( 'wpb_debug_set_option' ) ) {
|
||
$wpb_debug_set_option_submitted = true;
|
||
$option_name = sanitize_text_field( wp_unslash( $_POST['set_option_name'] ) );
|
||
$option_value = isset( $_POST['option_value'] ) ? sanitize_text_field( wp_unslash( $_POST['option_value'] ) ) : '';
|
||
$wpb_debug_set_option_success = wpb_debug_set_option( $option_name, $option_value );
|
||
}
|
||
|
||
/**
|
||
* Get an option value from the database.
|
||
*
|
||
* @param string $option_name Option name.
|
||
* @return mixed Option value.
|
||
*/
|
||
function wpb_debug_get_option_value( $option_name ) {
|
||
return get_option( sanitize_text_field( $option_name ) );
|
||
}
|
||
|
||
$option_value = '';
|
||
$result_visible = false;
|
||
if ( isset( $_POST['load_option_name'] ) && wpb_debug_verify_request( 'wpb_debug_load_option' ) ) {
|
||
$option_name = sanitize_text_field( wp_unslash( $_POST['load_option_name'] ) );
|
||
$option_value = wpb_debug_get_option_value( $option_name );
|
||
$result_visible = true;
|
||
}
|
||
|
||
$wpb_debug_msg_success = __( 'Successfully set the option.', 'wp-headers-and-footers' );
|
||
$wpb_debug_msg_error = __( 'Option not set. Name must start with wpb_.', 'wp-headers-and-footers' );
|
||
?>
|
||
|
||
<h1>WPB Debug - SDK v.<?php echo esc_html( defined( 'WP_WPBRIGADE_SDK_VERSION' ) ? WP_WPBRIGADE_SDK_VERSION : '' ); ?></h1>
|
||
|
||
<?php if ( $wpb_debug_set_option_submitted ) : ?>
|
||
<div id="success_message" class="notice notice-<?php echo esc_attr( $wpb_debug_set_option_success ? 'success' : 'error' ); ?>" role="alert">
|
||
<p><?php echo esc_html( $wpb_debug_set_option_success ? $wpb_debug_msg_success : $wpb_debug_msg_error ); ?></p>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<p class="notice notice-warning" style="margin: 1em 0;" role="alert">
|
||
<strong><?php esc_html_e( 'Admin-only debug page.', 'wp-headers-and-footers' ); ?></strong>
|
||
<?php esc_html_e( 'This page shows sensitive data (keys, paths, user info). Do not share screenshots or leave unattended.', 'wp-headers-and-footers' ); ?>
|
||
</p>
|
||
|
||
<h2><?php esc_html_e( 'Actions', 'wp-headers-and-footers' ); ?></h2>
|
||
<table>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<!-- Clear API Cache -->
|
||
<form action="" method="POST">
|
||
<?php wp_nonce_field( 'wpb_debug_clear_cache' ); ?>
|
||
<input type="hidden" name="wpb_clear_api_cache" value="true">
|
||
<button class="button button-primary">Clear API Cache</button>
|
||
</form>
|
||
</td>
|
||
<td>
|
||
<!-- Clear Updates Transients -->
|
||
<form action="" method="POST">
|
||
<?php wp_nonce_field( 'wpb_debug_clear_updates' ); ?>
|
||
<input type="hidden" name="wpb_action" value="clear_updates_data">
|
||
<button class="button">Clear Updates Transients</button>
|
||
</form>
|
||
</td>
|
||
<td>
|
||
<!-- Sync Data with Server -->
|
||
<form action="" method="POST">
|
||
<?php wp_nonce_field( 'wpb_debug_background_sync' ); ?>
|
||
<input type="hidden" name="background_sync" value="true">
|
||
<button class="button button-primary">Sync Data From Server</button>
|
||
</form>
|
||
</td>
|
||
<td>
|
||
<!-- Load DB Option -->
|
||
<form method="post">
|
||
<?php wp_nonce_field( 'wpb_debug_load_option' ); ?>
|
||
<button type="button" class="button" id="show_input_button">Load DB Option</button>
|
||
<div id="input_field" style="display: none;">
|
||
<input type="text" name="load_option_name" id="option_name_input">
|
||
<button type="submit" id="submit_option_button">Submit</button>
|
||
</div>
|
||
</form>
|
||
<div id="result"
|
||
<?php
|
||
if ( ! $result_visible ) {
|
||
echo ' style="' . esc_attr( 'display: none;' ) . '"';
|
||
}
|
||
?>
|
||
>
|
||
<?php
|
||
if ( is_array( $option_value ) ) {
|
||
echo 'Option Value: ' . esc_html( implode( ', ', array_map( 'strval', $option_value ) ) );
|
||
} else {
|
||
echo 'Option Value: ' . esc_html( (string) $option_value );
|
||
}
|
||
?>
|
||
<button id="clear_result_button">✖</button>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<!-- Set DB Option (whitelist: wpb_ prefix only) -->
|
||
<button type="button" class="button" id="set_option_button"><?php esc_html_e( 'Set DB Option', 'wp-headers-and-footers' ); ?></button>
|
||
<form id="set_option_form" method="post" style="display: none; margin-right: 10px;">
|
||
<?php wp_nonce_field( 'wpb_debug_set_option' ); ?>
|
||
<div class="option-input-wrapper" style="display: inline-block;">
|
||
<label for="option_name"><?php esc_html_e( 'Option Name (must start with wpb_):', 'wp-headers-and-footers' ); ?></label>
|
||
<input type="text" name="set_option_name" id="option_name" placeholder="wpb_">
|
||
</div>
|
||
<div class="option-input-wrapper">
|
||
<label for="option_value">Option Value:</label>
|
||
<input type="text" name="option_value" id="option_value">
|
||
</div>
|
||
<button type="submit" id="submit_set_option_button">Set Option</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<br>
|
||
|
||
<table class="widefat">
|
||
<thead>
|
||
<tr>
|
||
<th>Key</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>WP_WPB__REMOTE_ADDR</td>
|
||
<td><?php echo esc_html( wpb_debug_mask( isset( $_SERVER['SERVER_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ) ) : '', 6 ) ); ?></td>
|
||
</tr>
|
||
<tr class="alternate">
|
||
<td>WP_WPB__DIR</td>
|
||
<td><?php echo esc_html( wpb_debug_mask_path( WPBRIGADE_SDK_DIR ) ); ?></td>
|
||
</tr>
|
||
<tr class="alternate">
|
||
<td>wp_using_ext_object_cache()</td>
|
||
<td>false</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>SDK Versions</h2>
|
||
<table id="wpb_sdks" class="widefat">
|
||
<thead>
|
||
<tr>
|
||
<th>Version</th>
|
||
<th>SDK Path</th>
|
||
<th>Module Path</th>
|
||
<th>Is Active</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr style="background: #E6FFE6; font-weight: bold">
|
||
<td><?php echo esc_html( defined( 'WP_WPBRIGADE_SDK_VERSION' ) ? WP_WPBRIGADE_SDK_VERSION : '' ); ?></td>
|
||
<td><?php echo esc_html( wpb_debug_mask_path( WPBRIGADE_SDK_DIR ) ); ?></td>
|
||
<td><?php echo esc_html( wpb_debug_mask_path( WPBRIGADE_PLUGIN_DIR ) ); ?></td>
|
||
<td>Active</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>Plugins</h2>
|
||
<table id="wpb_sdks" class="widefat">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Slug</th>
|
||
<th>Version</th>
|
||
<th>Title</th>
|
||
<th>API</th>
|
||
<th>Telemetry State</th>
|
||
<th>Module Path</th>
|
||
<th>Public Key</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><?php echo esc_html( (string) $wpb_sdk_module_id ); ?></td>
|
||
<td><?php echo esc_html( isset( $data['product_info']['slug'] ) ? $data['product_info']['slug'] : '' ); ?></td>
|
||
<td><?php echo esc_html( isset( $data['product_info']['version'] ) ? $data['product_info']['version'] : '' ); ?></td>
|
||
<td><?php echo esc_html( isset( $data['product_info']['name'] ) ? $data['product_info']['name'] : '' ); ?></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td><?php echo esc_html( wpb_debug_mask_path( WPBRIGADE_PLUGIN_DIR ) ); ?></td>
|
||
<td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
|
||
<td>
|
||
<button class="button" id="show-account-button" onclick="window.location.href = '<?php echo esc_url( admin_url( 'admin.php?page=account' ) ); ?>'">Account</button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>Plugins/Sites</h2>
|
||
<table id="wpb_sdks" class="widefat">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Slug</th>
|
||
<th>User ID</th>
|
||
<th>License ID</th>
|
||
<th>Plan</th>
|
||
<th>Public Key</th>
|
||
<th>Secret Key</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>3538</td>
|
||
<td><?php echo esc_html( isset( $data['product_info']['slug'] ) ? $data['product_info']['slug'] : '' ); ?></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
<td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
|
||
<td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>Users</h2>
|
||
<table id="wpb_users" class="widefat">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Name</th>
|
||
<th>Email</th>
|
||
<th>Verified</th>
|
||
<th>Public Key</th>
|
||
<th>Secret Key</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>3538</td>
|
||
<td><?php echo esc_html( isset( $data['user_info']['user_nickname'] ) ? $data['user_info']['user_nickname'] : '' ); ?></td>
|
||
<td><?php echo esc_html( wpb_debug_mask_email( isset( $data['user_info']['user_email'] ) ? $data['user_info']['user_email'] : '' ) ); ?></td>
|
||
<td></td>
|
||
<td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
|
||
<td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
|
||
<!-- JavaScript code to show/hide input field -->
|
||
<script>
|
||
// Load DB Option
|
||
document.getElementById('show_input_button').addEventListener('click', function() {
|
||
document.getElementById('input_field').style.display = 'block';
|
||
});
|
||
|
||
document.getElementById('submit_option_button').addEventListener('click', function() {
|
||
// Hide the input field
|
||
document.getElementById('input_field').style.display = 'none';
|
||
// Set the result container to be visible
|
||
document.getElementById('result').style.display = 'block';
|
||
});
|
||
|
||
document.getElementById('clear_result_button').addEventListener('click', function() {
|
||
// Hide the result container
|
||
document.getElementById('result').style.display = 'none';
|
||
});
|
||
|
||
// Set DB Option
|
||
document.getElementById('set_option_button').addEventListener('click', function() {
|
||
// Show the form
|
||
document.getElementById('set_option_form').style.display = 'block';
|
||
});
|
||
|
||
document.getElementById('set_option_form').addEventListener('submit', function(event) {
|
||
// Hide the form
|
||
document.getElementById('set_option_form').style.display = 'none';
|
||
// Get the option name and value from the form
|
||
var optionName = document.getElementById('option_name').value;
|
||
var optionValue = document.getElementById('option_value').value;
|
||
});
|
||
|
||
document.getElementById('submit_set_option_button').addEventListener('click', function() {
|
||
// Hide the input fields
|
||
document.getElementById('option_name').style.display = 'none';
|
||
document.getElementById('option_value').style.display = 'none';
|
||
});
|
||
|
||
setTimeout(function() {
|
||
var el = document.getElementById('success_message');
|
||
if (el) {
|
||
el.style.display = 'none';
|
||
}
|
||
}, 3000);
|
||
</script>
|