[#12517] Implement logging product downloads into new logging table

This commit is contained in:
Josh Smith 2017-07-30 22:38:17 +00:00
parent 3959cf09a5
commit 399269a4f1
9 changed files with 554 additions and 30 deletions

View File

@ -0,0 +1,242 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class for customer download logs.
*
* @version 3.3.0
* @since 3.3.0
* @package WooCommerce/Classes
* @author WooThemes
*/
class WC_Customer_Download_Log extends WC_Data implements ArrayAccess {
/**
* This is the name of this object type.
* @var string
*/
protected $object_type = 'customer_download_log';
/**
* Download Log Data array.
*
* @var array
*/
protected $data = array(
'timestamp' => null,
'permission_id' => '',
'user_id' => null,
'user_ip_address' => null,
);
/**
* Constructor.
*
* @param int|object|array $download_log
*/
public function __construct( $download_log = 0 ) {
parent::__construct( $download_log );
if ( is_numeric( $download_log ) && $download_log > 0 ) {
$this->set_id( $download_log );
} elseif ( $download_log instanceof self ) {
$this->set_id( $download_log->get_id() );
} elseif ( is_object( $download_log ) && ! empty( $download_log->download_log_id ) ) {
$this->set_id( $download_log->download_log_id );
$this->set_props( (array) $download_log );
$this->set_object_read( true );
} else {
$this->set_object_read( true );
}
$this->data_store = WC_Data_Store::load( 'customer-download-log' );
if ( $this->get_id() > 0 ) {
$this->data_store->read( $this );
}
}
/*
|--------------------------------------------------------------------------
| Getters
|--------------------------------------------------------------------------
*/
/**
* Get timestamp.
*
* @param string $context
* @return WC_DateTime|null Object if the date is set or null if there is no date.
*/
public function get_timestamp( $context = 'view' ) {
return $this->get_prop( 'timestamp', $context );
}
/**
* Get permission id.
*
* @param string $context
* @return integer
*/
public function get_permission_id( $context = 'view' ) {
return $this->get_prop( 'permission_id', $context );
}
/**
* Get user id.
*
* @param string $context
* @return integer
*/
public function get_user_id( $context = 'view' ) {
return $this->get_prop( 'user_id', $context );
}
/**
* Get user ip address.
*
* @param string $context
* @return string
*/
public function get_user_ip_address( $context = 'view' ) {
return $this->get_prop( 'user_ip_address', $context );
}
/*
|--------------------------------------------------------------------------
| Setters
|--------------------------------------------------------------------------
*/
/**
* Set timestamp.
*
* @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date.
*/
public function set_timestamp( $date = null ) {
$this->set_date_prop( 'timestamp', $date );
}
/**
* Set permission id.
*
* @param int $value
*/
public function set_permission_id( $value ) {
$this->set_prop( 'permission_id', absint( $value ) );
}
/**
* Set user id.
*
* @param int $value
*/
public function set_user_id( $value ) {
$this->set_prop( 'user_id', absint( $value ) );
}
/**
* Set user ip address.
*
* @param string $value
*/
public function set_user_ip_address( $value ) {
$this->set_prop( 'user_ip_address', $value );
}
/*
|--------------------------------------------------------------------------
| CRUD methods
|--------------------------------------------------------------------------
*/
/**
* Save data to the database.
*
* @return int Log ID
*/
public function save() {
if ( $this->data_store ) {
// Trigger action before saving to the DB. Use a pointer to adjust object props before save.
do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
if ( $this->get_id() ) {
$this->data_store->update( $this );
} else {
$this->data_store->create( $this );
}
}
return $this->get_id();
}
/*
|--------------------------------------------------------------------------
| ArrayAccess/Backwards compatibility.
|--------------------------------------------------------------------------
*/
/**
* offsetGet
* @param string $offset
* @return mixed
*/
public function offsetGet( $offset ) {
if ( is_callable( array( $this, "get_$offset" ) ) ) {
return $this->{"get_$offset"}();
}
}
/**
* offsetSet
* @param string $offset
* @param mixed $value
*/
public function offsetSet( $offset, $value ) {
if ( is_callable( array( $this, "set_$offset" ) ) ) {
$this->{"set_$offset"}( $value );
}
}
/**
* offsetUnset
* @param string $offset
*/
public function offsetUnset( $offset ) {
if ( is_callable( array( $this, "set_$offset" ) ) ) {
$this->{"set_$offset"}( '' );
}
}
/**
* offsetExists
* @param string $offset
* @return bool
*/
public function offsetExists( $offset ) {
return in_array( $offset, array_keys( $this->data ) );
}
/**
* Magic __isset method for backwards compatibility. Legacy properties which could be accessed directly in the past.
*
* @param string $key Key name.
* @return bool
*/
public function __isset( $key ) {
return in_array( $offset, array_keys( $this->data ) );
}
/**
* Magic __get method for backwards compatibility. Maps legacy vars to new getters.
*
* @param string $key Key name.
* @return mixed
*/
public function __get( $key ) {
if ( is_callable( array( $this, "get_$key" ) ) ) {
return $this->{"get_$key"}( '' );
}
}
}

View File

@ -266,6 +266,46 @@ class WC_Customer_Download extends WC_Data implements ArrayAccess {
$this->set_prop( 'download_count', absint( $value ) ); $this->set_prop( 'download_count', absint( $value ) );
} }
/**
* Track a download on this permission.
*
* @since 3.3.0
* @param int user_id Id of the user performing the download
* @param string user_ip_address IP Address of the user performing the download
*/
public function track_download( $user_id = null, $user_ip_address = null ) {
// Must have a permission_id to track download log
if ( ! ( $this->get_id() > 0 ) ) {
throw new Exception( __( 'Invalid permission ID.', 'woocommerce' ) );
}
// Track download in download log
$download_log = new WC_Customer_Download_Log();
$download_log->set_timestamp( current_time( 'timestamp', true ) );
$download_log->set_permission_id( $this->get_id() );
if ( ! is_null( $user_id ) ) {
$download_log->set_user_id( $user_id );
}
if ( ! is_null( $user_ip_address ) ) {
$download_log->set_user_ip_address( $user_ip_address );
}
$download_log->save();
// Increment download count on parameter
$count = $this->get_download_count();
$remaining = $this->get_downloads_remaining();
$this->set_download_count( $count + 1 );
if ( '' !== $remaining ) {
$this->set_downloads_remaining( $remaining - 1 );
}
// Save the download at this point to ensure the download log matches the param count
$this->save();
}
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| CRUD methods | CRUD methods

