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 / Services / Client_Signal.php

Client_Signal.php in WCPOS – Point of Sale (POS) plugin for WooCommerce trunk, at includes/Services/Client_Signal.php

100 lines 3.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS client protocol signal telemetry.
4 *
5 * @package WCPOS\WooCommercePOS\Services
6 */
7
8 namespace WCPOS\WooCommercePOS\Services;
9
10 use WP_REST_Request;
11
12 /** Reads and records the client protocol signal carried by REST requests. */
13 final class Client_Signal {
14 /**
15 * Read a sanitized client signal from its header or query transport.
16 *
17 * @param WP_REST_Request $request Request to inspect.
18 * @return array{protocol: ?string, platform: ?string, app_version: ?string, channel: 'header'|'query'|'none'}
19 */
20 public static function read( WP_REST_Request $request ): array {
21 $params = $request->get_query_params();
22 $protocol_header = $request->get_header( 'X-WCPOS-Protocol' );
23 $client_header = $request->get_header( 'X-WCPOS-Client' );
24 $has_header = null !== $protocol_header || null !== $client_header;
25 $has_query = array_key_exists( 'wcpos_protocol', $params ) || array_key_exists( 'wcpos_client', $params );
26
27 $protocol_raw = null !== $protocol_header ? $protocol_header : ( $params['wcpos_protocol'] ?? null );
28 $client_raw = null !== $client_header ? $client_header : ( $params['wcpos_client'] ?? null );
29 $client = self::sanitize_client( $client_raw );
30
31 return array(
32 'protocol' => self::sanitize_protocol( $protocol_raw ),
33 'platform' => $client[0],
34 'app_version' => $client[1],
35 'channel' => $has_header ? 'header' : ( $has_query ? 'query' : 'none' ),
36 );
37 }
38
39 /**
40 * Record the signal once daily for each distinct signal tuple.
41 *
42 * @param WP_REST_Request $request Request to inspect.
43 */
44 public static function record( WP_REST_Request $request ): void {
45 $properties = self::read( $request );
46 $dedup_key = 'pos_client_signal:' . md5( wp_json_encode( $properties ) );
47 Analytics::instance()->capture_once( 'pos_client_signal', $properties, $dedup_key );
48 }
49
50 /**
51 * Sanitize a protocol value to at most eight digits.
52 *
53 * @param mixed $value Raw protocol value.
54 */
55 private static function sanitize_protocol( $value ): ?string {
56 if ( ! is_scalar( $value ) ) {
57 return null;
58 }
59
60 $value = substr( (string) preg_replace( '/[^0-9]/', '', (string) $value ), 0, 8 );
61 return '' === $value ? null : $value;
62 }
63
64 /**
65 * Split and sanitize a client value.
66 *
67 * @param mixed $value Raw client value.
68 * @return array{0: ?string, 1: ?string} Sanitized client parts.
69 */
70 private static function sanitize_client( $value ): array {
71 if ( ! is_scalar( $value ) ) {
72 return array( null, null );
73 }
74
75 $value = (string) $value;
76 if ( false === strpos( $value, '/' ) ) {
77 return array( self::sanitize_client_part( $value ), null );
78 }
79
80 $parts = explode( '/', $value, 2 );
81 $platform = self::sanitize_client_part( $parts[0] );
82 $app_version = self::sanitize_client_part( $parts[1] );
83 if ( null === $platform || null === $app_version ) {
84 return array( self::sanitize_client_part( $value ), null );
85 }
86
87 return array( $platform, $app_version );
88 }
89
90 /**
91 * Sanitize one client component to its 32-character wire shape.
92 *
93 * @param string $value Raw client component.
94 */
95 private static function sanitize_client_part( string $value ): ?string {
96 $value = substr( (string) preg_replace( '/[^a-z0-9._-]/', '', strtolower( $value ) ), 0, 32 );
97 return '' === $value ? null : $value;
98 }
99 }
100