File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/DataStore.php.tar
www/vhosts/uyarreklam.com.tr/httpdocs/wp-content/plugins/woocommerce/src/Admin/Notes/DataStore.php 0000644 00000042757 15154503242 0030260 0 ustar 00 var <?php
/**
* WC Admin Note Data_Store class file.
*/
namespace Automattic\WooCommerce\Admin\Notes;
defined( 'ABSPATH' ) || exit;
/**
* WC Admin Note Data Store (Custom Tables)
*/
class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Interface {
// Extensions should define their own contexts and use them to avoid applying woocommerce_note_where_clauses when not needed.
const WC_ADMIN_NOTE_OPER_GLOBAL = 'global';
/**
* Method to create a new note in the database.
*
* @param Note $note Admin note.
*/
public function create( &$note ) {
$date_created = time();
$note->set_date_created( $date_created );
global $wpdb;
$note_to_be_inserted = array(
'name' => $note->get_name(),
'type' => $note->get_type(),
'locale' => $note->get_locale(),
'title' => $note->get_title(),
'content' => $note->get_content(),
'status' => $note->get_status(),
'source' => $note->get_source(),
'is_snoozable' => (int) $note->get_is_snoozable(),
'layout' => $note->get_layout(),
'image' => $note->get_image(),
'is_deleted' => (int) $note->get_is_deleted(),
);
$note_to_be_inserted['content_data'] = wp_json_encode( $note->get_content_data() );
$note_to_be_inserted['date_created'] = gmdate( 'Y-m-d H:i:s', $date_created );
$note_to_be_inserted['date_reminder'] = null;
$wpdb->insert( $wpdb->prefix . 'wc_admin_notes', $note_to_be_inserted );
$note_id = $wpdb->insert_id;
$note->set_id( $note_id );
$this->save_actions( $note );
$note->apply_changes();
/**
* Fires when an admin note is created.
*
* @param int $note_id Note ID.
*/
do_action( 'woocommerce_note_created', $note_id );
}
/**
* Method to read a note.
*
* @param Note $note Admin note.
* @throws \Exception Throws exception when invalid data is found.
*/
public function read( &$note ) {
global $wpdb;
$note->set_defaults();
$note_row = false;
$note_id = $note->get_id();
if ( 0 !== $note_id || '0' !== $note_id ) {
$note_row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wc_admin_notes WHERE note_id = %d LIMIT 1",
$note->get_id()
)
);
}
if ( 0 === $note->get_id() || '0' === $note->get_id() ) {
$this->read_actions( $note );
$note->set_object_read( true );
/**
* Fires when an admin note is loaded.
*
* @param int $note_id Note ID.
*/
do_action( 'woocommerce_note_loaded', $note );
} elseif ( $note_row ) {
$note->set_name( $note_row->name );
$note->set_type( $note_row->type );
$note->set_locale( $note_row->locale );
$note->set_title( $note_row->title );
$note->set_content( $note_row->content );
// The default for 'content_value' used to be an array, so there might be rows with invalid data!
$content_data = json_decode( $note_row->content_data );
if ( ! $content_data ) {
$content_data = new \stdClass();
} elseif ( is_array( $content_data ) ) {
$content_data = (object) $content_data;
}
$note->set_content_data( $content_data );
$note->set_status( $note_row->status );
$note->set_source( $note_row->source );
$note->set_date_created( $note_row->date_created );
$note->set_date_reminder( $note_row->date_reminder );
$note->set_is_snoozable( $note_row->is_snoozable );
$note->set_is_deleted( (bool) $note_row->is_deleted );
isset( $note_row->is_read ) && $note->set_is_read( (bool) $note_row->is_read );
$note->set_layout( $note_row->layout );
$note->set_image( $note_row->image );
$this->read_actions( $note );
$note->set_object_read( true );
/**
* Fires when an admin note is loaded.
*
* @param int $note_id Note ID.
*/
do_action( 'woocommerce_note_loaded', $note );
} else {
throw new \Exception( __( 'Invalid admin note', 'woocommerce' ) );
}
}
/**
* Updates a note in the database.
*
* @param Note $note Admin note.
*/
public function update( &$note ) {
global $wpdb;
if ( $note->get_id() ) {
$date_created = $note->get_date_created();
$date_created_timestamp = $date_created->getTimestamp();
$date_created_to_db = gmdate( 'Y-m-d H:i:s', $date_created_timestamp );
$date_reminder = $note->get_date_reminder();
if ( is_null( $date_reminder ) ) {
$date_reminder_to_db = null;
} else {
$date_reminder_timestamp = $date_reminder->getTimestamp();
$date_reminder_to_db = gmdate( 'Y-m-d H:i:s', $date_reminder_timestamp );
}
$wpdb->update(
$wpdb->prefix . 'wc_admin_notes',
array(
'name' => $note->get_name(),
'type' => $note->get_type(),
'locale' => $note->get_locale(),
'title' => $note->get_title(),
'content' => $note->get_content(),
'content_data' => wp_json_encode( $note->get_content_data() ),
'status' => $note->get_status(),
'source' => $note->get_source(),
'date_created' => $date_created_to_db,
'date_reminder' => $date_reminder_to_db,
'is_snoozable' => $note->get_is_snoozable(),
'layout' => $note->get_layout(),
'image' => $note->get_image(),
'is_deleted' => $note->get_is_deleted(),
'is_read' => $note->get_is_read(),
),
array( 'note_id' => $note->get_id() )
);
}
$this->save_actions( $note );
$note->apply_changes();
/**
* Fires when an admin note is updated.
*
* @param int $note_id Note ID.
*/
do_action( 'woocommerce_note_updated', $note->get_id() );
}
/**
* Deletes a note from the database.
*
* @param Note $note Admin note.
* @param array $args Array of args to pass to the delete method (not used).
*/
public function delete( &$note, $args = array() ) {
$note_id = $note->get_id();
if ( $note_id ) {
global $wpdb;
$wpdb->delete( $wpdb->prefix . 'wc_admin_notes', array( 'note_id' => $note_id ) );
$wpdb->delete( $wpdb->prefix . 'wc_admin_note_actions', array( 'note_id' => $note_id ) );
$note->set_id( null );
}
/**
* Fires when an admin note is deleted.
*
* @param int $note_id Note ID.
*/
do_action( 'woocommerce_note_deleted', $note_id );
}
/**
* Read actions from the database.
*
* @param Note $note Admin note.
*/
private function read_actions( &$note ) {
global $wpdb;
$db_actions = $wpdb->get_results(
$wpdb->prepare(
"SELECT action_id, name, label, query, status, actioned_text, nonce_action, nonce_name
FROM {$wpdb->prefix}wc_admin_note_actions
WHERE note_id = %d",
$note->get_id()
)
);
$note_actions = array();
if ( $db_actions ) {
foreach ( $db_actions as $action ) {
$note_actions[] = (object) array(
'id' => (int) $action->action_id,
'name' => $action->name,
'label' => $action->label,
'query' => $action->query,
'status' => $action->status,
'actioned_text' => $action->actioned_text,
'nonce_action' => $action->nonce_action,
'nonce_name' => $action->nonce_name,
);
}
}
$note->set_actions( $note_actions );
}
/**
* Save actions to the database.
* This function clears old actions, then re-inserts new if any changes are found.
*
* @param Note $note Note object.
*
* @return bool|void
*/
private function save_actions( &$note ) {
global $wpdb;
$changed_props = array_keys( $note->get_changes() );
if ( ! in_array( 'actions', $changed_props, true ) ) {
return false;
}
// Process action removal. Actions are removed from
// the note if they aren't part of the changeset.
// See Note::add_action().
$changed_actions = $note->get_actions( 'edit' );
$actions_to_keep = array();
foreach ( $changed_actions as $action ) {
if ( ! empty( $action->id ) ) {
$actions_to_keep[] = (int) $action->id;
}
}
$clear_actions_query = $wpdb->prepare(
"DELETE FROM {$wpdb->prefix}wc_admin_note_actions WHERE note_id = %d",
$note->get_id()
);
if ( $actions_to_keep ) {
$clear_actions_query .= sprintf( ' AND action_id NOT IN (%s)', implode( ',', $actions_to_keep ) );
}
$wpdb->query( $clear_actions_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
// Update/insert the actions in this changeset.
foreach ( $changed_actions as $action ) {
$action_data = array(
'note_id' => $note->get_id(),
'name' => $action->name,
'label' => $action->label,
'query' => $action->query,
'status' => $action->status,
'actioned_text' => $action->actioned_text,
'nonce_action' => $action->nonce_action,
'nonce_name' => $action->nonce_name,
);
$data_format = array(
'%d',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
);
if ( ! empty( $action->id ) ) {
$action_data['action_id'] = $action->id;
$data_format[] = '%d';
}
$wpdb->replace(
$wpdb->prefix . 'wc_admin_note_actions',
$action_data,
$data_format
);
}
// Update actions from DB (to grab new IDs).
$this->read_actions( $note );
}
/**
* Return an ordered list of notes.
*
* @param array $args Query arguments.
* @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
* @return array An array of objects containing a note id.
*/
public function get_notes( $args = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) {
global $wpdb;
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date_created',
);
$args = wp_parse_args( $args, $defaults );
$offset = $args['per_page'] * ( $args['page'] - 1 );
$where_clauses = $this->get_notes_where_clauses( $args, $context );
// sanitize order and orderby.
$order_by = '`' . str_replace( '`', '', $args['orderby'] ) . '`';
$order_dir = 'asc' === strtolower( $args['order'] ) ? 'ASC' : 'DESC';
$query = $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT * FROM {$wpdb->prefix}wc_admin_notes WHERE 1=1{$where_clauses} ORDER BY {$order_by} {$order_dir} LIMIT %d, %d",
$offset,
$args['per_page']
);
return $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
/**
* Return an ordered list of notes, without paging or applying the 'woocommerce_note_where_clauses' filter.
* INTERNAL: This method is not intended to be used by external code, and may change without notice.
*
* @param array $args Query arguments.
* @return array An array of database records.
*/
public function lookup_notes( $args = array() ) {
global $wpdb;
$defaults = array(
'order' => 'DESC',
'orderby' => 'date_created',
);
$args = wp_parse_args( $args, $defaults );
$where_clauses = $this->args_to_where_clauses( $args );
// sanitize order and orderby.
$order_by = '`' . str_replace( '`', '', $args['orderby'] ) . '`';
$order_dir = 'asc' === strtolower( $args['order'] ) ? 'ASC' : 'DESC';
$query = "SELECT * FROM {$wpdb->prefix}wc_admin_notes WHERE 1=1{$where_clauses} ORDER BY {$order_by} {$order_dir}";
return $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
/**
* Return a count of notes.
*
* @param string $type Comma separated list of note types.
* @param string $status Comma separated list of statuses.
* @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
* @return string Count of objects with given type, status and context.
*/
public function get_notes_count( $type = array(), $status = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) {
global $wpdb;
$where_clauses = $this->get_notes_where_clauses(
array(
'type' => $type,
'status' => $status,
),
$context
);
if ( ! empty( $where_clauses ) ) {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_admin_notes WHERE 1=1{$where_clauses}" );
}
return $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_admin_notes" );
}
/**
* Parses the query arguments passed in as arrays and escapes the values.
*
* @param array $args the query arguments.
* @param string $key the key of the specific argument.
* @param array|null $allowed_types optional allowed_types if only a specific set is allowed.
* @return array the escaped array of argument values.
*/
private function get_escaped_arguments_array_by_key( $args = array(), $key = '', $allowed_types = null ) {
$arg_array = array();
if ( isset( $args[ $key ] ) ) {
foreach ( $args[ $key ] as $args_type ) {
$args_type = trim( $args_type );
$allowed = is_null( $allowed_types ) || in_array( $args_type, $allowed_types, true );
if ( $allowed ) {
$arg_array[] = sprintf( "'%s'", esc_sql( $args_type ) );
}
}
}
return $arg_array;
}
/**
* Return where clauses for getting notes by status and type. For use in both the count and listing queries.
* Applies woocommerce_note_where_clauses filter.
*
* @uses args_to_where_clauses
* @param array $args Array of args to pass.
* @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
* @return string Where clauses for the query.
*/
public function get_notes_where_clauses( $args = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) {
$where_clauses = $this->args_to_where_clauses( $args );
/**
* Filter the notes WHERE clause before retrieving the data.
*
* Allows modification of the notes select criterial.
*
* @param string $where_clauses The generated WHERE clause.
* @param array $args The original arguments for the request.
* @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
*/
return apply_filters( 'woocommerce_note_where_clauses', $where_clauses, $args, $context );
}
/**
* Return where clauses for notes queries without applying woocommerce_note_where_clauses filter.
* INTERNAL: This method is not intended to be used by external code, and may change without notice.
*
* @param array $args Array of arguments for query conditionals.
* @return string Where clauses.
*/
protected function args_to_where_clauses( $args = array() ) {
$allowed_types = Note::get_allowed_types();
$where_type_array = $this->get_escaped_arguments_array_by_key( $args, 'type', $allowed_types );
$allowed_statuses = Note::get_allowed_statuses();
$where_status_array = $this->get_escaped_arguments_array_by_key( $args, 'status', $allowed_statuses );
$escaped_is_deleted = '';
if ( isset( $args['is_deleted'] ) ) {
$escaped_is_deleted = esc_sql( $args['is_deleted'] );
}
$where_name_array = $this->get_escaped_arguments_array_by_key( $args, 'name' );
$where_excluded_name_array = $this->get_escaped_arguments_array_by_key( $args, 'excluded_name' );
$where_source_array = $this->get_escaped_arguments_array_by_key( $args, 'source' );
$escaped_where_types = implode( ',', $where_type_array );
$escaped_where_status = implode( ',', $where_status_array );
$escaped_where_names = implode( ',', $where_name_array );
$escaped_where_excluded_names = implode( ',', $where_excluded_name_array );
$escaped_where_source = implode( ',', $where_source_array );
$where_clauses = '';
if ( ! empty( $escaped_where_types ) ) {
$where_clauses .= " AND type IN ($escaped_where_types)";
}
if ( ! empty( $escaped_where_status ) ) {
$where_clauses .= " AND status IN ($escaped_where_status)";
}
if ( ! empty( $escaped_where_names ) ) {
$where_clauses .= " AND name IN ($escaped_where_names)";
}
if ( ! empty( $escaped_where_excluded_names ) ) {
$where_clauses .= " AND name NOT IN ($escaped_where_excluded_names)";
}
if ( ! empty( $escaped_where_source ) ) {
$where_clauses .= " AND source IN ($escaped_where_source)";
}
if ( isset( $args['is_read'] ) ) {
$where_clauses .= $args['is_read'] ? ' AND is_read = 1' : ' AND is_read = 0';
}
$where_clauses .= $escaped_is_deleted ? ' AND is_deleted = 1' : ' AND is_deleted = 0';
return $where_clauses;
}
/**
* Find all the notes with a given name.
*
* @param string $name Name to search for.
* @return array An array of matching note ids.
*/
public function get_notes_with_name( $name ) {
global $wpdb;
return $wpdb->get_col(
$wpdb->prepare(
"SELECT note_id FROM {$wpdb->prefix}wc_admin_notes WHERE name = %s ORDER BY note_id ASC",
$name
)
);
}
/**
* Find the ids of all notes with a given type.
*
* @param string $note_type Type to search for.
* @return array An array of matching note ids.
*/
public function get_note_ids_by_type( $note_type ) {
global $wpdb;
return $wpdb->get_col(
$wpdb->prepare(
"SELECT note_id FROM {$wpdb->prefix}wc_admin_notes WHERE type = %s ORDER BY note_id ASC",
$note_type
)
);
}
}
vhosts/uyarreklam.com.tr/httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/DataStore.php0000644 00000142667 15155214625 0031245 0 ustar 00 var/www <?php
/**
* Admin\API\Reports\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
/**
* Admin\API\Reports\DataStore: Common parent for custom report data stores.
*/
class DataStore extends SqlQuery {
/**
* Cache group for the reports.
*
* @var string
*/
protected $cache_group = 'reports';
/**
* Time out for the cache.
*
* @var int
*/
protected $cache_timeout = 3600;
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = '';
/**
* Table used as a data store for this report.
*
* @var string
*/
protected static $table_name = '';
/**
* Date field name.
*
* @var string
*/
protected $date_column_name = 'date_created';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array();
/**
* SQL columns to select in the db query.
*
* @var array
*/
protected $report_columns = array();
// @todo This does not really belong here, maybe factor out the comparison as separate class?
/**
* Order by property, used in the cmp function.
*
* @var string
*/
private $order_by = '';
/**
* Order property, used in the cmp function.
*
* @var string
*/
private $order = '';
/**
* Query limit parameters.
*
* @var array
*/
private $limit_parameters = array();
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'reports';
/**
* Subquery object for query nesting.
*
* @var SqlQuery
*/
protected $subquery;
/**
* Totals query object.
*
* @var SqlQuery
*/
protected $total_query;
/**
* Intervals query object.
*
* @var SqlQuery
*/
protected $interval_query;
/**
* Refresh the cache for the current query when true.
*
* @var bool
*/
protected $force_cache_refresh = false;
/**
* Include debugging information in the returned data when true.
*
* @var bool
*/
protected $debug_cache = true;
/**
* Debugging information to include in the returned data.
*
* @var array
*/
protected $debug_cache_data = array();
/**
* Class constructor.
*/
public function __construct() {
self::set_db_table_name();
$this->assign_report_columns();
if ( $this->report_columns ) {
$this->report_columns = apply_filters(
'woocommerce_admin_report_columns',
$this->report_columns,
$this->context,
self::get_db_table_name()
);
}
// Utilize enveloped responses to include debugging info.
// See https://querymonitor.com/blog/2021/05/debugging-wordpress-rest-api-requests/
if ( isset( $_GET['_envelope'] ) ) {
$this->debug_cache = true;
add_filter( 'rest_envelope_response', array( $this, 'add_debug_cache_to_envelope' ), 999, 2 );
}
}
/**
* Get table name from database class.
*/
public static function get_db_table_name() {
global $wpdb;
return isset( $wpdb->{static::$table_name} ) ? $wpdb->{static::$table_name} : $wpdb->prefix . static::$table_name;
}
/**
* Set table name from database class.
*/
protected static function set_db_table_name() {
global $wpdb;
if ( static::$table_name && ! isset( $wpdb->{static::$table_name} ) ) {
$wpdb->{static::$table_name} = $wpdb->prefix . static::$table_name;
}
}
/**
* Whether or not the report should use the caching layer.
*
* Provides an opportunity for plugins to prevent reports from using cache.
*
* @return boolean Whether or not to utilize caching.
*/
protected function should_use_cache() {
/**
* Determines if a report will utilize caching.
*
* @param bool $use_cache Whether or not to use cache.
* @param string $cache_key The report's cache key. Used to identify the report.
*/
return (bool) apply_filters( 'woocommerce_analytics_report_should_use_cache', true, $this->cache_key );
}
/**
* Returns string to be used as cache key for the data.
*
* @param array $params Query parameters.
* @return string
*/
protected function get_cache_key( $params ) {
if ( isset( $params['force_cache_refresh'] ) ) {
if ( true === $params['force_cache_refresh'] ) {
$this->force_cache_refresh = true;
}
// We don't want this param in the key.
unset( $params['force_cache_refresh'] );
}
if ( true === $this->debug_cache ) {
$this->debug_cache_data['query_args'] = $params;
}
return implode(
'_',
array(
'wc_report',
$this->cache_key,
md5( wp_json_encode( $params ) ),
)
);
}
/**
* Wrapper around Cache::get().
*
* @param string $cache_key Cache key.
* @return mixed
*/
protected function get_cached_data( $cache_key ) {
if ( true === $this->debug_cache ) {
$this->debug_cache_data['should_use_cache'] = $this->should_use_cache();
$this->debug_cache_data['force_cache_refresh'] = $this->force_cache_refresh;
$this->debug_cache_data['cache_hit'] = false;
}
if ( $this->should_use_cache() && false === $this->force_cache_refresh ) {
$cached_data = Cache::get( $cache_key );
$cache_hit = false !== $cached_data;
if ( true === $this->debug_cache ) {
$this->debug_cache_data['cache_hit'] = $cache_hit;
}
return $cached_data;
}
// Cached item has now functionally been refreshed. Reset the option.
$this->force_cache_refresh = false;
return false;
}
/**
* Wrapper around Cache::set().
*
* @param string $cache_key Cache key.
* @param mixed $value New value.
* @return bool
*/
protected function set_cached_data( $cache_key, $value ) {
if ( $this->should_use_cache() ) {
return Cache::set( $cache_key, $value );
}
return true;
}
/**
* Add cache debugging information to an enveloped API response.
*
* @param array $envelope
* @param \WP_REST_Response $response
*
* @return array
*/
public function add_debug_cache_to_envelope( $envelope, $response ) {
if ( 0 !== strncmp( '/wc-analytics', $response->get_matched_route(), 13 ) ) {
return $envelope;
}
if ( ! empty( $this->debug_cache_data ) ) {
$envelope['debug_cache'] = $this->debug_cache_data;
}
return $envelope;
}
/**
* Compares two report data objects by pre-defined object property and ASC/DESC ordering.
*
* @param stdClass $a Object a.
* @param stdClass $b Object b.
* @return string
*/
private function interval_cmp( $a, $b ) {
if ( '' === $this->order_by || '' === $this->order ) {
return 0;
// @todo Should return WP_Error here perhaps?
}
if ( $a[ $this->order_by ] === $b[ $this->order_by ] ) {
// As relative order is undefined in case of equality in usort, second-level sorting by date needs to be enforced
// so that paging is stable.
if ( $a['time_interval'] === $b['time_interval'] ) {
return 0; // This should never happen.
} elseif ( $a['time_interval'] > $b['time_interval'] ) {
return 1;
} elseif ( $a['time_interval'] < $b['time_interval'] ) {
return -1;
}
} elseif ( $a[ $this->order_by ] > $b[ $this->order_by ] ) {
return strtolower( $this->order ) === 'desc' ? -1 : 1;
} elseif ( $a[ $this->order_by ] < $b[ $this->order_by ] ) {
return strtolower( $this->order ) === 'desc' ? 1 : -1;
}
}
/**
* Sorts intervals according to user's request.
*
* They are pre-sorted in SQL, but after adding gaps, they need to be sorted including the added ones.
*
* @param stdClass $data Data object, must contain an array under $data->intervals.
* @param string $sort_by Ordering property.
* @param string $direction DESC/ASC.
*/
protected function sort_intervals( &$data, $sort_by, $direction ) {
$this->sort_array( $data->intervals, $sort_by, $direction );
}
/**
* Sorts array of arrays based on subarray key $sort_by.
*
* @param array $arr Array to sort.
* @param string $sort_by Ordering property.
* @param string $direction DESC/ASC.
*/
protected function sort_array( &$arr, $sort_by, $direction ) {
$this->order_by = $this->normalize_order_by( $sort_by );
$this->order = $direction;
usort( $arr, array( $this, 'interval_cmp' ) );
}
/**
* Fills in interval gaps from DB with 0-filled objects.
*
* @param array $db_intervals Array of all intervals present in the db.
* @param DateTime $start_datetime Start date.
* @param DateTime $end_datetime End date.
* @param string $time_interval Time interval, e.g. day, week, month.
* @param stdClass $data Data with SQL extracted intervals.
* @return stdClass
*/
protected function fill_in_missing_intervals( $db_intervals, $start_datetime, $end_datetime, $time_interval, &$data ) {
// @todo This is ugly and messy.
$local_tz = new \DateTimeZone( wc_timezone_string() );
// At this point, we don't know when we can stop iterating, as the ordering can be based on any value.
$time_ids = array_flip( wp_list_pluck( $data->intervals, 'time_interval' ) );
$db_intervals = array_flip( $db_intervals );
// Totals object used to get all needed properties.
$totals_arr = get_object_vars( $data->totals );
foreach ( $totals_arr as $key => $val ) {
$totals_arr[ $key ] = 0;
}
// @todo Should 'products' be in intervals?
unset( $totals_arr['products'] );
while ( $start_datetime <= $end_datetime ) {
$next_start = TimeInterval::iterate( $start_datetime, $time_interval );
$time_id = TimeInterval::time_interval_id( $time_interval, $start_datetime );
// Either create fill-zero interval or use data from db.
if ( $next_start > $end_datetime ) {
$interval_end = $end_datetime->format( 'Y-m-d H:i:s' );
} else {
$prev_end_timestamp = (int) $next_start->format( 'U' ) - 1;
$prev_end = new \DateTime();
$prev_end->setTimestamp( $prev_end_timestamp );
$prev_end->setTimezone( $local_tz );
$interval_end = $prev_end->format( 'Y-m-d H:i:s' );
}
if ( array_key_exists( $time_id, $time_ids ) ) {
// For interval present in the db for this time frame, just fill in dates.
$record = &$data->intervals[ $time_ids[ $time_id ] ];
$record['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' );
$record['date_end'] = $interval_end;
} elseif ( ! array_key_exists( $time_id, $db_intervals ) ) {
// For intervals present in the db outside of this time frame, do nothing.
// For intervals not present in the db, fabricate it.
$record_arr = array();
$record_arr['time_interval'] = $time_id;
$record_arr['date_start'] = $start_datetime->format( 'Y-m-d H:i:s' );
$record_arr['date_end'] = $interval_end;
$data->intervals[] = array_merge( $record_arr, $totals_arr );
}
$start_datetime = $next_start;
}
return $data;
}
/**
* Converts input datetime parameters to local timezone. If there are no inputs from the user in query_args,
* uses default from $defaults.
*
* @param array $query_args Array of query arguments.
* @param array $defaults Array of default values.
*/
protected function normalize_timezones( &$query_args, $defaults ) {
$local_tz = new \DateTimeZone( wc_timezone_string() );
foreach ( array( 'before', 'after' ) as $query_arg_key ) {
if ( isset( $query_args[ $query_arg_key ] ) && is_string( $query_args[ $query_arg_key ] ) ) {
// Assume that unspecified timezone is a local timezone.
$datetime = new \DateTime( $query_args[ $query_arg_key ], $local_tz );
// In case timezone was forced by using +HH:MM, convert to local timezone.
$datetime->setTimezone( $local_tz );
$query_args[ $query_arg_key ] = $datetime;
} elseif ( isset( $query_args[ $query_arg_key ] ) && is_a( $query_args[ $query_arg_key ], 'DateTime' ) ) {
// In case timezone is in other timezone, convert to local timezone.
$query_args[ $query_arg_key ]->setTimezone( $local_tz );
} else {
$query_args[ $query_arg_key ] = isset( $defaults[ $query_arg_key ] ) ? $defaults[ $query_arg_key ] : null;
}
}
}
/**
* Removes extra records from intervals so that only requested number of records get returned.
*
* @param stdClass $data Data from whose intervals the records get removed.
* @param int $page_no Offset requested by the user.
* @param int $items_per_page Number of records requested by the user.
* @param int $db_interval_count Database interval count.
* @param int $expected_interval_count Expected interval count on the output.
* @param string $order_by Order by field.
* @param string $order ASC or DESC.
*/
protected function remove_extra_records( &$data, $page_no, $items_per_page, $db_interval_count, $expected_interval_count, $order_by, $order ) {
if ( 'date' === strtolower( $order_by ) ) {
$offset = 0;
} else {
if ( 'asc' === strtolower( $order ) ) {
$offset = ( $page_no - 1 ) * $items_per_page;
} else {
$offset = ( $page_no - 1 ) * $items_per_page - $db_interval_count;
}
$offset = $offset < 0 ? 0 : $offset;
}
$count = $expected_interval_count - ( $page_no - 1 ) * $items_per_page;
if ( $count < 0 ) {
$count = 0;
} elseif ( $count > $items_per_page ) {
$count = $items_per_page;
}
$data->intervals = array_slice( $data->intervals, $offset, $count );
}
/**
* Returns expected number of items on the page in case of date ordering.
*
* @param int $expected_interval_count Expected number of intervals in total.
* @param int $items_per_page Number of items per page.
* @param int $page_no Page number.
*
* @return float|int
*/
protected function expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no ) {
$total_pages = (int) ceil( $expected_interval_count / $items_per_page );
if ( $page_no < $total_pages ) {
return $items_per_page;
} elseif ( $page_no === $total_pages ) {
return $expected_interval_count - ( $page_no - 1 ) * $items_per_page;
} else {
return 0;
}
}
/**
* Returns true if there are any intervals that need to be filled in the response.
*
* @param int $expected_interval_count Expected number of intervals in total.
* @param int $db_records Total number of records for given period in the database.
* @param int $items_per_page Number of items per page.
* @param int $page_no Page number.
* @param string $order asc or desc.
* @param string $order_by Column by which the result will be sorted.
* @param int $intervals_count Number of records for given (possibly shortened) time interval.
*
* @return bool
*/
protected function intervals_missing( $expected_interval_count, $db_records, $items_per_page, $page_no, $order, $order_by, $intervals_count ) {
if ( $expected_interval_count <= $db_records ) {
return false;
}
if ( 'date' === $order_by ) {
$expected_intervals_on_page = $this->expected_intervals_on_page( $expected_interval_count, $items_per_page, $page_no );
return $intervals_count < $expected_intervals_on_page;
}
if ( 'desc' === $order ) {
return $page_no > floor( $db_records / $items_per_page );
}
if ( 'asc' === $order ) {
return $page_no <= ceil( ( $expected_interval_count - $db_records ) / $items_per_page );
}
// Invalid ordering.
return false;
}
/**
* Updates the LIMIT query part for Intervals query of the report.
*
* If there are less records in the database than time intervals, then we need to remap offset in SQL query
* to fetch correct records.
*
* @param array $query_args Query arguments.
* @param int $db_interval_count Database interval count.
* @param int $expected_interval_count Expected interval count on the output.
* @param string $table_name Name of the db table relevant for the date constraint.
*/
protected function update_intervals_sql_params( &$query_args, $db_interval_count, $expected_interval_count, $table_name ) {
if ( $db_interval_count === $expected_interval_count ) {
return;
}
$params = $this->get_limit_params( $query_args );
$local_tz = new \DateTimeZone( wc_timezone_string() );
if ( 'date' === strtolower( $query_args['orderby'] ) ) {
// page X in request translates to slightly different dates in the db, in case some
// records are missing from the db.
$start_iteration = 0;
$end_iteration = 0;
if ( 'asc' === strtolower( $query_args['order'] ) ) {
// ORDER BY date ASC.
$new_start_date = $query_args['after'];
$intervals_to_skip = ( $query_args['page'] - 1 ) * $params['per_page'];
$latest_end_date = $query_args['before'];
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
if ( $new_start_date > $latest_end_date ) {
$new_start_date = $latest_end_date;
$start_iteration = 0;
break;
}
$new_start_date = TimeInterval::iterate( $new_start_date, $query_args['interval'] );
$start_iteration ++;
}
$new_end_date = clone $new_start_date;
for ( $i = 0; $i < $params['per_page']; $i++ ) {
if ( $new_end_date > $latest_end_date ) {
break;
}
$new_end_date = TimeInterval::iterate( $new_end_date, $query_args['interval'] );
$end_iteration ++;
}
if ( $new_end_date > $latest_end_date ) {
$new_end_date = $latest_end_date;
$end_iteration = 0;
}
if ( $end_iteration ) {
$new_end_date_timestamp = (int) $new_end_date->format( 'U' ) - 1;
$new_end_date->setTimestamp( $new_end_date_timestamp );
}
} else {
// ORDER BY date DESC.
$new_end_date = $query_args['before'];
$intervals_to_skip = ( $query_args['page'] - 1 ) * $params['per_page'];
$earliest_start_date = $query_args['after'];
for ( $i = 0; $i < $intervals_to_skip; $i++ ) {
if ( $new_end_date < $earliest_start_date ) {
$new_end_date = $earliest_start_date;
$end_iteration = 0;
break;
}
$new_end_date = TimeInterval::iterate( $new_end_date, $query_args['interval'], true );
$end_iteration ++;
}
$new_start_date = clone $new_end_date;
for ( $i = 0; $i < $params['per_page']; $i++ ) {
if ( $new_start_date < $earliest_start_date ) {
break;
}
$new_start_date = TimeInterval::iterate( $new_start_date, $query_args['interval'], true );
$start_iteration ++;
}
if ( $new_start_date < $earliest_start_date ) {
$new_start_date = $earliest_start_date;
$start_iteration = 0;
}
if ( $start_iteration ) {
// @todo Is this correct? should it only be added if iterate runs? other two iterate instances, too?
$new_start_date_timestamp = (int) $new_start_date->format( 'U' ) + 1;
$new_start_date->setTimestamp( $new_start_date_timestamp );
}
}
// @todo - Do this without modifying $query_args?
$query_args['adj_after'] = $new_start_date;
$query_args['adj_before'] = $new_end_date;
$adj_after = $new_start_date->format( TimeInterval::$sql_datetime_format );
$adj_before = $new_end_date->format( TimeInterval::$sql_datetime_format );
$this->interval_query->clear_sql_clause( array( 'where_time', 'limit' ) );
$this->interval_query->add_sql_clause( 'where_time', "AND {$table_name}.`{$this->date_column_name}` <= '$adj_before'" );
$this->interval_query->add_sql_clause( 'where_time', "AND {$table_name}.`{$this->date_column_name}` >= '$adj_after'" );
$this->clear_sql_clause( 'limit' );
$this->add_sql_clause( 'limit', 'LIMIT 0,' . $params['per_page'] );
} else {
if ( 'asc' === $query_args['order'] ) {
$offset = ( ( $query_args['page'] - 1 ) * $params['per_page'] ) - ( $expected_interval_count - $db_interval_count );
$offset = $offset < 0 ? 0 : $offset;
$count = $query_args['page'] * $params['per_page'] - ( $expected_interval_count - $db_interval_count );
if ( $count < 0 ) {
$count = 0;
} elseif ( $count > $params['per_page'] ) {
$count = $params['per_page'];
}
$this->clear_sql_clause( 'limit' );
$this->add_sql_clause( 'limit', 'LIMIT ' . $offset . ',' . $count );
}
// Otherwise no change in limit clause.
// @todo - Do this without modifying $query_args?
$query_args['adj_after'] = $query_args['after'];
$query_args['adj_before'] = $query_args['before'];
}
}
/**
* Casts strings returned from the database to appropriate data types for output.
*
* @param array $array Associative array of values extracted from the database.
* @return array|WP_Error
*/
protected function cast_numbers( $array ) {
$retyped_array = array();
$column_types = apply_filters( 'woocommerce_rest_reports_column_types', $this->column_types, $array );
foreach ( $array as $column_name => $value ) {
if ( is_array( $value ) ) {
$value = $this->cast_numbers( $value );
}
if ( isset( $column_types[ $column_name ] ) ) {
$retyped_array[ $column_name ] = $column_types[ $column_name ]( $value );
} else {
$retyped_array[ $column_name ] = $value;
}
}
return $retyped_array;
}
/**
* Returns a list of columns selected by the query_args formatted as a comma separated string.
*
* @param array $query_args User-supplied options.
* @return string
*/
protected function selected_columns( $query_args ) {
$selections = $this->report_columns;
if ( isset( $query_args['fields'] ) && is_array( $query_args['fields'] ) ) {
$keep = array();
foreach ( $query_args['fields'] as $field ) {
if ( isset( $selections[ $field ] ) ) {
$keep[ $field ] = $selections[ $field ];
}
}
$selections = implode( ', ', $keep );
} else {
$selections = implode( ', ', $selections );
}
return $selections;
}
/**
* Get the excluded order statuses used when calculating reports.
*
* @return array
*/
protected static function get_excluded_report_order_statuses() {
$excluded_statuses = \WC_Admin_Settings::get_option( 'woocommerce_excluded_report_order_statuses', array( 'pending', 'failed', 'cancelled' ) );
$excluded_statuses = array_merge( array( 'auto-draft', 'trash' ), array_map( 'esc_sql', $excluded_statuses ) );
return apply_filters( 'woocommerce_analytics_excluded_order_statuses', $excluded_statuses );
}
/**
* Maps order status provided by the user to the one used in the database.
*
* @param string $status Order status.
* @return string
*/
protected static function normalize_order_status( $status ) {
$status = trim( $status );
return 'wc-' . $status;
}
/**
* Normalizes order_by clause to match to SQL query.
*
* @param string $order_by Order by option requested by user.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return 'time_interval';
}
return $order_by;
}
/**
* Updates start and end dates for intervals so that they represent intervals' borders, not times when data in db were recorded.
*
* E.g. if there are db records for only Tuesday and Thursday this week, the actual week interval is [Mon, Sun], not [Tue, Thu].
*
* @param DateTime $start_datetime Start date.
* @param DateTime $end_datetime End date.
* @param string $time_interval Time interval, e.g. day, week, month.
* @param array $intervals Array of intervals extracted from SQL db.
*/
protected function update_interval_boundary_dates( $start_datetime, $end_datetime, $time_interval, &$intervals ) {
$local_tz = new \DateTimeZone( wc_timezone_string() );
foreach ( $intervals as $key => $interval ) {
$datetime = new \DateTime( $interval['datetime_anchor'], $local_tz );
$prev_start = TimeInterval::iterate( $datetime, $time_interval, true );
// @todo Not sure if the +1/-1 here are correct, especially as they are applied before the ?: below.
$prev_start_timestamp = (int) $prev_start->format( 'U' ) + 1;
$prev_start->setTimestamp( $prev_start_timestamp );
if ( $start_datetime ) {
$date_start = $prev_start < $start_datetime ? $start_datetime : $prev_start;
$intervals[ $key ]['date_start'] = $date_start->format( 'Y-m-d H:i:s' );
} else {
$intervals[ $key ]['date_start'] = $prev_start->format( 'Y-m-d H:i:s' );
}
$next_end = TimeInterval::iterate( $datetime, $time_interval );
$next_end_timestamp = (int) $next_end->format( 'U' ) - 1;
$next_end->setTimestamp( $next_end_timestamp );
if ( $end_datetime ) {
$date_end = $next_end > $end_datetime ? $end_datetime : $next_end;
$intervals[ $key ]['date_end'] = $date_end->format( 'Y-m-d H:i:s' );
} else {
$intervals[ $key ]['date_end'] = $next_end->format( 'Y-m-d H:i:s' );
}
$intervals[ $key ]['interval'] = $time_interval;
}
}
/**
* Change structure of intervals to form a correct response.
*
* Also converts local datetimes to GMT and adds them to the intervals.
*
* @param array $intervals Time interval, e.g. day, week, month.
*/
protected function create_interval_subtotals( &$intervals ) {
foreach ( $intervals as $key => $interval ) {
$start_gmt = TimeInterval::convert_local_datetime_to_gmt( $interval['date_start'] );
$end_gmt = TimeInterval::convert_local_datetime_to_gmt( $interval['date_end'] );
// Move intervals result to subtotals object.
$intervals[ $key ] = array(
'interval' => $interval['time_interval'],
'date_start' => $interval['date_start'],
'date_start_gmt' => $start_gmt->format( TimeInterval::$sql_datetime_format ),
'date_end' => $interval['date_end'],
'date_end_gmt' => $end_gmt->format( TimeInterval::$sql_datetime_format ),
);
unset( $interval['interval'] );
unset( $interval['date_start'] );
unset( $interval['date_end'] );
unset( $interval['datetime_anchor'] );
unset( $interval['time_interval'] );
$intervals[ $key ]['subtotals'] = (object) $this->cast_numbers( $interval );
}
}
/**
* Fills WHERE clause of SQL request with date-related constraints.
*
* @param array $query_args Parameters supplied by the user.
* @param string $table_name Name of the db table relevant for the date constraint.
*/
protected function add_time_period_sql_params( $query_args, $table_name ) {
$this->clear_sql_clause( array( 'from', 'where_time', 'where' ) );
if ( isset( $this->subquery ) ) {
$this->subquery->clear_sql_clause( 'where_time' );
}
if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) {
if ( is_a( $query_args['before'], 'WC_DateTime' ) ) {
$datetime_str = $query_args['before']->date( TimeInterval::$sql_datetime_format );
} else {
$datetime_str = $query_args['before']->format( TimeInterval::$sql_datetime_format );
}
if ( isset( $this->subquery ) ) {
$this->subquery->add_sql_clause( 'where_time', "AND {$table_name}.`{$this->date_column_name}` <= '$datetime_str'" );
} else {
$this->add_sql_clause( 'where_time', "AND {$table_name}.`{$this->date_column_name}` <= '$datetime_str'" );
}
}
if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) {
if ( is_a( $query_args['after'], 'WC_DateTime' ) ) {
$datetime_str = $query_args['after']->date( TimeInterval::$sql_datetime_format );
} else {
$datetime_str = $query_args['after']->format( TimeInterval::$sql_datetime_format );
}
if ( isset( $this->subquery ) ) {
$this->subquery->add_sql_clause( 'where_time', "AND {$table_name}.`{$this->date_column_name}` >= '$datetime_str'" );
} else {
$this->add_sql_clause( 'where_time', "AND {$table_name}.`{$this->date_column_name}` >= '$datetime_str'" );
}
}
}
/**
* Fills LIMIT clause of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_limit_sql_params( $query_args ) {
global $wpdb;
$params = $this->get_limit_params( $query_args );
$this->clear_sql_clause( 'limit' );
$this->add_sql_clause( 'limit', $wpdb->prepare( 'LIMIT %d, %d', $params['offset'], $params['per_page'] ) );
return $params;
}
/**
* Fills LIMIT parameters of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_limit_params( $query_args = array() ) {
if ( isset( $query_args['per_page'] ) && is_numeric( $query_args['per_page'] ) ) {
$this->limit_parameters['per_page'] = (int) $query_args['per_page'];
} else {
$this->limit_parameters['per_page'] = get_option( 'posts_per_page' );
}
$this->limit_parameters['offset'] = 0;
if ( isset( $query_args['page'] ) ) {
$this->limit_parameters['offset'] = ( (int) $query_args['page'] - 1 ) * $this->limit_parameters['per_page'];
}
return $this->limit_parameters;
}
/**
* Generates a virtual table given a list of IDs.
*
* @param array $ids Array of IDs.
* @param array $id_field Name of the ID field.
* @param array $other_values Other values that must be contained in the virtual table.
* @return array
*/
protected function get_ids_table( $ids, $id_field, $other_values = array() ) {
global $wpdb;
$selects = array();
foreach ( $ids as $id ) {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$new_select = $wpdb->prepare( "SELECT %s AS {$id_field}", $id );
foreach ( $other_values as $key => $value ) {
$new_select .= $wpdb->prepare( ", %s AS {$key}", $value );
}
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
array_push( $selects, $new_select );
}
return join( ' UNION ', $selects );
}
/**
* Returns a comma separated list of the fields in the `query_args`, if there aren't, returns `report_columns` keys.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_fields( $query_args ) {
if ( isset( $query_args['fields'] ) && is_array( $query_args['fields'] ) ) {
return $query_args['fields'];
}
return array_keys( $this->report_columns );
}
/**
* Returns a comma separated list of the field names prepared to be used for a selection after a join with `default_results`.
*
* @param array $fields Array of fields name.
* @param array $default_results_fields Fields to load from `default_results` table.
* @param array $outer_selections Array of fields that are not selected in the inner query.
* @return string
*/
protected function format_join_selections( $fields, $default_results_fields, $outer_selections = array() ) {
foreach ( $fields as $i => $field ) {
foreach ( $default_results_fields as $default_results_field ) {
if ( $field === $default_results_field ) {
$field = esc_sql( $field );
$fields[ $i ] = "default_results.{$field} AS {$field}";
}
}
if ( in_array( $field, $outer_selections, true ) && array_key_exists( $field, $this->report_columns ) ) {
$fields[ $i ] = $this->report_columns[ $field ];
}
}
return implode( ', ', $fields );
}
/**
* Fills ORDER BY clause of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
*/
protected function add_order_by_sql_params( $query_args ) {
if ( isset( $query_args['orderby'] ) ) {
$order_by_clause = $this->normalize_order_by( esc_sql( $query_args['orderby'] ) );
} else {
$order_by_clause = '';
}
$this->clear_sql_clause( 'order_by' );
$this->add_sql_clause( 'order_by', $order_by_clause );
$this->add_orderby_order_clause( $query_args, $this );
}
/**
* Fills FROM and WHERE clauses of SQL request for 'Intervals' section of data response based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @param string $table_name Name of the db table relevant for the date constraint.
*/
protected function add_intervals_sql_params( $query_args, $table_name ) {
$this->clear_sql_clause( array( 'from', 'where_time', 'where' ) );
$this->add_time_period_sql_params( $query_args, $table_name );
if ( isset( $query_args['interval'] ) && '' !== $query_args['interval'] ) {
$interval = $query_args['interval'];
$this->clear_sql_clause( 'select' );
$this->add_sql_clause( 'select', TimeInterval::db_datetime_format( $interval, $table_name, $this->date_column_name ) );
}
}
/**
* Get join and where clauses for refunds based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_refund_subquery( $query_args ) {
global $wpdb;
$table_name = $wpdb->prefix . 'wc_order_stats';
$sql_query = array(
'where_clause' => '',
'from_clause' => '',
);
if ( ! isset( $query_args['refunds'] ) ) {
return $sql_query;
}
if ( 'all' === $query_args['refunds'] ) {
$sql_query['where_clause'] .= 'parent_id != 0';
}
if ( 'none' === $query_args['refunds'] ) {
$sql_query['where_clause'] .= 'parent_id = 0';
}
if ( 'full' === $query_args['refunds'] || 'partial' === $query_args['refunds'] ) {
$operator = 'full' === $query_args['refunds'] ? '=' : '!=';
$sql_query['from_clause'] .= " JOIN {$table_name} parent_order_stats ON {$table_name}.parent_id = parent_order_stats.order_id";
$sql_query['where_clause'] .= "parent_order_stats.status {$operator} '{$this->normalize_order_status( 'refunded' )}'";
}
return $sql_query;
}
/**
* Returns an array of products belonging to given categories.
*
* @param array $categories List of categories IDs.
* @return array|stdClass
*/
protected function get_products_by_cat_ids( $categories ) {
$terms = get_terms(
array(
'taxonomy' => 'product_cat',
'include' => $categories,
)
);
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return array();
}
$args = array(
'category' => wc_list_pluck( $terms, 'slug' ),
'limit' => -1,
'return' => 'ids',
);
return wc_get_products( $args );
}
/**
* Get WHERE filter by object ids subquery.
*
* @param string $select_table Select table name.
* @param string $select_field Select table object ID field name.
* @param string $filter_table Lookup table name.
* @param string $filter_field Lookup table object ID field name.
* @param string $compare Comparison string (IN|NOT IN).
* @param string $id_list Comma separated ID list.
*
* @return string
*/
protected function get_object_where_filter( $select_table, $select_field, $filter_table, $filter_field, $compare, $id_list ) {
global $wpdb;
if ( empty( $id_list ) ) {
return '';
}
$lookup_name = isset( $wpdb->$filter_table ) ? $wpdb->$filter_table : $wpdb->prefix . $filter_table;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return " {$select_table}.{$select_field} {$compare} (
SELECT
DISTINCT {$filter_table}.{$select_field}
FROM
{$filter_table}
WHERE
{$filter_table}.{$filter_field} IN ({$id_list})
)";
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
/**
* Returns an array of ids of allowed products, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_included_products_array( $query_args ) {
$included_products = array();
$operator = $this->get_match_operator( $query_args );
if ( isset( $query_args['category_includes'] ) && is_array( $query_args['category_includes'] ) && count( $query_args['category_includes'] ) > 0 ) {
$included_products = $this->get_products_by_cat_ids( $query_args['category_includes'] );
// If no products were found in the specified categories, we will force an empty set
// by matching a product ID of -1, unless the filters are OR/any and products are specified.
if ( empty( $included_products ) ) {
$included_products = array( '-1' );
}
}
if ( isset( $query_args['product_includes'] ) && is_array( $query_args['product_includes'] ) && count( $query_args['product_includes'] ) > 0 ) {
if ( count( $included_products ) > 0 ) {
if ( 'AND' === $operator ) {
// AND results in an intersection between products from selected categories and manually included products.
$included_products = array_intersect( $included_products, $query_args['product_includes'] );
} elseif ( 'OR' === $operator ) {
// OR results in a union of products from selected categories and manually included products.
$included_products = array_merge( $included_products, $query_args['product_includes'] );
}
} else {
$included_products = $query_args['product_includes'];
}
}
return $included_products;
}
/**
* Returns comma separated ids of allowed products, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_products( $query_args ) {
$included_products = $this->get_included_products_array( $query_args );
return implode( ',', $included_products );
}
/**
* Returns comma separated ids of allowed variations, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_variations( $query_args ) {
return $this->get_filtered_ids( $query_args, 'variation_includes' );
}
/**
* Returns comma separated ids of excluded variations, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_excluded_variations( $query_args ) {
return $this->get_filtered_ids( $query_args, 'variation_excludes' );
}
/**
* Returns an array of ids of disallowed products, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_excluded_products_array( $query_args ) {
$excluded_products = array();
$operator = $this->get_match_operator( $query_args );
if ( isset( $query_args['category_excludes'] ) && is_array( $query_args['category_excludes'] ) && count( $query_args['category_excludes'] ) > 0 ) {
$excluded_products = $this->get_products_by_cat_ids( $query_args['category_excludes'] );
}
if ( isset( $query_args['product_excludes'] ) && is_array( $query_args['product_excludes'] ) && count( $query_args['product_excludes'] ) > 0 ) {
$excluded_products = array_merge( $excluded_products, $query_args['product_excludes'] );
}
return $excluded_products;
}
/**
* Returns comma separated ids of excluded products, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_excluded_products( $query_args ) {
$excluded_products = $this->get_excluded_products_array( $query_args );
return implode( ',', $excluded_products );
}
/**
* Returns comma separated ids of included categories, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_categories( $query_args ) {
return $this->get_filtered_ids( $query_args, 'category_includes' );
}
/**
* Returns comma separated ids of included coupons, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @param string $field Field name in the parameter list.
* @return string
*/
protected function get_included_coupons( $query_args, $field = 'coupon_includes' ) {
return $this->get_filtered_ids( $query_args, $field );
}
/**
* Returns comma separated ids of excluded coupons, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_excluded_coupons( $query_args ) {
return $this->get_filtered_ids( $query_args, 'coupon_excludes' );
}
/**
* Returns comma separated ids of included orders, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_orders( $query_args ) {
return $this->get_filtered_ids( $query_args, 'order_includes' );
}
/**
* Returns comma separated ids of excluded orders, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_excluded_orders( $query_args ) {
return $this->get_filtered_ids( $query_args, 'order_excludes' );
}
/**
* Returns comma separated ids of included users, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_users( $query_args ) {
return $this->get_filtered_ids( $query_args, 'user_includes' );
}
/**
* Returns comma separated ids of excluded users, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_excluded_users( $query_args ) {
return $this->get_filtered_ids( $query_args, 'user_excludes' );
}
/**
* Returns order status subquery to be used in WHERE SQL query, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @param string $operator AND or OR, based on match query argument.
* @return string
*/
protected function get_status_subquery( $query_args, $operator = 'AND' ) {
global $wpdb;
$subqueries = array();
$excluded_statuses = array();
if ( isset( $query_args['status_is'] ) && is_array( $query_args['status_is'] ) && count( $query_args['status_is'] ) > 0 ) {
$allowed_statuses = array_map( array( $this, 'normalize_order_status' ), esc_sql( $query_args['status_is'] ) );
if ( $allowed_statuses ) {
$subqueries[] = "{$wpdb->prefix}wc_order_stats.status IN ( '" . implode( "','", $allowed_statuses ) . "' )";
}
}
if ( isset( $query_args['status_is_not'] ) && is_array( $query_args['status_is_not'] ) && count( $query_args['status_is_not'] ) > 0 ) {
$excluded_statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['status_is_not'] );
}
if ( ( ! isset( $query_args['status_is'] ) || empty( $query_args['status_is'] ) )
&& ( ! isset( $query_args['status_is_not'] ) || empty( $query_args['status_is_not'] ) )
) {
$excluded_statuses = array_map( array( $this, 'normalize_order_status' ), $this->get_excluded_report_order_statuses() );
}
if ( $excluded_statuses ) {
$subqueries[] = "{$wpdb->prefix}wc_order_stats.status NOT IN ( '" . implode( "','", $excluded_statuses ) . "' )";
}
return implode( " $operator ", $subqueries );
}
/**
* Add order status SQL clauses if included in query.
*
* @param array $query_args Parameters supplied by the user.
* @param string $table_name Database table name.
* @param SqlQuery $sql_query Query object.
*/
protected function add_order_status_clause( $query_args, $table_name, &$sql_query ) {
global $wpdb;
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$sql_query->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id" );
$sql_query->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
}
}
/**
* Add order by SQL clause if included in query.
*
* @param array $query_args Parameters supplied by the user.
* @param SqlQuery $sql_query Query object.
* @return string Order by clause.
*/
protected function add_order_by_clause( $query_args, &$sql_query ) {
$order_by_clause = '';
$sql_query->clear_sql_clause( array( 'order_by' ) );
if ( isset( $query_args['orderby'] ) ) {
$order_by_clause = $this->normalize_order_by( esc_sql( $query_args['orderby'] ) );
$sql_query->add_sql_clause( 'order_by', $order_by_clause );
}
// Return ORDER BY clause to allow adding the sort field(s) to query via a JOIN.
return $order_by_clause;
}
/**
* Add order by order SQL clause.
*
* @param array $query_args Parameters supplied by the user.
* @param SqlQuery $sql_query Query object.
*/
protected function add_orderby_order_clause( $query_args, &$sql_query ) {
if ( isset( $query_args['order'] ) ) {
$sql_query->add_sql_clause( 'order_by', esc_sql( $query_args['order'] ) );
} else {
$sql_query->add_sql_clause( 'order_by', 'DESC' );
}
}
/**
* Returns customer subquery to be used in WHERE SQL query, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_customer_subquery( $query_args ) {
global $wpdb;
$customer_filter = '';
if ( isset( $query_args['customer_type'] ) ) {
if ( 'new' === strtolower( $query_args['customer_type'] ) ) {
$customer_filter = " {$wpdb->prefix}wc_order_stats.returning_customer = 0";
} elseif ( 'returning' === strtolower( $query_args['customer_type'] ) ) {
$customer_filter = " {$wpdb->prefix}wc_order_stats.returning_customer = 1";
}
}
return $customer_filter;
}
/**
* Returns product attribute subquery elements used in JOIN and WHERE clauses,
* based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_attribute_subqueries( $query_args ) {
global $wpdb;
$sql_clauses = array(
'join' => array(),
'where' => array(),
);
$match_operator = $this->get_match_operator( $query_args );
$post_meta_comparators = array(
'=' => 'attribute_is',
'!=' => 'attribute_is_not',
);
foreach ( $post_meta_comparators as $comparator => $arg ) {
if ( ! isset( $query_args[ $arg ] ) || ! is_array( $query_args[ $arg ] ) ) {
continue;
}
foreach ( $query_args[ $arg ] as $attribute_term ) {
// We expect tuples.
if ( ! is_array( $attribute_term ) || 2 !== count( $attribute_term ) ) {
continue;
}
// If the tuple is numeric, assume these are IDs.
if ( is_numeric( $attribute_term[0] ) && is_numeric( $attribute_term[1] ) ) {
$attribute_id = intval( $attribute_term[0] );
$term_id = intval( $attribute_term[1] );
// Invalid IDs.
if ( 0 === $attribute_id || 0 === $term_id ) {
continue;
}
// @todo: Use wc_get_attribute () instead ?
$attr_taxonomy = wc_attribute_taxonomy_name_by_id( $attribute_id );
// Invalid attribute ID.
if ( empty( $attr_taxonomy ) ) {
continue;
}
$attr_term = get_term_by( 'id', $term_id, $attr_taxonomy );
// Invalid term ID.
if ( false === $attr_term ) {
continue;
}
$meta_key = sanitize_title( $attr_taxonomy );
$meta_value = $attr_term->slug;
} else {
// Assume these are a custom attribute slug/value pair.
$meta_key = esc_sql( $attribute_term[0] );
$meta_value = esc_sql( $attribute_term[1] );
}
$join_alias = 'orderitemmeta1';
$table_to_join_on = "{$wpdb->prefix}wc_order_product_lookup";
if ( empty( $sql_clauses['join'] ) ) {
$sql_clauses['join'][] = "JOIN {$wpdb->prefix}woocommerce_order_items orderitems ON orderitems.order_id = {$table_to_join_on}.order_id";
}
// If we're matching all filters (AND), we'll need multiple JOINs on postmeta.
// If not, just one.
if ( 'AND' === $match_operator || 1 === count( $sql_clauses['join'] ) ) {
$join_idx = count( $sql_clauses['join'] );
$join_alias = 'orderitemmeta' . $join_idx;
$sql_clauses['join'][] = "JOIN {$wpdb->prefix}woocommerce_order_itemmeta as {$join_alias} ON {$join_alias}.order_item_id = {$table_to_join_on}.order_item_id";
}
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$sql_clauses['where'][] = $wpdb->prepare( "( {$join_alias}.meta_key = %s AND {$join_alias}.meta_value {$comparator} %s )", $meta_key, $meta_value );
}
}
// If we're matching multiple attributes and all filters (AND), make sure
// we're matching attributes on the same product.
$num_attribute_filters = count( $sql_clauses['join'] );
for ( $i = 2; $i < $num_attribute_filters; $i++ ) {
$join_alias = 'orderitemmeta' . $i;
$sql_clauses['join'][] = "AND orderitemmeta1.order_item_id = {$join_alias}.order_item_id";
}
return $sql_clauses;
}
/**
* Returns logic operator for WHERE subclause based on 'match' query argument.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_match_operator( $query_args ) {
$operator = 'AND';
if ( ! isset( $query_args['match'] ) ) {
return $operator;
}
if ( 'all' === strtolower( $query_args['match'] ) ) {
$operator = 'AND';
} elseif ( 'any' === strtolower( $query_args['match'] ) ) {
$operator = 'OR';
}
return $operator;
}
/**
* Returns filtered comma separated ids, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @param string $field Query field to filter.
* @param string $separator Field separator.
* @return string
*/
protected function get_filtered_ids( $query_args, $field, $separator = ',' ) {
global $wpdb;
$ids_str = '';
$ids = isset( $query_args[ $field ] ) && is_array( $query_args[ $field ] ) ? $query_args[ $field ] : array();
/**
* Filter the IDs before retrieving report data.
*
* Allows filtering of the objects included or excluded from reports.
*
* @param array $ids List of object Ids.
* @param array $query_args The original arguments for the request.
* @param string $field The object type.
* @param string $context The data store context.
*/
$ids = apply_filters( 'woocommerce_analytics_' . $field, $ids, $query_args, $field, $this->context );
if ( ! empty( $ids ) ) {
$placeholders = implode( $separator, array_fill( 0, count( $ids ), '%d' ) );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$ids_str = $wpdb->prepare( "{$placeholders}", $ids );
/* phpcs:enable */
}
return $ids_str;
}
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Variations/DataStore.php 0000644 00000042711 15155437604 0033355 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Variations\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Variations;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Variations\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_product_lookup';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'variations';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'date_start' => 'strval',
'date_end' => 'strval',
'product_id' => 'intval',
'variation_id' => 'intval',
'items_sold' => 'intval',
'net_revenue' => 'floatval',
'orders_count' => 'intval',
'name' => 'strval',
'price' => 'floatval',
'image' => 'strval',
'permalink' => 'strval',
'sku' => 'strval',
);
/**
* Extended product attributes to include in the data.
*
* @var array
*/
protected $extended_attributes = array(
'name',
'price',
'image',
'permalink',
'stock_status',
'stock_quantity',
'low_stock_amount',
'sku',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'variations';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'product_id' => 'product_id',
'variation_id' => 'variation_id',
'items_sold' => 'SUM(product_qty) as items_sold',
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
);
}
/**
* Fills FROM clause of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @param string $arg_name Target of the JOIN sql param.
*/
protected function add_from_sql_params( $query_args, $arg_name ) {
global $wpdb;
if ( 'sku' !== $query_args['orderby'] ) {
return;
}
$table_name = self::get_db_table_name();
$join = "LEFT JOIN {$wpdb->postmeta} AS postmeta ON {$table_name}.variation_id = postmeta.post_id AND postmeta.meta_key = '_sku'";
if ( 'inner' === $arg_name ) {
$this->subquery->add_sql_clause( 'join', $join );
} else {
$this->add_sql_clause( 'join', $join );
}
}
/**
* Generate a subquery for order_item_id based on the attribute filters.
*
* @param array $query_args Query arguments supplied by the user.
* @return string
*/
protected function get_order_item_by_attribute_subquery( $query_args ) {
$order_product_lookup_table = self::get_db_table_name();
$attribute_subqueries = $this->get_attribute_subqueries( $query_args );
if ( $attribute_subqueries['join'] && $attribute_subqueries['where'] ) {
// Perform a subquery for DISTINCT order items that match our attribute filters.
$attr_subquery = new SqlQuery( $this->context . '_attribute_subquery' );
$attr_subquery->add_sql_clause( 'select', "DISTINCT {$order_product_lookup_table}.order_item_id" );
$attr_subquery->add_sql_clause( 'from', $order_product_lookup_table );
if ( $this->should_exclude_simple_products( $query_args ) ) {
$attr_subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id != 0" );
}
foreach ( $attribute_subqueries['join'] as $attribute_join ) {
$attr_subquery->add_sql_clause( 'join', $attribute_join );
}
$operator = $this->get_match_operator( $query_args );
$attr_subquery->add_sql_clause( 'where', 'AND (' . implode( " {$operator} ", $attribute_subqueries['where'] ) . ')' );
return "AND {$order_product_lookup_table}.order_item_id IN ({$attr_subquery->get_query_statement()})";
}
return false;
}
/**
* Updates the database query with parameters used for Products report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$order_product_lookup_table = self::get_db_table_name();
$order_stats_lookup_table = $wpdb->prefix . 'wc_order_stats';
$order_item_meta_table = $wpdb->prefix . 'woocommerce_order_itemmeta';
$where_subquery = array();
$this->add_time_period_sql_params( $query_args, $order_product_lookup_table );
$this->get_limit_sql_params( $query_args );
$this->add_order_by_sql_params( $query_args );
$included_variations = $this->get_included_variations( $query_args );
if ( $included_variations > 0 ) {
$this->add_from_sql_params( $query_args, 'outer' );
} else {
$this->add_from_sql_params( $query_args, 'inner' );
}
$included_products = $this->get_included_products( $query_args );
if ( $included_products ) {
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.product_id IN ({$included_products})" );
}
$excluded_products = $this->get_excluded_products( $query_args );
if ( $excluded_products ) {
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.product_id NOT IN ({$excluded_products})" );
}
if ( $included_variations ) {
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id IN ({$included_variations})" );
} elseif ( ! $included_products ) {
if ( $this->should_exclude_simple_products( $query_args ) ) {
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id != 0" );
}
}
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$this->subquery->add_sql_clause( 'join', "JOIN {$order_stats_lookup_table} ON {$order_product_lookup_table}.order_id = {$order_stats_lookup_table}.order_id" );
$this->subquery->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
}
$attribute_order_items_subquery = $this->get_order_item_by_attribute_subquery( $query_args );
if ( $attribute_order_items_subquery ) {
// JOIN on product lookup if we haven't already.
if ( ! $order_status_filter ) {
$this->subquery->add_sql_clause( 'join', "JOIN {$order_product_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_product_lookup_table}.order_id" );
}
// Add subquery for matching attributes to WHERE.
$this->subquery->add_sql_clause( 'where', $attribute_order_items_subquery );
}
if ( 0 < count( $where_subquery ) ) {
$operator = $this->get_match_operator( $query_args );
$this->subquery->add_sql_clause( 'where', 'AND (' . implode( " {$operator} ", $where_subquery ) . ')' );
}
}
/**
* Maps ordering specified by the user to columns in the database/fields in the data.
*
* @param string $order_by Sorting criterion.
*
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return self::get_db_table_name() . '.date_created';
}
if ( 'sku' === $order_by ) {
return 'meta_value';
}
return $order_by;
}
/**
* Enriches the product data with attributes specified by the extended_attributes.
*
* @param array $products_data Product data.
* @param array $query_args Query parameters.
*/
protected function include_extended_info( &$products_data, $query_args ) {
foreach ( $products_data as $key => $product_data ) {
$extended_info = new \ArrayObject();
if ( $query_args['extended_info'] ) {
$extended_attributes = apply_filters( 'woocommerce_rest_reports_variations_extended_attributes', $this->extended_attributes, $product_data );
$parent_product = wc_get_product( $product_data['product_id'] );
$attributes = array();
// Base extended info off the parent variable product if the variation ID is 0.
// This is caused by simple products with prior sales being converted into variable products.
// See: https://github.com/woocommerce/woocommerce-admin/issues/2719.
$variation_id = (int) $product_data['variation_id'];
$variation_product = ( 0 === $variation_id ) ? $parent_product : wc_get_product( $variation_id );
// Fall back to the parent product if the variation can't be found.
$extended_attributes_product = is_a( $variation_product, 'WC_Product' ) ? $variation_product : $parent_product;
// If both product and variation is not found, set deleted to true.
if ( ! $extended_attributes_product ) {
$extended_info['deleted'] = true;
}
foreach ( $extended_attributes as $extended_attribute ) {
$function = 'get_' . $extended_attribute;
if ( is_callable( array( $extended_attributes_product, $function ) ) ) {
$value = $extended_attributes_product->{$function}();
$extended_info[ $extended_attribute ] = $value;
}
}
// If this is a variation, add its attributes.
// NOTE: We don't fall back to the parent product here because it will include all possible attribute options.
if (
0 < $variation_id &&
is_callable( array( $variation_product, 'get_variation_attributes' ) )
) {
$variation_attributes = $variation_product->get_variation_attributes();
foreach ( $variation_attributes as $attribute_name => $attribute ) {
$name = str_replace( 'attribute_', '', $attribute_name );
$option_term = get_term_by( 'slug', $attribute, $name );
$attributes[] = array(
'id' => wc_attribute_taxonomy_id_by_name( $name ),
'name' => str_replace( 'pa_', '', $name ),
'option' => $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute,
);
}
}
$extended_info['attributes'] = $attributes;
// If there is no set low_stock_amount, use the one in user settings.
if ( '' === $extended_info['low_stock_amount'] ) {
$extended_info['low_stock_amount'] = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
}
$extended_info = $this->cast_numbers( $extended_info );
}
$products_data[ $key ]['extended_info'] = $extended_info;
}
}
/**
* Returns if simple products should be excluded from the report.
*
* @internal
*
* @param array $query_args Query parameters.
*
* @return boolean
*/
protected function should_exclude_simple_products( array $query_args ) {
return apply_filters( 'experimental_woocommerce_analytics_variations_should_exclude_simple_products', true, $query_args );
}
/**
* Fill missing extended_info.name for the deleted products.
*
* @param array $products Product data.
*/
protected function fill_deleted_product_name( array &$products ) {
global $wpdb;
$product_variation_ids = [];
// Find products with missing extended_info.name.
foreach ( $products as $key => $product ) {
if ( ! isset( $product['extended_info']['name'] ) ) {
$product_variation_ids[ $key ] = [
'product_id' => $product['product_id'],
'variation_id' => $product['variation_id'],
];
}
}
if ( ! count( $product_variation_ids ) ) {
return;
}
$where_clauses = implode(
' or ',
array_map(
function( $ids ) {
return "(
product_lookup.product_id = {$ids['product_id']}
and
product_lookup.variation_id = {$ids['variation_id']}
)";
},
$product_variation_ids
)
);
$query = "
select
product_lookup.product_id,
product_lookup.variation_id,
order_items.order_item_name
from
{$wpdb->prefix}wc_order_product_lookup as product_lookup
left join {$wpdb->prefix}woocommerce_order_items as order_items
on product_lookup.order_item_id = order_items.order_item_id
where
{$where_clauses}
group by
product_lookup.product_id,
product_lookup.variation_id,
order_items.order_item_name
";
// phpcs:ignore
$results = $wpdb->get_results( $query );
$index = [];
foreach ( $results as $result ) {
$index[ $result->product_id . '_' . $result->variation_id ] = $result->order_item_name;
}
foreach ( $product_variation_ids as $product_key => $ids ) {
$product = $products[ $product_key ];
$index_key = $product['product_id'] . '_' . $product['variation_id'];
if ( isset( $index[ $index_key ] ) ) {
$products[ $product_key ]['extended_info']['name'] = $index[ $index_key ];
}
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
*
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'product_includes' => array(),
'variation_includes' => array(),
'extended_info' => false,
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$included_variations =
( isset( $query_args['variation_includes'] ) && is_array( $query_args['variation_includes'] ) )
? $query_args['variation_includes']
: array();
$params = $this->get_limit_params( $query_args );
$this->add_sql_query_params( $query_args );
if ( count( $included_variations ) > 0 ) {
$total_results = count( $included_variations );
$total_pages = (int) ceil( $total_results / $params['per_page'] );
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $selections );
if ( 'date' === $query_args['orderby'] ) {
$this->subquery->add_sql_clause( 'select', ", {$table_name}.date_created" );
}
$fields = $this->get_fields( $query_args );
$join_selections = $this->format_join_selections( $fields, array( 'variation_id' ) );
$ids_table = $this->get_ids_table( $included_variations, 'variation_id' );
$this->add_sql_clause( 'select', $join_selections );
$this->add_sql_clause( 'from', '(' );
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
$this->add_sql_clause( 'from', ") AS {$table_name}" );
$this->add_sql_clause(
'right_join',
"RIGHT JOIN ( {$ids_table} ) AS default_results
ON default_results.variation_id = {$table_name}.variation_id"
);
$variations_query = $this->get_query_statement();
} else {
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $selections );
/**
* Experimental: Filter the Variations SQL query allowing extensions to add additional SQL clauses.
*
* @since 7.4.0
* @param array $query_args Query parameters.
* @param SqlQuery $subquery Variations query class.
*/
apply_filters( 'experimental_woocommerce_analytics_variations_additional_clauses', $query_args, $this->subquery );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$db_records_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM (
{$this->subquery->get_query_statement()}
) AS tt"
);
/* phpcs:enable */
$total_results = $db_records_count;
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$variations_query = $this->subquery->get_query_statement();
}
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
$product_data = $wpdb->get_results(
$variations_query,
ARRAY_A
);
/* phpcs:enable */
if ( null === $product_data ) {
return $data;
}
$this->include_extended_info( $product_data, $query_args );
if ( $query_args['extended_info'] ) {
$this->fill_deleted_product_name( $product_data );
}
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
$data = (object) array(
'data' => $product_data,
'total' => $total_results,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'select', 'product_id' );
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
$this->subquery->add_sql_clause( 'group_by', 'product_id, variation_id' );
}
}
uyarreklam.com.tr/httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php 0000644 00000025746 15155552536 0032335 0 ustar 00 var/www/vhosts <?php
/**
* API\Reports\Taxes\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Taxes;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
/**
* API\Reports\Taxes\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_tax_lookup';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'taxes';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'tax_rate_id' => 'intval',
'name' => 'strval',
'tax_rate' => 'floatval',
'country' => 'strval',
'state' => 'strval',
'priority' => 'intval',
'total_tax' => 'floatval',
'order_tax' => 'floatval',
'shipping_tax' => 'floatval',
'orders_count' => 'intval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'taxes';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'tax_rate_id' => "{$table_name}.tax_rate_id",
'name' => 'tax_rate_name as name',
'tax_rate' => 'tax_rate',
'country' => 'tax_rate_country as country',
'state' => 'tax_rate_state as state',
'priority' => 'tax_rate_priority as priority',
'total_tax' => 'SUM(total_tax) as total_tax',
'order_tax' => 'SUM(order_tax) as order_tax',
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
'orders_count' => "COUNT( DISTINCT ( CASE WHEN total_tax >= 0 THEN {$table_name}.order_id END ) ) as orders_count",
);
}
/**
* Set up all the hooks for maintaining and populating table data.
*/
public static function init() {
add_action( 'woocommerce_analytics_delete_order_stats', array( __CLASS__, 'sync_on_order_delete' ), 15 );
}
/**
* Fills FROM clause of SQL request based on user supplied parameters.
*
* @param array $query_args Query arguments supplied by the user.
* @param string $order_status_filter Order status subquery.
*/
protected function add_from_sql_params( $query_args, $order_status_filter ) {
global $wpdb;
$table_name = self::get_db_table_name();
if ( $order_status_filter ) {
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id" );
}
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
$this->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_tax_rates ON default_results.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id" );
} else {
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_tax_rates ON {$table_name}.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id" );
}
}
/**
* Updates the database query with parameters used for Taxes report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$order_tax_lookup_table = self::get_db_table_name();
$this->add_time_period_sql_params( $query_args, $order_tax_lookup_table );
$this->get_limit_sql_params( $query_args );
$this->add_order_by_sql_params( $query_args );
$order_status_filter = $this->get_status_subquery( $query_args );
$this->add_from_sql_params( $query_args, $order_status_filter );
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
$allowed_taxes = self::get_filtered_ids( $query_args, 'taxes' );
$this->subquery->add_sql_clause( 'where', "AND {$order_tax_lookup_table}.tax_rate_id IN ({$allowed_taxes})" );
}
if ( $order_status_filter ) {
$this->subquery->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'tax_rate_id',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'taxes' => array(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$this->add_sql_query_params( $query_args );
$params = $this->get_limit_params( $query_args );
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
$total_results = count( $query_args['taxes'] );
$total_pages = (int) ceil( $total_results / $params['per_page'] );
$inner_selections = array( 'tax_rate_id', 'total_tax', 'order_tax', 'shipping_tax', 'orders_count' );
$outer_selections = array( 'name', 'tax_rate', 'country', 'state', 'priority' );
$selections = $this->selected_columns( array( 'fields' => $inner_selections ) );
$fields = $this->get_fields( $query_args );
$join_selections = $this->format_join_selections( $fields, array( 'tax_rate_id' ), $outer_selections );
$ids_table = $this->get_ids_table( $query_args['taxes'], 'tax_rate_id' );
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $this->selected_columns( array( 'fields' => $inner_selections ) ) );
$this->add_sql_clause( 'select', $join_selections );
$this->add_sql_clause( 'from', '(' );
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
$this->add_sql_clause( 'from', ") AS {$table_name}" );
$this->add_sql_clause(
'right_join',
"RIGHT JOIN ( {$ids_table} ) AS default_results
ON default_results.tax_rate_id = {$table_name}.tax_rate_id"
);
$taxes_query = $this->get_query_statement();
} else {
$db_records_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM (
{$this->subquery->get_query_statement()}
) AS tt"
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
$total_results = $db_records_count;
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$taxes_query = $this->subquery->get_query_statement();
}
$tax_data = $wpdb->get_results(
$taxes_query,
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $tax_data ) {
return $data;
}
$tax_data = array_map( array( $this, 'cast_numbers' ), $tax_data );
$data = (object) array(
'data' => $tax_data,
'total' => $total_results,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Maps ordering specified by the user to columns in the database/fields in the data.
*
* @param string $order_by Sorting criterion.
* @return string
*/
protected function normalize_order_by( $order_by ) {
global $wpdb;
if ( 'tax_code' === $order_by ) {
return 'CONCAT_WS( "-", NULLIF(tax_rate_country, ""), NULLIF(tax_rate_state, ""), NULLIF(tax_rate_name, ""), NULLIF(tax_rate_priority, "") )';
} elseif ( 'rate' === $order_by ) {
return "CAST({$wpdb->prefix}woocommerce_tax_rates.tax_rate as DECIMAL(7,4))";
}
return $order_by;
}
/**
* Create or update an entry in the wc_order_tax_lookup table for an order.
*
* @param int $order_id Order ID.
* @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success.
*/
public static function sync_order_taxes( $order_id ) {
global $wpdb;
$order = wc_get_order( $order_id );
if ( ! $order ) {
return -1;
}
$tax_items = $order->get_items( 'tax' );
$num_updated = 0;
foreach ( $tax_items as $tax_item ) {
$result = $wpdb->replace(
self::get_db_table_name(),
array(
'order_id' => $order->get_id(),
'date_created' => $order->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ),
'tax_rate_id' => $tax_item->get_rate_id(),
'shipping_tax' => $tax_item->get_shipping_tax_total(),
'order_tax' => $tax_item->get_tax_total(),
'total_tax' => (float) $tax_item->get_tax_total() + (float) $tax_item->get_shipping_tax_total(),
),
array(
'%d',
'%s',
'%d',
'%f',
'%f',
'%f',
)
);
/**
* Fires when tax's reports are updated.
*
* @param int $tax_rate_id Tax Rate ID.
* @param int $order_id Order ID.
*/
do_action( 'woocommerce_analytics_update_tax', $tax_item->get_rate_id(), $order->get_id() );
// Sum the rows affected. Using REPLACE can affect 2 rows if the row already exists.
$num_updated += 2 === intval( $result ) ? 1 : intval( $result );
}
return ( count( $tax_items ) === $num_updated );
}
/**
* Clean taxes data when an order is deleted.
*
* @param int $order_id Order ID.
*/
public static function sync_on_order_delete( $order_id ) {
global $wpdb;
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
/**
* Fires when tax's reports are removed from database.
*
* @param int $tax_rate_id Tax Rate ID.
* @param int $order_id Order ID.
*/
do_action( 'woocommerce_analytics_delete_tax', 0, $order_id );
ReportsCache::invalidate();
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'select', self::get_db_table_name() . '.tax_rate_id' );
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
$this->subquery->add_sql_clause( 'group_by', self::get_db_table_name() . '.tax_rate_id' );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Coupons/DataStore.php 0000644 00000036734 15155600362 0032665 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Coupons\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Coupons;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
/**
* API\Reports\Coupons\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_coupon_lookup';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'coupons';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'coupon_id' => 'intval',
'amount' => 'floatval',
'orders_count' => 'intval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'coupons';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'coupon_id' => 'coupon_id',
'amount' => 'SUM(discount_amount) as amount',
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
);
}
/**
* Set up all the hooks for maintaining and populating table data.
*/
public static function init() {
add_action( 'woocommerce_analytics_delete_order_stats', array( __CLASS__, 'sync_on_order_delete' ), 5 );
}
/**
* Returns an array of ids of included coupons, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return array
*/
protected function get_included_coupons_array( $query_args ) {
if ( isset( $query_args['coupons'] ) && is_array( $query_args['coupons'] ) && count( $query_args['coupons'] ) > 0 ) {
return $query_args['coupons'];
}
return array();
}
/**
* Updates the database query with parameters used for Products report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$order_coupon_lookup_table = self::get_db_table_name();
$this->add_time_period_sql_params( $query_args, $order_coupon_lookup_table );
$this->get_limit_sql_params( $query_args );
$included_coupons = $this->get_included_coupons( $query_args, 'coupons' );
if ( $included_coupons ) {
$this->subquery->add_sql_clause( 'where', "AND {$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})" );
$this->add_order_by_params( $query_args, 'outer', 'default_results.coupon_id' );
} else {
$this->add_order_by_params( $query_args, 'inner', "{$order_coupon_lookup_table}.coupon_id" );
}
$this->add_order_status_clause( $query_args, $order_coupon_lookup_table, $this->subquery );
}
/**
* Fills ORDER BY clause of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @param string $from_arg Target of the JOIN sql param.
* @param string $id_cell ID cell identifier, like `table_name.id_column_name`.
*/
protected function add_order_by_params( $query_args, $from_arg, $id_cell ) {
global $wpdb;
// Sanitize input: guarantee that the id cell in the join is quoted with backticks.
$id_cell_segments = explode( '.', str_replace( '`', '', $id_cell ) );
$id_cell_identifier = '`' . implode( '`.`', $id_cell_segments ) . '`';
$lookup_table = self::get_db_table_name();
$order_by_clause = $this->add_order_by_clause( $query_args, $this );
$join = "JOIN {$wpdb->posts} AS _coupons ON {$id_cell_identifier} = _coupons.ID";
$this->add_orderby_order_clause( $query_args, $this );
if ( 'inner' === $from_arg ) {
$this->subquery->clear_sql_clause( 'join' );
if ( false !== strpos( $order_by_clause, '_coupons' ) ) {
$this->subquery->add_sql_clause( 'join', $join );
}
} else {
$this->clear_sql_clause( 'join' );
if ( false !== strpos( $order_by_clause, '_coupons' ) ) {
$this->add_sql_clause( 'join', $join );
}
}
}
/**
* Maps ordering specified by the user to columns in the database/fields in the data.
*
* @param string $order_by Sorting criterion.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return 'time_interval';
}
if ( 'code' === $order_by ) {
return '_coupons.post_title';
}
return $order_by;
}
/**
* Enriches the coupon data with extra attributes.
*
* @param array $coupon_data Coupon data.
* @param array $query_args Query parameters.
*/
protected function include_extended_info( &$coupon_data, $query_args ) {
foreach ( $coupon_data as $idx => $coupon_datum ) {
$extended_info = new \ArrayObject();
if ( $query_args['extended_info'] ) {
$coupon_id = $coupon_datum['coupon_id'];
$coupon = new \WC_Coupon( $coupon_id );
if ( 0 === $coupon->get_id() ) {
// Deleted or otherwise invalid coupon.
$extended_info = array(
'code' => __( '(Deleted)', 'woocommerce' ),
'date_created' => '',
'date_created_gmt' => '',
'date_expires' => '',
'date_expires_gmt' => '',
'discount_type' => __( 'N/A', 'woocommerce' ),
);
} else {
$gmt_timzone = new \DateTimeZone( 'UTC' );
$date_expires = $coupon->get_date_expires();
if ( is_a( $date_expires, 'DateTime' ) ) {
$date_expires = $date_expires->format( TimeInterval::$iso_datetime_format );
$date_expires_gmt = new \DateTime( $date_expires );
$date_expires_gmt->setTimezone( $gmt_timzone );
$date_expires_gmt = $date_expires_gmt->format( TimeInterval::$iso_datetime_format );
} else {
$date_expires = '';
$date_expires_gmt = '';
}
$date_created = $coupon->get_date_created();
if ( is_a( $date_created, 'DateTime' ) ) {
$date_created = $date_created->format( TimeInterval::$iso_datetime_format );
$date_created_gmt = new \DateTime( $date_created );
$date_created_gmt->setTimezone( $gmt_timzone );
$date_created_gmt = $date_created_gmt->format( TimeInterval::$iso_datetime_format );
} else {
$date_created = '';
$date_created_gmt = '';
}
$extended_info = array(
'code' => $coupon->get_code(),
'date_created' => $date_created,
'date_created_gmt' => $date_created_gmt,
'date_expires' => $date_expires,
'date_expires_gmt' => $date_expires_gmt,
'discount_type' => $coupon->get_discount_type(),
);
}
}
$coupon_data[ $idx ]['extended_info'] = $extended_info;
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'coupon_id',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'coupons' => array(),
'extended_info' => false,
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$included_coupons = $this->get_included_coupons_array( $query_args );
$limit_params = $this->get_limit_params( $query_args );
$this->subquery->add_sql_clause( 'select', $selections );
$this->add_sql_query_params( $query_args );
if ( count( $included_coupons ) > 0 ) {
$total_results = count( $included_coupons );
$total_pages = (int) ceil( $total_results / $limit_params['per_page'] );
$fields = $this->get_fields( $query_args );
$ids_table = $this->get_ids_table( $included_coupons, 'coupon_id' );
$this->add_sql_clause( 'select', $this->format_join_selections( $fields, array( 'coupon_id' ) ) );
$this->add_sql_clause( 'from', '(' );
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
$this->add_sql_clause( 'from', ") AS {$table_name}" );
$this->add_sql_clause(
'right_join',
"RIGHT JOIN ( {$ids_table} ) AS default_results
ON default_results.coupon_id = {$table_name}.coupon_id"
);
$coupons_query = $this->get_query_statement();
} else {
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$coupons_query = $this->subquery->get_query_statement();
$this->subquery->clear_sql_clause( array( 'select', 'order_by', 'limit' ) );
$this->subquery->add_sql_clause( 'select', 'coupon_id' );
$coupon_subquery = "SELECT COUNT(*) FROM (
{$this->subquery->get_query_statement()}
) AS tt";
$db_records_count = (int) $wpdb->get_var(
$coupon_subquery // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
);
$total_results = $db_records_count;
$total_pages = (int) ceil( $db_records_count / $limit_params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
}
$coupon_data = $wpdb->get_results(
$coupons_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
ARRAY_A
);
if ( null === $coupon_data ) {
return $data;
}
$this->include_extended_info( $coupon_data, $query_args );
$coupon_data = array_map( array( $this, 'cast_numbers' ), $coupon_data );
$data = (object) array(
'data' => $coupon_data,
'total' => $total_results,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Get coupon ID for an order.
*
* Tries to get the ID from order item meta, then falls back to a query of published coupons.
*
* @param \WC_Order_Item_Coupon $coupon_item The coupon order item object.
* @return int Coupon ID on success, 0 on failure.
*/
public static function get_coupon_id( \WC_Order_Item_Coupon $coupon_item ) {
// First attempt to get coupon ID from order item data.
$coupon_data = $coupon_item->get_meta( 'coupon_data', true );
// Normal checkout orders should have this data.
// See: https://github.com/woocommerce/woocommerce/blob/3dc7df7af9f7ca0c0aa34ede74493e856f276abe/includes/abstracts/abstract-wc-order.php#L1206.
if ( isset( $coupon_data['id'] ) ) {
return $coupon_data['id'];
}
// Try to get the coupon ID using the code.
return wc_get_coupon_id_by_code( $coupon_item->get_code() );
}
/**
* Create or update an an entry in the wc_order_coupon_lookup table for an order.
*
* @since 3.5.0
* @param int $order_id Order ID.
* @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success.
*/
public static function sync_order_coupons( $order_id ) {
global $wpdb;
$order = wc_get_order( $order_id );
if ( ! $order ) {
return -1;
}
// Refunds don't affect coupon stats so return successfully if one is called here.
if ( 'shop_order_refund' === $order->get_type() ) {
return true;
}
$table_name = self::get_db_table_name();
$existing_items = $wpdb->get_col(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT coupon_id FROM {$table_name} WHERE order_id = %d",
$order_id
)
);
$existing_items = array_flip( $existing_items );
$coupon_items = $order->get_items( 'coupon' );
$coupon_items_count = count( $coupon_items );
$num_updated = 0;
$num_deleted = 0;
foreach ( $coupon_items as $coupon_item ) {
$coupon_id = self::get_coupon_id( $coupon_item );
unset( $existing_items[ $coupon_id ] );
if ( ! $coupon_id ) {
// Insert a unique, but obviously invalid ID for this deleted coupon.
$num_deleted++;
$coupon_id = -1 * $num_deleted;
}
$result = $wpdb->replace(
self::get_db_table_name(),
array(
'order_id' => $order_id,
'coupon_id' => $coupon_id,
'discount_amount' => $coupon_item->get_discount(),
'date_created' => $order->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ),
),
array(
'%d',
'%d',
'%f',
'%s',
)
);
/**
* Fires when coupon's reports are updated.
*
* @param int $coupon_id Coupon ID.
* @param int $order_id Order ID.
*/
do_action( 'woocommerce_analytics_update_coupon', $coupon_id, $order_id );
// Sum the rows affected. Using REPLACE can affect 2 rows if the row already exists.
$num_updated += 2 === intval( $result ) ? 1 : intval( $result );
}
if ( ! empty( $existing_items ) ) {
$existing_items = array_flip( $existing_items );
$format = array_fill( 0, count( $existing_items ), '%d' );
$format = implode( ',', $format );
array_unshift( $existing_items, $order_id );
$wpdb->query(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"DELETE FROM {$table_name} WHERE order_id = %d AND coupon_id in ({$format})",
$existing_items
)
);
}
return ( $coupon_items_count === $num_updated );
}
/**
* Clean coupons data when an order is deleted.
*
* @param int $order_id Order ID.
*/
public static function sync_on_order_delete( $order_id ) {
global $wpdb;
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
/**
* Fires when coupon's reports are removed from database.
*
* @param int $coupon_id Coupon ID.
* @param int $order_id Order ID.
*/
do_action( 'woocommerce_analytics_delete_coupon', 0, $order_id );
ReportsCache::invalidate();
}
/**
* Gets coupons based on the provided arguments.
*
* @todo Upon core merge, including this in core's `class-wc-coupon-data-store-cpt.php` might make more sense.
* @param array $args Array of args to filter the query by. Supports `include`.
* @return array Array of results.
*/
public function get_coupons( $args ) {
global $wpdb;
$query = "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_type='shop_coupon'";
$included_coupons = $this->get_included_coupons( $args, 'include' );
if ( ! empty( $included_coupons ) ) {
$query .= " AND ID IN ({$included_coupons})";
}
return $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
$this->subquery->add_sql_clause( 'group_by', 'coupon_id' );
}
}
uyarreklam.com.tr/httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php0000644 00000045535 15155631404 0032475 0 ustar 00 var/www/vhosts <?php
/**
* API\Reports\Orders\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Orders;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use Automattic\WooCommerce\Admin\API\Reports\Cache;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
/**
* API\Reports\Orders\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Dynamically sets the date column name based on configuration
*/
public function __construct() {
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
parent::__construct();
}
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_stats';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'orders';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'order_id' => 'intval',
'parent_id' => 'intval',
'date_created' => 'strval',
'date_created_gmt' => 'strval',
'status' => 'strval',
'customer_id' => 'intval',
'net_total' => 'floatval',
'total_sales' => 'floatval',
'num_items_sold' => 'intval',
'customer_type' => 'strval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'orders';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
// Avoid ambigious columns in SQL query.
$this->report_columns = array(
'order_id' => "DISTINCT {$table_name}.order_id",
'parent_id' => "{$table_name}.parent_id",
// Add 'date' field based on date type setting.
'date' => "{$table_name}.{$this->date_column_name} AS date",
'date_created' => "{$table_name}.date_created",
'date_created_gmt' => "{$table_name}.date_created_gmt",
'status' => "REPLACE({$table_name}.status, 'wc-', '') as status",
'customer_id' => "{$table_name}.customer_id",
'net_total' => "{$table_name}.net_total",
'total_sales' => "{$table_name}.total_sales",
'num_items_sold' => "{$table_name}.num_items_sold",
'customer_type' => "(CASE WHEN {$table_name}.returning_customer = 0 THEN 'new' ELSE 'returning' END) as customer_type",
);
}
/**
* Updates the database query with parameters used for orders report: coupons and products filters.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$order_stats_lookup_table = self::get_db_table_name();
$order_coupon_lookup_table = $wpdb->prefix . 'wc_order_coupon_lookup';
$order_product_lookup_table = $wpdb->prefix . 'wc_order_product_lookup';
$order_tax_lookup_table = $wpdb->prefix . 'wc_order_tax_lookup';
$operator = $this->get_match_operator( $query_args );
$where_subquery = array();
$have_joined_products_table = false;
$this->add_time_period_sql_params( $query_args, $order_stats_lookup_table );
$this->get_limit_sql_params( $query_args );
$this->add_order_by_sql_params( $query_args );
$status_subquery = $this->get_status_subquery( $query_args );
if ( $status_subquery ) {
if ( empty( $query_args['status_is'] ) && empty( $query_args['status_is_not'] ) ) {
$this->subquery->add_sql_clause( 'where', "AND {$status_subquery}" );
} else {
$where_subquery[] = $status_subquery;
}
}
$included_orders = $this->get_included_orders( $query_args );
if ( $included_orders ) {
$where_subquery[] = "{$order_stats_lookup_table}.order_id IN ({$included_orders})";
}
$excluded_orders = $this->get_excluded_orders( $query_args );
if ( $excluded_orders ) {
$where_subquery[] = "{$order_stats_lookup_table}.order_id NOT IN ({$excluded_orders})";
}
if ( $query_args['customer_type'] ) {
$returning_customer = 'returning' === $query_args['customer_type'] ? 1 : 0;
$where_subquery[] = "{$order_stats_lookup_table}.returning_customer = {$returning_customer}";
}
$refund_subquery = $this->get_refund_subquery( $query_args );
$this->subquery->add_sql_clause( 'from', $refund_subquery['from_clause'] );
if ( $refund_subquery['where_clause'] ) {
$where_subquery[] = $refund_subquery['where_clause'];
}
$included_coupons = $this->get_included_coupons( $query_args );
$excluded_coupons = $this->get_excluded_coupons( $query_args );
if ( $included_coupons || $excluded_coupons ) {
$this->subquery->add_sql_clause( 'join', "LEFT JOIN {$order_coupon_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_coupon_lookup_table}.order_id" );
}
if ( $included_coupons ) {
$where_subquery[] = "{$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})";
}
if ( $excluded_coupons ) {
$where_subquery[] = "({$order_coupon_lookup_table}.coupon_id IS NULL OR {$order_coupon_lookup_table}.coupon_id NOT IN ({$excluded_coupons}))";
}
$included_products = $this->get_included_products( $query_args );
$excluded_products = $this->get_excluded_products( $query_args );
if ( $included_products || $excluded_products ) {
$this->subquery->add_sql_clause( 'join', "LEFT JOIN {$order_product_lookup_table} product_lookup" );
$this->subquery->add_sql_clause( 'join', "ON {$order_stats_lookup_table}.order_id = product_lookup.order_id" );
}
if ( $included_products ) {
$this->subquery->add_sql_clause( 'join', "AND product_lookup.product_id IN ({$included_products})" );
$where_subquery[] = 'product_lookup.order_id IS NOT NULL';
}
if ( $excluded_products ) {
$this->subquery->add_sql_clause( 'join', "AND product_lookup.product_id IN ({$excluded_products})" );
$where_subquery[] = 'product_lookup.order_id IS NULL';
}
$included_variations = $this->get_included_variations( $query_args );
$excluded_variations = $this->get_excluded_variations( $query_args );
if ( $included_variations || $excluded_variations ) {
$this->subquery->add_sql_clause( 'join', "LEFT JOIN {$order_product_lookup_table} variation_lookup" );
$this->subquery->add_sql_clause( 'join', "ON {$order_stats_lookup_table}.order_id = variation_lookup.order_id" );
}
if ( $included_variations ) {
$this->subquery->add_sql_clause( 'join', "AND variation_lookup.variation_id IN ({$included_variations})" );
$where_subquery[] = 'variation_lookup.order_id IS NOT NULL';
}
if ( $excluded_variations ) {
$this->subquery->add_sql_clause( 'join', "AND variation_lookup.variation_id IN ({$excluded_variations})" );
$where_subquery[] = 'variation_lookup.order_id IS NULL';
}
$included_tax_rates = ! empty( $query_args['tax_rate_includes'] ) ? implode( ',', array_map( 'esc_sql', $query_args['tax_rate_includes'] ) ) : false;
$excluded_tax_rates = ! empty( $query_args['tax_rate_excludes'] ) ? implode( ',', array_map( 'esc_sql', $query_args['tax_rate_excludes'] ) ) : false;
if ( $included_tax_rates || $excluded_tax_rates ) {
$this->subquery->add_sql_clause( 'join', "LEFT JOIN {$order_tax_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_tax_lookup_table}.order_id" );
}
if ( $included_tax_rates ) {
$where_subquery[] = "{$order_tax_lookup_table}.tax_rate_id IN ({$included_tax_rates})";
}
if ( $excluded_tax_rates ) {
$where_subquery[] = "{$order_tax_lookup_table}.tax_rate_id NOT IN ({$excluded_tax_rates}) OR {$order_tax_lookup_table}.tax_rate_id IS NULL";
}
$attribute_subqueries = $this->get_attribute_subqueries( $query_args );
if ( $attribute_subqueries['join'] && $attribute_subqueries['where'] ) {
$this->subquery->add_sql_clause( 'join', "JOIN {$order_product_lookup_table} ON {$order_stats_lookup_table}.order_id = {$order_product_lookup_table}.order_id" );
// Add JOINs for matching attributes.
foreach ( $attribute_subqueries['join'] as $attribute_join ) {
$this->subquery->add_sql_clause( 'join', $attribute_join );
}
// Add WHEREs for matching attributes.
$where_subquery = array_merge( $where_subquery, $attribute_subqueries['where'] );
}
if ( 0 < count( $where_subquery ) ) {
$this->subquery->add_sql_clause( 'where', 'AND (' . implode( " {$operator} ", $where_subquery ) . ')' );
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => $this->date_column_name,
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'product_includes' => array(),
'product_excludes' => array(),
'coupon_includes' => array(),
'coupon_excludes' => array(),
'tax_rate_includes' => array(),
'tax_rate_excludes' => array(),
'customer_type' => null,
'status_is' => array(),
'extended_info' => false,
'refunds' => null,
'order_includes' => array(),
'order_excludes' => array(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$params = $this->get_limit_params( $query_args );
$this->add_sql_query_params( $query_args );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$db_records_count = (int) $wpdb->get_var(
"SELECT COUNT( DISTINCT tt.order_id ) FROM (
{$this->subquery->get_query_statement()}
) AS tt"
);
/* phpcs:enable */
if ( 0 === $params['per_page'] ) {
$total_pages = 0;
} else {
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
}
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
$data = (object) array(
'data' => array(),
'total' => $db_records_count,
'pages' => 0,
'page_no' => 0,
);
return $data;
}
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $selections );
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
/* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared */
$orders_data = $wpdb->get_results(
$this->subquery->get_query_statement(),
ARRAY_A
);
/* phpcs:enable */
if ( null === $orders_data ) {
return $data;
}
if ( $query_args['extended_info'] ) {
$this->include_extended_info( $orders_data, $query_args );
}
$orders_data = array_map( array( $this, 'cast_numbers' ), $orders_data );
$data = (object) array(
'data' => $orders_data,
'total' => $db_records_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Normalizes order_by clause to match to SQL query.
*
* @param string $order_by Order by option requeste by user.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return $this->date_column_name;
}
return $order_by;
}
/**
* Enriches the order data.
*
* @param array $orders_data Orders data.
* @param array $query_args Query parameters.
*/
protected function include_extended_info( &$orders_data, $query_args ) {
$mapped_orders = $this->map_array_by_key( $orders_data, 'order_id' );
$related_orders = $this->get_orders_with_parent_id( $mapped_orders );
$order_ids = array_merge( array_keys( $mapped_orders ), array_keys( $related_orders ) );
$products = $this->get_products_by_order_ids( $order_ids );
$coupons = $this->get_coupons_by_order_ids( array_keys( $mapped_orders ) );
$customers = $this->get_customers_by_orders( $orders_data );
$mapped_customers = $this->map_array_by_key( $customers, 'customer_id' );
$mapped_data = array();
foreach ( $products as $product ) {
if ( ! isset( $mapped_data[ $product['order_id'] ] ) ) {
$mapped_data[ $product['order_id'] ]['products'] = array();
}
$is_variation = '0' !== $product['variation_id'];
$product_data = array(
'id' => $is_variation ? $product['variation_id'] : $product['product_id'],
'name' => $product['product_name'],
'quantity' => $product['product_quantity'],
);
if ( $is_variation ) {
$variation = wc_get_product( $product_data['id'] );
/**
* Used to determine the separator for products and their variations titles.
*
* @since 4.0.0
*/
$separator = apply_filters( 'woocommerce_product_variation_title_attributes_separator', ' - ', $variation );
if ( false === strpos( $product_data['name'], $separator ) ) {
$attributes = wc_get_formatted_variation( $variation, true, false );
$product_data['name'] .= $separator . $attributes;
}
}
$mapped_data[ $product['order_id'] ]['products'][] = $product_data;
// If this product's order has another related order, it will be added to our mapped_data.
if ( isset( $related_orders [ $product['order_id'] ] ) ) {
$mapped_data[ $related_orders[ $product['order_id'] ]['order_id'] ] ['products'] [] = $product_data;
}
}
foreach ( $coupons as $coupon ) {
if ( ! isset( $mapped_data[ $coupon['order_id'] ] ) ) {
$mapped_data[ $product['order_id'] ]['coupons'] = array();
}
$mapped_data[ $coupon['order_id'] ]['coupons'][] = array(
'id' => $coupon['coupon_id'],
'code' => wc_format_coupon_code( $coupon['coupon_code'] ),
);
}
foreach ( $orders_data as $key => $order_data ) {
$defaults = array(
'products' => array(),
'coupons' => array(),
'customer' => array(),
);
$orders_data[ $key ]['extended_info'] = isset( $mapped_data[ $order_data['order_id'] ] ) ? array_merge( $defaults, $mapped_data[ $order_data['order_id'] ] ) : $defaults;
if ( $order_data['customer_id'] && isset( $mapped_customers[ $order_data['customer_id'] ] ) ) {
$orders_data[ $key ]['extended_info']['customer'] = $mapped_customers[ $order_data['customer_id'] ];
}
}
}
/**
* Returns oreders that have a parent id
*
* @param array $orders Orders array.
* @return array
*/
protected function get_orders_with_parent_id( $orders ) {
$related_orders = array();
foreach ( $orders as $order ) {
if ( '0' !== $order['parent_id'] ) {
$related_orders[ $order['parent_id'] ] = $order;
}
}
return $related_orders;
}
/**
* Returns the same array index by a given key
*
* @param array $array Array to be looped over.
* @param string $key Key of values used for new array.
* @return array
*/
protected function map_array_by_key( $array, $key ) {
$mapped = array();
foreach ( $array as $item ) {
$mapped[ $item[ $key ] ] = $item;
}
return $mapped;
}
/**
* Get product IDs, names, and quantity from order IDs.
*
* @param array $order_ids Array of order IDs.
* @return array
*/
protected function get_products_by_order_ids( $order_ids ) {
global $wpdb;
$order_product_lookup_table = $wpdb->prefix . 'wc_order_product_lookup';
$included_order_ids = implode( ',', $order_ids );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$products = $wpdb->get_results(
"SELECT
order_id,
product_id,
variation_id,
post_title as product_name,
product_qty as product_quantity
FROM {$wpdb->posts}
JOIN
{$order_product_lookup_table}
ON {$wpdb->posts}.ID = (
CASE WHEN variation_id > 0
THEN variation_id
ELSE product_id
END
)
WHERE
order_id IN ({$included_order_ids})
",
ARRAY_A
);
/* phpcs:enable */
return $products;
}
/**
* Get customer data from Order data.
*
* @param array $orders Array of orders data.
* @return array
*/
protected function get_customers_by_orders( $orders ) {
global $wpdb;
$customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup';
$customer_ids = array();
foreach ( $orders as $order ) {
if ( $order['customer_id'] ) {
$customer_ids[] = intval( $order['customer_id'] );
}
}
if ( empty( $customer_ids ) ) {
return array();
}
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$customer_ids = implode( ',', $customer_ids );
$customers = $wpdb->get_results(
"SELECT * FROM {$customer_lookup_table} WHERE customer_id IN ({$customer_ids})",
ARRAY_A
);
/* phpcs:enable */
return $customers;
}
/**
* Get coupon information from order IDs.
*
* @param array $order_ids Array of order IDs.
* @return array
*/
protected function get_coupons_by_order_ids( $order_ids ) {
global $wpdb;
$order_coupon_lookup_table = $wpdb->prefix . 'wc_order_coupon_lookup';
$included_order_ids = implode( ',', $order_ids );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$coupons = $wpdb->get_results(
"SELECT order_id, coupon_id, post_title as coupon_code
FROM {$wpdb->posts}
JOIN {$order_coupon_lookup_table} ON {$order_coupon_lookup_table}.coupon_id = {$wpdb->posts}.ID
WHERE
order_id IN ({$included_order_ids})
",
ARRAY_A
);
/* phpcs:enable */
return $coupons;
}
/**
* Get all statuses that have been synced.
*
* @return array Unique order statuses.
*/
public static function get_all_statuses() {
global $wpdb;
$cache_key = 'orders-all-statuses';
$statuses = Cache::get( $cache_key );
if ( false === $statuses ) {
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$table_name = self::get_db_table_name();
$statuses = $wpdb->get_col(
"SELECT DISTINCT status FROM {$table_name}"
);
/* phpcs:enable */
Cache::set( $cache_key, $statuses );
}
return $statuses;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'select', self::get_db_table_name() . '.order_id' );
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Products/DataStore.php 0000644 00000042526 15155644704 0033046 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Products\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Products;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
/**
* API\Reports\Products\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_product_lookup';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'products';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'date_start' => 'strval',
'date_end' => 'strval',
'product_id' => 'intval',
'items_sold' => 'intval',
'net_revenue' => 'floatval',
'orders_count' => 'intval',
// Extended attributes.
'name' => 'strval',
'price' => 'floatval',
'image' => 'strval',
'permalink' => 'strval',
'stock_status' => 'strval',
'stock_quantity' => 'intval',
'low_stock_amount' => 'intval',
'category_ids' => 'array_values',
'variations' => 'array_values',
'sku' => 'strval',
);
/**
* Extended product attributes to include in the data.
*
* @var array
*/
protected $extended_attributes = array(
'name',
'price',
'image',
'permalink',
'stock_status',
'stock_quantity',
'manage_stock',
'low_stock_amount',
'category_ids',
'variations',
'sku',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'products';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'product_id' => 'product_id',
'items_sold' => 'SUM(product_qty) as items_sold',
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
'orders_count' => "COUNT( DISTINCT ( CASE WHEN product_gross_revenue >= 0 THEN {$table_name}.order_id END ) ) as orders_count",
);
}
/**
* Set up all the hooks for maintaining and populating table data.
*/
public static function init() {
add_action( 'woocommerce_analytics_delete_order_stats', array( __CLASS__, 'sync_on_order_delete' ), 10 );
}
/**
* Fills FROM clause of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @param string $arg_name Target of the JOIN sql param.
* @param string $id_cell ID cell identifier, like `table_name.id_column_name`.
*/
protected function add_from_sql_params( $query_args, $arg_name, $id_cell ) {
global $wpdb;
$type = 'join';
// Order by product name requires extra JOIN.
switch ( $query_args['orderby'] ) {
case 'product_name':
$join = " JOIN {$wpdb->posts} AS _products ON {$id_cell} = _products.ID";
break;
case 'sku':
$join = " LEFT JOIN {$wpdb->postmeta} AS postmeta ON {$id_cell} = postmeta.post_id AND postmeta.meta_key = '_sku'";
break;
case 'variations':
$type = 'left_join';
$join = "LEFT JOIN ( SELECT post_parent, COUNT(*) AS variations FROM {$wpdb->posts} WHERE post_type = 'product_variation' GROUP BY post_parent ) AS _variations ON {$id_cell} = _variations.post_parent";
break;
default:
$join = '';
break;
}
if ( $join ) {
if ( 'inner' === $arg_name ) {
$this->subquery->add_sql_clause( $type, $join );
} else {
$this->add_sql_clause( $type, $join );
}
}
}
/**
* Updates the database query with parameters used for Products report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$order_product_lookup_table = self::get_db_table_name();
$this->add_time_period_sql_params( $query_args, $order_product_lookup_table );
$this->get_limit_sql_params( $query_args );
$this->add_order_by_sql_params( $query_args );
$included_products = $this->get_included_products( $query_args );
if ( $included_products ) {
$this->add_from_sql_params( $query_args, 'outer', 'default_results.product_id' );
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.product_id IN ({$included_products})" );
} else {
$this->add_from_sql_params( $query_args, 'inner', "{$order_product_lookup_table}.product_id" );
}
$included_variations = $this->get_included_variations( $query_args );
if ( $included_variations ) {
$this->subquery->add_sql_clause( 'where', "AND {$order_product_lookup_table}.variation_id IN ({$included_variations})" );
}
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id" );
$this->subquery->add_sql_clause( 'where', "AND ( {$order_status_filter} )" );
}
}
/**
* Maps ordering specified by the user to columns in the database/fields in the data.
*
* @param string $order_by Sorting criterion.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return self::get_db_table_name() . '.date_created';
}
if ( 'product_name' === $order_by ) {
return 'post_title';
}
if ( 'sku' === $order_by ) {
return 'meta_value';
}
return $order_by;
}
/**
* Enriches the product data with attributes specified by the extended_attributes.
*
* @param array $products_data Product data.
* @param array $query_args Query parameters.
*/
protected function include_extended_info( &$products_data, $query_args ) {
global $wpdb;
$product_names = array();
foreach ( $products_data as $key => $product_data ) {
$extended_info = new \ArrayObject();
if ( $query_args['extended_info'] ) {
$product_id = $product_data['product_id'];
$product = wc_get_product( $product_id );
// Product was deleted.
if ( ! $product ) {
if ( ! isset( $product_names[ $product_id ] ) ) {
$product_names[ $product_id ] = $wpdb->get_var(
$wpdb->prepare(
"SELECT i.order_item_name
FROM {$wpdb->prefix}woocommerce_order_items i, {$wpdb->prefix}woocommerce_order_itemmeta m
WHERE i.order_item_id = m.order_item_id
AND m.meta_key = '_product_id'
AND m.meta_value = %s
ORDER BY i.order_item_id DESC
LIMIT 1",
$product_id
)
);
}
/* translators: %s is product name */
$products_data[ $key ]['extended_info']['name'] = $product_names[ $product_id ] ? sprintf( __( '%s (Deleted)', 'woocommerce' ), $product_names[ $product_id ] ) : __( '(Deleted)', 'woocommerce' );
continue;
}
$extended_attributes = apply_filters( 'woocommerce_rest_reports_products_extended_attributes', $this->extended_attributes, $product_data );
foreach ( $extended_attributes as $extended_attribute ) {
if ( 'variations' === $extended_attribute ) {
if ( ! $product->is_type( 'variable' ) ) {
continue;
}
$function = 'get_children';
} else {
$function = 'get_' . $extended_attribute;
}
if ( is_callable( array( $product, $function ) ) ) {
$value = $product->{$function}();
$extended_info[ $extended_attribute ] = $value;
}
}
// If there is no set low_stock_amount, use the one in user settings.
if ( '' === $extended_info['low_stock_amount'] ) {
$extended_info['low_stock_amount'] = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
}
$extended_info = $this->cast_numbers( $extended_info );
}
$products_data[ $key ]['extended_info'] = $extended_info;
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'category_includes' => array(),
'product_includes' => array(),
'extended_info' => false,
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$included_products = $this->get_included_products_array( $query_args );
$params = $this->get_limit_params( $query_args );
$this->add_sql_query_params( $query_args );
if ( count( $included_products ) > 0 ) {
$filtered_products = array_diff( $included_products, array( '-1' ) );
$total_results = count( $filtered_products );
$total_pages = (int) ceil( $total_results / $params['per_page'] );
if ( 'date' === $query_args['orderby'] ) {
$selections .= ", {$table_name}.date_created";
}
$fields = $this->get_fields( $query_args );
$join_selections = $this->format_join_selections( $fields, array( 'product_id' ) );
$ids_table = $this->get_ids_table( $included_products, 'product_id' );
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $selections );
$this->add_sql_clause( 'select', $join_selections );
$this->add_sql_clause( 'from', '(' );
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
$this->add_sql_clause( 'from', ") AS {$table_name}" );
$this->add_sql_clause(
'right_join',
"RIGHT JOIN ( {$ids_table} ) AS default_results
ON default_results.product_id = {$table_name}.product_id"
);
$this->add_sql_clause( 'where', 'AND default_results.product_id != -1' );
$products_query = $this->get_query_statement();
} else {
$count_query = "SELECT COUNT(*) FROM (
{$this->subquery->get_query_statement()}
) AS tt";
$db_records_count = (int) $wpdb->get_var(
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
);
$total_results = $db_records_count;
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
if ( ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) ) {
return $data;
}
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $selections );
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$products_query = $this->subquery->get_query_statement();
}
$product_data = $wpdb->get_results(
$products_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
ARRAY_A
);
if ( null === $product_data ) {
return $data;
}
$product_data = array_map( array( $this, 'cast_numbers' ), $product_data );
$data = (object) array(
'data' => $product_data,
'total' => $total_results,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
$this->include_extended_info( $data->data, $query_args );
return $data;
}
/**
* Create or update an entry in the wc_admin_order_product_lookup table for an order.
*
* @since 3.5.0
* @param int $order_id Order ID.
* @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success.
*/
public static function sync_order_products( $order_id ) {
global $wpdb;
$order = wc_get_order( $order_id );
if ( ! $order ) {
return -1;
}
$table_name = self::get_db_table_name();
$existing_items = $wpdb->get_col(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT order_item_id FROM {$table_name} WHERE order_id = %d",
$order_id
)
);
$existing_items = array_flip( $existing_items );
$order_items = $order->get_items();
$num_updated = 0;
$decimals = wc_get_price_decimals();
$round_tax = 'no' === get_option( 'woocommerce_tax_round_at_subtotal' );
foreach ( $order_items as $order_item ) {
$order_item_id = $order_item->get_id();
unset( $existing_items[ $order_item_id ] );
$product_qty = $order_item->get_quantity( 'edit' );
$shipping_amount = $order->get_item_shipping_amount( $order_item );
$shipping_tax_amount = $order->get_item_shipping_tax_amount( $order_item );
$coupon_amount = $order->get_item_coupon_amount( $order_item );
// Skip line items without changes to product quantity.
if ( ! $product_qty ) {
$num_updated++;
continue;
}
// Tax amount.
$tax_amount = 0;
$order_taxes = $order->get_taxes();
$tax_data = $order_item->get_taxes();
foreach ( $order_taxes as $tax_item ) {
$tax_item_id = $tax_item->get_rate_id();
$tax_amount += isset( $tax_data['total'][ $tax_item_id ] ) ? (float) $tax_data['total'][ $tax_item_id ] : 0;
}
$net_revenue = round( $order_item->get_total( 'edit' ), $decimals );
if ( $round_tax ) {
$tax_amount = round( $tax_amount, $decimals );
}
$result = $wpdb->replace(
self::get_db_table_name(),
array(
'order_item_id' => $order_item_id,
'order_id' => $order->get_id(),
'product_id' => wc_get_order_item_meta( $order_item_id, '_product_id' ),
'variation_id' => wc_get_order_item_meta( $order_item_id, '_variation_id' ),
'customer_id' => $order->get_report_customer_id(),
'product_qty' => $product_qty,
'product_net_revenue' => $net_revenue,
'date_created' => $order->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ),
'coupon_amount' => $coupon_amount,
'tax_amount' => $tax_amount,
'shipping_amount' => $shipping_amount,
'shipping_tax_amount' => $shipping_tax_amount,
// @todo Can this be incorrect if modified by filters?
'product_gross_revenue' => $net_revenue + $tax_amount + $shipping_amount + $shipping_tax_amount,
),
array(
'%d', // order_item_id.
'%d', // order_id.
'%d', // product_id.
'%d', // variation_id.
'%d', // customer_id.
'%d', // product_qty.
'%f', // product_net_revenue.
'%s', // date_created.
'%f', // coupon_amount.
'%f', // tax_amount.
'%f', // shipping_amount.
'%f', // shipping_tax_amount.
'%f', // product_gross_revenue.
)
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
/**
* Fires when product's reports are updated.
*
* @param int $order_item_id Order Item ID.
* @param int $order_id Order ID.
*/
do_action( 'woocommerce_analytics_update_product', $order_item_id, $order->get_id() );
// Sum the rows affected. Using REPLACE can affect 2 rows if the row already exists.
$num_updated += 2 === intval( $result ) ? 1 : intval( $result );
}
if ( ! empty( $existing_items ) ) {
$existing_items = array_flip( $existing_items );
$format = array_fill( 0, count( $existing_items ), '%d' );
$format = implode( ',', $format );
array_unshift( $existing_items, $order_id );
$wpdb->query(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"DELETE FROM {$table_name} WHERE order_id = %d AND order_item_id in ({$format})",
$existing_items
)
);
}
return ( count( $order_items ) === $num_updated );
}
/**
* Clean products data when an order is deleted.
*
* @param int $order_id Order ID.
*/
public static function sync_on_order_delete( $order_id ) {
global $wpdb;
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
/**
* Fires when product's reports are removed from database.
*
* @param int $product_id Product ID.
* @param int $order_id Order ID.
*/
do_action( 'woocommerce_analytics_delete_product', 0, $order_id );
ReportsCache::invalidate();
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'select', 'product_id' );
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
$this->subquery->add_sql_clause( 'group_by', 'product_id' );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Customers/DataStore.php 0000644 00000072124 15155657513 0033226 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* Admin\API\Reports\Customers\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Customers;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
use Automattic\WooCommerce\Utilities\OrderUtil;
/**
* Admin\API\Reports\Customers\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_customer_lookup';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'customers';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'id' => 'intval',
'user_id' => 'intval',
'orders_count' => 'intval',
'total_spend' => 'floatval',
'avg_order_value' => 'floatval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'customers';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
global $wpdb;
$table_name = self::get_db_table_name();
$orders_count = 'SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END )';
$total_spend = 'SUM( total_sales )';
$this->report_columns = array(
'id' => "{$table_name}.customer_id as id",
'user_id' => 'user_id',
'username' => 'username',
'name' => "CONCAT_WS( ' ', first_name, last_name ) as name", // @xxx: What does this mean for RTL?
'email' => 'email',
'country' => 'country',
'city' => 'city',
'state' => 'state',
'postcode' => 'postcode',
'date_registered' => 'date_registered',
'date_last_active' => 'IF( date_last_active <= "0000-00-00 00:00:00", NULL, date_last_active ) AS date_last_active',
'date_last_order' => "MAX( {$wpdb->prefix}wc_order_stats.date_created ) as date_last_order",
'orders_count' => "{$orders_count} as orders_count",
'total_spend' => "{$total_spend} as total_spend",
'avg_order_value' => "CASE WHEN {$orders_count} = 0 THEN NULL ELSE {$total_spend} / {$orders_count} END AS avg_order_value",
);
}
/**
* Set up all the hooks for maintaining and populating table data.
*/
public static function init() {
add_action( 'woocommerce_new_customer', array( __CLASS__, 'update_registered_customer' ) );
add_action( 'woocommerce_update_customer', array( __CLASS__, 'update_registered_customer' ) );
add_action( 'profile_update', array( __CLASS__, 'update_registered_customer' ) );
add_action( 'added_user_meta', array( __CLASS__, 'update_registered_customer_via_last_active' ), 10, 3 );
add_action( 'updated_user_meta', array( __CLASS__, 'update_registered_customer_via_last_active' ), 10, 3 );
add_action( 'delete_user', array( __CLASS__, 'delete_customer_by_user_id' ) );
add_action( 'remove_user_from_blog', array( __CLASS__, 'delete_customer_by_user_id' ) );
add_action( 'woocommerce_privacy_remove_order_personal_data', array( __CLASS__, 'anonymize_customer' ) );
add_action( 'woocommerce_analytics_delete_order_stats', array( __CLASS__, 'sync_on_order_delete' ), 15, 2 );
}
/**
* Sync customers data after an order was deleted.
*
* When an order is deleted, the customer record is deleted from the
* table if the customer has no other orders.
*
* @param int $order_id Order ID.
* @param int $customer_id Customer ID.
*/
public static function sync_on_order_delete( $order_id, $customer_id ) {
$customer_id = absint( $customer_id );
if ( 0 === $customer_id ) {
return;
}
// Calculate the amount of orders remaining for this customer.
$order_count = self::get_order_count( $customer_id );
if ( 0 === $order_count ) {
self::delete_customer( $customer_id );
}
}
/**
* Sync customers data after an order was updated.
*
* Only updates customer if it is the customers last order.
*
* @param int $post_id of order.
* @return true|-1
*/
public static function sync_order_customer( $post_id ) {
global $wpdb;
if ( ! OrderUtil::is_order( $post_id, array( 'shop_order', 'shop_order_refund' ) ) ) {
return -1;
}
$order = wc_get_order( $post_id );
$customer_id = self::get_existing_customer_id_from_order( $order );
if ( false === $customer_id ) {
return -1;
}
$last_order = self::get_last_order( $customer_id );
if ( ! $last_order || $order->get_id() !== $last_order->get_id() ) {
return -1;
}
list($data, $format) = self::get_customer_order_data_and_format( $order );
$result = $wpdb->update( self::get_db_table_name(), $data, array( 'customer_id' => $customer_id ), $format );
/**
* Fires when a customer is updated.
*
* @param int $customer_id Customer ID.
* @since 4.0.0
*/
do_action( 'woocommerce_analytics_update_customer', $customer_id );
return 1 === $result;
}
/**
* Maps ordering specified by the user to columns in the database/fields in the data.
*
* @param string $order_by Sorting criterion.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'name' === $order_by ) {
return "CONCAT_WS( ' ', first_name, last_name )";
}
return $order_by;
}
/**
* Fills WHERE clause of SQL request with date-related constraints.
*
* @param array $query_args Parameters supplied by the user.
* @param string $table_name Name of the db table relevant for the date constraint.
*/
protected function add_time_period_sql_params( $query_args, $table_name ) {
global $wpdb;
$this->clear_sql_clause( array( 'where', 'where_time', 'having' ) );
$date_param_mapping = array(
'registered' => array(
'clause' => 'where',
'column' => $table_name . '.date_registered',
),
'order' => array(
'clause' => 'where',
'column' => $wpdb->prefix . 'wc_order_stats.date_created',
),
'last_active' => array(
'clause' => 'where',
'column' => $table_name . '.date_last_active',
),
'last_order' => array(
'clause' => 'having',
'column' => "MAX( {$wpdb->prefix}wc_order_stats.date_created )",
),
);
$match_operator = $this->get_match_operator( $query_args );
$where_time_clauses = array();
$having_time_clauses = array();
foreach ( $date_param_mapping as $query_param => $param_info ) {
$subclauses = array();
$before_arg = $query_param . '_before';
$after_arg = $query_param . '_after';
$column_name = $param_info['column'];
if ( ! empty( $query_args[ $before_arg ] ) ) {
$datetime = new \DateTime( $query_args[ $before_arg ] );
$datetime_str = $datetime->format( TimeInterval::$sql_datetime_format );
$subclauses[] = "{$column_name} <= '$datetime_str'";
}
if ( ! empty( $query_args[ $after_arg ] ) ) {
$datetime = new \DateTime( $query_args[ $after_arg ] );
$datetime_str = $datetime->format( TimeInterval::$sql_datetime_format );
$subclauses[] = "{$column_name} >= '$datetime_str'";
}
if ( $subclauses && ( 'where' === $param_info['clause'] ) ) {
$where_time_clauses[] = '(' . implode( ' AND ', $subclauses ) . ')';
}
if ( $subclauses && ( 'having' === $param_info['clause'] ) ) {
$having_time_clauses[] = '(' . implode( ' AND ', $subclauses ) . ')';
}
}
if ( $where_time_clauses ) {
$this->subquery->add_sql_clause( 'where_time', 'AND ' . implode( " {$match_operator} ", $where_time_clauses ) );
}
if ( $having_time_clauses ) {
$this->subquery->add_sql_clause( 'having', 'AND ' . implode( " {$match_operator} ", $having_time_clauses ) );
}
}
/**
* Updates the database query with parameters used for Customers report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$customer_lookup_table = self::get_db_table_name();
$order_stats_table_name = $wpdb->prefix . 'wc_order_stats';
$this->add_time_period_sql_params( $query_args, $customer_lookup_table );
$this->get_limit_sql_params( $query_args );
$this->add_order_by_sql_params( $query_args );
$this->subquery->add_sql_clause( 'left_join', "LEFT JOIN {$order_stats_table_name} ON {$customer_lookup_table}.customer_id = {$order_stats_table_name}.customer_id" );
$match_operator = $this->get_match_operator( $query_args );
$where_clauses = array();
$having_clauses = array();
$exact_match_params = array(
'name',
'username',
'email',
'country',
);
foreach ( $exact_match_params as $exact_match_param ) {
if ( ! empty( $query_args[ $exact_match_param . '_includes' ] ) ) {
$exact_match_arguments = $query_args[ $exact_match_param . '_includes' ];
$exact_match_arguments_escaped = array_map( 'esc_sql', explode( ',', $exact_match_arguments ) );
$included = implode( "','", $exact_match_arguments_escaped );
// 'country_includes' is a list of country codes, the others will be a list of customer ids.
$table_column = 'country' === $exact_match_param ? $exact_match_param : 'customer_id';
$where_clauses[] = "{$customer_lookup_table}.{$table_column} IN ('{$included}')";
}
if ( ! empty( $query_args[ $exact_match_param . '_excludes' ] ) ) {
$exact_match_arguments = $query_args[ $exact_match_param . '_excludes' ];
$exact_match_arguments_escaped = array_map( 'esc_sql', explode( ',', $exact_match_arguments ) );
$excluded = implode( "','", $exact_match_arguments_escaped );
// 'country_includes' is a list of country codes, the others will be a list of customer ids.
$table_column = 'country' === $exact_match_param ? $exact_match_param : 'customer_id';
$where_clauses[] = "{$customer_lookup_table}.{$table_column} NOT IN ('{$excluded}')";
}
}
$search_params = array(
'name',
'username',
'email',
'all',
);
if ( ! empty( $query_args['search'] ) ) {
$name_like = '%' . $wpdb->esc_like( $query_args['search'] ) . '%';
if ( empty( $query_args['searchby'] ) || 'name' === $query_args['searchby'] || ! in_array( $query_args['searchby'], $search_params, true ) ) {
$searchby = "CONCAT_WS( ' ', first_name, last_name )";
} elseif ( 'all' === $query_args['searchby'] ) {
$searchby = "CONCAT_WS( ' ', first_name, last_name, username, email )";
} else {
$searchby = $query_args['searchby'];
}
$where_clauses[] = $wpdb->prepare( "{$searchby} LIKE %s", $name_like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
$filter_empty_params = array(
'email',
'name',
'country',
'city',
'state',
'postcode',
);
if ( ! empty( $query_args['filter_empty'] ) ) {
$fields_to_filter_by = array_intersect( $query_args['filter_empty'], $filter_empty_params );
if ( in_array( 'name', $fields_to_filter_by, true ) ) {
$fields_to_filter_by = array_diff( $fields_to_filter_by, array( 'name' ) );
$fields_to_filter_by[] = "CONCAT_WS( ' ', first_name, last_name )";
}
$fields_with_not_condition = array_map(
function ( $field ) {
return $field . ' <> \'\'';
},
$fields_to_filter_by
);
$where_clauses[] = '(' . implode( ' AND ', $fields_with_not_condition ) . ')';
}
// Allow a list of customer IDs to be specified.
if ( ! empty( $query_args['customers'] ) ) {
$included_customers = $this->get_filtered_ids( $query_args, 'customers' );
$where_clauses[] = "{$customer_lookup_table}.customer_id IN ({$included_customers})";
}
// Allow a list of user IDs to be specified.
if ( ! empty( $query_args['users'] ) ) {
$included_users = $this->get_filtered_ids( $query_args, 'users' );
$where_clauses[] = "{$customer_lookup_table}.user_id IN ({$included_users})";
}
$numeric_params = array(
'orders_count' => array(
'column' => 'COUNT( order_id )',
'format' => '%d',
),
'total_spend' => array(
'column' => 'SUM( total_sales )',
'format' => '%f',
),
'avg_order_value' => array(
'column' => '( SUM( total_sales ) / COUNT( order_id ) )',
'format' => '%f',
),
);
foreach ( $numeric_params as $numeric_param => $param_info ) {
$subclauses = array();
$min_param = $numeric_param . '_min';
$max_param = $numeric_param . '_max';
$or_equal = isset( $query_args[ $min_param ] ) && isset( $query_args[ $max_param ] ) ? '=' : '';
if ( isset( $query_args[ $min_param ] ) ) {
$subclauses[] = $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
"{$param_info['column']} >{$or_equal} {$param_info['format']}",
$query_args[ $min_param ]
);
}
if ( isset( $query_args[ $max_param ] ) ) {
$subclauses[] = $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
"{$param_info['column']} <{$or_equal} {$param_info['format']}",
$query_args[ $max_param ]
);
}
if ( $subclauses ) {
$having_clauses[] = '(' . implode( ' AND ', $subclauses ) . ')';
}
}
if ( $where_clauses ) {
$preceding_match = empty( $this->get_sql_clause( 'where_time' ) ) ? ' AND ' : " {$match_operator} ";
$this->subquery->add_sql_clause( 'where', $preceding_match . implode( " {$match_operator} ", $where_clauses ) );
}
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$this->subquery->add_sql_clause( 'left_join', "AND ( {$order_status_filter} )" );
}
if ( $having_clauses ) {
$preceding_match = empty( $this->get_sql_clause( 'having' ) ) ? ' AND ' : " {$match_operator} ";
$this->subquery->add_sql_clause( 'having', $preceding_match . implode( " {$match_operator} ", $having_clauses ) );
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$customers_table_name = self::get_db_table_name();
$order_stats_table_name = $wpdb->prefix . 'wc_order_stats';
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date_registered',
'order_before' => TimeInterval::default_before(),
'order_after' => TimeInterval::default_after(),
'fields' => '*',
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$sql_query_params = $this->add_sql_query_params( $query_args );
$count_query = "SELECT COUNT(*) FROM (
{$this->subquery->get_query_statement()}
) as tt
";
$db_records_count = (int) $wpdb->get_var(
$count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
);
$params = $this->get_limit_params( $query_args );
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $selections );
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$customer_data = $wpdb->get_results(
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
ARRAY_A
);
if ( null === $customer_data ) {
return $data;
}
$customer_data = array_map( array( $this, 'cast_numbers' ), $customer_data );
$data = (object) array(
'data' => $customer_data,
'total' => $db_records_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Returns an existing customer ID for an order if one exists.
*
* @param object $order WC Order.
* @return int|bool
*/
public static function get_existing_customer_id_from_order( $order ) {
global $wpdb;
if ( ! is_a( $order, 'WC_Order' ) ) {
return false;
}
$user_id = $order->get_customer_id();
if ( 0 === $user_id ) {
$customer_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT customer_id FROM {$wpdb->prefix}wc_order_stats WHERE order_id = %d",
$order->get_id()
)
);
if ( $customer_id ) {
return $customer_id;
}
$email = $order->get_billing_email( 'edit' );
if ( $email ) {
return self::get_guest_id_by_email( $email );
} else {
return false;
}
} else {
return self::get_customer_id_by_user_id( $user_id );
}
}
/**
* Get or create a customer from a given order.
*
* @param object $order WC Order.
* @return int|bool
*/
public static function get_or_create_customer_from_order( $order ) {
if ( ! $order ) {
return false;
}
global $wpdb;
if ( ! is_a( $order, 'WC_Order' ) ) {
return false;
}
$returning_customer_id = self::get_existing_customer_id_from_order( $order );
if ( $returning_customer_id ) {
return $returning_customer_id;
}
list($data, $format) = self::get_customer_order_data_and_format( $order );
$result = $wpdb->insert( self::get_db_table_name(), $data, $format );
$customer_id = $wpdb->insert_id;
/**
* Fires when a new report customer is created.
*
* @param int $customer_id Customer ID.
* @since 4.0.0
*/
do_action( 'woocommerce_analytics_new_customer', $customer_id );
return $result ? $customer_id : false;
}
/**
* Returns a data object and format object of the customers data coming from the order.
*
* @param object $order WC_Order where we get customer info from.
* @param object|null $customer_user WC_Customer registered customer WP user.
* @return array ($data, $format)
*/
public static function get_customer_order_data_and_format( $order, $customer_user = null ) {
$data = array(
'first_name' => $order->get_customer_first_name(),
'last_name' => $order->get_customer_last_name(),
'email' => $order->get_billing_email( 'edit' ),
'city' => $order->get_billing_city( 'edit' ),
'state' => $order->get_billing_state( 'edit' ),
'postcode' => $order->get_billing_postcode( 'edit' ),
'country' => $order->get_billing_country( 'edit' ),
'date_last_active' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
);
$format = array(
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
);
// Add registered customer data.
if ( 0 !== $order->get_user_id() ) {
$user_id = $order->get_user_id();
if ( is_null( $customer_user ) ) {
$customer_user = new \WC_Customer( $user_id );
}
$data['user_id'] = $user_id;
$data['username'] = $customer_user->get_username( 'edit' );
$data['date_registered'] = $customer_user->get_date_created( 'edit' ) ? $customer_user->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ) : null;
$format[] = '%d';
$format[] = '%s';
$format[] = '%s';
}
return array( $data, $format );
}
/**
* Retrieve a guest ID (when user_id is null) by email.
*
* @param string $email Email address.
* @return false|array Customer array if found, boolean false if not.
*/
public static function get_guest_id_by_email( $email ) {
global $wpdb;
$table_name = self::get_db_table_name();
$customer_id = $wpdb->get_var(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT customer_id FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1",
$email
)
);
return $customer_id ? (int) $customer_id : false;
}
/**
* Retrieve a registered customer row id by user_id.
*
* @param string|int $user_id User ID.
* @return false|int Customer ID if found, boolean false if not.
*/
public static function get_customer_id_by_user_id( $user_id ) {
global $wpdb;
$table_name = self::get_db_table_name();
$customer_id = $wpdb->get_var(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT customer_id FROM {$table_name} WHERE user_id = %d LIMIT 1",
$user_id
)
);
return $customer_id ? (int) $customer_id : false;
}
/**
* Retrieve the last order made by a customer.
*
* @param int $customer_id Customer ID.
* @return object WC_Order|false.
*/
public static function get_last_order( $customer_id ) {
global $wpdb;
$orders_table = $wpdb->prefix . 'wc_order_stats';
$last_order = $wpdb->get_var(
$wpdb->prepare(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT order_id, date_created_gmt FROM {$orders_table}
WHERE customer_id = %d
ORDER BY date_created_gmt DESC, order_id DESC LIMIT 1",
// phpcs:enable
$customer_id
)
);
if ( ! $last_order ) {
return false;
}
return wc_get_order( absint( $last_order ) );
}
/**
* Retrieve the oldest orders made by a customer.
*
* @param int $customer_id Customer ID.
* @return array Orders.
*/
public static function get_oldest_orders( $customer_id ) {
global $wpdb;
$orders_table = $wpdb->prefix . 'wc_order_stats';
$excluded_statuses = array_map( array( __CLASS__, 'normalize_order_status' ), self::get_excluded_report_order_statuses() );
$excluded_statuses_condition = '';
if ( ! empty( $excluded_statuses ) ) {
$excluded_statuses_str = implode( "','", $excluded_statuses );
$excluded_statuses_condition = "AND status NOT IN ('{$excluded_statuses_str}')";
}
return $wpdb->get_results(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT order_id, date_created FROM {$orders_table} WHERE customer_id = %d {$excluded_statuses_condition} ORDER BY date_created, order_id ASC LIMIT 2",
$customer_id
)
);
}
/**
* Retrieve the amount of orders made by a customer.
*
* @param int $customer_id Customer ID.
* @return int|null Amount of orders for customer or null on failure.
*/
public static function get_order_count( $customer_id ) {
global $wpdb;
$customer_id = absint( $customer_id );
if ( 0 === $customer_id ) {
return null;
}
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT( order_id ) FROM {$wpdb->prefix}wc_order_stats WHERE customer_id = %d",
$customer_id
)
);
if ( is_null( $result ) ) {
return null;
}
return (int) $result;
}
/**
* Update the database with customer data.
*
* @param int $user_id WP User ID to update customer data for.
* @return int|bool|null Number or rows modified or false on failure.
*/
public static function update_registered_customer( $user_id ) {
global $wpdb;
$customer = new \WC_Customer( $user_id );
if ( ! self::is_valid_customer( $user_id ) ) {
return false;
}
$first_name = $customer->get_first_name();
$last_name = $customer->get_last_name();
if ( empty( $first_name ) ) {
$first_name = $customer->get_billing_first_name();
}
if ( empty( $last_name ) ) {
$last_name = $customer->get_billing_last_name();
}
$last_active = $customer->get_meta( 'wc_last_active', true, 'edit' );
$data = array(
'user_id' => $user_id,
'username' => $customer->get_username( 'edit' ),
'first_name' => $first_name,
'last_name' => $last_name,
'email' => $customer->get_email( 'edit' ),
'city' => $customer->get_billing_city( 'edit' ),
'state' => $customer->get_billing_state( 'edit' ),
'postcode' => $customer->get_billing_postcode( 'edit' ),
'country' => $customer->get_billing_country( 'edit' ),
'date_registered' => $customer->get_date_created( 'edit' ) ? $customer->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ) : null,
'date_last_active' => $last_active ? gmdate( 'Y-m-d H:i:s', $last_active ) : null,
);
$format = array(
'%d',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
);
$customer_id = self::get_customer_id_by_user_id( $user_id );
if ( $customer_id ) {
// Preserve customer_id for existing user_id.
$data['customer_id'] = $customer_id;
$format[] = '%d';
}
$results = $wpdb->replace( self::get_db_table_name(), $data, $format );
/**
* Fires when customser's reports are updated.
*
* @param int $customer_id Customer ID.
* @since 4.0.0
*/
do_action( 'woocommerce_analytics_update_customer', $customer_id );
ReportsCache::invalidate();
return $results;
}
/**
* Update the database if the "last active" meta value was changed.
* Function expects to be hooked into the `added_user_meta` and `updated_user_meta` actions.
*
* @param int $meta_id ID of updated metadata entry.
* @param int $user_id ID of the user being updated.
* @param string $meta_key Meta key being updated.
*/
public static function update_registered_customer_via_last_active( $meta_id, $user_id, $meta_key ) {
if ( 'wc_last_active' === $meta_key ) {
self::update_registered_customer( $user_id );
}
}
/**
* Check if a user ID is a valid customer or other user role with past orders.
*
* @param int $user_id User ID.
* @return bool
*/
protected static function is_valid_customer( $user_id ) {
$user = new \WP_User( $user_id );
if ( (int) $user_id !== $user->ID ) {
return false;
}
/**
* Filter the customer roles, used to check if the user is a customer.
*
* @param array List of customer roles.
* @since 4.0.0
*/
$customer_roles = (array) apply_filters( 'woocommerce_analytics_customer_roles', array( 'customer' ) );
if ( empty( $user->roles ) || empty( array_intersect( $user->roles, $customer_roles ) ) ) {
return false;
}
return true;
}
/**
* Delete a customer lookup row.
*
* @param int $customer_id Customer ID.
*/
public static function delete_customer( $customer_id ) {
global $wpdb;
$customer_id = (int) $customer_id;
$num_deleted = $wpdb->delete( self::get_db_table_name(), array( 'customer_id' => $customer_id ) );
if ( $num_deleted ) {
/**
* Fires when a customer is deleted.
*
* @param int $order_id Order ID.
* @since 4.0.0
*/
do_action( 'woocommerce_analytics_delete_customer', $customer_id );
ReportsCache::invalidate();
}
}
/**
* Delete a customer lookup row by WordPress User ID.
*
* @param int $user_id WordPress User ID.
*/
public static function delete_customer_by_user_id( $user_id ) {
global $wpdb;
if ( (int) $user_id < 1 || doing_action( 'wp_uninitialize_site' ) ) {
// Skip the deletion.
return;
}
$user_id = (int) $user_id;
$num_deleted = $wpdb->delete( self::get_db_table_name(), array( 'user_id' => $user_id ) );
if ( $num_deleted ) {
ReportsCache::invalidate();
}
}
/**
* Anonymize the customer data for a single order.
*
* @internal
* @param int $order_id Order id.
* @return void
*/
public static function anonymize_customer( $order_id ) {
global $wpdb;
$customer_id = $wpdb->get_var(
$wpdb->prepare( "SELECT customer_id FROM {$wpdb->prefix}wc_order_stats WHERE order_id = %d", $order_id )
);
if ( ! $customer_id ) {
return;
}
// Long form query because $wpdb->update rejects [deleted].
$deleted_text = __( '[deleted]', 'woocommerce' );
$updated = $wpdb->query(
$wpdb->prepare(
"UPDATE {$wpdb->prefix}wc_customer_lookup
SET
user_id = NULL,
username = %s,
first_name = %s,
last_name = %s,
email = %s,
country = '',
postcode = %s,
city = %s,
state = %s
WHERE
customer_id = %d",
array(
$deleted_text,
$deleted_text,
$deleted_text,
'deleted@site.invalid',
$deleted_text,
$deleted_text,
$deleted_text,
$customer_id,
)
)
);
// If the customer row was anonymized, flush the cache.
if ( $updated ) {
ReportsCache::invalidate();
}
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
$table_name = self::get_db_table_name();
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'from', $table_name );
$this->subquery->add_sql_clause( 'select', "{$table_name}.customer_id" );
$this->subquery->add_sql_clause( 'group_by', "{$table_name}.customer_id" );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Downloads/DataStore.php 0000644 00000030623 15155677777 0033210 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Downloads\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Downloads;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Downloads\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_download_log';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'downloads';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'id' => 'intval',
'date' => 'strval',
'date_gmt' => 'strval',
'download_id' => 'strval', // String because this can sometimes be a hash.
'file_name' => 'strval',
'product_id' => 'intval',
'order_id' => 'intval',
'user_id' => 'intval',
'ip_address' => 'strval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'downloads';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$this->report_columns = array(
'id' => 'download_log_id as id',
'date' => 'timestamp as date_gmt',
'download_id' => 'product_permissions.download_id',
'product_id' => 'product_permissions.product_id',
'order_id' => 'product_permissions.order_id',
'user_id' => 'product_permissions.user_id',
'ip_address' => 'user_ip_address as ip_address',
);
}
/**
* Updates the database query with parameters used for downloads report.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$lookup_table = self::get_db_table_name();
$permission_table = $wpdb->prefix . 'woocommerce_downloadable_product_permissions';
$operator = $this->get_match_operator( $query_args );
$where_filters = array();
$join = "JOIN {$permission_table} as product_permissions ON {$lookup_table}.permission_id = product_permissions.permission_id";
$where_time = $this->add_time_period_sql_params( $query_args, $lookup_table );
if ( $where_time ) {
if ( isset( $this->subquery ) ) {
$this->subquery->add_sql_clause( 'where_time', $where_time );
} else {
$this->interval_query->add_sql_clause( 'where_time', $where_time );
}
}
$this->get_limit_sql_params( $query_args );
$where_filters[] = $this->get_object_where_filter(
$lookup_table,
'permission_id',
$permission_table,
'product_id',
'IN',
$this->get_included_products( $query_args )
);
$where_filters[] = $this->get_object_where_filter(
$lookup_table,
'permission_id',
$permission_table,
'product_id',
'NOT IN',
$this->get_excluded_products( $query_args )
);
$where_filters[] = $this->get_object_where_filter(
$lookup_table,
'permission_id',
$permission_table,
'order_id',
'IN',
$this->get_included_orders( $query_args )
);
$where_filters[] = $this->get_object_where_filter(
$lookup_table,
'permission_id',
$permission_table,
'order_id',
'NOT IN',
$this->get_excluded_orders( $query_args )
);
$customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup';
$customer_lookup = "SELECT {$customer_lookup_table}.user_id FROM {$customer_lookup_table} WHERE {$customer_lookup_table}.customer_id IN (%s)";
$included_customers = $this->get_included_customers( $query_args );
$excluded_customers = $this->get_excluded_customers( $query_args );
if ( $included_customers ) {
$where_filters[] = $this->get_object_where_filter(
$lookup_table,
'permission_id',
$permission_table,
'user_id',
'IN',
sprintf( $customer_lookup, $included_customers )
);
}
if ( $excluded_customers ) {
$where_filters[] = $this->get_object_where_filter(
$lookup_table,
'permission_id',
$permission_table,
'user_id',
'NOT IN',
sprintf( $customer_lookup, $excluded_customers )
);
}
$included_ip_addresses = $this->get_included_ip_addresses( $query_args );
$excluded_ip_addresses = $this->get_excluded_ip_addresses( $query_args );
if ( $included_ip_addresses ) {
$where_filters[] = "{$lookup_table}.user_ip_address IN ('{$included_ip_addresses}')";
}
if ( $excluded_ip_addresses ) {
$where_filters[] = "{$lookup_table}.user_ip_address NOT IN ('{$excluded_ip_addresses}')";
}
$where_filters = array_filter( $where_filters );
$where_subclause = implode( " $operator ", $where_filters );
if ( $where_subclause ) {
if ( isset( $this->subquery ) ) {
$this->subquery->add_sql_clause( 'where', "AND ( $where_subclause )" );
} else {
$this->interval_query->add_sql_clause( 'where', "AND ( $where_subclause )" );
}
}
if ( isset( $this->subquery ) ) {
$this->subquery->add_sql_clause( 'join', $join );
} else {
$this->interval_query->add_sql_clause( 'join', $join );
}
$this->add_order_by( $query_args );
}
/**
* Returns comma separated ids of included ip address, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_ip_addresses( $query_args ) {
return $this->get_filtered_ip_addresses( $query_args, 'ip_address_includes' );
}
/**
* Returns comma separated ids of excluded ip address, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_excluded_ip_addresses( $query_args ) {
return $this->get_filtered_ip_addresses( $query_args, 'ip_address_excludes' );
}
/**
* Returns filtered comma separated ids, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @param string $field Query field to filter.
* @return string
*/
protected function get_filtered_ip_addresses( $query_args, $field ) {
if ( isset( $query_args[ $field ] ) && is_array( $query_args[ $field ] ) && count( $query_args[ $field ] ) > 0 ) {
$ip_addresses = array_map( 'esc_sql', $query_args[ $field ] );
/**
* Filter the IDs before retrieving report data.
*
* Allows filtering of the objects included or excluded from reports.
*
* @param array $ids List of object Ids.
* @param array $query_args The original arguments for the request.
* @param string $field The object type.
* @param string $context The data store context.
*/
$ip_addresses = apply_filters( 'woocommerce_analytics_' . $field, $ip_addresses, $query_args, $field, $this->context );
return implode( "','", $ip_addresses );
}
return '';
}
/**
* Returns comma separated ids of included customers, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_customers( $query_args ) {
return self::get_filtered_ids( $query_args, 'customer_includes' );
}
/**
* Returns comma separated ids of excluded customers, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_excluded_customers( $query_args ) {
return self::get_filtered_ids( $query_args, 'customer_excludes' );
}
/**
* Gets WHERE time clause of SQL request with date-related constraints.
*
* @param array $query_args Parameters supplied by the user.
* @param string $table_name Name of the db table relevant for the date constraint.
* @return string
*/
protected function add_time_period_sql_params( $query_args, $table_name ) {
$where_time = '';
if ( $query_args['before'] ) {
$datetime_str = $query_args['before']->format( TimeInterval::$sql_datetime_format );
$where_time .= " AND {$table_name}.timestamp <= '$datetime_str'";
}
if ( $query_args['after'] ) {
$datetime_str = $query_args['after']->format( TimeInterval::$sql_datetime_format );
$where_time .= " AND {$table_name}.timestamp >= '$datetime_str'";
}
return $where_time;
}
/**
* Fills ORDER BY clause of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
*/
protected function add_order_by( $query_args ) {
global $wpdb;
$this->clear_sql_clause( 'order_by' );
$order_by = '';
if ( isset( $query_args['orderby'] ) ) {
$order_by = $this->normalize_order_by( esc_sql( $query_args['orderby'] ) );
$this->add_sql_clause( 'order_by', $order_by );
}
if ( false !== strpos( $order_by, '_products' ) ) {
$this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->posts} AS _products ON product_permissions.product_id = _products.ID" );
}
$this->add_orderby_order_clause( $query_args, $this );
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'timestamp',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$this->add_sql_query_params( $query_args );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$db_records_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM (
{$this->subquery->get_query_statement()}
) AS tt"
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$params = $this->get_limit_params( $query_args );
$total_pages = (int) ceil( $db_records_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', $selections );
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$download_data = $wpdb->get_results(
$this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
ARRAY_A
);
if ( null === $download_data ) {
return $data;
}
$download_data = array_map( array( $this, 'cast_numbers' ), $download_data );
$data = (object) array(
'data' => $download_data,
'total' => $db_records_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Maps ordering specified by the user to columns in the database/fields in the data.
*
* @param string $order_by Sorting criterion.
* @return string
*/
protected function normalize_order_by( $order_by ) {
global $wpdb;
if ( 'date' === $order_by ) {
return $wpdb->prefix . 'wc_download_log.timestamp';
}
if ( 'product' === $order_by ) {
return '_products.post_title';
}
return $order_by;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
$table_name = self::get_db_table_name();
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'from', $table_name );
$this->subquery->add_sql_clause( 'select', "{$table_name}.download_log_id" );
$this->subquery->add_sql_clause( 'group_by', "{$table_name}.download_log_id" );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Categories/DataStore.php 0000644 00000025215 15155702455 0033322 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Categories\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Categories;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Categories\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_product_lookup';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'categories';
/**
* Order by setting used for sorting categories data.
*
* @var string
*/
private $order_by = '';
/**
* Order setting used for sorting categories data.
*
* @var string
*/
private $order = '';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'category_id' => 'intval',
'items_sold' => 'intval',
'net_revenue' => 'floatval',
'orders_count' => 'intval',
'products_count' => 'intval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'categories';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'items_sold' => 'SUM(product_qty) as items_sold',
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
'products_count' => "COUNT(DISTINCT {$table_name}.product_id) as products_count",
);
}
/**
* Return the database query with parameters used for Categories report: time span and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function add_sql_query_params( $query_args ) {
global $wpdb;
$order_product_lookup_table = self::get_db_table_name();
$this->add_time_period_sql_params( $query_args, $order_product_lookup_table );
// join wp_order_product_lookup_table with relationships and taxonomies
// @todo How to handle custom product tables?
$this->subquery->add_sql_clause( 'left_join', "LEFT JOIN {$wpdb->term_relationships} ON {$order_product_lookup_table}.product_id = {$wpdb->term_relationships}.object_id" );
// Adding this (inner) JOIN as a LEFT JOIN for ordering purposes. See comment in add_order_by_params().
$this->subquery->add_sql_clause( 'left_join', "JOIN {$wpdb->term_taxonomy} ON {$wpdb->term_taxonomy}.term_taxonomy_id = {$wpdb->term_relationships}.term_taxonomy_id" );
$included_categories = $this->get_included_categories( $query_args );
if ( $included_categories ) {
$this->subquery->add_sql_clause( 'where', "AND {$wpdb->term_relationships}.term_taxonomy_id IN ({$included_categories})" );
// Limit is left out here so that the grouping in code by PHP can be applied correctly.
// This also needs to be put after the term_taxonomy JOIN so that we can match the correct term name.
$this->add_order_by_params( $query_args, 'outer', 'default_results.category_id' );
} else {
$this->add_order_by_params( $query_args, 'inner', "{$wpdb->term_relationships}.term_taxonomy_id" );
}
$this->add_order_status_clause( $query_args, $order_product_lookup_table, $this->subquery );
$this->subquery->add_sql_clause( 'where', "AND {$wpdb->term_taxonomy}.taxonomy = 'product_cat'" );
}
/**
* Fills ORDER BY clause of SQL request based on user supplied parameters.
*
* @param array $query_args Parameters supplied by the user.
* @param string $from_arg Target of the JOIN sql param.
* @param string $id_cell ID cell identifier, like `table_name.id_column_name`.
*/
protected function add_order_by_params( $query_args, $from_arg, $id_cell ) {
global $wpdb;
// Sanitize input: guarantee that the id cell in the join is quoted with backticks.
$id_cell_segments = explode( '.', str_replace( '`', '', $id_cell ) );
$id_cell_identifier = '`' . implode( '`.`', $id_cell_segments ) . '`';
$lookup_table = self::get_db_table_name();
$order_by_clause = $this->add_order_by_clause( $query_args, $this );
$this->add_orderby_order_clause( $query_args, $this );
if ( false !== strpos( $order_by_clause, '_terms' ) ) {
$join = "JOIN {$wpdb->terms} AS _terms ON {$id_cell_identifier} = _terms.term_id";
if ( 'inner' === $from_arg ) {
// Even though this is an (inner) JOIN, we're adding it as a `left_join` to
// affect its order in the query statement. The SqlQuery::$sql_filters variable
// determines the order in which joins are concatenated.
// See: https://github.com/woocommerce/woocommerce-admin/blob/1f261998e7287b77bc13c3d4ee2e84b717da7957/src/API/Reports/SqlQuery.php#L46-L50.
$this->subquery->add_sql_clause( 'left_join', $join );
} else {
$this->add_sql_clause( 'join', $join );
}
}
}
/**
* Maps ordering specified by the user to columns in the database/fields in the data.
*
* @param string $order_by Sorting criterion.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return 'time_interval';
}
if ( 'category' === $order_by ) {
return '_terms.name';
}
return $order_by;
}
/**
* Returns an array of ids of included categories, based on query arguments from the user.
*
* @param array $query_args Parameters supplied by the user.
* @return string
*/
protected function get_included_categories_array( $query_args ) {
if ( isset( $query_args['category_includes'] ) && is_array( $query_args['category_includes'] ) && count( $query_args['category_includes'] ) > 0 ) {
return $query_args['category_includes'];
}
return array();
}
/**
* Returns the page of data according to page number and items per page.
*
* @param array $data Data to paginate.
* @param integer $page_no Page number.
* @param integer $items_per_page Number of items per page.
* @return array
*/
protected function page_records( $data, $page_no, $items_per_page ) {
$offset = ( $page_no - 1 ) * $items_per_page;
return array_slice( $data, $offset, $items_per_page );
}
/**
* Enriches the category data.
*
* @param array $categories_data Categories data.
* @param array $query_args Query parameters.
*/
protected function include_extended_info( &$categories_data, $query_args ) {
foreach ( $categories_data as $key => $category_data ) {
$extended_info = new \ArrayObject();
if ( $query_args['extended_info'] ) {
$extended_info['name'] = get_the_category_by_ID( $category_data['category_id'] );
}
$categories_data[ $key ]['extended_info'] = $extended_info;
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'category_includes' => array(),
'extended_info' => false,
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) );
$included_categories = $this->get_included_categories_array( $query_args );
$this->add_sql_query_params( $query_args );
if ( count( $included_categories ) > 0 ) {
$fields = $this->get_fields( $query_args );
$ids_table = $this->get_ids_table( $included_categories, 'category_id' );
$this->add_sql_clause( 'select', $this->format_join_selections( array_merge( array( 'category_id' ), $fields ), array( 'category_id' ) ) );
$this->add_sql_clause( 'from', '(' );
$this->add_sql_clause( 'from', $this->subquery->get_query_statement() );
$this->add_sql_clause( 'from', ") AS {$table_name}" );
$this->add_sql_clause(
'right_join',
"RIGHT JOIN ( {$ids_table} ) AS default_results
ON default_results.category_id = {$table_name}.category_id"
);
$categories_query = $this->get_query_statement();
} else {
$this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$categories_query = $this->subquery->get_query_statement();
}
$categories_data = $wpdb->get_results(
$categories_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
ARRAY_A
);
if ( null === $categories_data ) {
return new \WP_Error( 'woocommerce_analytics_categories_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ), array( 'status' => 500 ) );
}
$record_count = count( $categories_data );
$total_pages = (int) ceil( $record_count / $query_args['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$categories_data = $this->page_records( $categories_data, $query_args['page'], $query_args['per_page'] );
$this->include_extended_info( $categories_data, $query_args );
$categories_data = array_map( array( $this, 'cast_numbers' ), $categories_data );
$data = (object) array(
'data' => $categories_data,
'total' => $record_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
global $wpdb;
$this->subquery = new SqlQuery( $this->context . '_subquery' );
$this->subquery->add_sql_clause( 'select', "{$wpdb->term_taxonomy}.term_id as category_id," );
$this->subquery->add_sql_clause( 'from', self::get_db_table_name() );
$this->subquery->add_sql_clause( 'group_by', "{$wpdb->term_taxonomy}.term_id" );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/DataStore.php 0000644 00000023373 15155742034 0034136 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Products\Stats\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Products\Stats;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\Products\DataStore as ProductsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Products\Stats\DataStore.
*/
class DataStore extends ProductsDataStore implements DataStoreInterface {
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'date_start' => 'strval',
'date_end' => 'strval',
'product_id' => 'intval',
'items_sold' => 'intval',
'net_revenue' => 'floatval',
'orders_count' => 'intval',
'products_count' => 'intval',
'variations_count' => 'intval',
);
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'products_stats';
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'products_stats';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'items_sold' => 'SUM(product_qty) as items_sold',
'net_revenue' => 'SUM(product_net_revenue) AS net_revenue',
'orders_count' => "COUNT( DISTINCT ( CASE WHEN product_gross_revenue >= 0 THEN {$table_name}.order_id END ) ) as orders_count",
'products_count' => 'COUNT(DISTINCT product_id) as products_count',
'variations_count' => 'COUNT(DISTINCT variation_id) as variations_count',
);
}
/**
* Updates the database query with parameters used for Products Stats report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function update_sql_query_params( $query_args ) {
global $wpdb;
$products_where_clause = '';
$products_from_clause = '';
$order_product_lookup_table = self::get_db_table_name();
$included_products = $this->get_included_products( $query_args );
if ( $included_products ) {
$products_where_clause .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
}
$included_variations = $this->get_included_variations( $query_args );
if ( $included_variations ) {
$products_where_clause .= " AND {$order_product_lookup_table}.variation_id IN ({$included_variations})";
}
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$products_from_clause .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
$products_where_clause .= " AND ( {$order_status_filter} )";
}
$this->add_time_period_sql_params( $query_args, $order_product_lookup_table );
$this->total_query->add_sql_clause( 'where', $products_where_clause );
$this->total_query->add_sql_clause( 'join', $products_from_clause );
$this->add_intervals_sql_params( $query_args, $order_product_lookup_table );
$this->interval_query->add_sql_clause( 'where', $products_where_clause );
$this->interval_query->add_sql_clause( 'join', $products_from_clause );
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @since 3.5.0
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'category_includes' => array(),
'interval' => 'week',
'product_includes' => array(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$selections = $this->selected_columns( $query_args );
$params = $this->get_limit_params( $query_args );
$this->update_sql_query_params( $query_args );
$this->get_limit_sql_params( $query_args );
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
$db_intervals = $wpdb->get_col(
$this->interval_query->get_query_statement()
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
$db_interval_count = count( $db_intervals );
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return array();
}
$intervals = array();
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
$this->total_query->add_sql_clause( 'select', $selections );
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
$totals = $wpdb->get_results(
$this->total_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
// @todo remove these assignements when refactoring segmenter classes to use query objects.
$totals_query = array(
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
);
$intervals_query = array(
'select_clause' => $this->get_sql_clause( 'select' ),
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
'order_by' => $this->get_sql_clause( 'order_by' ),
'limit' => $this->get_sql_clause( 'limit' ),
);
$segmenter = new Segmenter( $query_args, $this->report_columns );
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
if ( null === $totals ) {
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
}
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
if ( '' !== $selections ) {
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
}
$intervals = $wpdb->get_results(
$this->interval_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $intervals ) {
return new \WP_Error( 'woocommerce_analytics_products_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
}
$totals = (object) $this->cast_numbers( $totals[0] );
$data = (object) array(
'totals' => $totals,
'intervals' => $intervals,
'total' => $expected_interval_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
} else {
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
}
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
$this->create_interval_subtotals( $data->intervals );
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Normalizes order_by clause to match to SQL query.
*
* @param string $order_by Order by option requeste by user.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return 'time_interval';
}
return $order_by;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
unset( $this->subquery );
$this->total_query = new SqlQuery( $this->context . '_total' );
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query = new SqlQuery( $this->context . '_interval' );
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Customers/Stats/DataStore.php 0000644 00000007230 15155763034 0034314 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Customers\Stats\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Customers\Stats;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
/**
* API\Reports\Customers\Stats\DataStore.
*/
class DataStore extends CustomersDataStore implements DataStoreInterface {
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'customers_count' => 'intval',
'avg_orders_count' => 'floatval',
'avg_total_spend' => 'floatval',
'avg_avg_order_value' => 'floatval',
);
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'customers_stats';
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'customers_stats';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$this->report_columns = array(
'customers_count' => 'COUNT( * ) as customers_count',
'avg_orders_count' => 'AVG( orders_count ) as avg_orders_count',
'avg_total_spend' => 'AVG( total_spend ) as avg_total_spend',
'avg_avg_order_value' => 'AVG( avg_order_value ) as avg_avg_order_value',
);
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$customers_table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date_registered',
'fields' => '*',
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'customers_count' => 0,
'avg_orders_count' => 0,
'avg_total_spend' => 0.0,
'avg_avg_order_value' => 0.0,
);
$selections = $this->selected_columns( $query_args );
$this->add_sql_query_params( $query_args );
// Clear SQL clauses set for parent class queries that are different here.
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', 'SUM( total_sales ) AS total_spend,' );
$this->subquery->add_sql_clause(
'select',
'SUM( CASE WHEN parent_id = 0 THEN 1 END ) as orders_count,'
);
$this->subquery->add_sql_clause(
'select',
'CASE WHEN SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) = 0 THEN NULL ELSE SUM( total_sales ) / SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) END AS avg_order_value'
);
$this->clear_sql_clause( array( 'order_by', 'limit' ) );
$this->add_sql_clause( 'select', $selections );
$this->add_sql_clause( 'from', "({$this->subquery->get_query_statement()}) AS tt" );
$report_data = $wpdb->get_results(
$this->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
ARRAY_A
);
if ( null === $report_data ) {
return $data;
}
$data = (object) $this->cast_numbers( $report_data[0] );
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/DataStore.php 0000644 00000015317 15156027313 0034261 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Downloads\Stats\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\Downloads\DataStore as DownloadsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Downloads\Stats\DataStore.
*/
class DataStore extends DownloadsDataStore implements DataStoreInterface {
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'download_count' => 'intval',
);
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'downloads_stats';
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'downloads_stats';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$this->report_columns = array(
'download_count' => 'COUNT(DISTINCT download_log_id) as download_count',
);
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'fields' => '*',
'interval' => 'week',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$selections = $this->selected_columns( $query_args );
$this->add_sql_query_params( $query_args );
$where_time = $this->add_time_period_sql_params( $query_args, $table_name );
$this->add_intervals_sql_params( $query_args, $table_name );
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
$this->interval_query->str_replace_clause( 'select', 'date_created', 'timestamp' );
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
$db_intervals = $wpdb->get_col(
$this->interval_query->get_query_statement()
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
$db_records_count = count( $db_intervals );
$params = $this->get_limit_params( $query_args );
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return array();
}
$this->update_intervals_sql_params( $query_args, $db_records_count, $expected_interval_count, $table_name );
$this->interval_query->str_replace_clause( 'where_time', 'date_created', 'timestamp' );
$this->total_query->add_sql_clause( 'select', $selections );
$this->total_query->add_sql_clause( 'where', $this->interval_query->get_sql_clause( 'where' ) );
if ( $where_time ) {
$this->total_query->add_sql_clause( 'where_time', $where_time );
}
$totals = $wpdb->get_results(
$this->total_query->get_query_statement(),
ARRAY_A
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
if ( null === $totals ) {
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
}
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$this->interval_query->add_sql_clause( 'select', ', MAX(timestamp) AS datetime_anchor' );
if ( '' !== $selections ) {
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
}
$intervals = $wpdb->get_results(
$this->interval_query->get_query_statement(),
ARRAY_A
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
if ( null === $intervals ) {
return new \WP_Error( 'woocommerce_analytics_downloads_stats_result_failed', __( 'Sorry, fetching downloads data failed.', 'woocommerce' ) );
}
$totals = (object) $this->cast_numbers( $totals[0] );
$data = (object) array(
'totals' => $totals,
'intervals' => $intervals,
'total' => $expected_interval_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
if ( $this->intervals_missing( $expected_interval_count, $db_records_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_records_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
} else {
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
}
$this->create_interval_subtotals( $data->intervals );
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Normalizes order_by clause to match to SQL query.
*
* @param string $order_by Order by option requeste by user.
* @return string
*/
protected function normalize_order_by( $order_by ) {
if ( 'date' === $order_by ) {
return 'time_interval';
}
return $order_by;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
unset( $this->subquery );
$this->total_query = new SqlQuery( $this->context . '_total' );
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query = new SqlQuery( $this->context . '_interval' );
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/DataStore.php 0000644 00000023636 15156106470 0033420 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Taxes\Stats\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Taxes\Stats\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_tax_lookup';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'taxes_stats';
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'tax_codes' => 'intval',
'total_tax' => 'floatval',
'order_tax' => 'floatval',
'shipping_tax' => 'floatval',
'orders_count' => 'intval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'taxes_stats';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'tax_codes' => 'COUNT(DISTINCT tax_rate_id) as tax_codes',
'total_tax' => 'SUM(total_tax) AS total_tax',
'order_tax' => 'SUM(order_tax) as order_tax',
'shipping_tax' => 'SUM(shipping_tax) as shipping_tax',
'orders_count' => "COUNT( DISTINCT ( CASE WHEN parent_id = 0 THEN {$table_name}.order_id END ) ) as orders_count",
);
}
/**
* Updates the database query with parameters used for Taxes Stats report
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function update_sql_query_params( $query_args ) {
global $wpdb;
$taxes_where_clause = '';
$order_tax_lookup_table = self::get_db_table_name();
if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) {
$tax_id_placeholders = implode( ',', array_fill( 0, count( $query_args['taxes'] ), '%d' ) );
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$taxes_where_clause .= $wpdb->prepare( " AND {$order_tax_lookup_table}.tax_rate_id IN ({$tax_id_placeholders})", $query_args['taxes'] );
/* phpcs:enable */
}
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$taxes_where_clause .= " AND ( {$order_status_filter} )";
}
$this->add_time_period_sql_params( $query_args, $order_tax_lookup_table );
$this->total_query->add_sql_clause( 'where', $taxes_where_clause );
$this->add_intervals_sql_params( $query_args, $order_tax_lookup_table );
$this->interval_query->add_sql_clause( 'where', $taxes_where_clause );
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
$this->interval_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
}
/**
* Get taxes associated with a store.
*
* @param array $args Array of args to filter the query by. Supports `include`.
* @return array An array of all taxes.
*/
public static function get_taxes( $args ) {
global $wpdb;
$query = "
SELECT
tax_rate_id,
tax_rate_country,
tax_rate_state,
tax_rate_name,
tax_rate_priority
FROM {$wpdb->prefix}woocommerce_tax_rates
";
if ( ! empty( $args['include'] ) ) {
/* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */
$tax_placeholders = implode( ',', array_fill( 0, count( $args['include'] ), '%d' ) );
$query .= $wpdb->prepare( " WHERE tax_rate_id IN ({$tax_placeholders})", $args['include'] );
/* phpcs:enable */
}
return $wpdb->get_results( $query, ARRAY_A ); // WPCS: cache ok, DB call ok, unprepared SQL ok.
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'tax_rate_id',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'taxes' => array(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'totals' => (object) array(),
'intervals' => (object) array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$params = $this->get_limit_params( $query_args );
$order_stats_join = "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
$this->update_sql_query_params( $query_args );
$this->interval_query->add_sql_clause( 'join', $order_stats_join );
$db_intervals = $wpdb->get_col(
$this->interval_query->get_query_statement()
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
$db_interval_count = count( $db_intervals );
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->total_query->add_sql_clause( 'select', $selections );
$this->total_query->add_sql_clause( 'join', $order_stats_join );
$this->total_query->add_sql_clause( 'where_time', $this->get_sql_clause( 'where_time' ) );
$totals = $wpdb->get_results(
$this->total_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $totals ) {
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
}
// @todo remove these assignements when refactoring segmenter classes to use query objects.
$totals_query = array(
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
);
$intervals_query = array(
'select_clause' => $this->get_sql_clause( 'select' ),
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
);
$segmenter = new Segmenter( $query_args, $this->report_columns );
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
if ( '' !== $selections ) {
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
}
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$intervals = $wpdb->get_results(
$this->interval_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $intervals ) {
return new \WP_Error( 'woocommerce_analytics_taxes_stats_result_failed', __( 'Sorry, fetching tax data failed.', 'woocommerce' ) );
}
$totals = (object) $this->cast_numbers( $totals[0] );
$data = (object) array(
'totals' => $totals,
'intervals' => $intervals,
'total' => $expected_interval_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
} else {
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
}
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
$this->create_interval_subtotals( $data->intervals );
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
unset( $this->subquery );
$this->total_query = new SqlQuery( $this->context . '_total' );
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query = new SqlQuery( $this->context . '_interval' );
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Stock/Stats/DataStore.php 0000644 00000010565 15156177673 0033431 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Stock\Stats\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Stock\Stats;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
/**
* API\Reports\Stock\Stats\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Get stock counts for the whole store.
*
* @param array $query Not used for the stock stats data store, but needed for the interface.
* @return array Array of counts.
*/
public function get_data( $query ) {
$report_data = array();
$cache_expire = DAY_IN_SECONDS * 30;
$low_stock_transient_name = 'wc_admin_stock_count_lowstock';
$low_stock_count = get_transient( $low_stock_transient_name );
if ( false === $low_stock_count ) {
$low_stock_count = $this->get_low_stock_count();
set_transient( $low_stock_transient_name, $low_stock_count, $cache_expire );
} else {
$low_stock_count = intval( $low_stock_count );
}
$report_data['lowstock'] = $low_stock_count;
$status_options = wc_get_product_stock_status_options();
foreach ( $status_options as $status => $label ) {
$transient_name = 'wc_admin_stock_count_' . $status;
$count = get_transient( $transient_name );
if ( false === $count ) {
$count = $this->get_count( $status );
set_transient( $transient_name, $count, $cache_expire );
} else {
$count = intval( $count );
}
$report_data[ $status ] = $count;
}
$product_count_transient_name = 'wc_admin_product_count';
$product_count = get_transient( $product_count_transient_name );
if ( false === $product_count ) {
$product_count = $this->get_product_count();
set_transient( $product_count_transient_name, $product_count, $cache_expire );
} else {
$product_count = intval( $product_count );
}
$report_data['products'] = $product_count;
return $report_data;
}
/**
* Get low stock count (products with stock < low stock amount, but greater than no stock amount).
*
* @return int Low stock count.
*/
private function get_low_stock_count() {
global $wpdb;
$no_stock_amount = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) );
$low_stock_amount = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
return (int) $wpdb->get_var(
$wpdb->prepare(
"
SELECT count( DISTINCT posts.ID ) FROM {$wpdb->posts} posts
LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON posts.ID = wc_product_meta_lookup.product_id
LEFT JOIN {$wpdb->postmeta} low_stock_amount_meta ON posts.ID = low_stock_amount_meta.post_id AND low_stock_amount_meta.meta_key = '_low_stock_amount'
WHERE posts.post_type IN ( 'product', 'product_variation' )
AND wc_product_meta_lookup.stock_quantity IS NOT NULL
AND wc_product_meta_lookup.stock_status = 'instock'
AND (
(
low_stock_amount_meta.meta_value > ''
AND wc_product_meta_lookup.stock_quantity <= CAST(low_stock_amount_meta.meta_value AS SIGNED)
AND wc_product_meta_lookup.stock_quantity > %d
)
OR (
(
low_stock_amount_meta.meta_value IS NULL OR low_stock_amount_meta.meta_value <= ''
)
AND wc_product_meta_lookup.stock_quantity <= %d
AND wc_product_meta_lookup.stock_quantity > %d
)
)
",
$no_stock_amount,
$low_stock_amount,
$no_stock_amount
)
);
}
/**
* Get count for the passed in stock status.
*
* @param string $status Status slug.
* @return int Count.
*/
private function get_count( $status ) {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"
SELECT count( DISTINCT posts.ID ) FROM {$wpdb->posts} posts
LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON posts.ID = wc_product_meta_lookup.product_id
WHERE posts.post_type IN ( 'product', 'product_variation' )
AND wc_product_meta_lookup.stock_status = %s
",
$status
)
);
}
/**
* Get product count for the store.
*
* @return int Product count.
*/
private function get_product_count() {
$query_args = array();
$query_args['post_type'] = array( 'product', 'product_variation' );
$query = new \WP_Query();
$query->query( $query_args );
return intval( $query->found_posts );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php 0000644 00000064022 15156414046 0033565 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Orders\Stats\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Orders\Stats;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
use Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore;
use Automattic\WooCommerce\Utilities\OrderUtil;
/**
* API\Reports\Orders\Stats\DataStore.
*/
class DataStore extends ReportsDataStore implements DataStoreInterface {
/**
* Table used to get the data.
*
* @var string
*/
protected static $table_name = 'wc_order_stats';
/**
* Cron event name.
*/
const CRON_EVENT = 'wc_order_stats_update';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'orders_stats';
/**
* Type for each column to cast values correctly later.
*
* @var array
*/
protected $column_types = array(
'orders_count' => 'intval',
'num_items_sold' => 'intval',
'gross_sales' => 'floatval',
'total_sales' => 'floatval',
'coupons' => 'floatval',
'coupons_count' => 'intval',
'refunds' => 'floatval',
'taxes' => 'floatval',
'shipping' => 'floatval',
'net_revenue' => 'floatval',
'avg_items_per_order' => 'floatval',
'avg_order_value' => 'floatval',
'total_customers' => 'intval',
'products' => 'intval',
'segment_id' => 'intval',
);
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'orders_stats';
/**
* Dynamically sets the date column name based on configuration
*/
public function __construct() {
$this->date_column_name = get_option( 'woocommerce_date_type', 'date_paid' );
parent::__construct();
}
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
// Avoid ambigious columns in SQL query.
$refunds = "ABS( SUM( CASE WHEN {$table_name}.net_total < 0 THEN {$table_name}.net_total ELSE 0 END ) )";
$gross_sales =
"( SUM({$table_name}.total_sales)" .
' + COALESCE( SUM(discount_amount), 0 )' . // SUM() all nulls gives null.
" - SUM({$table_name}.tax_total)" .
" - SUM({$table_name}.shipping_total)" .
" + {$refunds}" .
' ) as gross_sales';
$this->report_columns = array(
'orders_count' => "SUM( CASE WHEN {$table_name}.parent_id = 0 THEN 1 ELSE 0 END ) as orders_count",
'num_items_sold' => "SUM({$table_name}.num_items_sold) as num_items_sold",
'gross_sales' => $gross_sales,
'total_sales' => "SUM({$table_name}.total_sales) AS total_sales",
'coupons' => 'COALESCE( SUM(discount_amount), 0 ) AS coupons', // SUM() all nulls gives null.
'coupons_count' => 'COALESCE( coupons_count, 0 ) as coupons_count',
'refunds' => "{$refunds} AS refunds",
'taxes' => "SUM({$table_name}.tax_total) AS taxes",
'shipping' => "SUM({$table_name}.shipping_total) AS shipping",
'net_revenue' => "SUM({$table_name}.net_total) AS net_revenue",
'avg_items_per_order' => "SUM( {$table_name}.num_items_sold ) / SUM( CASE WHEN {$table_name}.parent_id = 0 THEN 1 ELSE 0 END ) AS avg_items_per_order",
'avg_order_value' => "SUM( {$table_name}.net_total ) / SUM( CASE WHEN {$table_name}.parent_id = 0 THEN 1 ELSE 0 END ) AS avg_order_value",
'total_customers' => "COUNT( DISTINCT( {$table_name}.customer_id ) ) as total_customers",
);
}
/**
* Set up all the hooks for maintaining and populating table data.
*/
public static function init() {
add_action( 'woocommerce_before_delete_order', array( __CLASS__, 'delete_order' ) );
add_action( 'delete_post', array( __CLASS__, 'delete_order' ) );
}
/**
* Updates the totals and intervals database queries with parameters used for Orders report: categories, coupons and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function orders_stats_sql_filter( $query_args ) {
// phpcs:ignore Generic.Commenting.Todo.TaskFound
// @todo Performance of all of this?
global $wpdb;
$from_clause = '';
$orders_stats_table = self::get_db_table_name();
$product_lookup = $wpdb->prefix . 'wc_order_product_lookup';
$coupon_lookup = $wpdb->prefix . 'wc_order_coupon_lookup';
$tax_rate_lookup = $wpdb->prefix . 'wc_order_tax_lookup';
$operator = $this->get_match_operator( $query_args );
$where_filters = array();
// Products filters.
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$product_lookup,
'product_id',
'IN',
$this->get_included_products( $query_args )
);
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$product_lookup,
'product_id',
'NOT IN',
$this->get_excluded_products( $query_args )
);
// Variations filters.
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$product_lookup,
'variation_id',
'IN',
$this->get_included_variations( $query_args )
);
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$product_lookup,
'variation_id',
'NOT IN',
$this->get_excluded_variations( $query_args )
);
// Coupons filters.
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$coupon_lookup,
'coupon_id',
'IN',
$this->get_included_coupons( $query_args )
);
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$coupon_lookup,
'coupon_id',
'NOT IN',
$this->get_excluded_coupons( $query_args )
);
// Tax rate filters.
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$tax_rate_lookup,
'tax_rate_id',
'IN',
implode( ',', $query_args['tax_rate_includes'] )
);
$where_filters[] = $this->get_object_where_filter(
$orders_stats_table,
'order_id',
$tax_rate_lookup,
'tax_rate_id',
'NOT IN',
implode( ',', $query_args['tax_rate_excludes'] )
);
// Product attribute filters.
$attribute_subqueries = $this->get_attribute_subqueries( $query_args );
if ( $attribute_subqueries['join'] && $attribute_subqueries['where'] ) {
// Build a subquery for getting order IDs by product attribute(s).
// Done here since our use case is a little more complicated than get_object_where_filter() can handle.
$attribute_subquery = new SqlQuery();
$attribute_subquery->add_sql_clause( 'select', "{$orders_stats_table}.order_id" );
$attribute_subquery->add_sql_clause( 'from', $orders_stats_table );
// JOIN on product lookup.
$attribute_subquery->add_sql_clause( 'join', "JOIN {$product_lookup} ON {$orders_stats_table}.order_id = {$product_lookup}.order_id" );
// Add JOINs for matching attributes.
foreach ( $attribute_subqueries['join'] as $attribute_join ) {
$attribute_subquery->add_sql_clause( 'join', $attribute_join );
}
// Add WHEREs for matching attributes.
$attribute_subquery->add_sql_clause( 'where', 'AND (' . implode( " {$operator} ", $attribute_subqueries['where'] ) . ')' );
// Generate subquery statement and add to our where filters.
$where_filters[] = "{$orders_stats_table}.order_id IN (" . $attribute_subquery->get_query_statement() . ')';
}
$where_filters[] = $this->get_customer_subquery( $query_args );
$refund_subquery = $this->get_refund_subquery( $query_args );
$from_clause .= $refund_subquery['from_clause'];
if ( $refund_subquery['where_clause'] ) {
$where_filters[] = $refund_subquery['where_clause'];
}
$where_filters = array_filter( $where_filters );
$where_subclause = implode( " $operator ", $where_filters );
// Append status filter after to avoid matching ANY on default statuses.
$order_status_filter = $this->get_status_subquery( $query_args, $operator );
if ( $order_status_filter ) {
if ( empty( $query_args['status_is'] ) && empty( $query_args['status_is_not'] ) ) {
$operator = 'AND';
}
$where_subclause = implode( " $operator ", array_filter( array( $where_subclause, $order_status_filter ) ) );
}
// To avoid requesting the subqueries twice, the result is applied to all queries passed to the method.
if ( $where_subclause ) {
$this->total_query->add_sql_clause( 'where', "AND ( $where_subclause )" );
$this->total_query->add_sql_clause( 'join', $from_clause );
$this->interval_query->add_sql_clause( 'where', "AND ( $where_subclause )" );
$this->interval_query->add_sql_clause( 'join', $from_clause );
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only applied when not using REST API, as the API has its own defaults that overwrite these for most values (except before, after, etc).
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'interval' => 'week',
'fields' => '*',
'segmentby' => '',
'match' => 'all',
'status_is' => array(),
'status_is_not' => array(),
'product_includes' => array(),
'product_excludes' => array(),
'coupon_includes' => array(),
'coupon_excludes' => array(),
'tax_rate_includes' => array(),
'tax_rate_excludes' => array(),
'customer_type' => '',
'category_includes' => array(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'totals' => (object) array(),
'intervals' => (object) array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$this->add_time_period_sql_params( $query_args, $table_name );
$this->add_intervals_sql_params( $query_args, $table_name );
$this->add_order_by_sql_params( $query_args );
$where_time = $this->get_sql_clause( 'where_time' );
$params = $this->get_limit_sql_params( $query_args );
$coupon_join = "LEFT JOIN (
SELECT
order_id,
SUM(discount_amount) AS discount_amount,
COUNT(DISTINCT coupon_id) AS coupons_count
FROM
{$wpdb->prefix}wc_order_coupon_lookup
GROUP BY
order_id
) order_coupon_lookup
ON order_coupon_lookup.order_id = {$wpdb->prefix}wc_order_stats.order_id";
// Additional filtering for Orders report.
$this->orders_stats_sql_filter( $query_args );
$this->total_query->add_sql_clause( 'select', $selections );
$this->total_query->add_sql_clause( 'left_join', $coupon_join );
$this->total_query->add_sql_clause( 'where_time', $where_time );
$totals = $wpdb->get_results(
$this->total_query->get_query_statement(),
ARRAY_A
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
if ( null === $totals ) {
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
}
// phpcs:ignore Generic.Commenting.Todo.TaskFound
// @todo Remove these assignements when refactoring segmenter classes to use query objects.
$totals_query = array(
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
'where_time_clause' => $where_time,
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
);
$intervals_query = array(
'select_clause' => $this->get_sql_clause( 'select' ),
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
'where_time_clause' => $where_time,
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
'limit' => $this->get_sql_clause( 'limit' ),
);
$unique_products = $this->get_unique_product_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
$totals[0]['products'] = $unique_products;
$segmenter = new Segmenter( $query_args, $this->report_columns );
$unique_coupons = $this->get_unique_coupon_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
$totals[0]['coupons_count'] = $unique_coupons;
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
$totals = (object) $this->cast_numbers( $totals[0] );
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) . ' AS time_interval' );
$this->interval_query->add_sql_clause( 'left_join', $coupon_join );
$this->interval_query->add_sql_clause( 'where_time', $where_time );
$db_intervals = $wpdb->get_col(
$this->interval_query->get_query_statement()
); // phpcs:ignore cache ok, DB call ok, , unprepared SQL ok.
$db_interval_count = count( $db_intervals );
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
$total_pages = (int) ceil( $expected_interval_count / $params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
if ( '' !== $selections ) {
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
}
$intervals = $wpdb->get_results(
$this->interval_query->get_query_statement(),
ARRAY_A
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
if ( null === $intervals ) {
return new \WP_Error( 'woocommerce_analytics_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'woocommerce' ) );
}
if ( isset( $intervals[0] ) ) {
$unique_coupons = $this->get_unique_coupon_count( $intervals_query['from_clause'], $intervals_query['where_time_clause'], $intervals_query['where_clause'], true );
$intervals[0]['coupons_count'] = $unique_coupons;
}
$data = (object) array(
'totals' => $totals,
'intervals' => $intervals,
'total' => $expected_interval_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
$this->remove_extra_records( $data, $query_args['page'], $params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
} else {
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
}
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
$this->create_interval_subtotals( $data->intervals );
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Get unique products based on user time query
*
* @param string $from_clause From clause with date query.
* @param string $where_time_clause Where clause with date query.
* @param string $where_clause Where clause with date query.
* @return integer Unique product count.
*/
public function get_unique_product_count( $from_clause, $where_time_clause, $where_clause ) {
global $wpdb;
$table_name = self::get_db_table_name();
return $wpdb->get_var(
"SELECT
COUNT( DISTINCT {$wpdb->prefix}wc_order_product_lookup.product_id )
FROM
{$wpdb->prefix}wc_order_product_lookup JOIN {$table_name} ON {$wpdb->prefix}wc_order_product_lookup.order_id = {$table_name}.order_id
{$from_clause}
WHERE
1=1
{$where_time_clause}
{$where_clause}"
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
}
/**
* Get unique coupons based on user time query
*
* @param string $from_clause From clause with date query.
* @param string $where_time_clause Where clause with date query.
* @param string $where_clause Where clause with date query.
* @return integer Unique product count.
*/
public function get_unique_coupon_count( $from_clause, $where_time_clause, $where_clause ) {
global $wpdb;
$table_name = self::get_db_table_name();
return $wpdb->get_var(
"SELECT
COUNT(DISTINCT coupon_id)
FROM
{$wpdb->prefix}wc_order_coupon_lookup JOIN {$table_name} ON {$wpdb->prefix}wc_order_coupon_lookup.order_id = {$table_name}.order_id
{$from_clause}
WHERE
1=1
{$where_time_clause}
{$where_clause}"
); // phpcs:ignore cache ok, DB call ok, unprepared SQL ok.
}
/**
* Add order information to the lookup table when orders are created or modified.
*
* @param int $post_id Post ID.
* @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success.
*/
public static function sync_order( $post_id ) {
if ( ! OrderUtil::is_order( $post_id, array( 'shop_order', 'shop_order_refund' ) ) ) {
return -1;
}
$order = wc_get_order( $post_id );
if ( ! $order ) {
return -1;
}
return self::update( $order );
}
/**
* Update the database with stats data.
*
* @param WC_Order|WC_Order_Refund $order Order or refund to update row for.
* @return int|bool Returns -1 if order won't be processed, or a boolean indicating processing success.
*/
public static function update( $order ) {
global $wpdb;
$table_name = self::get_db_table_name();
if ( ! $order->get_id() || ! $order->get_date_created() ) {
return -1;
}
/**
* Filters order stats data.
*
* @param array $data Data written to order stats lookup table.
* @param WC_Order $order Order object.
*
* @since 4.0.0
*/
$data = apply_filters(
'woocommerce_analytics_update_order_stats_data',
array(
'order_id' => $order->get_id(),
'parent_id' => $order->get_parent_id(),
'date_created' => $order->get_date_created()->date( 'Y-m-d H:i:s' ),
'date_paid' => $order->get_date_paid() ? $order->get_date_paid()->date( 'Y-m-d H:i:s' ) : null,
'date_completed' => $order->get_date_completed() ? $order->get_date_completed()->date( 'Y-m-d H:i:s' ) : null,
'date_created_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created()->getTimestamp() ),
'num_items_sold' => self::get_num_items_sold( $order ),
'total_sales' => $order->get_total(),
'tax_total' => $order->get_total_tax(),
'shipping_total' => $order->get_shipping_total(),
'net_total' => self::get_net_total( $order ),
'status' => self::normalize_order_status( $order->get_status() ),
'customer_id' => $order->get_report_customer_id(),
'returning_customer' => $order->is_returning_customer(),
),
$order
);
$format = array(
'%d',
'%d',
'%s',
'%s',
'%s',
'%s',
'%d',
'%f',
'%f',
'%f',
'%f',
'%s',
'%d',
'%d',
);
if ( 'shop_order_refund' === $order->get_type() ) {
$parent_order = wc_get_order( $order->get_parent_id() );
if ( $parent_order ) {
$data['parent_id'] = $parent_order->get_id();
$data['status'] = self::normalize_order_status( $parent_order->get_status() );
}
/**
* Set date_completed and date_paid the same as date_created to avoid problems
* when they are being used to sort the data, as refunds don't have them filled
*/
$data['date_completed'] = $data['date_created'];
$data['date_paid'] = $data['date_created'];
}
// Update or add the information to the DB.
$result = $wpdb->replace( $table_name, $data, $format );
/**
* Fires when order's stats reports are updated.
*
* @param int $order_id Order ID.
*
* @since 4.0.0.
*/
do_action( 'woocommerce_analytics_update_order_stats', $order->get_id() );
// Check the rows affected for success. Using REPLACE can affect 2 rows if the row already exists.
return ( 1 === $result || 2 === $result );
}
/**
* Deletes the order stats when an order is deleted.
*
* @param int $post_id Post ID.
*/
public static function delete_order( $post_id ) {
global $wpdb;
$order_id = (int) $post_id;
if ( ! OrderUtil::is_order( $post_id, array( 'shop_order', 'shop_order_refund' ) ) ) {
return;
}
// Retrieve customer details before the order is deleted.
$order = wc_get_order( $order_id );
$customer_id = absint( CustomersDataStore::get_existing_customer_id_from_order( $order ) );
// Delete the order.
$wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) );
/**
* Fires when orders stats are deleted.
*
* @param int $order_id Order ID.
* @param int $customer_id Customer ID.
*
* @since 4.0.0
*/
do_action( 'woocommerce_analytics_delete_order_stats', $order_id, $customer_id );
ReportsCache::invalidate();
}
/**
* Calculation methods.
*/
/**
* Get number of items sold among all orders.
*
* @param array $order WC_Order object.
* @return int
*/
protected static function get_num_items_sold( $order ) {
$num_items = 0;
$line_items = $order->get_items( 'line_item' );
foreach ( $line_items as $line_item ) {
$num_items += $line_item->get_quantity();
}
return $num_items;
}
/**
* Get the net amount from an order without shipping, tax, or refunds.
*
* @param array $order WC_Order object.
* @return float
*/
protected static function get_net_total( $order ) {
$net_total = floatval( $order->get_total() ) - floatval( $order->get_total_tax() ) - floatval( $order->get_shipping_total() );
return (float) $net_total;
}
/**
* Check to see if an order's customer has made previous orders or not
*
* @param array $order WC_Order object.
* @param int|false $customer_id Customer ID. Optional.
* @return bool
*/
public static function is_returning_customer( $order, $customer_id = null ) {
if ( is_null( $customer_id ) ) {
$customer_id = \Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore::get_existing_customer_id_from_order( $order );
}
if ( ! $customer_id ) {
return false;
}
$oldest_orders = \Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore::get_oldest_orders( $customer_id );
if ( empty( $oldest_orders ) ) {
return false;
}
$first_order = $oldest_orders[0];
$second_order = isset( $oldest_orders[1] ) ? $oldest_orders[1] : false;
$excluded_statuses = self::get_excluded_report_order_statuses();
// Order is older than previous first order.
if ( $order->get_date_created() < wc_string_to_datetime( $first_order->date_created ) &&
! in_array( $order->get_status(), $excluded_statuses, true )
) {
self::set_customer_first_order( $customer_id, $order->get_id() );
return false;
}
// The current order is the oldest known order.
$is_first_order = (int) $order->get_id() === (int) $first_order->order_id;
// Order date has changed and next oldest is now the first order.
$date_change = $second_order &&
$order->get_date_created() > wc_string_to_datetime( $first_order->date_created ) &&
wc_string_to_datetime( $second_order->date_created ) < $order->get_date_created();
// Status has changed to an excluded status and next oldest order is now the first order.
$status_change = $second_order &&
in_array( $order->get_status(), $excluded_statuses, true );
if ( $is_first_order && ( $date_change || $status_change ) ) {
self::set_customer_first_order( $customer_id, $second_order->order_id );
return true;
}
return (int) $order->get_id() !== (int) $first_order->order_id;
}
/**
* Set a customer's first order and all others to returning.
*
* @param int $customer_id Customer ID.
* @param int $order_id Order ID.
*/
protected static function set_customer_first_order( $customer_id, $order_id ) {
global $wpdb;
$orders_stats_table = self::get_db_table_name();
$wpdb->query(
$wpdb->prepare(
// phpcs:ignore Generic.Commenting.Todo.TaskFound
// TODO: use the %i placeholder to prepare the table name when available in the the minimum required WordPress version.
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"UPDATE {$orders_stats_table} SET returning_customer = CASE WHEN order_id = %d THEN false ELSE true END WHERE customer_id = %d",
$order_id,
$customer_id
)
);
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
unset( $this->subquery );
$this->total_query = new SqlQuery( $this->context . '_total' );
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query = new SqlQuery( $this->context . '_interval' );
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
}
}
httpdocs/wp-content/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/DataStore.php 0000644 00000021305 15156430212 0033743 0 ustar 00 var/www/vhosts/uyarreklam.com.tr <?php
/**
* API\Reports\Coupons\Stats\DataStore class file.
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Coupons\Stats\DataStore.
*/
class DataStore extends CouponsDataStore implements DataStoreInterface {
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'date_start' => 'strval',
'date_end' => 'strval',
'date_start_gmt' => 'strval',
'date_end_gmt' => 'strval',
'amount' => 'floatval',
'coupons_count' => 'intval',
'orders_count' => 'intval',
);
/**
* SQL columns to select in the db query.
*
* @var array
*/
protected $report_columns;
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'coupons_stats';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'coupons_stats';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'amount' => 'SUM(discount_amount) as amount',
'coupons_count' => 'COUNT(DISTINCT coupon_id) as coupons_count',
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
);
}
/**
* Updates the database query with parameters used for Products Stats report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function update_sql_query_params( $query_args ) {
global $wpdb;
$clauses = array(
'where' => '',
'join' => '',
);
$order_coupon_lookup_table = self::get_db_table_name();
$included_coupons = $this->get_included_coupons( $query_args, 'coupons' );
if ( $included_coupons ) {
$clauses['where'] .= " AND {$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})";
}
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$clauses['join'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_coupon_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
$clauses['where'] .= " AND ( {$order_status_filter} )";
}
$this->add_time_period_sql_params( $query_args, $order_coupon_lookup_table );
$this->add_intervals_sql_params( $query_args, $order_coupon_lookup_table );
$clauses['where_time'] = $this->get_sql_clause( 'where_time' );
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) );
$this->interval_query->add_sql_clause( 'select', 'AS time_interval' );
foreach ( array( 'join', 'where_time', 'where' ) as $clause ) {
$this->interval_query->add_sql_clause( $clause, $clauses[ $clause ] );
$this->total_query->add_sql_clause( $clause, $clauses[ $clause ] );
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @since 3.5.0
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'interval' => 'week',
'coupons' => array(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$totals_query = array();
$intervals_query = array();
$limit_params = $this->get_limit_sql_params( $query_args );
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
$db_intervals = $wpdb->get_col(
$this->interval_query->get_query_statement()
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
$db_interval_count = count( $db_intervals );
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
$total_pages = (int) ceil( $expected_interval_count / $limit_params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->total_query->add_sql_clause( 'select', $selections );
$totals = $wpdb->get_results(
$this->total_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $totals ) {
return $data;
}
// @todo remove these assignements when refactoring segmenter classes to use query objects.
$totals_query = array(
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
);
$intervals_query = array(
'select_clause' => $this->get_sql_clause( 'select' ),
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
'limit' => $this->get_sql_clause( 'limit' ),
);
$segmenter = new Segmenter( $query_args, $this->report_columns );
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
$totals = (object) $this->cast_numbers( $totals[0] );
// Intervals.
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
if ( '' !== $selections ) {
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
}
$intervals = $wpdb->get_results(
$this->interval_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $intervals ) {
return $data;
}
$data = (object) array(
'totals' => $totals,
'intervals' => $intervals,
'total' => $expected_interval_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $limit_params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
$this->remove_extra_records( $data, $query_args['page'], $limit_params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
} else {
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
}
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
$this->create_interval_subtotals( $data->intervals );
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
unset( $this->subquery );
$this->total_query = new SqlQuery( $this->context . '_total' );
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query = new SqlQuery( $this->context . '_interval' );
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
}
}