View File

@ -28,24 +28,25 @@ class WC_Data_Store {
* Ran through `woocommerce_data_stores`. * Ran through `woocommerce_data_stores`.
*/ */
private $stores = array( private $stores = array(
'coupon' => 'WC_Coupon_Data_Store_CPT', 'coupon' => 'WC_Coupon_Data_Store_CPT',
'customer' => 'WC_Customer_Data_Store', 'customer' => 'WC_Customer_Data_Store',
'customer-download' => 'WC_Customer_Download_Data_Store', 'customer-download' => 'WC_Customer_Download_Data_Store',
'customer-session' => 'WC_Customer_Data_Store_Session', 'customer-download-log' => 'WC_Customer_Download_Log_Data_Store',
'order' => 'WC_Order_Data_Store_CPT', 'customer-session' => 'WC_Customer_Data_Store_Session',
'order-refund' => 'WC_Order_Refund_Data_Store_CPT', 'order' => 'WC_Order_Data_Store_CPT',
'order-item' => 'WC_Order_Item_Data_Store', 'order-refund' => 'WC_Order_Refund_Data_Store_CPT',
'order-item-coupon' => 'WC_Order_Item_Coupon_Data_Store', 'order-item' => 'WC_Order_Item_Data_Store',
'order-item-fee' => 'WC_Order_Item_Fee_Data_Store', 'order-item-coupon' => 'WC_Order_Item_Coupon_Data_Store',
'order-item-product' => 'WC_Order_Item_Product_Data_Store', 'order-item-fee' => 'WC_Order_Item_Fee_Data_Store',
'order-item-shipping' => 'WC_Order_Item_Shipping_Data_Store', 'order-item-product' => 'WC_Order_Item_Product_Data_Store',
'order-item-tax' => 'WC_Order_Item_Tax_Data_Store', 'order-item-shipping' => 'WC_Order_Item_Shipping_Data_Store',
'payment-token' => 'WC_Payment_Token_Data_Store', 'order-item-tax' => 'WC_Order_Item_Tax_Data_Store',
'product' => 'WC_Product_Data_Store_CPT', 'payment-token' => 'WC_Payment_Token_Data_Store',
'product-grouped' => 'WC_Product_Grouped_Data_Store_CPT', 'product' => 'WC_Product_Data_Store_CPT',
'product-variable' => 'WC_Product_Variable_Data_Store_CPT', 'product-grouped' => 'WC_Product_Grouped_Data_Store_CPT',
'product-variation' => 'WC_Product_Variation_Data_Store_CPT', 'product-variable' => 'WC_Product_Variable_Data_Store_CPT',
'shipping-zone' => 'WC_Shipping_Zone_Data_Store', 'product-variation' => 'WC_Product_Variation_Data_Store_CPT',
'shipping-zone' => 'WC_Shipping_Zone_Data_Store',
); );
/** /**

View File

@ -72,13 +72,12 @@ class WC_Download_Handler {
$download->get_download_id(), $download->get_download_id(),
$download->get_order_id() $download->get_order_id()
); );
$count = $download->get_download_count(); $download->save();
$remaining = $download->get_downloads_remaining();
$download->set_download_count( $count + 1 ); // Track the download in logs and change remaining/counts
if ( '' !== $remaining ) { $current_user_id = get_current_user_id();
$download->set_downloads_remaining( $remaining - 1 ); $ip_address = WC_Geolocation::get_ip_address();
} $download->track_download( $current_user_id > 0 ? $current_user_id : null, !empty( $ip_address ) ? $ip_address : null );
$download->save();
self::download( $product->get_file_download_path( $download->get_download_id() ), $download->get_product_id() ); self::download( $product->get_file_download_path( $download->get_download_id() ), $download->get_product_id() );
} }

View File

@ -87,7 +87,7 @@ class WC_Install {
'3.2.0' => array( '3.2.0' => array(
'wc_update_320_mexican_states', 'wc_update_320_mexican_states',
'wc_update_320_db_version', 'wc_update_320_db_version',
), ),
); );
/** @var object Background update class */ /** @var object Background update class */
@ -623,6 +623,16 @@ CREATE TABLE {$wpdb->prefix}woocommerce_log (
context longtext NULL, context longtext NULL,
PRIMARY KEY (log_id), PRIMARY KEY (log_id),
KEY level (level) KEY level (level)
) $collate;
CREATE TABLE {$wpdb->prefix}woocommerce_downloadable_product_download_log (
download_log_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
timestamp datetime NOT NULL,
permission_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NULL,
user_ip_address VARCHAR(100) NULL DEFAULT '',
PRIMARY KEY (download_log_id),
KEY permission_id (permission_id),
KEY timestamp (timestamp)
) $collate; ) $collate;
"; ";

