PluginProbe
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) / trunk
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) vtrunk
9.1.2 9.1.1 9.1.0 9.0.2 9.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 All 153 releases
wp-analytify / lib / wpb-sdk / views / wpb-debug.php

wpb-debug.php in Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) trunk, at lib/wpb-sdk/views/wpb-debug.php

546 lines 17.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPB Debug view.
4 *
5 * HIGH RISK – Admin-only debug UI. Outputs sensitive data (keys, paths, user info).
6 * Only load when is_admin(), manage_options, and WPBRIGADE_SDK__DEV_MODE are satisfied.
7 *
8 * @package wpbrigade_sdk
9 */
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
16 wp_die(
17 esc_html__( 'You do not have permission to access this page.', 'wpbrigade-sdk' ),
18 '',
19 array( 'response' => 403 )
20 );
21 }
22
23 if ( ! defined( 'WPBRIGADE_SDK__DEV_MODE' ) || true !== WPBRIGADE_SDK__DEV_MODE ) {
24 wp_die(
25 esc_html__( 'Debug mode is not enabled.', 'wpbrigade-sdk' ),
26 '',
27 array( 'response' => 403 )
28 );
29 }
30
31 /**
32 * Enqueue CSS file for admin debugging.
33 *
34 * @param string $hook_suffix Current admin screen hook suffix.
35 * @return void
36 */
37 function wpb_debug_enqueue_styles( $hook_suffix ) {
38 if ( 'toplevel_page_wpb-debug-mode' !== $hook_suffix || ! current_user_can( 'manage_options' ) ) {
39 return;
40 }
41
42 $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
43 $css_path = dirname( __DIR__ ) . "/assets/css/debug{$suffix}.css";
44 if ( ! is_readable( $css_path ) ) {
45 return;
46 }
47
48 wp_enqueue_style(
49 'wpb-sdk-debug-style',
50 plugins_url( "assets/css/debug{$suffix}.css", dirname( __DIR__ ) . '/start.php' ),
51 array(),
52 defined( 'WP_WPBRIGADE_SDK_VERSION' ) ? WP_WPBRIGADE_SDK_VERSION : '1.0.0'
53 );
54 }
55 add_action( 'admin_enqueue_scripts', 'wpb_debug_enqueue_styles' );
56
57 /**
58 * Verify POST request: method, capability, and nonce for a given action.
59 *
60 * @param string $action Nonce action (e.g. 'wpb_debug_clear_cache').
61 * @return bool True if valid POST with valid nonce and capability.
62 */
63 function wpb_debug_verify_request( $action ) {
64 if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
65 return false;
66 }
67 if ( ! current_user_can( 'manage_options' ) ) {
68 return false;
69 }
70 if ( ! isset( $_POST['_wpnonce'] ) ) {
71 return false;
72 }
73 return (bool) wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), $action );
74 }
75
76 /**
77 * Mask a sensitive string (show last N chars, rest as bullets).
78 *
79 * @param string $value Raw value.
80 * @param int $visible Number of trailing characters to show.
81 * @return string Masked value.
82 */
83 function wpb_debug_mask( $value, $visible = 4 ) {
84 if ( '' === (string) $value ) {
85 return '';
86 }
87 $value = (string) $value;
88 $len = strlen( $value );
89 if ( $len <= $visible ) {
90 return str_repeat( '', $len );
91 }
92 return str_repeat( '', $len - $visible ) . substr( $value, - $visible );
93 }
94
95 /**
96 * Mask email for display (e.g. a***@***.com).
97 *
98 * @param string $email Email address.
99 * @return string Masked email.
100 */
101 function wpb_debug_mask_email( $email ) {
102 if ( '' === (string) $email || ! is_email( $email ) ) {
103 return '';
104 }
105 $parts = explode( '@', $email, 2 );
106 if ( 2 !== count( $parts ) ) {
107 return wpb_debug_mask( $email, 0 );
108 }
109 $local = $parts[0];
110 $domain = $parts[1];
111 $local_display = strlen( $local ) > 2 ? substr( $local, 0, 1 ) . str_repeat( '', strlen( $local ) - 1 ) : '••';
112 $domain_display = strlen( $domain ) > 4 ? '•••' . substr( $domain, -4 ) : '••••';
113 return $local_display . '@' . $domain_display;
114 }
115
116 /**
117 * Shorten path for display (show last two segments to avoid exposing full server path).
118 *
119 * @param string $path Full path.
120 * @return string Shortened path.
121 */
122 function wpb_debug_mask_path( $path ) {
123 if ( '' === (string) $path ) {
124 return '';
125 }
126 $path = str_replace( array( '\\', '/' ), '/', (string) $path );
127 $parts = array_filter( explode( '/', $path ) );
128 $tail = array_slice( $parts, -2 );
129 return ( count( $parts ) > 2 ? '…/' : '' ) . implode( '/', $tail );
130 }
131
132 $resolved = function_exists( 'wpb_sdk_dev_view_resolve_product' )
133 ? wpb_sdk_dev_view_resolve_product()
134 : array(
135 'slug' => '',
136 'module_id' => '1',
137 );
138 $slug = isset( $resolved['slug'] ) ? (string) $resolved['slug'] : '';
139 $wpb_sdk_module_id = isset( $resolved['module_id'] ) ? (string) $resolved['module_id'] : '1';
140
141 $all_plugins = array();
142
143 $data = function_exists( 'wpb_sdk_dev_view_load_logs_data' )
144 ? wpb_sdk_dev_view_load_logs_data( $slug )
145 : array();
146
147 if ( empty( $data ) && function_exists( 'wpb_sdk_dev_view_default_logs_data' ) ) {
148 $data = wpb_sdk_dev_view_default_logs_data();
149 }
150
151 $plugin_path = isset( $data['product_info']['path'] ) ? $data['product_info']['path'] : '';
152 $installed_plugin_slugs = array_keys( get_plugins() );
153 $active_plugins = get_option( 'active_plugins', array() );
154 $sdk_path = WPBRIGADE_SDK_DIR;
155
156 $this_sdk_path = strstr( $sdk_path, $slug );
157 if ( false !== $this_sdk_path ) {
158 $this_sdk_path = '\\' . ltrim( $this_sdk_path, '\\' );
159 }
160
161 // Clear API cache.
162 if ( isset( $_POST['wpb_clear_api_cache'] ) && 'true' === $_POST['wpb_clear_api_cache'] && wpb_debug_verify_request( 'wpb_debug_clear_cache' ) ) {
163 update_option( 'wpb_api_cache', null );
164 }
165
166 // Clear updates data.
167 if ( isset( $_POST['wpb_action'] ) && 'clear_updates_data' === $_POST['wpb_action'] && wpb_debug_verify_request( 'wpb_debug_clear_updates' ) ) {
168 set_site_transient( 'update_plugins', null );
169 set_site_transient( 'update_themes', null );
170 }
171
172 // Background sync.
173 if ( isset( $_POST['background_sync'] ) && 'true' === $_POST['background_sync'] && wpb_debug_verify_request( 'wpb_debug_background_sync' ) ) {
174 $response = wp_remote_post(
175 WPBRIGADE_SDK_API_ENDPOINT,
176 array(
177 'method' => 'POST',
178 'body' => $data,
179 'timeout' => 5,
180 'headers' => array(),
181 )
182 );
183
184 if ( is_wp_error( $response ) ) {
185 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- SDK debug UI only.
186 error_log( 'WPB SDK debug: background sync failed — ' . $response->get_error_message() );
187 } else {
188 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- SDK debug UI only.
189 error_log(
190 'WPB SDK debug: background sync completed — HTTP ' . (string) wp_remote_retrieve_response_code( $response )
191 );
192 }
193 }
194
195 /** Option name prefix allowed for load/set DB option tools (strict whitelist by prefix). */
196 define( 'WPB_DEBUG_OPTION_PREFIX', 'wpb_' );
197
198 /**
199 * Whether an option name is allowed for debug load/set tools.
200 *
201 * @param string $option_name Raw option name.
202 * @return bool
203 */
204 function wpb_debug_option_name_is_allowed( $option_name ) {
205 if ( ! current_user_can( 'manage_options' ) ) {
206 return false;
207 }
208
209 $option_name = sanitize_key( (string) $option_name );
210 if ( '' === $option_name ) {
211 return false;
212 }
213
214 return 0 === strpos( $option_name, WPB_DEBUG_OPTION_PREFIX );
215 }
216
217 /**
218 * Set an option value only if it is in the allowed prefix scope.
219 *
220 * @param string $option_name Option name (must start with WPB_DEBUG_OPTION_PREFIX).
221 * @param mixed $option_value Option value.
222 * @return bool True on success, false if not allowed.
223 */
224 function wpb_debug_set_option( $option_name, $option_value ) {
225 if ( ! wpb_debug_option_name_is_allowed( $option_name ) ) {
226 return false;
227 }
228
229 $option_name = sanitize_key( (string) $option_name );
230 update_option( $option_name, $option_value );
231
232 return true;
233 }
234
235 $wpb_debug_set_option_success = false;
236 $wpb_debug_set_option_submitted = false;
237 if ( isset( $_POST['set_option_name'], $_POST['option_value'] ) && wpb_debug_verify_request( 'wpb_debug_set_option' ) ) {
238 $wpb_debug_set_option_submitted = true;
239 $option_name = sanitize_text_field( wp_unslash( $_POST['set_option_name'] ) );
240 $option_value = isset( $_POST['option_value'] ) ? sanitize_text_field( wp_unslash( $_POST['option_value'] ) ) : '';
241 $wpb_debug_set_option_success = wpb_debug_set_option( $option_name, $option_value );
242 }
243
244 /**
245 * Get an option value from the database (wpb_* options only).
246 *
247 * @param string $option_name Option name.
248 * @return mixed Option value or null when not allowed.
249 */
250 function wpb_debug_get_option_value( $option_name ) {
251 if ( ! wpb_debug_option_name_is_allowed( $option_name ) ) {
252 return null;
253 }
254
255 return get_option( sanitize_key( (string) $option_name ), null );
256 }
257
258 $option_value = '';
259 $result_visible = false;
260 $wpb_debug_load_option_error = false;
261 if ( isset( $_POST['load_option_name'] ) && wpb_debug_verify_request( 'wpb_debug_load_option' ) ) {
262 $option_name = sanitize_text_field( wp_unslash( $_POST['load_option_name'] ) );
263 if ( wpb_debug_option_name_is_allowed( $option_name ) ) {
264 $option_value = wpb_debug_get_option_value( $option_name );
265 $result_visible = true;
266 } else {
267 $wpb_debug_load_option_error = true;
268 }
269 }
270
271 $wpb_debug_msg_success = __( 'Successfully set the option.', 'wpbrigade-sdk' );
272 $wpb_debug_msg_error = __( 'Option not set. Name must start with wpb_.', 'wpbrigade-sdk' );
273 $wpb_debug_msg_load_error = __( 'Option not loaded. Name must start with wpb_.', 'wpbrigade-sdk' );
274 ?>
275
276 <h1>WPB Debug - SDK v.<?php echo esc_html( defined( 'WP_WPBRIGADE_SDK_VERSION' ) ? WP_WPBRIGADE_SDK_VERSION : '' ); ?></h1>
277
278 <?php if ( $wpb_debug_set_option_submitted ) : ?>
279 <div id="success_message" class="notice notice-<?php echo esc_attr( $wpb_debug_set_option_success ? 'success' : 'error' ); ?>" role="alert">
280 <p><?php echo esc_html( $wpb_debug_set_option_success ? $wpb_debug_msg_success : $wpb_debug_msg_error ); ?></p>
281 </div>
282 <?php endif; ?>
283
284 <?php if ( $wpb_debug_load_option_error ) : ?>
285 <div class="notice notice-error" role="alert">
286 <p><?php echo esc_html( $wpb_debug_msg_load_error ); ?></p>
287 </div>
288 <?php endif; ?>
289
290 <p class="notice notice-warning" style="margin: 1em 0;" role="alert">
291 <strong><?php esc_html_e( 'Admin-only debug page.', 'wpbrigade-sdk' ); ?></strong>
292 <?php esc_html_e( 'This page shows sensitive data (keys, paths, user info). Do not share screenshots or leave unattended.', 'wpbrigade-sdk' ); ?>
293 </p>
294
295 <h2><?php esc_html_e( 'Actions', 'wpbrigade-sdk' ); ?></h2>
296 <table>
297 <tbody>
298 <tr>
299 <td>
300 <!-- Clear API Cache -->
301 <form action="" method="POST">
302 <?php wp_nonce_field( 'wpb_debug_clear_cache' ); ?>
303 <input type="hidden" name="wpb_clear_api_cache" value="true">
304 <button class="button button-primary">Clear API Cache</button>
305 </form>
306 </td>
307 <td>
308 <!-- Clear Updates Transients -->
309 <form action="" method="POST">
310 <?php wp_nonce_field( 'wpb_debug_clear_updates' ); ?>
311 <input type="hidden" name="wpb_action" value="clear_updates_data">
312 <button class="button">Clear Updates Transients</button>
313 </form>
314 </td>
315 <td>
316 <!-- Sync Data with Server -->
317 <form action="" method="POST">
318 <?php wp_nonce_field( 'wpb_debug_background_sync' ); ?>
319 <input type="hidden" name="background_sync" value="true">
320 <button class="button button-primary">Sync Data From Server</button>
321 </form>
322 </td>
323 <td>
324 <!-- Load DB Option -->
325 <form method="post">
326 <?php wp_nonce_field( 'wpb_debug_load_option' ); ?>
327 <button type="button" class="button" id="show_input_button">Load DB Option</button>
328 <div id="input_field" style="display: none;">
329 <input type="text" name="load_option_name" id="option_name_input">
330 <button type="submit" id="submit_option_button">Submit</button>
331 </div>
332 </form>
333 <div id="result"
334 <?php
335 if ( ! $result_visible ) {
336 echo ' style="' . esc_attr( 'display: none;' ) . '"';
337 }
338 ?>
339 >
340 <?php
341 if ( null === $option_value ) {
342 esc_html_e( 'Option not found.', 'wpbrigade-sdk' );
343 } elseif ( is_array( $option_value ) ) {
344 echo esc_html__( 'Option Value:', 'wpbrigade-sdk' ) . ' ' . esc_html( wp_json_encode( $option_value ) );
345 } else {
346 echo esc_html__( 'Option Value:', 'wpbrigade-sdk' ) . ' ' . esc_html( (string) $option_value );
347 }
348 ?>
349 <button id="clear_result_button"></button>
350 </div>
351 </td>
352 <td>
353 <!-- Set DB Option (whitelist: wpb_ prefix only) -->
354 <button type="button" class="button" id="set_option_button"><?php esc_html_e( 'Set DB Option', 'wpbrigade-sdk' ); ?></button>
355 <form id="set_option_form" method="post" style="display: none; margin-right: 10px;">
356 <?php wp_nonce_field( 'wpb_debug_set_option' ); ?>
357 <div class="option-input-wrapper" style="display: inline-block;">
358 <label for="option_name"><?php esc_html_e( 'Option Name (must start with wpb_):', 'wpbrigade-sdk' ); ?></label>
359 <input type="text" name="set_option_name" id="option_name" placeholder="wpb_">
360 </div>
361 <div class="option-input-wrapper">
362 <label for="option_value">Option Value:</label>
363 <input type="text" name="option_value" id="option_value">
364 </div>
365 <button type="submit" id="submit_set_option_button">Set Option</button>
366 </form>
367 </td>
368 </tr>
369 </tbody>
370 </table>
371
372 <br>
373
374 <table class="widefat">
375 <thead>
376 <tr>
377 <th>Key</th>
378 <th>Value</th>
379 </tr>
380 </thead>
381 <tbody>
382 <tr>
383 <td>WP_WPB__REMOTE_ADDR</td>
384 <td><?php echo esc_html( wpb_debug_mask( isset( $_SERVER['SERVER_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ) ) : '', 6 ) ); ?></td>
385 </tr>
386 <tr class="alternate">
387 <td>WP_WPB__DIR</td>
388 <td><?php echo esc_html( wpb_debug_mask_path( WPBRIGADE_SDK_DIR ) ); ?></td>
389 </tr>
390 <tr class="alternate">
391 <td>wp_using_ext_object_cache()</td>
392 <td>false</td>
393 </tr>
394 </tbody>
395 </table>
396
397 <h2>SDK Versions</h2>
398 <table id="wpb_sdks" class="widefat">
399 <thead>
400 <tr>
401 <th>Version</th>
402 <th>SDK Path</th>
403 <th>Module Path</th>
404 <th>Is Active</th>
405 </tr>
406 </thead>
407 <tbody>
408 <tr style="background: #E6FFE6; font-weight: bold">
409 <td><?php echo esc_html( defined( 'WP_WPBRIGADE_SDK_VERSION' ) ? WP_WPBRIGADE_SDK_VERSION : '' ); ?></td>
410 <td><?php echo esc_html( wpb_debug_mask_path( WPBRIGADE_SDK_DIR ) ); ?></td>
411 <td><?php echo esc_html( wpb_debug_mask_path( dirname( WPBRIGADE_SDK_DIR ) ) ); ?></td>
412 <td>Active</td>
413 </tr>
414 </tbody>
415 </table>
416
417 <h2>Plugins</h2>
418 <table id="wpb_sdks" class="widefat">
419 <thead>
420 <tr>
421 <th>ID</th>
422 <th>Slug</th>
423 <th>Version</th>
424 <th>Title</th>
425 <th>API</th>
426 <th>Telemetry State</th>
427 <th>Module Path</th>
428 <th>Public Key</th>
429 <th>Actions</th>
430 </tr>
431 </thead>
432 <tbody>
433 <tr>
434 <td><?php echo esc_html( (string) $wpb_sdk_module_id ); ?></td>
435 <td><?php echo esc_html( isset( $data['product_info']['slug'] ) ? $data['product_info']['slug'] : '' ); ?></td>
436 <td><?php echo esc_html( isset( $data['product_info']['version'] ) ? $data['product_info']['version'] : '' ); ?></td>
437 <td><?php echo esc_html( isset( $data['product_info']['name'] ) ? $data['product_info']['name'] : '' ); ?></td>
438 <td></td>
439 <td></td>
440 <td><?php echo esc_html( wpb_debug_mask_path( dirname( WPBRIGADE_SDK_DIR ) ) ); ?></td>
441 <td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
442 <td>
443 <button class="button" id="show-account-button" onclick="window.location.href = '<?php echo esc_url( admin_url( 'admin.php?page=account' ) ); ?>'">Account</button>
444 </td>
445 </tr>
446 </tbody>
447 </table>
448
449 <h2>Plugins/Sites</h2>
450 <table id="wpb_sdks" class="widefat">
451 <thead>
452 <tr>
453 <th>ID</th>
454 <th>Slug</th>
455 <th>User ID</th>
456 <th>License ID</th>
457 <th>Plan</th>
458 <th>Public Key</th>
459 <th>Secret Key</th>
460 </tr>
461 </thead>
462 <tbody>
463 <tr>
464 <td>3538</td>
465 <td><?php echo esc_html( isset( $data['product_info']['slug'] ) ? $data['product_info']['slug'] : '' ); ?></td>
466 <td></td>
467 <td></td>
468 <td></td>
469 <td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
470 <td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
471 </tr>
472 </tbody>
473 </table>
474
475 <h2>Users</h2>
476 <table id="wpb_users" class="widefat">
477 <thead>
478 <tr>
479 <th>ID</th>
480 <th>Name</th>
481 <th>Email</th>
482 <th>Verified</th>
483 <th>Public Key</th>
484 <th>Secret Key</th>
485 </tr>
486 </thead>
487 <tbody>
488 <tr>
489 <td>3538</td>
490 <td><?php echo esc_html( isset( $data['user_info']['user_nickname'] ) ? $data['user_info']['user_nickname'] : '' ); ?></td>
491 <td><?php echo esc_html( wpb_debug_mask_email( isset( $data['user_info']['user_email'] ) ? $data['user_info']['user_email'] : '' ) ); ?></td>
492 <td></td>
493 <td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
494 <td><?php echo esc_html( wpb_debug_mask( isset( $data['authentication']['public_key'] ) ? $data['authentication']['public_key'] : '', 4 ) ); ?></td>
495 </tr>
496 </tbody>
497 </table>
498
499
500 <!-- JavaScript code to show/hide input field -->
501 <script>
502 // Load DB Option
503 document.getElementById('show_input_button').addEventListener('click', function() {
504 document.getElementById('input_field').style.display = 'block';
505 });
506
507 document.getElementById('submit_option_button').addEventListener('click', function() {
508 // Hide the input field
509 document.getElementById('input_field').style.display = 'none';
510 // Set the result container to be visible
511 document.getElementById('result').style.display = 'block';
512 });
513
514 document.getElementById('clear_result_button').addEventListener('click', function() {
515 // Hide the result container
516 document.getElementById('result').style.display = 'none';
517 });
518
519 // Set DB Option
520 document.getElementById('set_option_button').addEventListener('click', function() {
521 // Show the form
522 document.getElementById('set_option_form').style.display = 'block';
523 });
524
525 document.getElementById('set_option_form').addEventListener('submit', function(event) {
526 // Hide the form
527 document.getElementById('set_option_form').style.display = 'none';
528 // Get the option name and value from the form
529 var optionName = document.getElementById('option_name').value;
530 var optionValue = document.getElementById('option_value').value;
531 });
532
533 document.getElementById('submit_set_option_button').addEventListener('click', function() {
534 // Hide the input fields
535 document.getElementById('option_name').style.display = 'none';
536 document.getElementById('option_value').style.display = 'none';
537 });
538
539 setTimeout(function() {
540 var el = document.getElementById('success_message');
541 if (el) {
542 el.style.display = 'none';
543 }
544 }, 3000);
545 </script>
546