mirror of
https://github.com/discourse/wp-discourse.git
synced 2025-08-17 18:11:19 +08:00
* 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>
405 lines
11 KiB
PHP
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";
|
|
}
|
|
}
|