|
|
@@ -0,0 +1,409 @@
|
|
|
+<?php
|
|
|
+/**
|
|
|
+ * Database Manager Class - Handles all database operations (HPOS Compatible)
|
|
|
+ *
|
|
|
+ * @package StudiouWCCustomReports
|
|
|
+ */
|
|
|
+
|
|
|
+if (!defined('ABSPATH')) {
|
|
|
+ exit;
|
|
|
+}
|
|
|
+
|
|
|
+class StudiouWC_DB_Manager {
|
|
|
+
|
|
|
+ public function get_product_categories_summary($date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ if (empty($statuses)) {
|
|
|
+ return array();
|
|
|
+ }
|
|
|
+
|
|
|
+ $settings_manager = new StudiouWC_Settings_Manager();
|
|
|
+ $settings = $settings_manager->get_settings();
|
|
|
+ $default_property = $settings['default_property'] ?? '';
|
|
|
+
|
|
|
+ $statuses_placeholder = implode(',', array_fill(0, count($statuses), '%s'));
|
|
|
+ $offset = ($paged - 1) * $per_page;
|
|
|
+
|
|
|
+ // Check if HPOS is enabled
|
|
|
+ if ($this->is_hpos_enabled()) {
|
|
|
+ $sql = $this->get_hpos_categories_query($statuses_placeholder);
|
|
|
+ } else {
|
|
|
+ $sql = $this->get_legacy_categories_query($statuses_placeholder);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add ordering
|
|
|
+ $sql .= $this->get_order_clause($orderby, $order);
|
|
|
+ $sql .= " LIMIT %d OFFSET %d";
|
|
|
+
|
|
|
+ $query_params = array_merge(
|
|
|
+ array($date_from, $date_to),
|
|
|
+ $statuses,
|
|
|
+ array($per_page, $offset)
|
|
|
+ );
|
|
|
+
|
|
|
+ $results = $wpdb->get_results($wpdb->prepare($sql, $query_params), ARRAY_A);
|
|
|
+
|
|
|
+ // Get property counts for each category if property is set
|
|
|
+ foreach ($results as &$result) {
|
|
|
+ $result['property_counts'] = array();
|
|
|
+ if (!empty($default_property)) {
|
|
|
+ $result['property_counts'] = $this->get_property_counts_for_category(
|
|
|
+ $result['category'],
|
|
|
+ $date_from,
|
|
|
+ $date_to,
|
|
|
+ $statuses,
|
|
|
+ $default_property
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $results;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function get_property_counts_for_category($category, $date_from, $date_to, $statuses, $property) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ if (empty($statuses)) {
|
|
|
+ return array();
|
|
|
+ }
|
|
|
+
|
|
|
+ $statuses_placeholder = implode(',', array_fill(0, count($statuses), '%s'));
|
|
|
+ $taxonomy = 'pa_' . $property; // Add the pa_ prefix for WooCommerce attributes
|
|
|
+
|
|
|
+ if ($this->is_hpos_enabled()) {
|
|
|
+ $sql = $this->get_hpos_property_counts_query($statuses_placeholder);
|
|
|
+ } else {
|
|
|
+ $sql = $this->get_legacy_property_counts_query($statuses_placeholder);
|
|
|
+ }
|
|
|
+
|
|
|
+ $query_params = array_merge(
|
|
|
+ array($date_from, $date_to),
|
|
|
+ $statuses,
|
|
|
+ array($category, $taxonomy)
|
|
|
+ );
|
|
|
+
|
|
|
+ $results = $wpdb->get_results($wpdb->prepare($sql, $query_params), ARRAY_A);
|
|
|
+
|
|
|
+ $property_counts = array();
|
|
|
+ foreach ($results as $result) {
|
|
|
+ if (!empty($result['property_value'])) {
|
|
|
+ $property_counts[$result['property_value']] = $result['count'];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $property_counts;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function get_total_items($date_from, $date_to, $statuses) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ if (empty($statuses)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ $statuses_placeholder = implode(',', array_fill(0, count($statuses), '%s'));
|
|
|
+
|
|
|
+ if ($this->is_hpos_enabled()) {
|
|
|
+ $sql = $this->get_hpos_total_items_query($statuses_placeholder);
|
|
|
+ } else {
|
|
|
+ $sql = $this->get_legacy_total_items_query($statuses_placeholder);
|
|
|
+ }
|
|
|
+
|
|
|
+ $query_params = array_merge(
|
|
|
+ array($date_from, $date_to),
|
|
|
+ $statuses
|
|
|
+ );
|
|
|
+
|
|
|
+ return $wpdb->get_var($wpdb->prepare($sql, $query_params));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get product attributes (not meta keys) for dropdown
|
|
|
+ */
|
|
|
+ public function get_product_properties() {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ // Get WooCommerce product attributes
|
|
|
+ $attributes = $wpdb->get_results("
|
|
|
+ SELECT attribute_name, attribute_label
|
|
|
+ FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
|
|
|
+ ORDER BY attribute_label
|
|
|
+ ", ARRAY_A);
|
|
|
+
|
|
|
+ $properties = array();
|
|
|
+ foreach ($attributes as $attribute) {
|
|
|
+ $properties[$attribute['attribute_name']] = $attribute['attribute_label'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $properties;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get property values for a specific attribute
|
|
|
+ */
|
|
|
+ public function get_property_values($property) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ if (empty($property)) {
|
|
|
+ return array();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build taxonomy name for WooCommerce attributes
|
|
|
+ $taxonomy = 'pa_' . $property;
|
|
|
+
|
|
|
+ // Get terms for this attribute taxonomy
|
|
|
+ $sql = "
|
|
|
+ SELECT t.name, t.slug
|
|
|
+ FROM {$wpdb->terms} t
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
|
|
|
+ WHERE tt.taxonomy = %s
|
|
|
+ ORDER BY t.name
|
|
|
+ ";
|
|
|
+
|
|
|
+ $results = $wpdb->get_results($wpdb->prepare($sql, $taxonomy), ARRAY_A);
|
|
|
+
|
|
|
+ $values = array();
|
|
|
+ foreach ($results as $result) {
|
|
|
+ $values[$result['slug']] = $result['name'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $values;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get attribute name from attribute slug
|
|
|
+ */
|
|
|
+ public function get_attribute_name($attribute_slug) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ $result = $wpdb->get_var($wpdb->prepare("
|
|
|
+ SELECT attribute_label
|
|
|
+ FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
|
|
|
+ WHERE attribute_name = %s
|
|
|
+ ", $attribute_slug));
|
|
|
+
|
|
|
+ return $result ? $result : $attribute_slug;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if HPOS (High-Performance Order Storage) is enabled
|
|
|
+ */
|
|
|
+ private function is_hpos_enabled() {
|
|
|
+ return class_exists('Automattic\WooCommerce\Utilities\OrderUtil') &&
|
|
|
+ \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get HPOS compatible query for categories summary
|
|
|
+ */
|
|
|
+ private function get_hpos_categories_query($statuses_placeholder) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ return "
|
|
|
+ SELECT
|
|
|
+ t.name as category,
|
|
|
+ COUNT(DISTINCT o.id) as orders_count,
|
|
|
+ SUM(oi.product_net_revenue) as total_price
|
|
|
+ FROM {$wpdb->prefix}wc_orders o
|
|
|
+ INNER JOIN {$wpdb->prefix}wc_order_product_lookup oi ON o.id = oi.order_id
|
|
|
+ INNER JOIN {$wpdb->posts} p ON oi.product_id = p.ID OR oi.variation_id = p.ID
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN
|
|
|
+ (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
|
|
|
+ WHERE o.date_created_gmt >= %s
|
|
|
+ AND o.date_created_gmt <= %s
|
|
|
+ AND o.status IN ($statuses_placeholder)
|
|
|
+ AND tt.taxonomy = 'product_cat'
|
|
|
+ GROUP BY t.term_id, t.name
|
|
|
+ ";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get legacy query for categories summary (pre-HPOS)
|
|
|
+ */
|
|
|
+ private function get_legacy_categories_query($statuses_placeholder) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ return "
|
|
|
+ SELECT
|
|
|
+ t.name as category,
|
|
|
+ COUNT(DISTINCT p_order.ID) as orders_count,
|
|
|
+ SUM(oi.product_net_revenue) as total_price
|
|
|
+ FROM {$wpdb->posts} p_order
|
|
|
+ INNER JOIN {$wpdb->prefix}wc_order_product_lookup oi ON p_order.ID = oi.order_id
|
|
|
+ INNER JOIN {$wpdb->posts} p ON oi.product_id = p.ID OR oi.variation_id = p.ID
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN
|
|
|
+ (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
|
|
|
+ WHERE p_order.post_date >= %s
|
|
|
+ AND p_order.post_date <= %s
|
|
|
+ AND p_order.post_status IN ($statuses_placeholder)
|
|
|
+ AND p_order.post_type = 'shop_order'
|
|
|
+ AND tt.taxonomy = 'product_cat'
|
|
|
+ GROUP BY t.term_id, t.name
|
|
|
+ ";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get HPOS compatible query for property counts
|
|
|
+ */
|
|
|
+ private function get_hpos_property_counts_query($statuses_placeholder) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ return "
|
|
|
+ SELECT
|
|
|
+ t_attr.slug as property_value,
|
|
|
+ COUNT(DISTINCT oi.order_item_id) as count
|
|
|
+ FROM {$wpdb->prefix}wc_orders o
|
|
|
+ INNER JOIN {$wpdb->prefix}wc_order_product_lookup oi ON o.id = oi.order_id
|
|
|
+ INNER JOIN {$wpdb->posts} p ON oi.product_id = p.ID OR oi.variation_id = p.ID
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr_cat ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN
|
|
|
+ (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr_cat.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt_cat ON tr_cat.term_taxonomy_id = tt_cat.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t_cat ON tt_cat.term_id = t_cat.term_id
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr_attr ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN oi.variation_id
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr_attr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt_attr ON tr_attr.term_taxonomy_id = tt_attr.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t_attr ON tt_attr.term_id = t_attr.term_id
|
|
|
+ WHERE o.date_created_gmt >= %s
|
|
|
+ AND o.date_created_gmt <= %s
|
|
|
+ AND o.status IN ($statuses_placeholder)
|
|
|
+ AND tt_cat.taxonomy = 'product_cat'
|
|
|
+ AND t_cat.name = %s
|
|
|
+ AND tt_attr.taxonomy = %s
|
|
|
+ GROUP BY t_attr.term_id, t_attr.slug
|
|
|
+ ";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get legacy query for property counts (pre-HPOS)
|
|
|
+ */
|
|
|
+ private function get_legacy_property_counts_query($statuses_placeholder) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ return "
|
|
|
+ SELECT
|
|
|
+ t_attr.slug as property_value,
|
|
|
+ COUNT(DISTINCT oi.order_item_id) as count
|
|
|
+ FROM {$wpdb->posts} p_order
|
|
|
+ INNER JOIN {$wpdb->prefix}wc_order_product_lookup oi ON p_order.ID = oi.order_id
|
|
|
+ INNER JOIN {$wpdb->posts} p ON oi.product_id = p.ID OR oi.variation_id = p.ID
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr_cat ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN
|
|
|
+ (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr_cat.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt_cat ON tr_cat.term_taxonomy_id = tt_cat.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t_cat ON tt_cat.term_id = t_cat.term_id
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr_attr ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN oi.variation_id
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr_attr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt_attr ON tr_attr.term_taxonomy_id = tt_attr.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t_attr ON tt_attr.term_id = t_attr.term_id
|
|
|
+ WHERE p_order.post_date >= %s
|
|
|
+ AND p_order.post_date <= %s
|
|
|
+ AND p_order.post_status IN ($statuses_placeholder)
|
|
|
+ AND p_order.post_type = 'shop_order'
|
|
|
+ AND tt_cat.taxonomy = 'product_cat'
|
|
|
+ AND t_cat.name = %s
|
|
|
+ AND tt_attr.taxonomy = %s
|
|
|
+ GROUP BY t_attr.term_id, t_attr.slug
|
|
|
+ ";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get HPOS compatible query for total items
|
|
|
+ */
|
|
|
+ private function get_hpos_total_items_query($statuses_placeholder) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ return "
|
|
|
+ SELECT COUNT(DISTINCT t.term_id)
|
|
|
+ FROM {$wpdb->prefix}wc_orders o
|
|
|
+ INNER JOIN {$wpdb->prefix}wc_order_product_lookup oi ON o.id = oi.order_id
|
|
|
+ INNER JOIN {$wpdb->posts} p ON oi.product_id = p.ID OR oi.variation_id = p.ID
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN
|
|
|
+ (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
|
|
|
+ WHERE o.date_created_gmt >= %s
|
|
|
+ AND o.date_created_gmt <= %s
|
|
|
+ AND o.status IN ($statuses_placeholder)
|
|
|
+ AND tt.taxonomy = 'product_cat'
|
|
|
+ ";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get legacy query for total items (pre-HPOS)
|
|
|
+ */
|
|
|
+ private function get_legacy_total_items_query($statuses_placeholder) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ return "
|
|
|
+ SELECT COUNT(DISTINCT t.term_id)
|
|
|
+ FROM {$wpdb->posts} p_order
|
|
|
+ INNER JOIN {$wpdb->prefix}wc_order_product_lookup oi ON p_order.ID = oi.order_id
|
|
|
+ INNER JOIN {$wpdb->posts} p ON oi.product_id = p.ID OR oi.variation_id = p.ID
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr ON (
|
|
|
+ CASE
|
|
|
+ WHEN oi.variation_id > 0 THEN
|
|
|
+ (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
|
|
|
+ ELSE oi.product_id
|
|
|
+ END
|
|
|
+ ) = tr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
|
|
|
+ INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
|
|
|
+ WHERE p_order.post_date >= %s
|
|
|
+ AND p_order.post_date <= %s
|
|
|
+ AND p_order.post_status IN ($statuses_placeholder)
|
|
|
+ AND p_order.post_type = 'shop_order'
|
|
|
+ AND tt.taxonomy = 'product_cat'
|
|
|
+ ";
|
|
|
+ }
|
|
|
+
|
|
|
+ private function get_order_clause($orderby, $order) {
|
|
|
+ $order = ($order === 'DESC') ? 'DESC' : 'ASC';
|
|
|
+
|
|
|
+ switch ($orderby) {
|
|
|
+ case 'orders_count':
|
|
|
+ return " ORDER BY orders_count $order";
|
|
|
+ case 'total_price':
|
|
|
+ return " ORDER BY total_price $order";
|
|
|
+ case 'category':
|
|
|
+ default:
|
|
|
+ return " ORDER BY t.name $order";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|