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 ); }