PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.19
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.19
1.10.19 1.10.18 1.10.17 1.10.16 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 All 163 releases
← All changes | includes/Sync/Collection_Rules.php +79 -7 1.10.161.10.19 View file →
@@ -34,11 +34,11 @@
34 34 * collection yields an EMPTY plan whose `filter()` is the identity and whose
35 35 * `around()` merely runs its callable. That is the adoption mechanism: a collection
36 36 * can be routed through the module before it has any rows, and nothing changes.
37 37 * - `Collection_Rules_Plan::filter()` — the direct lane. Type-preserving clause
38 - * bodies; it NEVER touches global filter state, so a v1 controller keeps owning its
39 - * own `add_filter` topology (Pro subclasses those callbacks).
40 - * - `Collection_Rules_Plan::around()` — the proxy lane, and the ONLY install path.
38 + * bodies; it NEVER touches global filter state. Legacy callbacks can still delegate
39 + * to it; collection reads use `around()` for search and visibility.
40 + * - `Collection_Rules_Plan::around()` — the scoped read lanes, and the ONLY install path.
41 41 * Callbacks are installed, the forward runs, and every binding is unwound in reverse
42 42 * in a `finally`.
43 43 * - `orderby_enum()` / `collection_params()` — schema PROJECTIONS of the same rows, so
44 44 * the REST schema and the proxy's claim list cannot disagree with the clause logic.
@@ -78,9 +78,30 @@
78 78 * @var string
79 79 */
80 80 public const STORAGE_HPOS = 'hpos';
81 81
82 + /** Shared search bound; over-limit policies remain collection-specific. */
83 + public const SEARCH_TERM_CAP = 10;
84 +
82 85 /**
86 + * Split literal terms. Orders now also drop Unicode control characters between terms.
87 + *
88 + * Existing defaults: product phrase null uses WP_Query terms; orders use the supplied
89 + * string; variation args/discovery default to '', and validation casts null to ''.
90 + * Product phrases collapse over-cap terms; orders slice; v2 variations reject.
91 + * Every product/variation lane uses this splitter; v1 collapses over-cap, v2 flat variations reject.
92 + * Malformed UTF-8 yields an empty array, including offset-capture callers.
93 + *
94 + * @param string $search Search text.
95 + * @param int $flags Split flags.
96 + * @return array
97 + */
98 + public static function search_terms( $search, $flags = PREG_SPLIT_NO_EMPTY ) {
99 + $terms = preg_split( '/[\s\p{Z}\p{C}]+/u', $search, -1, $flags );
100 + return false === $terms ? array() : $terms;
101 + }
102 +
103 + /**
83 104 * Legacy storage — `wp_posts` plus `wp_postmeta`.
84 105 *
85 106 * @var string
86 107 */
@@ -86,9 +107,9 @@
86 107 */
87 108 public const STORAGE_POSTS = 'posts';
88 109
89 110 /**
90 - * Memoized plans, keyed by collection, request identity, storage and param map.
111 + * Memoized plans, keyed by collection, request identity/content, storage and param map.
91 112 *
92 113 * Each entry is `array( WP_REST_Request, Collection_Rules_Plan )`; the request is
93 114 * kept so a recycled `spl_object_id` can never serve another request's plan.
94 115 *
@@ -117,9 +138,9 @@
117 138 * @return Collection_Rules_Plan
118 139 */
119 140 public static function for_request( string $collection, WP_REST_Request $request, array $param_map = array(), ?string $storage = null ) {
120 141 $storage = $storage ?? self::detect_storage( $collection );
121 - $key = $collection . '|' . spl_object_id( $request ) . '|' . $storage . '|' . md5( (string) wp_json_encode( $param_map ) );
142 + $key = $collection . '|' . spl_object_id( $request ) . '|' . $storage . '|' . md5( (string) wp_json_encode( array( $param_map, $request->get_route(), $request->get_params() ) ) );
122 143
123 144 if ( isset( self::$plans[ $key ] ) && self::$plans[ $key ][0] === $request ) {
124 145 return self::$plans[ $key ][1];
125 146 }
@@ -216,13 +237,27 @@
216 237 * @internal
217 238 *
218 239 * @param string $collection Collection slug.
219 240 *
220 - * @return array{sorts?: array<string, array>, filters?: array<string, array>}
241 + * @return array{sorts?: array<string, array>, filters?: array<string, array>, search?: array<string, mixed>, visibility?: array<string, mixed>}
221 242 */
222 243 public static function rules( string $collection ): array {
223 244 $rules = array(
224 245 'orders' => array(
246 + 'search' => array(
247 + 'param' => 'search',
248 + 'term_cap' => self::SEARCH_TERM_CAP,
249 + 'rank_exact' => false,
250 + 'carriers' => array( 'id', 'billing_email', 'first_name', 'last_name', 'company', 'email', 'phone' ),
251 + 'hpos' => array(
252 + 'orders' => array( 'id', 'billing_email' ),
253 + 'addresses' => array( 'first_name', 'last_name', 'company', 'email', 'phone' ),
254 + ),
255 + 'posts' => array(
256 + 'id' => 'ID',
257 + 'meta' => array( '_billing_first_name', '_billing_last_name', '_billing_company', '_billing_email', '_billing_phone' ),
258 + ),
259 + ),
225 260 'sorts' => array(
226 261 'status' => array(
227 262 'hpos' => array( 'column' => 'status' ),
228 263 'posts' => array( 'posts_orderby' => 'post_status' ),
@@ -311,11 +346,48 @@
311 346 * Neither collection is ever HPOS — both are posts on every store — so there is
312 347 * no `hpos` half to these rows.
313 348 */
314 349 'products' => array(
350 + 'search' => array(
351 + 'param' => 'search',
352 + // Both product lanes use the literal splitter and collapse over-cap terms to the phrase.
353 + 'term_cap' => self::SEARCH_TERM_CAP,
354 + 'rank_exact' => true,
355 + 'carriers' => array_merge( array( 'post_title' ), Barcode_Field::search_keys() ),
356 + 'posts' => array( 'meta' => Barcode_Field::search_keys() ),
357 + 'hpos' => null,
358 + ),
359 + 'visibility' => array(
360 + 'type' => array(
361 + 'direct' => Pos_Visibility::PRODUCTS,
362 + 'proxy' => Pos_Visibility::CATALOG,
363 + ),
364 + 'where_backstop' => true,
365 + ),
315 366 'sorts' => self::catalog_meta_sorts(),
316 367 ),
317 368 'variations' => array(
369 + 'search' => array(
370 + 'param' => 'search',
371 + 'term_cap' => self::SEARCH_TERM_CAP,
372 + 'rank_exact' => false,
373 + 'carriers' => Barcode_Field::search_keys(),
374 + 'posts' => array( 'meta' => Barcode_Field::search_keys() ),
375 + 'hpos' => null,
376 + 'query' => 'meta_query',
377 + 'exact_sku_param' => 'sku',
378 + // Both lanes split literal terms; v1 keeps over-cap collapse/EXISTS, v2 rejects/meta_query.
379 + 'lanes' => array(
380 + 'direct' => array(
381 + 'query' => 'wp_terms',
382 + 'exact_sku_param' => null,
383 + ),
384 + ),
385 + ),
386 + 'visibility' => array(
387 + 'type' => Pos_Visibility::VARIATIONS,
388 + 'where_backstop' => true,
389 + ),
318 390 'sorts' => self::catalog_meta_sorts(),
319 391 ),
320 392
321 393 /*
@@ -402,9 +474,9 @@
402 474 * @param string $collection Collection slug.
403 475 *
404 476 * @return string
405 477 */
406 - private static function detect_storage( string $collection ): string {
478 + public static function detect_storage( string $collection ): string {
407 479 if ( 'orders' !== $collection ) {
408 480 return self::STORAGE_POSTS;
409 481 }
410 482