PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 1.9.12 1.9.11 1.9.10 All 159 releases
woocommerce-pos / includes / API / V2 / Proxy / Stable_Sort.php

Stable_Sort.php in WCPOS – Point of Sale (POS) plugin for WooCommerce trunk, at includes/API/V2/Proxy/Stable_Sort.php

80 lines 2.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Stable secondary sort keys for the catalog proxy.
4 *
5 * @package WCPOS\WooCommercePOS\API\V2\Proxy
6 */
7
8 namespace WCPOS\WooCommercePOS\API\V2\Proxy;
9
10 /**
11 * Pins a deterministic id tiebreak under mutable, tie-prone sort keys.
12 *
13 * ORDER BY a tied column alone gives MySQL no total order ACROSS separate
14 * offset queries, so a row tied at a page boundary can appear on two pages or
15 * on neither while the POS client walks a multi-page window (mono#1372). The
16 * client renders ties in ascending id order (its declared tiebreak), so the
17 * secondary key here is always `id ASC` — wire pages and rendered rows agree
18 * exactly, whatever the primary direction.
19 */
20 final class Stable_Sort {
21 /**
22 * Post orderbys whose values routinely tie (duplicate titles, shared
23 * import timestamps). `id`/`include` are total orders already.
24 */
25 private const TIED_POST_ORDERBYS = array( 'title', 'date', 'modified' );
26
27 /**
28 * Rewrite a tied post-query orderby to its array form carrying the id tiebreak.
29 *
30 * @param array $args WP_Query args prepared by the wc/v3 controller.
31 *
32 * @return array
33 */
34 public static function with_post_id_tiebreak( array $args ): array {
35 $orderby = $args['orderby'] ?? null;
36 // WC's CRUD controller already appends ' ID' for date/modified ("for
37 // consistency with pagination") — but in the space-string form, where the
38 // GLOBAL direction applies to every field, so a desc sort ties by ID DESC
39 // while the client renders ties by id ASC. Normalize that form back to the
40 // bare key so the rewrite below pins ID ASC either way.
41 if ( \is_string( $orderby ) && ' ID' === substr( $orderby, -3 ) ) {
42 $orderby = substr( $orderby, 0, -3 );
43 }
44 if ( ! \is_string( $orderby ) || ! \in_array( $orderby, self::TIED_POST_ORDERBYS, true ) ) {
45 return $args;
46 }
47 $order = 'ASC' === strtoupper( (string) ( $args['order'] ?? 'DESC' ) ) ? 'ASC' : 'DESC';
48 $args['orderby'] = array(
49 $orderby => $order,
50 'ID' => 'ASC',
51 );
52
53 return $args;
54 }
55
56 /**
57 * Append the term_id tiebreak to a tie-prone term-query ORDER BY clause.
58 *
59 * The get_terms API has no array-orderby form, so the tiebreak lands at the clause
60 * level. Only `name` can tie (same-named children under different parents);
61 * the direction moves INTO the orderby clause because WP appends the free
62 * `order` to the LAST expression only.
63 *
64 * @param array $clauses WP_Term_Query SQL clauses.
65 *
66 * @return array
67 */
68 public static function with_term_id_tiebreak( array $clauses ): array {
69 $orderby = (string) ( $clauses['orderby'] ?? '' );
70 if ( 'ORDER BY t.name' !== $orderby ) {
71 return $clauses;
72 }
73 $order = 'DESC' === strtoupper( (string) ( $clauses['order'] ?? 'ASC' ) ) ? 'DESC' : 'ASC';
74 $clauses['orderby'] = $orderby . ' ' . $order . ', t.term_id';
75 $clauses['order'] = 'ASC';
76
77 return $clauses;
78 }
79 }
80