| @@ -37,8 +37,10 @@ | ||
| 37 | 37 | add_action( 'wp_ajax_sureforms_recommended_plugin_activate', [ $this, 'required_plugin_activate' ] ); |
| 38 | 38 | add_action( 'wp_ajax_sureforms_recommended_plugin_install', 'wp_ajax_install_plugin' ); |
| 39 | 39 | add_action( 'wp_ajax_sureforms_integration', [ $this, 'generate_data_for_suretriggers_integration' ] ); |
| 40 | 40 | add_action( 'wp_ajax_srfm_download_export', [ $this, 'download_export_file' ] ); |
| 41 | + add_action( 'wp_ajax_srfm_download_logs', [ $this, 'download_client_log' ] ); | |
| 42 | + add_action( 'wp_ajax_srfm_clear_logs', [ $this, 'clear_client_log' ] ); | |
| 41 | 43 | |
| 42 | 44 | add_filter( SRFM_SLUG . '_admin_filter', [ $this, 'localize_script_integration' ] ); |
| 43 | 45 | } |
| 44 | 46 | |
| @@ -457,5 +459,91 @@ | ||
| 457 | 459 | wp_delete_file( $filepath ); |
| 458 | 460 | |
| 459 | 461 | exit; |
| 460 | 462 | } |
| 463 | + /** | |
| 464 | + * Stream the client debug log to an administrator. | |
| 465 | + * | |
| 466 | + * Takes no filename parameter. There is exactly one log file and the server | |
| 467 | + * derives its path, which removes the path-traversal question entirely rather | |
| 468 | + * than guarding against it -- and keeps the unguessable file name, which is | |
| 469 | + * what actually protects the log on nginx, out of the page. | |
| 470 | + * | |
| 471 | + * @since 2.12.6 | |
| 472 | + * @return void | |
| 473 | + */ | |
| 474 | + public function download_client_log() { | |
| 475 | + $this->verify_log_request(); | |
| 476 | + | |
| 477 | + $path = Client_Logger::get_log_path( false ); | |
| 478 | + $has_log = '' !== $path && file_exists( $path ); | |
| 479 | + | |
| 480 | + // The buttons are always offered while logging is on, so downloading before | |
| 481 | + // anything has failed is a normal thing to do. Hand back an explanatory file | |
| 482 | + // rather than a wp_die() screen -- an empty log is the good outcome. | |
| 483 | + if ( ! $has_log ) { | |
| 484 | + header( 'Content-Type: text/plain; charset=utf-8' ); | |
| 485 | + header( 'X-Content-Type-Options: nosniff' ); | |
| 486 | + header( 'Content-Disposition: attachment; filename="sureforms-debug-log.txt"' ); | |
| 487 | + | |
| 488 | + if ( ob_get_level() ) { | |
| 489 | + ob_end_clean(); | |
| 490 | + } | |
| 491 | + | |
| 492 | + echo esc_html__( 'No form submission failures have been recorded.', 'sureforms' ); | |
| 493 | + exit; | |
| 494 | + } | |
| 495 | + | |
| 496 | + $size = filesize( $path ); | |
| 497 | + | |
| 498 | + header( 'Content-Type: text/plain; charset=utf-8' ); | |
| 499 | + header( 'X-Content-Type-Options: nosniff' ); | |
| 500 | + header( 'Content-Disposition: attachment; filename="sureforms-debug-log.txt"' ); | |
| 501 | + | |
| 502 | + if ( is_int( $size ) ) { | |
| 503 | + header( 'Content-Length: ' . $size ); | |
| 504 | + } | |
| 505 | + | |
| 506 | + header( 'Cache-Control: private, max-age=0, must-revalidate' ); | |
| 507 | + | |
| 508 | + if ( ob_get_level() ) { | |
| 509 | + ob_end_clean(); | |
| 510 | + } | |
| 511 | + | |
| 512 | + readfile( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile, WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Direct file output is required to stream the download. | |
| 513 | + exit; | |
| 514 | + } | |
| 515 | + | |
| 516 | + /** | |
| 517 | + * Delete the client debug log. | |
| 518 | + * | |
| 519 | + * @since 2.12.6 | |
| 520 | + * @return void | |
| 521 | + */ | |
| 522 | + public function clear_client_log() { | |
| 523 | + $this->verify_log_request(); | |
| 524 | + | |
| 525 | + Client_Logger::clear(); | |
| 526 | + | |
| 527 | + wp_send_json_success(); | |
| 528 | + } | |
| 529 | + | |
| 530 | + /** | |
| 531 | + * Capability and nonce gate shared by both log actions. | |
| 532 | + * | |
| 533 | + * Capability first, ahead of the nonce, matching the ordering of the sibling | |
| 534 | + * handlers in this class. | |
| 535 | + * | |
| 536 | + * @since 2.12.6 | |
| 537 | + * @return void | |
| 538 | + */ | |
| 539 | + private function verify_log_request() { | |
| 540 | + if ( ! Helper::current_user_can() ) { | |
| 541 | + wp_die( esc_html__( 'You do not have permission to access this file.', 'sureforms' ) ); | |
| 542 | + } | |
| 543 | + | |
| 544 | + if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'srfm_client_logs' ) ) { | |
| 545 | + wp_die( esc_html__( 'Security check failed.', 'sureforms' ) ); | |
| 546 | + } | |
| 547 | + } | |
| 548 | + | |
| 461 | 549 | } |