JezK
Edit File: AdminDashboardStatsStore.php
<?php namespace WeDevs\Dokan\Models\DataStore; use WeDevs\Dokan\Utilities\ReportUtil; /** * Admin Dashboard Stats Store Class * * @since 4.1.0 */ class AdminDashboardStatsStore extends BaseDataStore { /** * Vendor Order Stats Store instance. * * @var VendorOrderStatsStore */ protected $vendor_order_stats_store; /** * Constructor. * * @since 4.1.0 */ public function __construct() { $this->vendor_order_stats_store = new VendorOrderStatsStore(); parent::__construct(); } /** * Get the fields with format as an array where key is the db field name and value is the format. * * @since 4.1.0 * * @return array */ protected function get_fields_with_format(): array { return [ 'ID' => '%d', 'post_author' => '%d', 'post_date' => '%s', 'post_date_gmt' => '%s', 'post_content' => '%s', 'post_title' => '%s', 'post_excerpt' => '%s', 'post_status' => '%s', 'comment_status' => '%s', 'ping_status' => '%s', 'post_password' => '%s', 'post_name' => '%s', 'to_ping' => '%s', 'pinged' => '%s', 'post_modified' => '%s', 'post_modified_gmt' => '%s', 'post_content_filtered' => '%s', 'post_parent' => '%d', 'guid' => '%s', 'menu_order' => '%d', 'post_type' => '%s', 'post_mime_type' => '%s', 'comment_count' => '%d', ]; } /** * Get the table name. * * @since 4.1.0 * * @return string */ public function get_table_name(): string { return 'posts'; } /** * Get the ID field name. * * @since 4.1.0 * * @return string */ protected function get_id_field_name(): string { return 'ID'; } /** * Get recurring customers data for current and previous periods using wc_order_stats. * * @since 4.1.0 * * @param array $date_range Array containing date range information. * * @return array Recurring customers data. */ public function get_recurring_customers_data( array $date_range ): array { global $wpdb; // Get the order statuses to exclude from the report. $exclude_order_statuses = ReportUtil::get_exclude_order_statuses(); $this->clear_all_clauses(); // phpcs:disable $this->add_sql_clause( 'select', $wpdb->prepare( 'COUNT(DISTINCT CASE WHEN DATE(os1.date_created) BETWEEN %s AND %s THEN os1.customer_id END) as current_count,', $date_range['current_month_start'], $date_range['current_month_end'] ) ); $this->add_sql_clause( 'select', $wpdb->prepare( 'COUNT(DISTINCT CASE WHEN DATE(os1.date_created) BETWEEN %s AND %s THEN os1.customer_id END) as previous_count', $date_range['previous_month_start'], $date_range['previous_month_end'] ) ); $this->add_sql_clause( 'from', "{$wpdb->prefix}wc_order_stats os1" ); $this->add_sql_clause( 'where', " AND os1.status NOT IN ( '" . implode( "','", $exclude_order_statuses ) . "' )" ); $this->add_sql_clause( 'where', " AND os1.customer_id > 0" ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND ((DATE(os1.date_created) BETWEEN %s AND %s) OR (DATE(os1.date_created) BETWEEN %s AND %s))', $date_range['current_month_start'], $date_range['current_month_end'], $date_range['previous_month_start'], $date_range['previous_month_end'] ) ); // Only include customers who had orders the comparison periods ago $this->add_sql_clause( 'where', $wpdb->prepare( "AND os1.customer_id IN ( SELECT DISTINCT customer_id FROM {$wpdb->prefix}wc_order_stats os2 WHERE os2.status NOT IN ( '" . implode( "','", $exclude_order_statuses ) . "' ) AND os2.customer_id > 0 AND DATE(os2.date_created) < %s )", $date_range['previous_month_start'] ) ); $query_statement = $this->get_query_statement(); $result = $wpdb->get_row( $query_statement, ARRAY_A ); // phpcs:ignore // phpcs:enable return apply_filters( 'dokan_admin_dashboard_recurring_customers_data', [ 'icon' => 'FileUser', 'current' => (int) ( $result['current_count'] ?? 0 ), 'previous' => (int) ( $result['previous_count'] ?? 0 ), 'title' => esc_html__( 'Recurring Customers', 'dokan-lite' ), 'tooltip' => esc_html__( 'Customers who returned and purchased again in the time period', 'dokan-lite' ), 'position' => 50, ], $date_range ); } /** * Get new customers data for current and previous periods. * * @since 4.1.0 * * @param array $date_range Array containing date range information. * * @return array New customers data. */ public function get_new_customers_data( array $date_range ): array { global $wpdb; $this->clear_all_clauses(); $this->add_sql_clause( 'select', $wpdb->prepare( 'SUM(CASE WHEN u.user_registered BETWEEN %s AND %s THEN 1 ELSE 0 END) as current_count,', $date_range['current_month_start'], $date_range['current_month_end'] ) ); $this->add_sql_clause( 'select', $wpdb->prepare( 'SUM(CASE WHEN u.user_registered BETWEEN %s AND %s THEN 1 ELSE 0 END) as previous_count', $date_range['previous_month_start'], $date_range['previous_month_end'] ) ); $this->add_sql_clause( 'from', "{$wpdb->users} u" ); $this->add_sql_clause( 'join', "JOIN {$wpdb->usermeta} um ON u.ID = um.user_id" ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND u.user_registered BETWEEN %s AND %s', $date_range['previous_month_start'], $date_range['current_month_end'] ) ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND um.meta_key = %s', $wpdb->prefix . 'capabilities' ) ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND um.meta_value LIKE %s', '%customer%' ) ); $query_statement = $this->get_query_statement(); $result = $wpdb->get_row( $query_statement, ARRAY_A ); // phpcs:ignore return apply_filters( 'dokan_admin_dashboard_new_customers_data', [ 'icon' => 'FileUser', 'current' => (int) ( $result['current_count'] ?? 0 ), 'previous' => (int) ( $result['previous_count'] ?? 0 ), 'title' => esc_html__( 'New Customers', 'dokan-lite' ), 'tooltip' => esc_html__( 'Total new customers registered in the time period', 'dokan-lite' ), 'position' => 40, ], $date_range ); } /** * Get order cancellation rate data for current and previous periods. * * @since 4.1.0 * * @param array $date_range Array containing date range information. * * @return array Order cancellation rate data. */ public function get_order_cancellation_rate_data( array $date_range ): array { global $wpdb; $this->clear_all_clauses(); $this->add_sql_clause( 'select', $wpdb->prepare( 'CASE WHEN post_date BETWEEN %s AND %s THEN \'current\' WHEN post_date BETWEEN %s AND %s THEN \'previous\' END as period,', $date_range['current_month_start'], $date_range['current_month_end'], $date_range['previous_month_start'], $date_range['previous_month_end'] ) ); $this->add_sql_clause( 'select', 'COUNT(*) as total_orders,' ); $this->add_sql_clause( 'select', 'SUM(CASE WHEN post_status = \'wc-cancelled\' THEN 1 ELSE 0 END) as cancelled_orders' ); $this->add_sql_clause( 'from', $this->get_table_name_with_prefix() ); $this->add_sql_clause( 'where', " AND post_type = 'shop_order'" ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND ((post_date BETWEEN %s AND %s) OR (post_date BETWEEN %s AND %s))', $date_range['current_month_start'], $date_range['current_month_end'], $date_range['previous_month_start'], $date_range['previous_month_end'] ) ); $this->add_sql_clause( 'group_by', 'period' ); $query_statement = $this->get_query_statement(); $stats = $wpdb->get_results( $query_statement ); // phpcs:ignore // Initialize order stats with default values. $order_stats = [ 'current' => [ 'total_orders' => 0, 'cancelled_orders' => 0, ], 'previous' => [ 'total_orders' => 0, 'cancelled_orders' => 0, ], ]; foreach ( $stats as $stat ) { $order_stats[ $stat->period ] = [ 'total_orders' => (int) ( $stat->total_orders ?? 0 ), 'cancelled_orders' => (int) ( $stat->cancelled_orders ?? 0 ), ]; } return apply_filters( 'dokan_admin_dashboard_order_cancellation_rate_data', array_merge( $order_stats, [ 'icon' => 'BanknoteX', 'title' => esc_html__( 'Order Cancellation Rate', 'dokan-lite' ), 'tooltip' => esc_html__( 'Rate of orders which got cancelled in the time period', 'dokan-lite' ), 'position' => 80, ] ), $date_range ); } /** * Get new products data for current and previous periods. * * @since 4.1.0 * * @param array $date_range Array containing date range information. * * @return array New products' data. */ public function get_new_products_data( array $date_range ): array { $advertise_product = get_option( 'dokan_advertisement_product_id', 0 ); $product_types = $this->get_filtered_product_types(); $args = [ 'status' => 'publish', 'type' => array_keys( $product_types ), 'limit' => -1, 'return' => 'ids', 'post__not_in' => [ $advertise_product ], ]; $current_products = wc_get_products( array_merge( $args, [ 'date_created' => $date_range['current_month_start'] . '...' . $date_range['current_month_end'] ] ) ); $previous_products = wc_get_products( array_merge( $args, [ 'date_created' => $date_range['previous_month_start'] . '...' . $date_range['previous_month_end'] ] ) ); return apply_filters( 'dokan_admin_dashboard_new_products_data', [ 'icon' => 'Box', 'current' => count( $current_products ), 'previous' => count( $previous_products ), 'title' => esc_html__( 'New Products', 'dokan-lite' ), 'tooltip' => esc_html__( 'New products published in the month', 'dokan-lite' ), 'position' => 10, ], $date_range ); } /** * Get active vendors data for current and previous periods using VendorOrderStatsStore. * * @since 4.1.0 * * @param array $date_range Array containing date range information. * * @return array Active vendors data. */ public function get_active_vendors_data( array $date_range ): array { // Use VendorOrderStatsStore for dokan_order_stats queries $current_count = $this->vendor_order_stats_store->get_active_vendors_count( $date_range['current_month_start'], $date_range['current_month_end'] ); $previous_count = $this->vendor_order_stats_store->get_active_vendors_count( $date_range['previous_month_start'], $date_range['previous_month_end'] ); return apply_filters( 'dokan_admin_dashboard_active_vendors_data', [ 'icon' => 'UserCog', 'current' => (int) $current_count, 'previous' => (int) $previous_count, 'title' => esc_html__( 'Active Vendors', 'dokan-lite' ), 'tooltip' => esc_html__( 'Vendors sold minimum 1 product', 'dokan-lite' ), 'position' => 20, ], $date_range ); } /** * Get new vendor registration data for current and previous periods. * * @since 4.1.0 * * @param array $date_range Array containing date range information. * * @return array New vendor registration data. */ public function get_new_vendor_registration_data( array $date_range ): array { global $wpdb; $this->clear_all_clauses(); $this->add_sql_clause( 'select', $wpdb->prepare( 'SUM(CASE WHEN u.user_registered BETWEEN %s AND %s THEN 1 ELSE 0 END) as current_count,', $date_range['current_month_start'] . ' 00:00:00', $date_range['current_month_end'] . ' 23:59:59' ) ); $this->add_sql_clause( 'select', $wpdb->prepare( 'SUM(CASE WHEN u.user_registered BETWEEN %s AND %s THEN 1 ELSE 0 END) as previous_count', $date_range['previous_month_start'] . ' 00:00:00', $date_range['previous_month_end'] . ' 23:59:59' ) ); $this->add_sql_clause( 'from', "{$wpdb->users} u" ); $this->add_sql_clause( 'join', "JOIN {$wpdb->usermeta} um ON u.ID = um.user_id" ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND u.user_registered BETWEEN %s AND %s', $date_range['previous_month_start'] . ' 00:00:00', $date_range['current_month_end'] . ' 23:59:59' ) ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND um.meta_key = %s', $wpdb->prefix . 'capabilities' ) ); $this->add_sql_clause( 'where', $wpdb->prepare( ' AND um.meta_value LIKE %s', '%seller%' ) ); $query_statement = $this->get_query_statement(); $result = $wpdb->get_row( $query_statement, ARRAY_A ); // phpcs:ignore return apply_filters( 'dokan_admin_dashboard_new_vendor_registration_data', [ 'icon' => 'UserRoundPlus', 'current' => (int) ( $result['current_count'] ?? 0 ), 'previous' => (int) ( $result['previous_count'] ?? 0 ), 'title' => esc_html__( 'New Vendor Registration', 'dokan-lite' ), 'tooltip' => esc_html__( 'Total vendors who got registered in the time period', 'dokan-lite' ), 'position' => 30, ], $date_range ); } /** * Get monthly overview data including new customers and order stats. * * @since 4.1.0 * * @param array $date_range Array containing date range information. * * @return array Monthly overview data. */ public function get_monthly_overview( array $date_range ): array { return apply_filters( 'dokan_admin_dashboard_monthly_overview', [ 'new_products' => $this->get_new_products_data( $date_range ), 'active_vendors' => $this->get_active_vendors_data( $date_range ), 'new_vendor_registration' => $this->get_new_vendor_registration_data( $date_range ), 'new_customers' => $this->get_new_customers_data( $date_range ), 'order_cancellation_rate' => $this->get_order_cancellation_rate_data( $date_range ), 'recurring_customers' => $this->get_recurring_customers_data( $date_range ), ], $date_range ); } /** * Get top performing vendors using VendorOrderStatsStore. * * @since 4.1.0 * * @param string $start_date Start date in Y-m-d format. Optional. * @param string $end_date End date in Y-m-d format. Optional. * @param int $limit Number of vendors to retrieve. Default 5. * * @return array Array of vendor data with sales metrics. */ public function get_top_performing_vendors( string $start_date, string $end_date, int $limit = 5 ): array { // Use VendorOrderStatsStore for dokan_order_stats queries return $this->vendor_order_stats_store->get_top_performing_vendors( $start_date, $end_date, $limit ); } /** * Get vendor metrics data. * * @since 4.1.0 * * @param string $start_date Start date in Y-m-d format. * @param string $end_date End date in Y-m-d format. * * @return array Vendor metrics data. */ public function get_vendor_metrics( string $start_date, string $end_date ): array { return apply_filters( 'dokan_rest_admin_dashboard_vendor_metrics_data', [], $start_date, $end_date ); } /** * Get filtered product types. * * @since 4.1.0 * * @return array Filtered product types. */ public function get_filtered_product_types(): array { $exclude_product_types = apply_filters( 'dokan_rest_admin_dashboard_exclude_product_types', [ 'product_pack', 'subscription' ] ); return array_filter( wc_get_product_types(), function ( $type ) use ( $exclude_product_types ) { return ! in_array( $type, $exclude_product_types, true ); } ); } }