| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- <?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) || empty($property)) {
- return array();
- }
-
- $statuses_placeholder = implode(',', array_fill(0, count($statuses), '%s'));
- $meta_key = 'attribute_pa_' . $property; // WooCommerce stores variation attributes as meta
-
- // Get order items for this category and date range, then check their variation meta
- if ($this->is_hpos_enabled()) {
- $base_sql = "
- SELECT oi.product_id, oi.variation_id, oi.product_qty, pm.meta_value as attr_value
- 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_main ON (
- CASE
- WHEN oi.variation_id > 0 THEN
- (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
- ELSE oi.product_id
- END
- ) = p_main.ID
- INNER JOIN {$wpdb->term_relationships} tr_cat ON p_main.ID = 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
- LEFT JOIN {$wpdb->postmeta} pm ON (
- CASE
- WHEN oi.variation_id > 0 THEN oi.variation_id
- ELSE oi.product_id
- END
- ) = pm.post_id AND pm.meta_key = %s
- 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 pm.meta_value IS NOT NULL
- AND pm.meta_value != ''
- ";
- } else {
- $base_sql = "
- SELECT oi.product_id, oi.variation_id, oi.product_qty, pm.meta_value as attr_value
- 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_main ON (
- CASE
- WHEN oi.variation_id > 0 THEN
- (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
- ELSE oi.product_id
- END
- ) = p_main.ID
- INNER JOIN {$wpdb->term_relationships} tr_cat ON p_main.ID = 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
- LEFT JOIN {$wpdb->postmeta} pm ON (
- CASE
- WHEN oi.variation_id > 0 THEN oi.variation_id
- ELSE oi.product_id
- END
- ) = pm.post_id AND pm.meta_key = %s
- 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 pm.meta_value IS NOT NULL
- AND pm.meta_value != ''
- ";
- }
-
- $query_params = array_merge(
- array($meta_key, $date_from, $date_to),
- $statuses,
- array($category)
- );
-
- // Debug: Log the query for troubleshooting
- if (defined('WP_DEBUG') && WP_DEBUG) {
- error_log('[Studiou Debug] Updated property counts query: ' . $wpdb->prepare($base_sql, $query_params));
- }
-
- $results = $wpdb->get_results($wpdb->prepare($base_sql, $query_params), ARRAY_A);
-
- if (defined('WP_DEBUG') && WP_DEBUG) {
- error_log('[Studiou Debug] Found ' . count($results) . ' items with attribute values');
- error_log('[Studiou Debug] Sample results: ' . print_r(array_slice($results, 0, 3), true));
- }
-
- // Count quantities by attribute value
- $property_counts = array();
- foreach ($results as $result) {
- $attr_value = $result['attr_value'];
- $qty = $result['product_qty'];
-
- if (!empty($attr_value)) {
- if (!isset($property_counts[$attr_value])) {
- $property_counts[$attr_value] = 0;
- }
- $property_counts[$attr_value] += $qty;
- }
- }
-
- if (defined('WP_DEBUG') && WP_DEBUG) {
- error_log('[Studiou Debug] Final property counts: ' . print_r($property_counts, true));
- }
-
- 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,
- SUM(oi.product_qty) 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_main ON (
- CASE
- WHEN oi.variation_id > 0 THEN
- (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
- ELSE oi.product_id
- END
- ) = p_main.ID
- INNER JOIN {$wpdb->term_relationships} tr_cat ON p_main.ID = 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,
- SUM(oi.product_qty) 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_main ON (
- CASE
- WHEN oi.variation_id > 0 THEN
- (SELECT post_parent FROM {$wpdb->posts} WHERE ID = oi.variation_id)
- ELSE oi.product_id
- END
- ) = p_main.ID
- INNER JOIN {$wpdb->term_relationships} tr_cat ON p_main.ID = 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";
- }
- }
- }
|