laipower/wp-content/plugins/easy-digital-downloads/includes/class-edd-logging.php

858 lines
20 KiB
PHP

<?php
/**
* Class for logging events and errors
*
* @package EDD
* @subpackage Logging
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.3.1
*/
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* EDD_Logging Class
*
* A general use class for logging events and errors.
*
* @since 1.3.1
* @since 3.0 - Updated to work with the new tables and classes as part of the migration to custom tables.
*/
class EDD_Logging {
/**
* Whether the debug log file is writable or not.
*
* @var bool
*/
public $is_writable = true;
/**
* Filename of the debug log.
*
* @var string
*/
private $filename = '';
/**
* File path to the debug log.
*
* @var string
*/
private $file = '';
/**
* Set up the EDD Logging Class
*
* @since 1.3.1
*/
public function __construct() {
add_action( 'plugins_loaded', array( $this, 'setup_log_file' ), 8 );
}
/**
* Get log types.
*
* @since 1.3.1
*
* @return array $terms Log types.
*/
public function log_types() {
return apply_filters( 'edd_log_types', array(
'sale',
'file_download',
'gateway_error',
'api_request'
) );
}
/**
* Check if a log type is valid
*
* Checks to see if the specified type is in the registered list of types
*
* @since 1.3.1
*
* @param string $type Log type.
* @return bool True if valid log type, false otherwise.
*/
public function valid_type( $type ) {
return in_array( $type, $this->log_types() );
}
/**
* Create new log entry
*
* This is just a simple and fast way to log something. Use $this->insert_log()
* if you need to store custom meta data
*
* @since 1.3.1
*
* @param string $title Log entry title.
* @param string $message Log entry message.
* @param int $parent Download ID.
* @param string $type Log type (default: null).
*
* @return int ID of the newly created log item.
*/
public function add( $title = '', $message = '', $parent = 0, $type = null ) {
return $this->insert_log( array(
'post_title' => $title,
'post_content' => $message,
'post_parent' => $parent,
'log_type' => $type
) );
}
/**
* Easily retrieves log items for a particular object ID.
*
* @since 1.3.1
*
* @param int $object_id Object ID (default: 0).
* @param string $type Log type (default: null).
* @param int $paged Page number (default: null).
*
* @return array Array of the connected logs.
*/
public function get_logs( $object_id = 0, $type = null, $paged = null ) {
return $this->get_connected_logs( array(
'post_parent' => $object_id,
'paged' => $paged,
'log_type' => $type
) );
}
/**
* Stores a log entry.
*
* @since 1.3.1
* @since 3.0 Updated to use the new database classes as part of the migration to custom tables.
*
* @param array $log_data Log entry data.
* @param array $log_meta Log entry meta.
* @return int The ID of the newly created log item.
*/
public function insert_log( $log_data = array(), $log_meta = array() ) {
// Parse args
$args = wp_parse_args( $log_data, array(
'post_type' => 'edd_log',
'post_status' => 'publish',
'post_parent' => 0,
'post_content' => '',
'log_type' => false,
) );
/**
* Triggers just before a log is inserted.
*
* @param array $args Log entry data.
* @param array $log_meta Log meta data.
*/
do_action( 'edd_pre_insert_log', $args, $log_meta );
// Used to dynamically dispatch the method call to insert() to the correct class.
$insert_method = 'edd_add_log';
// Set up variables to hold data to go into the logs table by default.
$data = array(
'content' => $args['post_content'],
'object_id' => isset( $args['post_parent'] )
? $args['post_parent']
: 0,
'object_type' => isset( $args['log_type'] )
? $args['log_type']
: null,
/*
* Fallback user ID is the current user, due to it previously being set to that by WordPress
* core when setting post_author on the CPT.
*/
'user_id' => ! empty( $log_meta['user'] ) ? $log_meta['user'] : get_current_user_id()
);
$type = $args['log_type'];
if ( ! empty( $type ) ) {
$data['type'] = $type;
}
if ( array_key_exists( 'post_title', $args ) ) {
$data['title'] = $args['post_title'];
}
$meta_to_unset = array( 'user' );
// Override $data and $insert_method based on the log type.
if ( 'api_request' === $args['log_type'] ) {
$insert_method = 'edd_add_api_request_log';
$data = array(
'user_id' => ! empty( $log_meta['user'] ) ? $log_meta['user'] : 0,
'api_key' => ! empty( $log_meta['key'] ) ? $log_meta['key'] : 'public',
'token' => ! empty( $log_meta['token'] ) ? $log_meta['token'] : 'public',
'version' => ! empty( $log_meta['version'] ) ? $log_meta['version'] : '',
'request' => ! empty( $args['post_excerpt'] ) ? $args['post_excerpt'] : '',
'error' => ! empty( $args['post_content'] ) ? $args['post_content'] : '',
'ip' => ! empty( $log_meta['request_ip'] ) ? $log_meta['request_ip'] : '',
'time' => ! empty( $log_meta['time'] ) ? $log_meta['time'] : '',
);
$meta_to_unset = array( 'user', 'key', 'token', 'version', 'request_ip', 'time' );
} elseif ( 'file_download' === $args['log_type'] ) {
$insert_method = 'edd_add_file_download_log';
if ( ! class_exists( 'Browser' ) ) {
require_once EDD_PLUGIN_DIR . 'includes/libraries/browser.php';
}
$browser = new Browser();
$user_agent = $browser->getBrowser() . ' ' . $browser->getVersion() . '/' . $browser->getPlatform();
$data = array(
'product_id' => $args['post_parent'],
'file_id' => ! empty( $log_meta['file_id'] ) ? $log_meta['file_id'] : 0,
'order_id' => ! empty( $log_meta['payment_id'] ) ? $log_meta['payment_id'] : 0,
'price_id' => ! empty( $log_meta['price_id'] ) ? $log_meta['price_id'] : 0,
'customer_id' => ! empty( $log_meta['customer_id'] ) ? $log_meta['customer_id'] : 0,
'ip' => ! empty( $log_meta['ip'] ) ? $log_meta['ip'] : '',
'user_agent' => $user_agent,
);
$meta_to_unset = array( 'file_id', 'payment_id', 'price_id', 'customer_id', 'ip', 'user_id' );
}
// Now unset the meta we've used up in the main data array.
foreach ( $meta_to_unset as $meta_key ) {
unset( $log_meta[ $meta_key ] );
}
// Get the log ID if method is callable
$log_id = is_callable( $insert_method )
? call_user_func( $insert_method, $data )
: false;
// Set log meta, if any
if ( $log_id && ! empty( $log_meta ) ) {
// Use the right log fetching function based on the type of log this is.
if ( 'edd_add_api_request_log' === $insert_method ) {
$add_meta_function = 'edd_add_api_request_log_meta';
} elseif ( 'edd_add_file_download_log' === $insert_method ) {
$add_meta_function = 'edd_add_file_download_log_meta';
} else {
$add_meta_function = 'edd_add_log_meta';
}
if ( is_callable( $add_meta_function ) ) {
foreach ( (array) $log_meta as $key => $meta ) {
$add_meta_function( $log_id, sanitize_key( $key ), $meta );
}
}
}
/**
* Triggers after a log has been inserted.
*
* @param int $log_id ID of the new log.
* @param array $args Log data.
* @param array $log_meta Log meta data.
*/
do_action( 'edd_post_insert_log', $log_id, $args, $log_meta );
return $log_id;
}
/**
* Update and existing log item
*
* @since 1.3.1
* @since 3.0 - Added $log_id parameter and boolean return type.
*
* @param array $log_data Log entry data.
* @param array $log_meta Log entry meta.
* @param int $log_id Log ID.
* @return bool True on success, false otherwise.
*/
public function update_log( $log_data = array(), $log_meta = array(), $log_id = 0 ) {
// $log_id is at the end because it was introduced in 3.0
do_action( 'edd_pre_update_log', $log_data, $log_meta, $log_id );
$defaults = array(
'post_content' => '',
'post_title' => '',
'object_id' => 0,
'object_type' => '',
);
$args = wp_parse_args( $log_data, $defaults );
if ( isset( $args['ID'] ) && empty( $log_id ) ) {
$log_id = $args['ID'];
}
// Bail if the log ID is still empty.
if ( empty( $log_id ) ) {
return false;
}
// Used to dynamically dispatch the method call to insert() to the correct class.
$update_method = 'edd_update_log';
$update_meta_function = 'edd_update_log_meta';
$type = $args['log_type'];
if ( ! empty( $type ) ) {
$data['type'] = $args['log_type'];
}
$data = array(
'object_id' => $args['object_id'],
'object_type' => $args['object_type'],
'title' => $args['title'],
'message' => $args['message'],
);
if ( 'api_request' === $data['type'] ) {
$update_meta_function = 'edd_update_api_request_log_meta';
$legacy = array(
'user' => 'user_id',
'key' => 'api_key',
'token' => 'token',
'version' => 'version',
'post_excerpt' => 'request',
'post_content' => 'error',
'request_ip' => 'ip',
'time' => 'time',
);
foreach ( $legacy as $old_key => $new_key ) {
if ( isset( $log_meta[ $old_key ] ) ) {
$data[ $new_key ] = $log_meta[ $old_key ];
unset( $log_meta[ $old_key ] );
}
}
} elseif ( 'file_download' === $data['type'] ) {
$update_meta_function = 'edd_update_file_download_log_meta';
$legacy = array(
'file_id' => 'file_id',
'payment_id' => 'payment_id',
'price_id' => 'price_id',
'user_id' => 'user_id',
'ip' => 'ip',
);
foreach ( $legacy as $old_key => $new_key ) {
if ( isset( $log_meta[ $old_key ] ) ) {
$data[ $new_key ] = $log_meta[ $old_key ];
unset( $log_meta[ $old_key ] );
}
}
if ( isset( $args['post_parent'] ) ) {
$data['download_id'] = $args['post_parent'];
}
}
unset( $data['type'] );
// Bail if not callable
if ( ! is_callable( $update_method ) ) {
return false;
}
call_user_func( $update_method, $data );
// Set log meta, if any
if ( is_callable( $update_meta_function ) ) {
if ( 'edd_update_log' === $update_method && ! empty( $log_meta ) ) {
foreach ( (array) $log_meta as $key => $meta ) {
$update_meta_function( $log_id, sanitize_key( $key ), $meta );
}
}
}
do_action( 'edd_post_update_log', $log_id, $log_data, $log_meta );
}
/**
* Retrieve all connected logs.
*
* Used for retrieving logs related to particular items, such as a specific purchase.
*
* @access public
* @since 1.3.1
*
* @param array $args Query arguments.
* @return mixed array Logs fetched, false otherwise.
*/
public function get_connected_logs( $args = array() ) {
$log_type = isset( $args['log_type'] )
? $args['log_type']
: false;
// Parse arguments
$r = $this->parse_args( $args );
// Used to dynamically dispatch the call to the correct class.
$log_type = $this->get_log_table( $log_type );
$func = "edd_get_{$log_type}";
$logs = is_callable( $func )
? call_user_func( $func, $r )
: false;
// Return the logs (or false)
return $logs;
}
/**
* Retrieves number of log entries connected to particular object ID.
*
* @since 1.3.1
* @since 1.9 - Added date query support.
*
* @param int $object_id Object ID (default: 0).
* @param string $type Log type (default: null).
* @param array $meta_query Log meta query (default: null).
* @param array $date_query Log date query (default: null) [since 1.9].
*
* @return int Log count.
*/
public function get_log_count( $object_id = 0, $type = null, $meta_query = null, $date_query = null ) {
$r = array(
$this->get_object_id_column_name_for_type( $type ) => $object_id,
);
if ( ! empty( $type ) && $this->valid_type( $type ) ) {
$r['type'] = $type;
}
if ( ! empty( $meta_query ) ) {
$r['meta_query'] = $meta_query;
}
if ( ! empty( $date_query ) ) {
$r['date_query'] = $date_query;
}
// Used to dynamically dispatch the call to the correct class.
$log_type = $this->get_log_table( $type );
// Call the func, or not
$func = "edd_count_{$log_type}";
$count = is_callable( $func )
? call_user_func( $func, $r )
: 0;
return $count;
}
/**
* Delete logs based on parameters passed.
*
* @since 1.3.1
*
* @param int $object_id Object ID (default: 0).
* @param string $type Log type (default: null).
* @param array $meta_query Log meta query (default: null).
*/
public function delete_logs( $object_id = 0, $type = null, $meta_query = null ) {
$r = array(
$this->get_object_id_column_name_for_type( $type ) => $object_id,
);
if ( ! empty( $type ) && $this->valid_type( $type ) ) {
$r['type'] = $type;
}
if ( ! empty( $meta_query ) ) {
$r['meta_query'] = $meta_query;
}
// Used to dynamically dispatch the call to the correct class.
$log_type = $this->get_log_table( $type );
// Call the func, or not.
$func = "edd_get_{$log_type}";
$logs = is_callable( $func )
? call_user_func( $func, $r )
: array();
// Bail if no logs.
if ( empty( $logs ) ) {
return;
}
// Maybe bail if delete function does not exist.
$func = rtrim( "edd_delete_{$log_type}", 's' );
if ( ! is_callable( $func ) ) {
return;
}
// Loop through and delete logs.
foreach ( $logs as $log ) {
call_user_func( $func, $log->id );
}
}
/**
* Get the new log type from the old type.
*
* @since 3.0
*
* @param string $type
*
* @return string
*/
private function get_log_table( $type = '' ) {
$retval = 'logs';
if ( 'api_request' === $type ) {
$retval = 'api_request_logs';
} elseif ( 'file_download' === $type ) {
$retval = 'file_download_logs';
}
return $retval;
}
/**
* Parse arguments. Contains back-compat argument aliasing.
*
* @since 3.0
*
* @param array $args
* @return array
*/
private function parse_args( $args = array() ) {
// Parse args
$r = wp_parse_args( $args, array(
'log_type' => false,
'post_type' => 'edd_log',
'post_status' => 'publish',
'post_parent' => 0,
'posts_per_page' => 20,
'paged' => get_query_var( 'paged' ),
'orderby' => 'id',
) );
// Back-compat for ID ordering
if ( 'ID' === $r['orderby'] ) {
$r['orderby'] = 'id';
}
// Back-compat for log_type
if ( ! empty( $r['log_type'] ) ) {
$r['type'] = $r['log_type'];
}
// Back-compat for post_parent.
if ( ! empty( $r['post_parent'] ) ) {
$type = ! empty( $r['log_type'] ) ? $r['log_type'] : '';
$r[ $this->get_object_id_column_name_for_type( $type ) ] = $r['post_parent'];
}
// Back compat for posts_per_page
$r['number'] = $r['posts_per_page'];
// Unset old keys
unset(
$r['posts_per_page'],
$r['post_parent'],
$r['post_status'],
$r['post_type'],
$r['log_type']
);
if ( ! isset( $r['offset'] ) ) {
$r['offset'] = $r['paged'] > 1
? ( ( $r['paged'] - 1 ) * $r['number'] )
: 0;
unset( $r['paged'] );
}
// Return parsed args
return $r;
}
/**
* Gets the object ID column name based on the log type.
*
* @since 3.1
* @param string $type The log type.
* @return string The column name to query for the object ID.
*/
private function get_object_id_column_name_for_type( $type = '' ) {
switch ( $type ) {
case 'file_download':
$object_id = 'product_id';
break;
case 'api_request':
$object_id = 'user_id';
break;
default:
$object_id = 'object_id';
break;
}
return $object_id;
}
/** File System ***********************************************************/
/**
* Sets up the log file if it is writable
*
* @since 2.8.7
* @return void
*/
public function setup_log_file() {
$this->init_fs();
$upload_dir = wp_upload_dir();
$this->filename = wp_hash( home_url( '/' ) ) . '-edd-debug.log';
$this->file = trailingslashit( $upload_dir['basedir'] ) . $this->filename;
if ( ! $this->get_fs()->is_writable( $upload_dir['basedir'] ) ) {
$this->is_writable = false;
}
}
/**
* Initialize the WordPress file system
*
* @since 3.0
*
* @global WP_Filesystem_Base $wp_filesystem
*/
private function init_fs() {
global $wp_filesystem;
if ( ! empty( $wp_filesystem ) ) {
return;
}
// Include the file-system
require_once ABSPATH . 'wp-admin/includes/file.php';
// Initialize the file system
WP_Filesystem();
}
/**
* Get the WordPress file-system
*
* @since 3.0
*
* @return WP_Filesystem_Base
*/
private function get_fs() {
return ! empty( $GLOBALS['wp_filesystem'] )
? $GLOBALS['wp_filesystem']
: false;
}
/**
* Log message to file.
*
* @access public
* @since 2.8.7
*
* @param string $message Message to insert in the log.
*/
public function log_to_file( $message = '' ) {
$message = date( 'Y-n-d H:i:s' ) . ' - ' . $message . "\r\n";
$this->write_to_log( $message );
}
/**
* Return the location of the log file that EDD_Logging will use.
*
* @since 2.9.1
*
* @return string
*/
public function get_log_file_path() {
return $this->file;
}
/**
* Retrieve the log data.
*
* @access public
* @since 2.8.7
*
* @return string Log data.
*/
public function get_file_contents() {
return $this->get_file();
}
/**
* Retrieve the file data is written to
*
* @access protected
* @since 2.8.7
*
* @return string File data.
*/
protected function get_file() {
$file = '';
if ( $this->get_fs()->exists( $this->file ) ) {
if ( ! $this->get_fs()->is_writable( $this->file ) ) {
$this->is_writable = false;
}
$file = $this->get_fs()->get_contents( $this->file );
} else {
$this->get_fs()->put_contents( $this->file, '' );
$this->get_fs()->chmod( $this->file, 0664 );
}
return $file;
}
/**
* Write the log message.
*
* @access protected
* @since 2.8.7
*/
protected function write_to_log( $message = '' ) {
file_put_contents( $this->file, $message, FILE_APPEND );
}
/**
* Delete the log file or removes all contents in the log file if we cannot delete it.
*
* @access public
* @since 2.8.7
*
* @return bool True if the log was cleared, false otherwise.
*/
public function clear_log_file() {
$this->get_fs()->delete( $this->file );
if ( $this->get_fs()->exists( $this->file ) ) {
// It's still there, so maybe server doesn't have delete rights
$this->get_fs()->chmod( $this->file, 0664 );
$this->get_fs()->delete( $this->file );
// See if it's still there...
if ( $this->get_fs()->exists( $this->file ) ) {
$this->get_fs()->put_contents( $this->file, '' );
}
}
$this->file = '';
return true;
}
/** Deprecated ************************************************************/
/**
* Registers the edd_log post type.
*
* @since 1.3.1
* @deprecated 3.0 Due to migration to custom tables.
*/
public function register_post_type() {
_edd_deprecated_function( __FUNCTION__, '3.0.0' );
}
/**
* Register the log type taxonomy.
*
* @since 1.3.1
* @deprecated 3.0 Due to migration to custom tables.
*/
public function register_taxonomy() {
_edd_deprecated_function( __FUNCTION__, '3.0.0' );
}
}
/**
* Helper method to insert a new log into the database.
*
* @since 1.3.3
*
* @see EDD_Logging::add()
*
* @param string $title Log title.
* @param string $message Log message.
* @param int $parent Download ID.
* @param null $type Log type.
*
* @return int ID of the new log.
*/
function edd_record_log( $title = '', $message = '', $parent = 0, $type = null ) {
$edd_logs = EDD()->debug_log;
return $edd_logs->add( $title, $message, $parent, $type );
}
/**
* Logs a message to the debug log file.
*
* @since 2.8.7
* @since 2.9.4 Added the 'force' option.
*
* @param string $message Log message.
* @param bool $force Whether to force a log entry to be added. Default false.
*/
function edd_debug_log( $message = '', $force = false ) {
$edd_logs = EDD()->debug_log;
if ( edd_is_debug_mode() || $force ) {
if ( function_exists( 'mb_convert_encoding' ) ) {
$message = mb_convert_encoding( $message, 'UTF-8' );
}
$edd_logs->log_to_file( $message );
}
}
/**
* Logs an exception to the debug log file.
*
* @since 3.0
*
* @param \Exception $exception Exception object.
*/
function edd_debug_log_exception( $exception ) {
$label = get_class( $exception );
if ( $exception->getCode() ) {
$message = sprintf( '%1$s: %2$s - %3$s',
$label,
$exception->getCode(),
$exception->getMessage()
);
} else {
$message = sprintf( '%1$s: %2$s',
$label,
$exception->getMessage()
);
}
edd_debug_log( $message );
}