PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.1
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.1
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
woocommerce-pos / includes / API.php

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

650 lines 24.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WCPOS REST API class.
4 *
5 * @author Paul Kilmurray <paul@kilbot.com>
6 *
7 * @see http://wcpos.com
8 * @package WCPOS\WooCommercePOS
9 */
10
11 namespace WCPOS\WooCommercePOS;
12
13 use WCPOS\WooCommercePOS\Services\Auth;
14 use WCPOS\WooCommercePOS\Services\Settings as SettingsService;
15 use WP_HTTP_Response;
16 use WP_REST_Request;
17 use WP_REST_Response;
18 use WP_REST_Server;
19
20 /**
21 * API class.
22 */
23 class API {
24 /**
25 * WCPOS REST API namespaces.
26 */
27 public const ROUTE_NAMESPACES = array( 'wcpos/v1', 'wcpos/v2' );
28
29 /**
30 * WCPOS REST API namespaces and endpoints.
31 *
32 * @var array
33 */
34 protected $controllers = array();
35
36 /**
37 * Map of route patterns to controller keys.
38 * Built during register_routes() for use in rest_dispatch_request().
39 *
40 * @var array<string, string>
41 */
42 protected $route_map = array();
43
44 /**
45 * Route permission-gate classifier.
46 *
47 * @var API\Route_Classifier
48 */
49 protected $route_classifier;
50
51 /**
52 * Flag to check if authentication has been checked.
53 *
54 * @var bool
55 */
56 protected $is_auth_checked = false;
57
58 /**
59 * Flag to track whether WCPOS successfully authenticated the current request
60 * via its own Bearer token. Used to suppress errors from third-party JWT
61 * plugins that inspected the same Authorization header but could not validate
62 * a WCPOS-issued token with their own secret.
63 *
64 * @var bool
65 */
66 protected $authenticated_via_wcpos = false;
67
68 /**
69 * Constructor.
70 */
71 public function __construct() {
72 $this->register_routes();
73
74 /*
75 * Adds authentication to for JWT bearer tokens
76 * - We run determine_current_user at 20 to allow other plugins to run first
77 */
78 add_filter( 'determine_current_user', array( $this, 'determine_current_user' ), 20 );
79 add_filter( 'rest_authentication_errors', array( $this, 'rest_authentication_errors' ), 50, 1 );
80
81 // Adds info about the WordPress install.
82 add_filter( 'rest_index', array( $this, 'rest_index' ), 10, 1 );
83
84 // These filters allow changes to the WC REST API response.
85 add_filter( 'rest_dispatch_request', array( $this, 'rest_dispatch_request' ), 10, 4 );
86 add_filter( 'rest_pre_dispatch', array( $this, 'rest_pre_dispatch' ), 10, 3 );
87 add_filter( 'rest_post_dispatch', array( $this, 'rest_post_dispatch' ), 10, 3 );
88 }
89
90 /**
91 * Get the WCPOS REST API namespaces.
92 *
93 * @return string[] REST API namespaces.
94 */
95 public function get_route_namespaces(): array {
96 /**
97 * Filter the list of namespaces used in the WCPOS REST API.
98 *
99 * This filter is strictly additive: plugins can register additional WCPOS REST
100 * API namespaces, but the core namespaces cannot be removed — the central
101 * permission gate must keep covering every registered core route. Controllers
102 * remain responsible for declaring any special route classifications within
103 * added namespaces.
104 *
105 * @since 1.10.0
106 *
107 * @param string[] $namespaces REST API namespaces.
108 */
109 $namespaces = apply_filters( 'woocommerce_pos_rest_namespaces', self::ROUTE_NAMESPACES );
110
111 return array_values( array_unique( array_merge( self::ROUTE_NAMESPACES, (array) $namespaces ) ) );
112 }
113
114 /**
115 * Register routes for all controllers.
116 */
117 public function register_routes(): void {
118 $route_namespaces = $this->get_route_namespaces();
119 $this->route_classifier = new API\Route_Classifier( $route_namespaces );
120
121 /**
122 * Filter the list of controller classes used in the WCPOS REST API.
123 *
124 * This filter allows customizing or extending the set of controller classes that handle
125 * REST API routes for the WCPOS. By filtering these controllers, plugins can
126 * modify existing endpoints or add new controllers for additional functionality.
127 * Core legacy controllers use their versioned WCPOS\WooCommercePOS\API\V1 FQCNs.
128 *
129 * @since 1.5.0
130 *
131 * @param array $controllers Associative array of controller identifiers to their corresponding class names.
132 * - 'auth' => Fully qualified name of the class handling authentication.
133 * - 'settings' => Fully qualified name of the class handling settings.
134 * - 'cashier' => Fully qualified name of the class handling cashier management.
135 * - 'products' => Fully qualified name of the class handling products.
136 * - 'product_variations' => Fully qualified name of the class handling product variations.
137 * - 'orders' => Fully qualified name of the class handling orders.
138 * - 'customers' => Fully qualified name of the class handling customers.
139 * - 'product_tags' => Fully qualified name of the class handling product tags.
140 * - 'product_categories' => Fully qualified name of the class handling product categories.
141 * - 'taxes' => Fully qualified name of the class handling taxes.
142 * - 'shipping_methods' => Fully qualified name of the class handling shipping methods.
143 * - 'tax_classes' => Fully qualified name of the class handling tax classes.
144 * - 'order_statuses' => Fully qualified name of the class handling order statuses.
145 */
146 $classes = apply_filters(
147 'woocommerce_pos_rest_api_controllers',
148 array(
149 // WCPOS rest api controllers.
150 'auth' => API\V1\Auth::class,
151 'settings' => API\V1\Settings::class,
152 'cashier' => API\V1\Cashier::class,
153 'templates' => API\V1\Templates_Controller::class,
154 'receipts' => API\V1\Receipts_Controller::class,
155 'print_jobs' => API\V1\Print_Jobs_Controller::class,
156
157 // TODO: remove this?
158 'stores' => API\V1\Stores::class,
159 'extensions' => API\V1\Extensions::class,
160 'logs' => API\V1\Logs::class,
161 'payment_gateways' => API\V1\Payment_Gateways::class,
162 'gateway_bootstrap' => API\V1\Gateway_Bootstrap_Controller::class,
163 'checkout' => API\V1\Checkout_Controller::class,
164
165 // extend WC REST API controllers.
166 'products' => API\V1\Products_Controller::class,
167 'product_variations' => API\V1\Product_Variations_Controller::class,
168 'orders' => API\V1\Orders_Controller::class,
169 'customers' => API\V1\Customers_Controller::class,
170 'product_tags' => API\V1\Product_Tags_Controller::class,
171 'product_categories' => API\V1\Product_Categories_Controller::class,
172 'product_brands' => API\V1\Product_Brands_Controller::class,
173 'coupons' => API\V1\Coupons_Controller::class,
174 'taxes' => API\V1\Taxes_Controller::class,
175 'shipping_methods' => API\V1\Shipping_Methods_Controller::class,
176 'tax_classes' => API\V1\Tax_Classes_Controller::class,
177 'order_statuses' => API\V1\Data_Order_Statuses_Controller::class,
178 )
179 );
180
181 /**
182 * Filter the wcpos/v2 service pass-through controllers (additive to the
183 * frozen v1 surface — the legacy data controllers stay v1-only).
184 *
185 * Extensions that replace a v1 service through
186 * `woocommerce_pos_rest_api_controllers` must carry their service onto
187 * the v2 surface here with their own pass-through subclass (override
188 * `$namespace = 'wcpos/v2'`), exactly as core does — the v2 map is not
189 * derived from the v1 map, so a v1 replacement alone leaves the v2
190 * twin serving core behavior.
191 *
192 * @since 1.10.0
193 *
194 * @param array $controllers Associative array of v2 service controller class names.
195 */
196 $v2_classes = apply_filters(
197 'woocommerce_pos_rest_api_v2_controllers',
198 array(
199 'ping' => API\V2\Ping::class,
200 'echo_probe' => API\V2\Echo_Probe::class,
201 'site' => API\V2\Site::class,
202 'auth' => API\V2\Auth::class,
203 'settings' => API\V2\Settings::class,
204 'cashier' => API\V2\Cashier::class,
205 'templates' => API\V2\Templates_Controller::class,
206 'receipts' => API\V2\Receipts_Controller::class,
207 'print_jobs' => API\V2\Print_Jobs_Controller::class,
208 'stores' => API\V2\Stores::class,
209 'extensions' => API\V2\Extensions::class,
210 'logs' => API\V2\Logs::class,
211 'payment_gateways' => API\V2\Payment_Gateways::class,
212 'gateway_bootstrap' => API\V2\Gateway_Bootstrap_Controller::class,
213 'checkout' => API\V2\Checkout_Controller::class,
214 'order_email' => API\V2\Order_Email_Controller::class,
215 'shipping_methods' => API\V2\Shipping_Methods_Controller::class,
216 'tax_classes' => API\V2\Tax_Classes_Controller::class,
217 'order_statuses' => API\V2\Data_Order_Statuses_Controller::class,
218 )
219 );
220 foreach ( $v2_classes as $key => $class ) {
221 $classes[ 'v2-' . $key ] = $class;
222 }
223 $legacy_classifications = array(
224 'auth' => array( 'public' => array( '/wcpos/v1/auth/test', '/wcpos/v1/auth/refresh' ) ),
225 'print_jobs' => array( 'printer_token' => array( '/wcpos/v1/print-jobs/cloudprnt', '/wcpos/v1/print-jobs/epson-sdp' ) ),
226 'receipts' => array( 'permission_error_passthrough' => array( '/wcpos/v1/receipts/' ) ),
227 );
228
229 foreach ( $classes as $key => $class ) {
230 if ( class_exists( $class ) ) {
231 $this->controllers[ $key ] = new $class();
232 $this->controllers[ $key ]->register_routes();
233
234 if ( method_exists( $this->controllers[ $key ], 'wcpos_route_classifications' ) ) {
235 $this->route_classifier->merge( $this->controllers[ $key ]->wcpos_route_classifications() );
236 } elseif ( isset( $legacy_classifications[ $key ] ) ) {
237 $this->route_classifier->merge( $legacy_classifications[ $key ] );
238 }
239 }
240 }
241
242 // Sync classifications are independent of feature-gated route registration.
243 $this->route_classifier->merge( Sync\Api::route_classifications() );
244
245 // Build route map for use in rest_dispatch_request().
246 $rest_server = rest_get_server();
247
248 foreach ( $route_namespaces as $route_namespace ) {
249 $all_routes = $rest_server->get_routes( $route_namespace );
250
251 foreach ( $all_routes as $route_pattern => $route_handlers ) {
252 foreach ( $route_handlers as $route_handler ) {
253 $callback = $route_handler['callback'] ?? null;
254
255 // Extract the controller object from the callback.
256 $controller_obj = null;
257 if ( \is_array( $callback ) && isset( $callback[0] ) && \is_object( $callback[0] ) ) {
258 $controller_obj = $callback[0];
259 } elseif ( $callback instanceof \Closure ) {
260 // WC 10.5+ RestApiCache wraps callbacks in closures.
261 // Use reflection to extract the bound $this.
262 $ref = new \ReflectionFunction( $callback );
263 $controller_obj = $ref->getClosureThis();
264 }
265
266 if ( ! $controller_obj ) {
267 continue;
268 }
269
270 // Find which controller key this object belongs to.
271 foreach ( $this->controllers as $key => $registered_controller ) {
272 if ( $controller_obj === $registered_controller ) {
273 $this->route_map[ $route_pattern ] = $key;
274 break;
275 }
276 }
277 }
278 }
279 }
280 }
281
282 /**
283 * Check request for any login tokens.
284 *
285 * Runs at priority 20, after other plugins (e.g. third-party JWT plugins at
286 * priority 10) have had a chance to authenticate the user. If another plugin
287 * already returned a valid user ID, we trust it. Otherwise we attempt our own
288 * WCPOS Bearer-token authentication.
289 *
290 * Note: some JWT plugins pass a WP_Error through this filter when they fail to
291 * validate a Bearer token. We treat that the same as false so we can still
292 * authenticate the request with our own JWT.
293 *
294 * Note: this filter may not be called at all when WordPress has already cached
295 * the current user (WooCommerce issue #26847). The rest_authentication_errors
296 * fallback below handles that scenario.
297 *
298 * @param false|int|\WP_Error $user_id User ID if one has been determined, false otherwise.
299 *
300 * @return false|int
301 */
302 public function determine_current_user( $user_id ) {
303 $this->is_auth_checked = true;
304
305 // Trust a valid user ID set by another plugin (e.g. JWT plugin with its own token).
306 // Treat a WP_Error the same as false — another plugin rejected its own token,
307 // but we should still attempt authentication with our Bearer token.
308 if ( ! empty( $user_id ) && ! is_wp_error( $user_id ) ) {
309 return $user_id;
310 }
311
312 $result = $this->authenticate( false );
313 if ( $result && ! is_wp_error( $result ) ) {
314 $this->authenticated_via_wcpos = true;
315 return $result;
316 }
317
318 // If neither we nor another plugin authenticated the user, return false
319 // (not authenticated) rather than a WP_Error from $user_id. WordPress core
320 // expects determine_current_user to return false|int, not WP_Error.
321 // The JWT plugin's error will surface via rest_authentication_errors instead.
322 return is_wp_error( $user_id ) ? false : $user_id;
323 }
324
325 /**
326 * Handles two distinct failure modes:
327 *
328 * 1. WooCommerce issue #26847: determine_current_user may not be called when
329 * WordPress has already cached the current user. We attempt auth here as a
330 * fallback.
331 *
332 * 2. JWT plugin conflict: a third-party JWT plugin (e.g. jwt-authentication-for-wp-rest-api)
333 * sees our Bearer token, fails to validate it with its own secret, and returns
334 * a WP_Error via rest_authentication_errors at priority 10. We run at priority 50
335 * and attempt our own Bearer-token validation. If it succeeds, we clear the
336 * stale error — our authentication wins.
337 *
338 * @param mixed $errors Authentication errors.
339 *
340 * @return mixed
341 */
342 public function rest_authentication_errors( $errors ) {
343 // If there is already an error from a previous filter (e.g. a JWT plugin that
344 // rejected our Bearer token), attempt WCPOS authentication before passing it
345 // through. This covers the case where determine_current_user was skipped
346 // (WC #26847) or where the JWT plugin ran at a higher priority.
347 if ( ! empty( $errors ) ) {
348 // Only clear errors that originate from JWT authentication plugins. Errors
349 // from other mechanisms (maintenance locks, IP restrictions, etc.) should
350 // be passed through even when the WCPOS Bearer token is valid.
351 $is_jwt_plugin_error = is_wp_error( $errors ) && 0 === strpos( $errors->get_error_code(), 'jwt_auth_' );
352
353 if ( $is_jwt_plugin_error && ! $this->authenticated_via_wcpos ) {
354 $user_id = $this->authenticate( false );
355 if ( $user_id && ! is_wp_error( $user_id ) ) {
356 wp_set_current_user( $user_id );
357 $this->authenticated_via_wcpos = true;
358 }
359 }
360
361 if ( $this->authenticated_via_wcpos && $is_jwt_plugin_error ) {
362 return null;
363 }
364
365 return $errors;
366 }
367
368 // check if determine_current_user has been called.
369 if ( ! $this->is_auth_checked ) {
370 // Authentication hasn't occurred during `determine_current_user`, so check auth.
371 $user_id = $this->authenticate( false );
372 if ( $user_id && ! is_wp_error( $user_id ) ) {
373 wp_set_current_user( $user_id );
374 $this->authenticated_via_wcpos = true;
375
376 return true;
377 }
378 }
379
380 return $errors;
381 }
382
383 /**
384 * Extract the Authorization Bearer token from the request.
385 *
386 * @return false|string
387 */
388 public function get_auth_header() {
389 return Auth::instance()->get_auth_header();
390 }
391
392 /**
393 * Adds info to the WP REST API index response.
394 * - UUID
395 * - Version Info.
396 *
397 * @param WP_REST_Response $response Response data.
398 *
399 * @return WP_REST_Response
400 */
401 public function rest_index( WP_REST_Response $response ): WP_REST_Response {
402 $uuid = wcpos_get_site_uuid();
403 $response->data['uuid'] = $uuid;
404 $response->data['wp_version'] = get_bloginfo( 'version' );
405 $response->data['wc_version'] = WC()->version;
406 $response->data['wcpos_version'] = VERSION;
407 $response->data['use_jwt_as_param'] = SettingsService::instance()->use_jwt_as_param_enabled();
408
409 // Add WCPOS authentication endpoint to the response.
410 $response->data['authentication']['wcpos'] = array(
411 'endpoints' => array(
412 'authorization' => Template_Router::get_auth_url(),
413 ),
414 );
415
416 /**
417 * Remove the routes from the response.
418 *
419 * Some wordpress sites have a huge number of routes, like 2MB of data. It shouldn;t matter, but it seems
420 * to cause issues with the desktop application sometimes. We don't use the routes at the moment, so we
421 * can remove them from the response.
422 */
423 $data = $response->get_data();
424 unset( $data['routes'] );
425 $response->set_data( $data );
426
427 return $response;
428 }
429
430 /**
431 * Filters the pre-calculated result of a REST API dispatch request.
432 *
433 * Allow hijacking the request before dispatching by returning a non-empty. The returned value
434 * will be used to serve the request instead.
435 *
436 * @param mixed $result Response to replace the requested version with. Can be anything
437 * a normal endpoint can return, or null to not hijack the request.
438 * @param WP_REST_Server $server Server instance.
439 * @param WP_REST_Request $request Request used to generate the response.
440 *
441 * @return mixed
442 */
443 public function rest_pre_dispatch( $result, $server, $request ) {
444 if ( ! $this->route_classifier->in_wcpos_namespace( $request->get_route() ) ) {
445 return $result;
446 }
447
448 // Latch the till's store scope for the whole request (pro#425). Set
449 // unconditionally — including to null — so a scope never leaks from one
450 // dispatch into the next. Inner `wc/v3` forwards do not reach this line
451 // (they are outside the WCPOS namespace), which is exactly right: the
452 // OUTER request owns the scope and stamps it onto the inner ones.
453 \WCPOS\WooCommercePOS\Sync\Store_Scope::set_current(
454 \WCPOS\WooCommercePOS\Sync\Store_Scope::resolve( $request )
455 );
456
457 // CORS preflights carry no credentials (browsers strip Authorization from OPTIONS),
458 // so the permission gate must never answer them with 401 — a non-2xx preflight blocks
459 // every cross-origin standalone client from the entire namespace. WP core serves
460 // OPTIONS with route metadata and Rest_Cors::rest_pre_serve_request adds the CORS headers.
461 if ( 'OPTIONS' === $request->get_method() ) {
462 return $result;
463 }
464
465 // Baseline permission gate: POS endpoints require access_woocommerce_pos; the three
466 // sync admin operations instead use their route-level manage_woocommerce check.
467 // Exempt public auth, printer-token polling, and authenticated receipt denials that need
468 // the receipt-specific error code.
469 $route = $request->get_route();
470 $has_route_specific_permission_error = is_user_logged_in() && $this->route_classifier->is_permission_error_passthrough( $route );
471 $is_public_auth_route = $this->route_classifier->is_public( $route );
472 $is_printer_token_route = $this->route_classifier->is_printer_token( $route );
473 $is_sync_admin_route = is_user_logged_in() && current_user_can( 'manage_woocommerce' ) && $this->route_classifier->is_admin_op( $route );
474
475 if ( ! $is_public_auth_route && ! $has_route_specific_permission_error && ! $is_printer_token_route && ! $is_sync_admin_route ) {
476 if ( ! current_user_can( 'access_woocommerce_pos' ) ) {
477 if ( ! is_user_logged_in() ) {
478 return new \WP_Error(
479 'woocommerce_pos_rest_unauthorized',
480 __( 'Authentication required.', 'woocommerce-pos' ),
481 array( 'status' => 401 )
482 );
483 }
484
485 return new \WP_Error(
486 'woocommerce_pos_rest_forbidden',
487 __( 'You do not have permission to access the POS.', 'woocommerce-pos' ),
488 array( 'status' => 403 )
489 );
490 }
491 }
492
493 $max_length = 10000;
494
495 // The sync sub-surface speaks its own wire contract (include = raw id
496 // list validated by its controllers); the wcpos_include/exclude rewrite
497 // below is a legacy extended-WC-controller workaround and must not
498 // mangle sync routes.
499 if ( $this->route_classifier->is_rewrite_exempt( $route ) ) {
500 return $result;
501 }
502
503 // Process 'include' parameter.
504 $include = $request->get_param( 'include' );
505 if ( $include ) {
506 $processed_include = $this->shorten_param_array( $include, $max_length );
507 $request->set_param( 'wcpos_include', $processed_include );
508 unset( $request['include'] );
509 }
510
511 // Process 'exclude' parameter.
512 $exclude = $request->get_param( 'exclude' );
513 if ( $exclude ) {
514 $processed_exclude = $this->shorten_param_array( $exclude, $max_length );
515 $request->set_param( 'wcpos_exclude', $processed_exclude );
516 unset( $request['exclude'] );
517 }
518
519 return $result;
520 }
521
522 /**
523 * Add the server pressure bucket to WCPOS REST responses.
524 *
525 * @param mixed $response REST response.
526 * @param WP_REST_Server $server REST server.
527 * @param WP_REST_Request $request REST request.
528 *
529 * @return mixed
530 */
531 public function rest_post_dispatch( $response, $server, $request ) {
532 if ( is_wp_error( $response ) ) {
533 return $response;
534 }
535
536 try {
537 if ( ! $response instanceof WP_HTTP_Response || ! $this->route_classifier->in_wcpos_namespace( $request->get_route() ) ) {
538 return $response;
539 }
540 $pressure_bucket = API\V2\Ping::pressure_bucket();
541 if ( null !== $pressure_bucket ) {
542 $response->header( 'X-WCPOS-Pressure', $pressure_bucket );
543 }
544 } catch ( \Throwable $e ) {
545 return $response;
546 }
547
548 return $response;
549 }
550
551 /**
552 * Filters the REST API dispatch request result.
553 *
554 * @param mixed $dispatch_result Dispatch result, will be used if not empty.
555 * @param WP_REST_Request $request Request used to generate the response.
556 * @param string $route Route matched for the request.
557 * @param array $handler Route handler used for the request.
558 *
559 * @return mixed
560 */
561 public function rest_dispatch_request( $dispatch_result, $request, $route, $handler ) {
562 // Only process mapped WCPOS routes.
563 if ( ! isset( $this->route_map[ $route ] ) ) {
564 return $dispatch_result;
565 }
566
567 /*
568 * POS-specific PHP settings to prevent errors in JSON and float weirdness.
569 *
570 * - error_reporting(0) - Turn off error reporting
571 * - ini_set('display_errors', 0) - Turn off error display
572 * - ini_set('precision', 10) - Set the precision of floating point numbers
573 * - ini_set('serialize_precision', 10) - Set the precision of floating point numbers for serialization
574 *
575 * This is to prevent any PHP errors from being displayed in the response.
576 *
577 * The precision settings are to prevent floating point weirdness, eg: stock_quantity 3.6 becomes 3.6000000000000001
578 */
579 error_reporting( 0 );
580 @ini_set( 'display_errors', '0' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Disallowed -- intentionally disabling error display for POS API responses.
581 @ini_set( 'precision', '10' );
582 @ini_set( 'serialize_precision', '10' );
583
584 $key = $this->route_map[ $route ];
585 $controller = $this->controllers[ $key ] ?? null;
586
587 if ( $controller && method_exists( $controller, 'wcpos_dispatch_request' ) ) {
588 return $controller->wcpos_dispatch_request( $dispatch_result, $request, $route, $handler );
589 }
590
591 return $dispatch_result;
592 }
593
594 /**
595 * Some servers have a limit on the number of include/exclude we can use in a request.
596 * Worst thing is there is often no error message, the request returns an empty response.
597 *
598 * For example, WP Engine has a limit of 1024 characters?
599 * https://wpengine.com/support/using-dev-tools/#Long_Queries_in_wp_db
600 *
601 * @TODO - For long queries, I should find a better solution than this.
602 *
603 * @param array|string $param_value The parameter value.
604 * @param int $max_length The maximum length.
605 *
606 * @return array
607 */
608 private function shorten_param_array( $param_value, $max_length ) {
609 $param_array = \is_array( $param_value ) ? $param_value : explode( ',', $param_value );
610 $param_string = implode( ',', $param_array );
611
612 if ( \strlen( $param_string ) > $max_length ) {
613 shuffle( $param_array ); // Shuffle to randomize.
614
615 $new_param_string = '';
616 $random_param_array = array();
617
618 foreach ( $param_array as $id ) {
619 if ( \strlen( $new_param_string . $id ) < $max_length ) {
620 $new_param_string .= $id . ',';
621 $random_param_array[] = $id;
622 } else {
623 break; // Stop when maximum length is reached.
624 }
625 }
626
627 return $random_param_array;
628 }
629
630 return $param_array;
631 }
632
633 /**
634 * Check the Authorization header for a Bearer token.
635 *
636 * @param false|int $user_id User ID if one has been determined, false otherwise.
637 *
638 * @return false|int|\WP_Error
639 */
640 private function authenticate( $user_id ) {
641 $authenticated_user_id = Auth::instance()->authenticate_request();
642
643 if ( is_wp_error( $authenticated_user_id ) ) {
644 return false === $user_id ? $authenticated_user_id : $user_id;
645 }
646
647 return false === $authenticated_user_id ? $user_id : $authenticated_user_id;
648 }
649 }
650