| @@ -184,8 +184,17 @@ | ||
| 184 | 184 | 'items' => array(), |
| 185 | 185 | 'pagination' => array(), |
| 186 | 186 | ); |
| 187 | 187 | |
| 188 | + // On free, compute revenue for the whole page in a single grouped query instead of one query per flow. | |
| 189 | + $is_free_revenue = ! _is_cartflows_pro() && ! is_wcf_pro_plan(); | |
| 190 | + $revenue_map = array(); | |
| 191 | + $zero_revenue = str_replace( ' ', '', wc_price( 0 ) ); | |
| 192 | + | |
| 193 | + if ( $is_free_revenue && ! empty( $result->posts ) ) { | |
| 194 | + $revenue_map = $this->get_flows_revenue( wp_list_pluck( $result->posts, 'ID' ) ); | |
| 195 | + } | |
| 196 | + | |
| 188 | 197 | if ( $result->have_posts() ) { |
| 189 | 198 | while ( $result->have_posts() ) { |
| 190 | 199 | $result->the_post(); |
| 191 | 200 | |
| @@ -246,10 +255,10 @@ | ||
| 246 | 255 | ), |
| 247 | 256 | ); |
| 248 | 257 | |
| 249 | 258 | // Fetch the revenue only for free version for the PRO it will be fetched and added by the filter later in the code. |
| 250 | - if ( ! _is_cartflows_pro() && ! is_wcf_pro_plan() ) { | |
| 251 | - $post_data['revenue'] = $this->get_per_flow_revenue( $post->ID ); | |
| 259 | + if ( $is_free_revenue ) { | |
| 260 | + $post_data['revenue'] = isset( $revenue_map[ $post->ID ] ) ? $revenue_map[ $post->ID ] : $zero_revenue; | |
| 252 | 261 | } |
| 253 | 262 | |
| 254 | 263 | $data['items'][] = $post_data; |
| 255 | 264 | } |
| @@ -355,6 +364,113 @@ | ||
| 355 | 364 | } |
| 356 | 365 | } |
| 357 | 366 | |
| 358 | 367 | return str_replace( ' ', '', wc_price( $gross_sale ) ); |
| 368 | + } | |
| 369 | + | |
| 370 | + /** | |
| 371 | + * Get gross revenue for many flows in a single grouped query. | |
| 372 | + * | |
| 373 | + * Replaces the per-flow N+1 lookup; sums completed/processing order totals | |
| 374 | + * grouped by `_wcf_flow_id`, excluding orders placed by flow managers. | |
| 375 | + * | |
| 376 | + * @since 3.1.3 | |
| 377 | + * | |
| 378 | + * @param int[] $flow_ids Flow IDs to fetch revenue for. | |
| 379 | + * @return array<int,string> Map of flow ID => formatted revenue string. | |
| 380 | + */ | |
| 381 | + public function get_flows_revenue( $flow_ids ) { | |
| 382 | + | |
| 383 | + $revenue = array(); | |
| 384 | + | |
| 385 | + // Return if WooCommerce is not active. | |
| 386 | + if ( ! function_exists( 'WC' ) ) { | |
| 387 | + return $revenue; | |
| 388 | + } | |
| 389 | + | |
| 390 | + $flow_ids = array_filter( array_map( 'absint', (array) $flow_ids ) ); | |
| 391 | + if ( empty( $flow_ids ) ) { | |
| 392 | + return $revenue; | |
| 393 | + } | |
| 394 | + | |
| 395 | + global $wpdb; | |
| 396 | + | |
| 397 | + // Exclude orders placed by users who can manage flows (e.g. internal test orders). | |
| 398 | + $excluded_user_ids = array_map( | |
| 399 | + 'absint', | |
| 400 | + get_users( | |
| 401 | + array( | |
| 402 | + 'capability' => 'cartflows_manage_flows_steps', | |
| 403 | + 'fields' => 'ID', | |
| 404 | + ) | |
| 405 | + ) | |
| 406 | + ); | |
| 407 | + | |
| 408 | + $decimals = wc_get_price_decimals(); | |
| 409 | + $flow_placeholder = implode( ',', array_fill( 0, count( $flow_ids ), '%d' ) ); | |
| 410 | + $query_args = $flow_ids; | |
| 411 | + | |
| 412 | + $is_hpos = class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); | |
| 413 | + | |
| 414 | + if ( $is_hpos ) { | |
| 415 | + $order_table = $wpdb->prefix . 'wc_orders'; | |
| 416 | + $order_meta_table = $wpdb->prefix . 'wc_orders_meta'; | |
| 417 | + | |
| 418 | + $exclude_clause = ''; | |
| 419 | + if ( ! empty( $excluded_user_ids ) ) { | |
| 420 | + $exclude_clause = ' AND o.customer_id NOT IN ( ' . implode( ',', array_fill( 0, count( $excluded_user_ids ), '%d' ) ) . ' )'; | |
| 421 | + $query_args = array_merge( $query_args, $excluded_user_ids ); | |
| 422 | + } | |
| 423 | + | |
| 424 | + //phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare | |
| 425 | + $rows = $wpdb->get_results( | |
| 426 | + $wpdb->prepare( | |
| 427 | + "SELECT om.meta_value AS flow_id, ROUND( SUM( o.total_amount ), $decimals ) AS revenue | |
| 428 | + FROM $order_table o | |
| 429 | + INNER JOIN $order_meta_table om ON o.id = om.order_id AND om.meta_key = '_wcf_flow_id' | |
| 430 | + WHERE o.type = 'shop_order' | |
| 431 | + AND o.status IN ( 'wc-completed', 'wc-processing' ) | |
| 432 | + AND om.meta_value IN ( $flow_placeholder ) | |
| 433 | + $exclude_clause | |
| 434 | + GROUP BY om.meta_value", | |
| 435 | + $query_args | |
| 436 | + ) | |
| 437 | + ); | |
| 438 | + //phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare | |
| 439 | + } else { | |
| 440 | + $order_table = $wpdb->prefix . 'posts'; | |
| 441 | + $order_meta_table = $wpdb->prefix . 'postmeta'; | |
| 442 | + | |
| 443 | + $exclude_clause = ''; | |
| 444 | + if ( ! empty( $excluded_user_ids ) ) { | |
| 445 | + $exclude_clause = ' AND ( cu.meta_value IS NULL OR cu.meta_value NOT IN ( ' . implode( ',', array_fill( 0, count( $excluded_user_ids ), '%d' ) ) . ' ) )'; | |
| 446 | + $query_args = array_merge( $query_args, $excluded_user_ids ); | |
| 447 | + } | |
| 448 | + | |
| 449 | + //phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare | |
| 450 | + $rows = $wpdb->get_results( | |
| 451 | + $wpdb->prepare( | |
| 452 | + "SELECT om.meta_value AS flow_id, ROUND( SUM( m.meta_value ), $decimals ) AS revenue | |
| 453 | + FROM $order_table o | |
| 454 | + INNER JOIN $order_meta_table m ON o.ID = m.post_id AND m.meta_key = '_order_total' | |
| 455 | + INNER JOIN $order_meta_table om ON o.ID = om.post_id AND om.meta_key = '_wcf_flow_id' | |
| 456 | + LEFT JOIN $order_meta_table cu ON o.ID = cu.post_id AND cu.meta_key = '_customer_user' | |
| 457 | + WHERE o.post_type = 'shop_order' | |
| 458 | + AND o.post_status IN ( 'wc-completed', 'wc-processing' ) | |
| 459 | + AND om.meta_value IN ( $flow_placeholder ) | |
| 460 | + $exclude_clause | |
| 461 | + GROUP BY om.meta_value", | |
| 462 | + $query_args | |
| 463 | + ) | |
| 464 | + ); | |
| 465 | + //phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare | |
| 466 | + } | |
| 467 | + | |
| 468 | + if ( ! empty( $rows ) && is_array( $rows ) ) { | |
| 469 | + foreach ( $rows as $row ) { | |
| 470 | + $revenue[ absint( $row->flow_id ) ] = str_replace( ' ', '', wc_price( (float) $row->revenue ) ); | |
| 471 | + } | |
| 472 | + } | |
| 473 | + | |
| 474 | + return $revenue; | |
| 359 | 475 | } |
| 360 | 476 | } |