File: /www/wwwroot/www.cytocare.cn/wp-content/plugins/hummingbird-performance/core/class-logger.php
<?php
/**
* Logger class.
*
* This one will not use Filesystem class,
* because it is used for creating new files only.
*
* @package Hummingbird\Core
* @author: WPMUDEV, Anton Vanyukov (vanyukov)
*/
namespace Hummingbird\Core;
use Exception;
use WP_Error;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Logger
*/
class Logger {
/**
* Plugin instance
*
* @since 1.9.2
*
* @var null|Logger
*/
private static $instance = null;
/**
* Registered log files.
*
* @since 1.7.2
*
* @access private
* @var array $files
*/
private $files = array();
/**
* Log directory.
*
* @since 1.7.2
*
* @access private
* @var string $log_dir
*/
private $log_dir;
/**
* Registered modules.
*
* @since 1.9.2
* @access private
* @var array $modules
*/
private $modules = array();
/**
* Logger status.
*
* @since 1.7.2
*
* @access private
* @var WP_Error|bool $status
*/
private $status = false;
/**
* Return the plugin instance
*
* @return Logger
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Logger constructor.
*
* @since 1.7.2
*
* @access private
*/
private function __construct() {
if ( ! defined( 'WP_CONTENT_DIR' ) ) {
define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
}
$this->create_log_dir();
add_action( 'wp_loaded', array( $this, 'process_actions' ) );
// Add cron schedule to clean out outdated logs.
add_action( 'wphb_clear_logs', array( $this, 'clear_logs' ) );
add_action( 'admin_init', array( $this, 'check_cron_schedule' ) );
}
/**
* Register module.
*
* @since 1.9.2
*
* @param string $module Module slug.
*/
public function register_module( $module ) {
if ( in_array( $module, $this->modules, true ) ) {
return;
}
$this->modules[] = $module;
$this->prepare_file( $module );
}
/**
* Prepare filename.
*
* @since 1.7.2
*
* @access private
*
* @param string $module Module slug.
*/
private function prepare_file( $module ) {
$file = $module . '-debug.log';
// Only the minification module has a per/site configuration.
if ( 'minify' === $module ) {
$file = $this->get_domain_prefix() . $file;
}
$this->files[ $module ] = $this->log_dir . $file;
}
/**
* Get site url to prefix the log file.
*
* @since 1.7.2
*
* @access private
*
* @return string
*/
private function get_domain_prefix() {
if ( ! is_multisite() ) {
return '';
}
$blog = get_blog_details();
if ( '/' === $blog->path ) {
return $blog->domain . '-';
} elseif ( defined( 'SUBDOMAIN_INSTALL' ) && ! SUBDOMAIN_INSTALL ) {
return $blog->domain . '-' . str_replace( '/', '', $blog->path ) . '-';
}
return $blog->path . '-';
}
/**
* Check if log directory is already create, if not - create it.
*
* @since 1.7.2
*
* @access private
*/
private function create_log_dir() {
$this->log_dir = WP_CONTENT_DIR . '/wphb-logs/';
if ( is_dir( $this->log_dir ) && is_writeable( $this->log_dir ) ) {
return;
}
if ( ! mkdir( $this->log_dir ) ) {
$error = error_get_last();
$this->status = new WP_Error( 'log-dir-error', $error['message'] );
}
}
/**
* Attempt to write file.
*
* @since 1.7.2
*
* @access private
*
* @param string $mode Accepts any mode from the list: http://php.net/manual/en/function.fopen.php.
* @param string $message String to write to file.
* @param string $module Module slug.
*/
private function write_file( $mode, $message = '', $module = '' ) {
/**
* Return if log directory is not exist.
* Some log requests come after running Hummingbird\Core\Logger::cleanup() by WP_Hummingbird::flush_cache();
* At this moment log directory is not exist.
*
* @since 2.5.0
*/
if ( ! file_exists( $this->log_dir ) ) {
return;
}
try {
$fp = fopen( $this->files[ $module ], $mode );
flock( $fp, LOCK_EX );
fwrite( $fp, $message );
flock( $fp, LOCK_UN );
fclose( $fp );
} catch ( Exception $e ) {
$this->status = new WP_Error( 'log-write-error', $e->getMessage() );
}
}
/**
* Cleanup on uninstall.
*
* @since 1.7.2
*/
public static function cleanup() {
if ( ! defined( 'WP_CONTENT_DIR' ) ) {
define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
}
$log_dir = WP_CONTENT_DIR . '/wphb-logs/';
// If no directory is present - exit.
if ( ! is_dir( $log_dir ) ) {
return;
}
try {
$dir = opendir( $log_dir );
while ( false !== ( $file = readdir( $dir ) ) ) {
if ( ( '.' === $file ) || ( '..' === $file ) ) {
continue;
}
$full = $log_dir . $file;
if ( is_dir( $full ) ) {
rmdir( $full );
} else {
unlink( $full );
}
}
closedir( $dir );
rmdir( $log_dir );
} catch ( Exception $e ) {
error_log( '[' . current_time( 'mysql' ) . '] - Unable to clean Hummingbird log directory. Error: ' . $e->getMessage() );
}
}
/**
* Check if module should log or not.
*
* @since 1.7.2
*
* @param string $module Module to log for.
*
* @return bool
*/
private function should_log( $module ) {
// Don't log if there's an error.
if ( is_wp_error( $this->status ) ) {
return false;
}
// See if the module is registered to log.
if ( ! in_array( $module, $this->modules, true ) ) {
return false;
}
$do_log = false;
switch ( $module ) {
case 'minify':
// Log for minification only if debug is enabled.
$options = Utils::get_module( 'minify' )->get_options();
if ( $options['log'] ) {
$do_log = true;
}
break;
default:
// Default to logging only when wp debug is set.
$debug = defined( 'WP_DEBUG' ) && WP_DEBUG;
$debug_log = defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG;
if ( $debug && $debug_log ) {
$do_log = true;
}
break;
}
return $do_log;
}
/**
* Main logging function.
*
* @since 1.7.2
*
* @param mixed $message Data to write to log.
* @param string $module Module slug.
*/
public function log( $message, $module ) {
if ( ! $this->should_log( $module ) ) {
return;
}
if ( ! is_string( $message ) || is_array( $message ) || is_object( $message ) ) {
$message = print_r( $message, true );
}
$message = '[' . date( 'c' ) . '] ' . $message . PHP_EOL;
$this->write_file( 'a', $message, $module );
}
/**
* Getter method for $file.
*
* @since 1.9.2
*
* @param string $module Module slug.
*
* @return string
*/
public function get_file( $module ) {
return $this->files[ $module ];
}
/**
* Process logger actions.
*
* Accepts module name (slug) and action. So far only 'download' actions is supported.
*
* @since 1.9.2
*/
public function process_actions() {
if ( ! isset( $_GET['logs'] ) || ! isset( $_GET['module'] ) || ! check_admin_referer( 'wphb-log-action' ) ) { // Input var ok.
return;
}
$action = sanitize_text_field( wp_unslash( $_GET['logs'] ) ); // Input var ok.
$module = sanitize_text_field( wp_unslash( $_GET['module'] ) ); // Input var ok.
// Not called by a registered module.
if ( ! in_array( $module, $this->modules, true ) ) {
return;
}
// Only allow these actions.
if ( 'download' !== $action ) {
return;
}
if ( method_exists( $this, $action ) ) {
call_user_func( array( $this, $action ), $module );
}
}
/**
* Download logs.
*
* @since 1.9.2
*
* @param string $module Module slug.
*/
private function download( $module ) {
if ( 'page_cache' === $module ) {
$content = file_get_contents( WP_CONTENT_DIR . '/wphb-logs/page-caching-log.php' );
/* Remove <?php die(); ?> from file */
$content = substr( $content, 15 );
} else {
$content = file_get_contents( $this->files[ $module ] );
}
// No file - exit.
if ( ! $content ) {
return;
}
header( 'Content-Description: Hummingbird log download' );
header( 'Content-Type: text/plain' );
header( "Content-Disposition: attachment; filename={$module}.log" );
header( 'Content-Transfer-Encoding: binary' );
header( 'Content-Length: ' . strlen( $content ) );
header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
header( 'Expires: 0' );
header( 'Pragma: public' );
echo $content;
exit;
}
/**
* Clear log file.
*
* @since 1.9.2
*
* @param string $module Module slug.
*
* @return bool
*/
public function clear( $module ) {
if ( 'page_cache' === $module ) {
$file = WP_CONTENT_DIR . '/wphb-logs/page-caching-log.php';
} else {
$file = $this->files[ $module ];
}
if ( file_exists( $file ) ) {
wp_delete_file( $file );
return true;
}
return false;
}
/**
* Check if the logger cron is scheduled to run.
*
* @since 1.9.2
*/
public function check_cron_schedule() {
if ( ! wp_next_scheduled( 'wphb_clear_logs' ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wphb_clear_logs' );
}
}
/**
* Clear out lines that are older than 30 days.
*
* @since 1.9.2
*/
public function clear_logs() {
$now = date( 'c' );
foreach ( $this->modules as $slug ) {
if ( 'page_cache' === $slug ) {
$file = WP_CONTENT_DIR . '/wphb-logs/page-caching-log.php';
} else {
$file = $this->get_file( $slug );
}
if ( ! file_exists( $file ) ) {
continue;
}
$content = file( $file );
$size_of_content = count( $content );
$delete = false;
foreach ( $content as $i => $line ) {
// If the line does not start with '[' (it's probably not a new entry).
$first_char = substr( $line, 0, 1 );
if ( '[' !== $first_char ) {
// If not marked for delete - skip.
if ( ! $delete ) {
continue;
}
// Delete.
unset( $content[ $i ] );
}
/**
* Get the date from entry. Items can be an array it two cases - if there's a valid date, or if the line
* contained something like [header] in the start. Cannot make assumptions just on the fact it's an array.
*/
preg_match( '/\[(.*)\]/', $line, $items );
// If, for some reason, can't get the date, or it's not the size of an ISO 8601 date.
if ( ! isset( $items[1] ) || 25 !== strlen( $items[1] ) ) {
// If not marked for delete - skip.
if ( ! $delete ) {
continue;
}
// Delete.
unset( $content[ $i ] );
} else {
// It looks like it's a valid date string, compare with today.
$time_diff = strtotime( $now ) - strtotime( $items[1] );
// We don't need to continue on, because if this entry is not older than 30 days, the next one will not be as well.
if ( $time_diff < MONTH_IN_SECONDS ) {
break;
}
$delete = true;
unset( $content[ $i ] );
}
}
// Nothing changed - do nothing.
if ( count( $content ) === $size_of_content ) {
continue;
}
// Glue back together and write back to file.
$content = implode( '', $content );
if ( 'page_cache' === $slug ) {
global $wphb_fs;
if ( ! $wphb_fs && class_exists( 'Filesystem' ) ) {
$wphb_fs = Filesystem::instance();
}
if ( $wphb_fs ) {
$wphb_fs->write( $file, $content );
}
} else {
$this->write_file( 'w', $content, $slug );
}
}
}
}