| @@ -1,5 +1,5 @@ | ||
| 1 | -<?php | |
| 1 | +<?php //phpcs:disable | |
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | 4 | * OrderQuery — focused queries for order data. |
| 5 | 5 | * |
| @@ -205,13 +205,18 @@ | ||
| 205 | 205 | /** |
| 206 | 206 | * Resolves the SQL sort column expression, direction, and the JOINs the |
| 207 | 207 | * sort expression needs. |
| 208 | 208 | * |
| 209 | - * Whitelist: order_date, order_total, discount_amount. Totals/discounts are | |
| 210 | - * cast to DECIMAL so sorting is numeric, not lexicographic. The order_stats | |
| 211 | - * and discount-meta JOINs are emitted only when the chosen sort actually | |
| 212 | - * references them — display amounts are resolved separately in PHP. | |
| 209 | + * Whitelist: order_date, order_total, discount_amount, quantity. Totals/discounts | |
| 210 | + * are cast to DECIMAL so sorting is numeric, not lexicographic. Quantity sums the | |
| 211 | + * order's line-item quantities from wc_order_product_lookup. The order_stats, | |
| 212 | + * discount-meta, and product-lookup JOINs are emitted only when the chosen sort | |
| 213 | + * actually references them — display amounts are resolved separately in PHP. | |
| 213 | 214 | * |
| 215 | + * The product-lookup JOIN is safe against SUM inflation because the WHERE clause | |
| 216 | + * already restricts order_meta to a single disco_campaign row per order via | |
| 217 | + * get_campaign_dedup_condition(). | |
| 218 | + * | |
| 214 | 219 | * @param array $args orderby, order. |
| 215 | 220 | * @param array $context From build_list_context(). |
| 216 | 221 | * @return array { column: string, direction: 'ASC'|'DESC', joins: string } |
| 217 | 222 | */ |
| @@ -220,14 +225,15 @@ | ||
| 220 | 225 | |
| 221 | 226 | $tables = $context['tables']; |
| 222 | 227 | $clauses = $context['clauses']; |
| 223 | 228 | $id_col = $context['id_col']; |
| 224 | - $sort = $this->resolve_sort( $args, array( 'order_date', 'order_total', 'discount_amount' ), 'order_date' ); | |
| 229 | + $sort = $this->resolve_sort( $args, array( 'order_date', 'order_total', 'discount_amount', 'quantity' ), 'order_date' ); | |
| 225 | 230 | |
| 226 | 231 | $orderby_column_map = array( |
| 227 | 232 | 'order_date' => $clauses['date_col'], |
| 228 | 233 | 'order_total' => "CAST(({$clauses['total_expr']} - COALESCE(order_stats.shipping_total, 0)) AS DECIMAL(10,2))", |
| 229 | 234 | 'discount_amount' => "CAST({$clauses['discount_expr']} AS DECIMAL(10,2))", |
| 235 | + 'quantity' => 'COALESCE(SUM(sort_qty_lookup.product_qty), 0)', | |
| 230 | 236 | ); |
| 231 | 237 | |
| 232 | 238 | $sort_join_map = array( |
| 233 | 239 | 'order_date' => '', |
| @@ -232,8 +238,9 @@ | ||
| 232 | 238 | $sort_join_map = array( |
| 233 | 239 | 'order_date' => '', |
| 234 | 240 | 'order_total' => "LEFT JOIN {$wpdb->prefix}wc_order_stats order_stats ON order_stats.order_id = order_meta.{$id_col}", |
| 235 | 241 | 'discount_amount' => $clauses['discount_join'], |
| 242 | + 'quantity' => "LEFT JOIN {$tables['product_lookup']} sort_qty_lookup ON sort_qty_lookup.order_id = order_meta.{$id_col}", | |
| 236 | 243 | ); |
| 237 | 244 | |
| 238 | 245 | return array( |
| 239 | 246 | 'column' => $orderby_column_map[ $sort['orderby'] ], |