PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
← All changes | admin-core/api/flows.php +118 -2 3.1.0 → 3.3.0 View file →
@@ -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( '&nbsp;', '', wc_price( (float) $row->revenue ) );
471 + }
472 + }
473 +
474 + return $revenue;
359 475 }
360 476 }