PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
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 1.9.9 All 158 releases
woocommerce-pos / includes / API / Route_Classifier.php

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

174 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS REST route classifier.
4 *
5 * @package WCPOS\WooCommercePOS\API
6 */
7
8 namespace WCPOS\WooCommercePOS\API;
9
10 /**
11 * Classifies WCPOS REST routes without applying authentication or capabilities.
12 *
13 * WordPress matches REST route regexes case-insensitively (WP_REST_Server adds
14 * the `i` flag), so a mixed-case path still dispatches to the real controller.
15 * Every predicate here must therefore compare case-insensitively too — a
16 * case-sensitive comparison would let /WCPOS/V1/... skip the permission gate.
17 */
18 final class Route_Classifier {
19 /**
20 * WCPOS REST namespaces, stored lowercase.
21 *
22 * @var string[]
23 */
24 private $namespaces;
25
26 /**
27 * Routes grouped by permission-gate classification, stored lowercase.
28 * Incoming routes are lowercased before every comparison.
29 *
30 * @var array<string, string[]>
31 */
32 private $classifications = array(
33 'public' => array(),
34 'printer_token' => array(),
35 'admin_op' => array(),
36 'permission_error_passthrough' => array(),
37 'rewrite_exempt' => array(),
38 );
39
40 /**
41 * Constructor.
42 *
43 * @param string[] $namespaces WCPOS REST namespaces.
44 */
45 public function __construct( array $namespaces ) {
46 $this->namespaces = array_map( 'strtolower', array_filter( $namespaces, 'is_string' ) );
47 }
48
49 /**
50 * Merge route classifications discovered during registration.
51 *
52 * @param array<string, string[]> $classifications Route classifications.
53 */
54 public function merge( array $classifications ): void {
55 foreach ( $classifications as $classification => $routes ) {
56 if ( ! isset( $this->classifications[ $classification ] ) || ! \is_array( $routes ) ) {
57 continue;
58 }
59
60 $this->classifications[ $classification ] = array_values(
61 array_unique( array_merge( $this->classifications[ $classification ], array_map( 'strtolower', array_filter( $routes, 'is_string' ) ) ) )
62 );
63 }
64 }
65
66 /**
67 * Check whether a route belongs to a registered WCPOS namespace.
68 *
69 * @param string $route REST route.
70 */
71 public function in_wcpos_namespace( string $route ): bool {
72 $route = strtolower( $route );
73
74 foreach ( $this->namespaces as $namespace ) {
75 if ( 0 === strpos( $route, '/' . $namespace . '/' ) ) {
76 return true;
77 }
78 }
79
80 return false;
81 }
82
83 /**
84 * Check whether a route is public.
85 *
86 * @param string $route REST route.
87 */
88 public function is_public( string $route ): bool {
89 return $this->is_exact_match( 'public', $route );
90 }
91
92 /**
93 * Check whether a route authenticates with a printer token.
94 *
95 * Matches the registered route exactly or as a slash-delimited prefix:
96 * printer polls also arrive on path-credential URLs such as
97 * cloudprnt/<printer_id>/<pt> (Star URL-encodes query strings), and those
98 * concrete routes must stay exempt from the capability gate without
99 * widening the match to sibling routes that merely share a name prefix.
100 *
101 * @param string $route REST route.
102 */
103 public function is_printer_token( string $route ): bool {
104 $route = strtolower( $route );
105
106 if ( $this->is_exact_match( 'printer_token', $route ) ) {
107 return true;
108 }
109
110 foreach ( $this->classifications['printer_token'] as $base ) {
111 if ( 0 === strpos( $route, $base . '/' ) ) {
112 return true;
113 }
114 }
115
116 return false;
117 }
118
119 /**
120 * Check whether a route is an out-of-band admin operation.
121 *
122 * @param string $route REST route.
123 */
124 public function is_admin_op( string $route ): bool {
125 return $this->is_exact_match( 'admin_op', $route );
126 }
127
128 /**
129 * Check whether a route supplies its own permission error.
130 *
131 * @param string $route REST route.
132 */
133 public function is_permission_error_passthrough( string $route ): bool {
134 return $this->is_prefix_match( 'permission_error_passthrough', $route );
135 }
136
137 /**
138 * Check whether a route bypasses include/exclude rewriting.
139 *
140 * @param string $route REST route.
141 */
142 public function is_rewrite_exempt( string $route ): bool {
143 return $this->is_prefix_match( 'rewrite_exempt', $route );
144 }
145
146 /**
147 * Check for an exact route classification match.
148 *
149 * @param string $classification Classification key.
150 * @param string $route REST route.
151 */
152 private function is_exact_match( string $classification, string $route ): bool {
153 return \in_array( strtolower( $route ), $this->classifications[ $classification ], true );
154 }
155
156 /**
157 * Check for a route classification prefix match.
158 *
159 * @param string $classification Classification key.
160 * @param string $route REST route.
161 */
162 private function is_prefix_match( string $classification, string $route ): bool {
163 $route = strtolower( $route );
164
165 foreach ( $this->classifications[ $classification ] as $prefix ) {
166 if ( 0 === strpos( $route, $prefix ) ) {
167 return true;
168 }
169 }
170
171 return false;
172 }
173 }
174