View File

@ -5,7 +5,7 @@
* @author Automattic * @author Automattic
* @category API * @category API
* @package WooCommerce * @package WooCommerce
* @since 3.2.0 * @since 3.3.0
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@ -16,7 +16,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* Main WooCommerce Class. * Main WooCommerce Class.
* *
* @class WooCommerce * @class WooCommerce
* @version 3.2.0 * @version 3.3.0
*/ */
final class WooCommerce { final class WooCommerce {
@ -25,7 +25,7 @@ final class WooCommerce {
* *
* @var string * @var string
*/ */
public $version = '3.2.0'; public $version = '3.3.0';
/** /**
* The single instance of the class. * The single instance of the class.
@ -275,6 +275,7 @@ final class WooCommerce {
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-coupon-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/interfaces/class-wc-coupon-data-store-interface.php' );
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-data-store-interface.php' );
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-download-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-download-data-store-interface.php' );
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-download-log-data-store-interface.php' );
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-object-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/interfaces/class-wc-object-data-store-interface.php' );
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-data-store-interface.php' );
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-data-store-interface.php' );
@ -355,6 +356,7 @@ final class WooCommerce {
include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store.php' );
include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store-session.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store-session.php' );
include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-download-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-download-data-store.php' );
include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-download-log-data-store.php' );
include_once( WC_ABSPATH . 'includes/data-stores/class-wc-shipping-zone-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-shipping-zone-data-store.php' );
include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-data-store-cpt.php' );
include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-data-store-cpt.php' );

View File

@ -0,0 +1,199 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Customer Download Log Data Store.
*
* @version 3.3.0
* @category Class
* @author WooThemes
*/
class WC_Customer_Download_Log_Data_Store implements WC_Customer_Download_Log_Data_Store_Interface {
/**
* Create download log entry.
*
* @param WC_Customer_Download_Log $download_log
*/
public function create( &$download_log ) {
global $wpdb;
// Always set a timestamp
if ( is_null( $download_log->get_timestamp( 'edit' ) ) ) {
$download_log->set_timestamp( current_time( 'timestamp', true ) );
}
$data = array(
'timestamp' => date( 'Y-m-d H:i:s', $download_log->get_timestamp( 'edit' )->getTimestamp() ),
'permission_id' => $download_log->get_permission_id( 'edit' ),
'user_id' => $download_log->get_user_id( 'edit' ),
'user_ip_address' => $download_log->get_user_ip_address( 'edit' ),
);
$format = array(
'%s',
'%s',
'%s',
'%s',
);
$result = $wpdb->insert(
$wpdb->prefix . 'woocommerce_downloadable_product_download_log',
apply_filters( 'woocommerce_downloadable_product_download_log_data', $data ),
apply_filters( 'woocommerce_downloadable_product_download_log_format', $format, $data )
);
do_action( 'woocommerce_downloadable_product_download_log', $data );
if ( $result ) {
$download_log->set_id( $wpdb->insert_id );
$download_log->apply_changes();
}
}
/**
* Method to read a download log from the database.
*
* @param $download_log
*
* @throws Exception
*/
public function read( &$download_log ) {
global $wpdb;
$download_log->set_defaults();
if ( ! $download_log->get_id() || ! ( $raw_download_log = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_download_log WHERE download_log_id = %d", $download_log->get_id() ) ) ) ) {
throw new Exception( __( 'Invalid download log.', 'woocommerce' ) );
}
$download_log->set_props( array(
'timestamp' => strtotime( $raw_download_log->timestamp ),
'permission_id' => $raw_download_log->permission_id,
'user_id' => $raw_download_log->user_id,
'user_ip_address' => $raw_download_log->user_ip_address,
) );
$download_log->set_object_read( true );
}
/**
* Method to update a download log in the database.
*
* @param WC_Customer_Download_Log $download_log
*/
public function update( &$download_log ) {
global $wpdb;
$data = array(
'timestamp' => date( 'Y-m-d H:i:s', $download_log->get_timestamp( 'edit' )->getTimestamp() ),
'permission_id' => $download_log->get_permission_id( 'edit' ),
'user_id' => $download_log->get_user_id( 'edit' ),
'user_ip_address' => $download_log->get_user_ip_address( 'edit' ),
);
$format = array(
'%s',
'%s',
'%s',
'%s',
);
$wpdb->update(
$wpdb->prefix . 'woocommerce_downloadable_product_download_log',
$data,
array(
'download_log_id' => $download_log->get_id(),
),
$format
);
$download_log->apply_changes();
}
/**
* Get a download log object.
*
* @param array $data From the DB.
* @return WC_Customer_Download_Log
*/
private function get_download_log( $data ) {
return new WC_Customer_Download_Log( $data );
}
/**
* Get array of download log ids by specified args.
*
* @param array $args
* @return array
*/
public function get_download_logs( $args = array() ) {
global $wpdb;
$args = wp_parse_args( $args, array(
'permission_id' => '',
'user_id' => '',
'user_ip_address' => '',
'orderby' => 'download_log_id',
'order' => 'DESC',
'limit' => -1,
'return' => 'objects',
) );
$query = array();
$query[] = "SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_download_log WHERE 1=1";
if ( $args['permission_id'] ) {
$query[] = $wpdb->prepare( "AND permission_id = %d", sanitize_email( $args['permission_id'] ) );
}
if ( $args['user_id'] ) {
$query[] = $wpdb->prepare( "AND user_id = %d", $args['user_id'] );
}
if ( $args['user_ip_address'] ) {
$query[] = $wpdb->prepare( "AND user_ip_address = %s", $args['user_ip_address'] );
}
$allowed_orders = array( 'download_log_id', 'timestamp', 'permission_id', 'user_id' );
$order = in_array( $args['order'], $allowed_orders ) ? $args['order'] : 'download_log_id';
$orderby = 'DESC' === strtoupper( $args['orderby'] ) ? 'DESC' : 'ASC';
$orderby_sql = sanitize_sql_orderby( "{$order} {$orderby}" );
$query[] = "ORDER BY {$orderby_sql}";
if ( 0 < $args['limit'] ) {
$query[] = $wpdb->prepare( "LIMIT %d", $args['limit'] );
}
$raw_download_logs = $wpdb->get_results( implode( ' ', $query ) );
switch ( $args['return'] ) {
case 'ids' :
return wp_list_pluck( $raw_download_logs, 'download_log_id' );
default :
return array_map( array( $this, 'get_download_log' ), $raw_download_logs );
}
}
/**
* Get logs for a specific download permission.
*
* @param int $permission_id
* @return array
*/
public function get_download_logs_for_permission( $permission_id ) {
global $wpdb;
return $wpdb->get_results(
$wpdb->prepare( "
SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_download_log as logs
WHERE permission_id = %d
ORDER BY logs.timestamp DESC;
",
$permission_id
)
);
}
}

View File

@ -0,0 +1,31 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Customer Download Log Data Store Interface.
*
* @version 3.3.0
* @category Interface
* @author WooThemes
*/
interface WC_Customer_Download_Log_Data_Store_Interface {
/**
* Get array of download log ids by specified args.
*
* @param array $args
* @return array of WC_Customer_Download_Log
*/
public function get_download_logs( $args = array() );
/**
* Get logs for a specific download permission.
*
* @param int $permission_id
* @return array
*/
public function get_download_logs_for_permission( $permission_id );
}

View File

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce * Plugin Name: WooCommerce
* Plugin URI: https://woocommerce.com/ * Plugin URI: https://woocommerce.com/
* Description: An e-commerce toolkit that helps you sell anything. Beautifully. * Description: An e-commerce toolkit that helps you sell anything. Beautifully.
* Version: 3.2.0-dev * Version: 3.3.0-dev
* Author: Automattic * Author: Automattic
* Author URI: https://woocommerce.com * Author URI: https://woocommerce.com
* Requires at least: 4.4 * Requires at least: 4.4