Merged in feature/280-dev-dev01 (pull request #21)
auto-patch 280-dev-dev01-2024-01-19T16_41_58 * auto-patch 280-dev-dev01-2024-01-19T16_41_58
This commit is contained in:
@@ -4,10 +4,13 @@ declare( strict_types = 1 );
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Logging;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ FileController, ListTable };
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ File, FileController, FileListTable, SearchListTable };
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use WC_Admin_Status;
|
||||
use WC_Log_Handler_File, WC_Log_Handler_DB;
|
||||
use WC_Log_Levels;
|
||||
use WP_List_Table;
|
||||
|
||||
/**
|
||||
* PageController class.
|
||||
@@ -24,9 +27,9 @@ class PageController {
|
||||
private $file_controller;
|
||||
|
||||
/**
|
||||
* Instance of ListTable.
|
||||
* Instance of FileListTable or SearchListTable.
|
||||
*
|
||||
* @var ListTable
|
||||
* @var FileListTable|SearchListTable
|
||||
*/
|
||||
private $list_table;
|
||||
|
||||
@@ -81,7 +84,7 @@ class PageController {
|
||||
$handler = Constants::get_constant( 'WC_LOG_HANDLER' );
|
||||
|
||||
if ( is_null( $handler ) || ! class_exists( $handler ) ) {
|
||||
$handler = \WC_Log_Handler_File::class;
|
||||
$handler = WC_Log_Handler_File::class;
|
||||
}
|
||||
|
||||
return $handler;
|
||||
@@ -97,8 +100,7 @@ class PageController {
|
||||
|
||||
switch ( $handler ) {
|
||||
case LogHandlerFileV2::class:
|
||||
$params = $this->get_query_params();
|
||||
$this->render_filev2( $params );
|
||||
$this->render_filev2();
|
||||
break;
|
||||
case 'WC_Log_Handler_DB':
|
||||
WC_Admin_Status::status_logs_db();
|
||||
@@ -112,20 +114,21 @@ class PageController {
|
||||
/**
|
||||
* Render the views for the FileV2 log handler.
|
||||
*
|
||||
* @param array $params Args for rendering the views.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_filev2( array $params = array() ): void {
|
||||
$view = $params['view'] ?? '';
|
||||
private function render_filev2(): void {
|
||||
$params = $this->get_query_params( array( 'view' ) );
|
||||
|
||||
switch ( $view ) {
|
||||
switch ( $params['view'] ) {
|
||||
case 'list_files':
|
||||
default:
|
||||
$this->render_file_list_page( $params );
|
||||
$this->render_list_files_view();
|
||||
break;
|
||||
case 'search_results':
|
||||
$this->render_search_results_view();
|
||||
break;
|
||||
case 'single_file':
|
||||
$this->render_single_file_page( $params );
|
||||
$this->render_single_file_view();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -133,18 +136,21 @@ class PageController {
|
||||
/**
|
||||
* Render the file list view.
|
||||
*
|
||||
* @param array $params Args for rendering the view.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_file_list_page( array $params = array() ): void {
|
||||
$defaults = $this->get_query_param_defaults();
|
||||
private function render_list_files_view(): void {
|
||||
$params = $this->get_query_params( array( 'order', 'orderby', 'source', 'view' ) );
|
||||
$defaults = $this->get_query_param_defaults();
|
||||
$list_table = $this->get_list_table( $params['view'] );
|
||||
|
||||
$list_table->prepare_items();
|
||||
|
||||
?>
|
||||
<header id="logs-header" class="wc-logs-header">
|
||||
<h2>
|
||||
<?php esc_html_e( 'Browse log files', 'woocommerce' ); ?>
|
||||
</h2>
|
||||
<?php $this->render_search_field(); ?>
|
||||
</header>
|
||||
<form id="logs-list-table-form" method="get">
|
||||
<input type="hidden" name="page" value="wc-status" />
|
||||
@@ -158,8 +164,7 @@ class PageController {
|
||||
/>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php $this->get_list_table()->prepare_items(); ?>
|
||||
<?php $this->get_list_table()->display(); ?>
|
||||
<?php $list_table->display(); ?>
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
@@ -167,12 +172,11 @@ class PageController {
|
||||
/**
|
||||
* Render the single file view.
|
||||
*
|
||||
* @param array $params Args for rendering the view.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_single_file_page( array $params ): void {
|
||||
$file = $this->file_controller->get_file_by_id( $params['file_id'] );
|
||||
private function render_single_file_view(): void {
|
||||
$params = $this->get_query_params( array( 'file_id', 'view' ) );
|
||||
$file = $this->file_controller->get_file_by_id( $params['file_id'] );
|
||||
|
||||
if ( is_wp_error( $file ) ) {
|
||||
?>
|
||||
@@ -194,22 +198,28 @@ class PageController {
|
||||
$rotations = $this->file_controller->get_file_rotations( $file->get_file_id() );
|
||||
$rotation_url_base = add_query_arg( 'view', 'single_file', $this->get_logs_tab_url() );
|
||||
|
||||
$delete_url = add_query_arg(
|
||||
$download_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'export',
|
||||
'file_id' => array( $file->get_file_id() ),
|
||||
),
|
||||
wp_nonce_url( $this->get_logs_tab_url(), 'bulk-log-files' )
|
||||
);
|
||||
$delete_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'delete',
|
||||
'file_id' => array( $file->get_file_id() ),
|
||||
),
|
||||
wp_nonce_url( $this->get_logs_tab_url(), 'bulk-log-files' )
|
||||
);
|
||||
|
||||
$stream = $file->get_stream();
|
||||
$line_number = 1;
|
||||
|
||||
$delete_confirmation_js = sprintf(
|
||||
"return window.confirm( '%s' )",
|
||||
esc_js( __( 'Delete this log file permanently?', 'woocommerce' ) )
|
||||
);
|
||||
|
||||
$stream = $file->get_stream();
|
||||
$line_number = 1;
|
||||
|
||||
?>
|
||||
<header id="logs-header" class="wc-logs-header">
|
||||
<h2>
|
||||
@@ -253,6 +263,14 @@ class PageController {
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
<div class="wc-logs-single-file-actions">
|
||||
<?php
|
||||
// Download button.
|
||||
printf(
|
||||
'<a href="%1$s" class="button button-secondary">%2$s</a>',
|
||||
esc_url( $download_url ),
|
||||
esc_html__( 'Download', 'woocommerce' )
|
||||
);
|
||||
?>
|
||||
<?php
|
||||
// Delete button.
|
||||
printf(
|
||||
@@ -279,6 +297,26 @@ class PageController {
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the search results view.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_search_results_view(): void {
|
||||
$params = $this->get_query_params( array( 'order', 'orderby', 'search', 'source', 'view' ) );
|
||||
$list_table = $this->get_list_table( $params['view'] );
|
||||
|
||||
$list_table->prepare_items();
|
||||
|
||||
?>
|
||||
<header id="logs-header" class="wc-logs-header">
|
||||
<h2><?php esc_html_e( 'Search results', 'woocommerce' ); ?></h2>
|
||||
<?php $this->render_search_field(); ?>
|
||||
</header>
|
||||
<?php $list_table->display(); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default values for URL query params for FileV2 views.
|
||||
*
|
||||
@@ -289,6 +327,7 @@ class PageController {
|
||||
'file_id' => '',
|
||||
'order' => $this->file_controller::DEFAULTS_GET_FILES['order'],
|
||||
'orderby' => $this->file_controller::DEFAULTS_GET_FILES['orderby'],
|
||||
'search' => '',
|
||||
'source' => $this->file_controller::DEFAULTS_GET_FILES['source'],
|
||||
'view' => 'list_files',
|
||||
);
|
||||
@@ -297,9 +336,11 @@ class PageController {
|
||||
/**
|
||||
* Get and validate URL query params for FileV2 views.
|
||||
*
|
||||
* @param array $param_keys Optional. The names of the params you want to get.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_query_params(): array {
|
||||
public function get_query_params( array $param_keys = array() ): array {
|
||||
$defaults = $this->get_query_param_defaults();
|
||||
$params = filter_input_array(
|
||||
INPUT_GET,
|
||||
@@ -307,7 +348,7 @@ class PageController {
|
||||
'file_id' => array(
|
||||
'filter' => FILTER_CALLBACK,
|
||||
'options' => function( $file_id ) {
|
||||
return sanitize_file_name( $file_id );
|
||||
return sanitize_file_name( wp_unslash( $file_id ) );
|
||||
},
|
||||
),
|
||||
'order' => array(
|
||||
@@ -324,16 +365,22 @@ class PageController {
|
||||
'default' => $defaults['orderby'],
|
||||
),
|
||||
),
|
||||
'search' => array(
|
||||
'filter' => FILTER_CALLBACK,
|
||||
'options' => function( $search ) {
|
||||
return esc_html( wp_unslash( $search ) );
|
||||
},
|
||||
),
|
||||
'source' => array(
|
||||
'filter' => FILTER_CALLBACK,
|
||||
'options' => function( $source ) {
|
||||
return $this->file_controller->sanitize_source( wp_unslash( $source ) );
|
||||
return File::sanitize_source( wp_unslash( $source ) );
|
||||
},
|
||||
),
|
||||
'view' => array(
|
||||
'filter' => FILTER_VALIDATE_REGEXP,
|
||||
'options' => array(
|
||||
'regexp' => '/^(list_files|single_file)$/',
|
||||
'regexp' => '/^(list_files|single_file|search_results)$/',
|
||||
'default' => $defaults['view'],
|
||||
),
|
||||
),
|
||||
@@ -342,20 +389,33 @@ class PageController {
|
||||
);
|
||||
$params = wp_parse_args( $params, $defaults );
|
||||
|
||||
if ( count( $param_keys ) > 0 ) {
|
||||
$params = array_intersect_key( $params, array_flip( $param_keys ) );
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and cache an instance of the list table.
|
||||
*
|
||||
* @return ListTable
|
||||
* @param string $view The current view, which determines which list table class to get.
|
||||
*
|
||||
* @return FileListTable|SearchListTable
|
||||
*/
|
||||
private function get_list_table(): ListTable {
|
||||
if ( $this->list_table instanceof ListTable ) {
|
||||
private function get_list_table( string $view ) {
|
||||
if ( $this->list_table instanceof WP_List_Table ) {
|
||||
return $this->list_table;
|
||||
}
|
||||
|
||||
$this->list_table = new ListTable( $this->file_controller, $this );
|
||||
switch ( $view ) {
|
||||
case 'list_files':
|
||||
$this->list_table = new FileListTable( $this->file_controller, $this );
|
||||
break;
|
||||
case 'search_results':
|
||||
$this->list_table = new SearchListTable( $this->file_controller, $this );
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->list_table;
|
||||
}
|
||||
@@ -366,17 +426,30 @@ class PageController {
|
||||
* @return void
|
||||
*/
|
||||
private function setup_screen_options(): void {
|
||||
$params = $this->get_query_params();
|
||||
$params = $this->get_query_params( array( 'view' ) );
|
||||
$handler = $this->get_default_handler();
|
||||
$list_table = null;
|
||||
|
||||
if ( 'list_files' === $params['view'] ) {
|
||||
// Ensure list table columns are initialized early enough to enable column hiding.
|
||||
$this->get_list_table()->prepare_column_headers();
|
||||
switch ( $handler ) {
|
||||
case LogHandlerFileV2::class:
|
||||
if ( in_array( $params['view'], array( 'list_files', 'search_results' ), true ) ) {
|
||||
$list_table = $this->get_list_table( $params['view'] );
|
||||
}
|
||||
break;
|
||||
case 'WC_Log_Handler_DB':
|
||||
$list_table = WC_Admin_Status::get_db_log_list_table();
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $list_table instanceof WP_List_Table ) {
|
||||
// Ensure list table columns are initialized early enough to enable column hiding, if available.
|
||||
$list_table->prepare_column_headers();
|
||||
|
||||
add_screen_option(
|
||||
'per_page',
|
||||
array(
|
||||
'default' => 20,
|
||||
'option' => ListTable::PER_PAGE_USER_OPTION_KEY,
|
||||
'default' => $list_table->get_per_page_default(),
|
||||
'option' => $list_table::PER_PAGE_USER_OPTION_KEY,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -388,13 +461,19 @@ class PageController {
|
||||
* @return void
|
||||
*/
|
||||
private function handle_list_table_bulk_actions(): void {
|
||||
// Bail if we're not using the file handler.
|
||||
if ( LogHandlerFileV2::class !== $this->get_default_handler() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$params = $this->get_query_params( array( 'file_id', 'view' ) );
|
||||
|
||||
// Bail if this is not the list table view.
|
||||
$params = $this->get_query_params();
|
||||
if ( 'list_files' !== $params['view'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $this->get_list_table()->current_action();
|
||||
$action = $this->get_list_table( $params['view'] )->current_action();
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : $this->get_logs_tab_url();
|
||||
@@ -409,16 +488,7 @@ class PageController {
|
||||
$sendback = remove_query_arg( array( 'deleted' ), wp_get_referer() );
|
||||
|
||||
// Multiple file_id[] params will be filtered separately, but assigned to $files as an array.
|
||||
$file_ids = filter_input(
|
||||
INPUT_GET,
|
||||
'file_id',
|
||||
FILTER_CALLBACK,
|
||||
array(
|
||||
'options' => function( $file ) {
|
||||
return sanitize_file_name( wp_unslash( $file ) );
|
||||
},
|
||||
)
|
||||
);
|
||||
$file_ids = $params['file_id'];
|
||||
|
||||
if ( ! is_array( $file_ids ) || count( $file_ids ) < 1 ) {
|
||||
wp_safe_redirect( $sendback );
|
||||
@@ -426,6 +496,17 @@ class PageController {
|
||||
}
|
||||
|
||||
switch ( $action ) {
|
||||
case 'export':
|
||||
if ( 1 === count( $file_ids ) ) {
|
||||
$export_error = $this->file_controller->export_single_file( reset( $file_ids ) );
|
||||
} else {
|
||||
$export_error = $this->file_controller->export_multiple_files( $file_ids );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $export_error ) ) {
|
||||
wp_die( wp_kses_post( $export_error ) );
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
$deleted = $this->file_controller->delete_files( $file_ids );
|
||||
$sendback = add_query_arg( 'deleted', $deleted, $sendback );
|
||||
@@ -475,28 +556,31 @@ class PageController {
|
||||
/**
|
||||
* Format a log file line.
|
||||
*
|
||||
* @param string $text The unformatted log file line.
|
||||
* @param string $line The unformatted log file line.
|
||||
* @param int $line_number The line number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function format_line( string $text, int $line_number ): string {
|
||||
private function format_line( string $line, int $line_number ): string {
|
||||
$severity_levels = WC_Log_Levels::get_all_severity_levels();
|
||||
$classes = array( 'line' );
|
||||
|
||||
$text = esc_html( trim( $text ) );
|
||||
if ( empty( $text ) ) {
|
||||
$text = ' ';
|
||||
$line = esc_html( trim( $line ) );
|
||||
if ( empty( $line ) ) {
|
||||
$line = ' ';
|
||||
}
|
||||
|
||||
$segments = explode( ' ', $text, 3 );
|
||||
$segments = explode( ' ', $line, 3 );
|
||||
$has_timestamp = false;
|
||||
$has_level = false;
|
||||
|
||||
if ( isset( $segments[0] ) && false !== strtotime( $segments[0] ) ) {
|
||||
$classes[] = 'log-entry';
|
||||
$segments[0] = sprintf(
|
||||
$classes[] = 'log-entry';
|
||||
$segments[0] = sprintf(
|
||||
'<span class="log-timestamp">%s</span>',
|
||||
$segments[0]
|
||||
);
|
||||
$has_timestamp = true;
|
||||
}
|
||||
|
||||
if ( isset( $segments[1] ) && in_array( strtolower( $segments[1] ), $severity_levels, true ) ) {
|
||||
@@ -505,10 +589,32 @@ class PageController {
|
||||
esc_attr( 'log-level log-level--' . strtolower( $segments[1] ) ),
|
||||
esc_html( $segments[1] )
|
||||
);
|
||||
$has_level = true;
|
||||
}
|
||||
|
||||
if ( isset( $segments[2] ) && $has_timestamp && $has_level ) {
|
||||
$message_chunks = explode( 'CONTEXT:', $segments[2], 2 );
|
||||
if ( isset( $message_chunks[1] ) ) {
|
||||
try {
|
||||
$maybe_json = stripslashes( html_entity_decode( trim( $message_chunks[1] ) ) );
|
||||
$context = json_decode( $maybe_json, false, 512, JSON_THROW_ON_ERROR );
|
||||
|
||||
$message_chunks[1] = sprintf(
|
||||
'<details><summary>%1$s</summary><pre>%2$s</pre></details>',
|
||||
esc_html__( 'Additional context', 'woocommerce' ),
|
||||
wp_json_encode( $context, JSON_PRETTY_PRINT )
|
||||
);
|
||||
|
||||
$segments[2] = implode( ' ', $message_chunks );
|
||||
$classes[] = 'has-context';
|
||||
} catch ( \JsonException $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
||||
// It's not valid JSON so don't do anything with it.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $segments ) > 1 ) {
|
||||
$text = implode( ' ', $segments );
|
||||
$line = implode( ' ', $segments );
|
||||
}
|
||||
|
||||
$classes = implode( ' ', $classes );
|
||||
@@ -523,8 +629,65 @@ class PageController {
|
||||
),
|
||||
sprintf(
|
||||
'<span class="line-content">%s</span>',
|
||||
wp_kses_post( $text )
|
||||
wp_kses_post( $line )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a form for searching within log files.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_search_field(): void {
|
||||
$params = $this->get_query_params( array( 'date_end', 'date_filter', 'date_start', 'search', 'source' ) );
|
||||
$defaults = $this->get_query_param_defaults();
|
||||
$file_count = $this->file_controller->get_files( $params, true );
|
||||
|
||||
if ( $file_count > 0 ) {
|
||||
?>
|
||||
<form id="logs-search" class="wc-logs-search" method="get">
|
||||
<fieldset class="wc-logs-search-fieldset">
|
||||
<input type="hidden" name="page" value="wc-status" />
|
||||
<input type="hidden" name="tab" value="logs" />
|
||||
<input type="hidden" name="view" value="search_results" />
|
||||
<?php foreach ( $params as $key => $value ) : ?>
|
||||
<?php if ( $value !== $defaults[ $key ] ) : ?>
|
||||
<input
|
||||
type="hidden"
|
||||
name="<?php echo esc_attr( $key ); ?>"
|
||||
value="<?php echo esc_attr( $value ); ?>"
|
||||
/>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<label for="logs-search-field">
|
||||
<?php esc_html_e( 'Search within these files', 'woocommerce' ); ?>
|
||||
<input
|
||||
id="logs-search-field"
|
||||
class="wc-logs-search-field"
|
||||
type="text"
|
||||
name="search"
|
||||
value="<?php echo esc_attr( $params['search'] ); ?>"
|
||||
/>
|
||||
</label>
|
||||
<?php submit_button( __( 'Search', 'woocommerce' ), 'secondary', null, false ); ?>
|
||||
</fieldset>
|
||||
<?php if ( $file_count >= $this->file_controller::SEARCH_MAX_FILES ) : ?>
|
||||
<div class="wc-logs-search-notice">
|
||||
<?php
|
||||
printf(
|
||||
// translators: %s is a number.
|
||||
esc_html__(
|
||||
'⚠️ Only %s files can be searched at one time. Try filtering the file list before searching.',
|
||||
'woocommerce'
|
||||
),
|
||||
esc_html( number_format_i18n( $this->file_controller::SEARCH_MAX_FILES ) )
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user