2
0
Fork 0
mirror of https://github.com/discourse/wp-discourse.git synced 2025-08-17 18:11:19 +08:00
wp-discourse/admin/log-viewer.php
Simon Cossar 43bd053450
Bump version to 2.2.4 (#404)
* Add base log classes

* Return maxFiles to normal level

* Use protected class variables for folder names in folder-manager

* Add unit tests for logger classes && various logger improvements

* Add log viewer

* Fix initialization sequence in LogViewer

* Add wp-discourse settings to plugin meta

* Remove metafile comments

* Add partial coverage and annotate LogViewer

* Add code coverage reporting and a tests readme

* Tests readme xdebug section formatting

* Add logging and tests to discourse-publish

This abstracts remote post components to make it possible to add consistent error and log handling. Also adds basic tests coverage for discourse-publish.

* Add successful publication test

* Add working tests for publish_after_create and publish_after_update

* Always remove test files and database upon install

* Cleanup copy and assertions for existing tests

* Final cleanup && verbose setting

* Improve structure of publish test

* Final tests, linting, security and cleanup

* PHP 7.0 Compatibility

* PHP 5.6 Compatibility

* JSHint fixes

* Update file-handler.php

* Update log viewer title

* Use older monolog and update file_handler function signatures

* Add nonce to other view_log action

* Namespace production composer packages and define build process

* Update COMPOSER.md

* Update FORMATTING.md

* Log viewer style, naming and log-refresh improvements

* Filter out all return type declarations during scoping

* JsHint: Don't use default params

* Update COMPOSER.md

* Copy fix

* Update scoper patchers notes

* Address syntax issues

- Remove >php7 syntax from non-required files
- Add phpcs pattern exclusions to phpcs.xml
- update formatting docs

* discourse-publish: address all phpcs notices and add more tests

Note: also added dealerdirect/phpcodesniffer-composer-installer to handle local requiring of codesniffer

* Handle all phpcs warnings in lib/logs

* Add todo: review phpcs exclusions to discourse-publish

* Monolog cleanup

- Remove unused monolog handlers, processors and formatters
- Add vendor_namespaced to excluded phpcs patterns

* Update CI versions to those used in composer

* Switch to using composer directly in CI actions

* Composer is packaged in shivammathur/setup-php

* Setup PHPCS via shivammathur/setup-php

* Incorrect tools key

* Use vendor/bin version of phpcs

* Install composer dependencies via ramsey/composer-install

* Update composer.lock to composer 2 and --ignore-platform-reqs

* Install lowest version of dependencies

* Move dependency-versions key

* Move composer-options key

* Exclude vendor directory from syntax checker

* Add vendor to jshintignore

* Update phpcs.xml to properly exclude js css and config files

* Address phpcs issues in log-viewer

* Fix remaining whitespace issues created in this PR

* Remove out of date sniffs and exclude specific code where necessary

* Final cleanup

* Properly escape html in log viewer

* Remove unnecessary verbiage from documentation

* Bump plugin's version to 2.2.4

Co-authored-by: Angus McLeod <angus@mcleod.org.au>
2021-05-11 15:31:24 -07:00

405 lines
11 KiB
PHP

<?php
/**
* Log Viewer.
*
* @package WPDiscourse
*/
namespace WPDiscourse\Admin;
use WPDiscourse\Logs\FileManager;
use WPDiscourse\Logs\FileHandler;
use WPDiscourse\Shared\PluginUtilities;
/**
* Class LogViewer
*/
class LogViewer {
use PluginUtilities;
/**
* Flag to determine whether LogViewer is enabled.
*
* @var bool
*/
protected $enabled;
/**
* LogViewer's instance of FileHandler
*
* @var \WPDiscourse\Logs\FileHandler
*/
protected $file_handler;
/**
* LogViewer's log list
*
* @var mixed
*/
protected $logs;
/**
* Current log in LogViewer
*
* @var object
*/
protected $selected_log;
/**
* Metafile name
*
* @var string
*/
protected $metafile_name;
/**
* LogViewer constructor.
*/
public function __construct() {
$this->metafile_name = 'logs-metafile';
add_action( 'admin_init', array( $this, 'setup_log_viewer' ) );
}
/**
* Run LogViewer setup tasks.
*
* @param object $file_handler Instance of \WPDiscourse\Logs\FileHandler.
*/
public function setup_log_viewer( $file_handler = null ) {
if ( $file_handler ) {
$this->file_handler = $file_handler;
} else {
$this->file_handler = new FileHandler( new FileManager() );
}
$this->enabled = $this->file_handler->enabled();
if ( $this->enabled ) {
$this->setup_logs();
$this->update_meta_file();
add_action( 'wp_ajax_wpdc_view_log', array( $this, 'log_file_contents' ) );
add_action( 'wp_ajax_wpdc_view_logs_metafile', array( $this, 'meta_file_contents' ) );
add_action( 'wp_ajax_wpdc_download_logs', array( $this, 'download_logs' ) );
}
$this->register_log_viewer();
}
/**
* Add settings section and register the setting.
*/
public function register_log_viewer() {
add_settings_section(
'discourse_log_viewer',
__( 'Logs', 'wp-discourse' ),
array(
$this,
'log_viewer_markup',
),
'discourse_logs'
);
register_setting( 'discourse_logs', 'discourse_logs' );
}
/**
* Setup logs
*/
public function setup_logs() {
$this->retrieve_logs();
if ( ! empty( $this->logs ) && empty( $this->selected_log ) ) {
$this->selected_log = reset( $this->logs );
}
}
/**
* Outputs the markup for the log viewer.
*/
public function log_viewer_markup() {
$selected_log_key = null;
if ( ! empty( $this->selected_log ) ) {
$selected_log_key = $this->build_log_key( $this->selected_log );
}
?>
<?php if ( $this->enabled ) : ?>
<?php /* translators: placeholder interpolates url to documentation on meta.discourse.org */ ?>
<p><?php printf( esc_html__( 'Logging is not yet available for all functionality. Please see %s for details.', 'wp-discourse' ), sprintf( '<a href="%s">%s</a>', esc_url( 'https://meta.discourse.org/t/50752' ), esc_html__( 'WP Discourse Setup', 'text-domain' ) ) ); ?></p>
<?php if ( ! empty( $this->logs ) ) : ?>
<div id="wpdc-log-viewer-controls">
<div class="name">
<h3>
<?php esc_html_e( 'Log for ', 'wp-discourse' ); ?>
<?php echo esc_html( $this->file_name( $this->selected_log ) ); ?>
</h3>
<a class="load-log">
<span class="refresh"><?php esc_html_e( 'Refresh', 'wp-discourse' ); ?></span>
<span class="return-to"><?php esc_html_e( 'Return to log', 'wp-discourse' ); ?></span>
</a>
</div>
<div class="select">
<select>
<?php foreach ( $this->logs as $log_key => $log_info ) : ?>
<option value="<?php echo esc_attr( $log_key ); ?>">
<?php echo esc_attr( $this->file_name( $log_info ) ); ?>
</option>
<?php endforeach; ?>
</select>
<div class="view-meta button"><?php esc_html_e( 'View Meta', 'wp-discourse' ); ?></div>
<div class="download-logs button"><?php esc_html_e( 'Download', 'wp-discourse' ); ?></div>
</div>
</div>
<div id="wpdc-log-viewer" data-log-key="<?php echo esc_attr( $selected_log_key ); ?>">
<span class="spinner"></span>
<pre><?php echo esc_html( file_get_contents( $this->selected_log['file'] ) ); ?></pre>
</div>
<?php else : ?>
<div class="inline"><p><?php esc_html_e( 'There are currently no logs to view.', 'wp-discourse' ); ?></p></div>
<?php endif; ?>
<?php else : ?>
<div class="inline"><p><?php esc_html_e( 'Logs are disabled.', 'wp-discourse' ); ?></p></div>
<?php endif; ?>
<?php
}
/**
* Retrieve log files.
*/
public function retrieve_logs() {
$file_handler = $this->file_handler;
$log_files = $file_handler->list_files();
$this->logs = array_reduce(
$log_files,
function ( $result, $log_file ) use ( $file_handler ) {
$date = $file_handler->get_date_from_url( $log_file );
$number = $file_handler->get_number_from_url( $log_file );
$log = array(
'date' => $date,
'number' => $number,
'file' => $log_file,
);
$result[ $this->build_log_key( $log ) ] = $log;
return $result;
},
array()
);
}
/**
* Return log file contents for selected key.
*/
public function log_file_contents() {
// See further https://github.com/WordPress/WordPress-Coding-Standards/issues/869.
if ( ! isset( $_REQUEST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['nonce'] ), 'admin-ajax-nonce' ) || ! isset( $_POST['key'] ) ) {
wp_send_json_error();
return;
}
$log_key = sanitize_text_field( wp_unslash( $_POST ['key'] ) );
$log = $this->logs[ $log_key ];
if ( $log ) {
$this->selected_log = $log;
$response = array(
'contents' => file_get_contents( $this->selected_log['file'] ),
'name' => $this->file_name( $this->selected_log ),
);
wp_send_json_success( $response );
} else {
wp_send_json_error();
}
}
/**
* Return log meta file contents.
*/
public function meta_file_contents() {
$response = array(
'contents' => file_get_contents( $this->get_metafile_path() ),
'name' => 'Log Meta File',
);
wp_send_json_success( $response );
}
/**
* Download bundled log files.
*/
public function download_logs() {
$log_files = $this->file_handler->list_files();
$date_range = $this->build_date_range( $log_files );
$plugin_data = get_plugin_data( WPDISCOURSE_PATH . 'wp-discourse.php' );
$plugin_name = $plugin_data['TextDomain'];
$site_title = str_replace( ' ', '-', strtolower( get_bloginfo( 'name' ) ) );
$filename = "{$site_title}-{$plugin_name}-logs-$date_range.zip";
$file = tempnam( 'tmp', $filename );
$zip = new \ZipArchive();
$zip->open( $file, \ZipArchive::OVERWRITE );
foreach ( $log_files as $log_file ) {
$name = $this->file_handler->get_filename( $log_file );
$zip->addFile( $log_file, "$name.log" );
}
$metafile_name = $this->metafile_name;
$metafile_path = $this->get_metafile_path();
$metafile_filename = "{$plugin_name}-{$metafile_name}-{$date_range}.txt";
$zip->addFile( $metafile_path, $metafile_filename );
$zip->close();
header( 'Content-type: application/zip' );
header( 'Content-Length: ' . filesize( $file ) );
header( "Content-Disposition: attachment; filename=$filename" );
readfile( $file );
unlink( $file );
wp_die();
}
/**
* Update meta file.
*/
public function update_meta_file() {
$filename = $this->get_metafile_path();
$contents = $this->build_metafile_contents();
file_put_contents( $filename, $contents );
}
/**
* Retrieve logs.
*/
public function get_logs() {
return $this->logs;
}
/**
* Retrieve enabled state.
*/
public function is_enabled() {
return $this->enabled;
}
/**
* Generate file name.
*
* @param array $log_info Log info array.
*/
protected function file_name( $log_info ) {
$date = gmdate( get_option( 'date_format' ), strtotime( $log_info['date'] ) );
$number = $log_info['number'];
$name = esc_html( $date );
if ( $number > 1 ) {
$name .= ' (' . esc_html( $number ) . ')';
}
return $name;
}
/**
* Build log key from log in logs list.
*
* @param object $item Log object.
*/
protected function build_log_key( $item ) {
$date = $item['date'];
$number = $item['number'];
return "$date-$number";
}
/**
* Generate server statistics file.
*/
protected function build_metafile_contents() {
$contents = "### This file is included in log downloads ###\n\n";
global $wpdb;
global $wp_version;
if ( method_exists( $wpdb, 'db_version' ) ) {
$mysql = preg_replace( '/[^0-9.].*/', '', $wpdb->db_version() );
} else {
$mysql = 'N/A';
}
$wp = $wp_version;
$php = phpversion();
$multisite = is_multisite();
$contents .= "### Server ###\n\n";
$contents .= "WordPress - $wp\n";
$contents .= "PHP - $php\n";
$contents .= "MySQL - $mysql\n\n";
$active_plugins = get_option( 'active_plugins' );
$all_plugins = get_plugins();
$plugins = array();
$contents .= "### Active Plugins ###\n\n";
foreach ( $all_plugins as $plugin_folder => $plugin_data ) {
if ( in_array( $plugin_folder, $active_plugins, true ) ) {
$contents .= "{$plugin_data["Name"]} - {$plugin_data["Version"]}\n";
}
}
$contents .= "\n### WP Discourse Settings (Secrets Excluded) ###\n\n";
$excluded_keys = array(
'url',
'key',
'secret',
'text',
'publish-username',
'publish-category',
'publish-failure-email',
'login-path',
'existing-comments-heading',
'sso-client-login-form-redirect',
);
foreach ( $this->get_options() as $key => $value ) {
$exclude = false;
foreach ( $excluded_keys as $excluded_key ) {
if ( strpos( $key, $excluded_key ) !== false ) {
$exclude = true;
}
}
if ( ! $exclude ) {
if ( is_array( $value ) ) {
$value = implode( ',', $value );
}
$contents .= "$key - $value\n";
}
}
return $contents;
}
/**
* Get metafile name.
*/
protected function get_metafile_path() {
$metafile_dir = $this->file_handler->file_manager->upload_dir;
return "$metafile_dir/{$this->metafile_name}.txt";
}
/**
* Build date range.
*
* @param array $log_files List of log files.
*/
protected function build_date_range( $log_files ) {
$log_values = array_values( $log_files );
$newest_file = reset( $log_files );
$oldest_file = end( $log_values );
$date_end = $this->file_handler->get_date_from_url( $newest_file );
$date_start = $this->file_handler->get_date_from_url( $oldest_file );
return "$date_start-$date_end";
}
}