PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.17
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.17
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 1.9.13 All 162 releases
woocommerce-pos / includes / API / V1 / Bulk_ID_Fast_Path.php

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

263 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bulk ID fast path helpers.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\API\V1;
9
10 \defined( 'ABSPATH' ) || die;
11
12 use WCPOS\WooCommercePOS\Logger;
13 use WP_Error;
14 use WP_REST_Request;
15
16 /**
17 * Shared mechanics for the POS offline-sync bulk-ID fast path.
18 *
19 * Controllers still own the entity-specific data source. This class owns the
20 * shallow mechanics that were duplicated across those implementations: request
21 * shape detection, id-list filtering, modified-after parsing, and response
22 * headers.
23 */
24 class Bulk_ID_Fast_Path {
25 /**
26 * Determine whether the request is the supported bulk-ID fast-path shape.
27 *
28 * @param WP_REST_Request $request Request object.
29 *
30 * @return bool
31 */
32 public static function supports_request( WP_REST_Request $request ): bool {
33 if ( -1 !== (int) $request->get_param( 'posts_per_page' ) ) {
34 return false;
35 }
36
37 $fields = $request->get_param( 'fields' );
38
39 return 'id' === $fields
40 || (
41 \is_array( $fields )
42 && ( array( 'id' ) === $fields || array( 'id', 'date_modified_gmt' ) === $fields )
43 );
44 }
45
46 /**
47 * Determine whether the caller requested date_modified_gmt.
48 *
49 * @param WP_REST_Request $request Request object.
50 *
51 * @return bool
52 */
53 public static function wants_modified_date( WP_REST_Request $request ): bool {
54 return array( 'id', 'date_modified_gmt' ) === $request->get_param( 'fields' );
55 }
56
57 /**
58 * Build a select list for an id column and optional modified-date column.
59 *
60 * @param WP_REST_Request $request Request object.
61 * @param string $id_column SQL id column/expression.
62 * @param string|null $modified_column SQL modified-date column/expression.
63 *
64 * @return string
65 */
66 public static function select_fields( WP_REST_Request $request, string $id_column, ?string $modified_column = null ): string {
67 if ( self::wants_modified_date( $request ) && null !== $modified_column ) {
68 return "{$id_column} as id, {$modified_column} as date_modified_gmt";
69 }
70
71 return "{$id_column} as id";
72 }
73
74 /**
75 * Append wcpos_include/wcpos_exclude filters to a SQL statement.
76 *
77 * @param string $sql Existing SQL statement.
78 * @param WP_REST_Request $request Request object.
79 * @param string $id_column SQL id column/expression.
80 *
81 * @return string
82 */
83 public static function append_id_filters_sql( string $sql, WP_REST_Request $request, string $id_column ): string {
84 global $wpdb;
85
86 $include_ids = self::id_list_from_request( $request, 'wcpos_include' );
87 if ( ! empty( $include_ids ) ) {
88 $ids_format = implode( ',', array_fill( 0, \count( $include_ids ), '%d' ) );
89 $sql .= self::sql_conjunction( $sql ) . $wpdb->prepare( "{$id_column} IN ($ids_format)", $include_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholders are generated by array_fill and the column is caller-owned SQL.
90 }
91
92 $exclude_ids = self::id_list_from_request( $request, 'wcpos_exclude' );
93 if ( ! empty( $exclude_ids ) ) {
94 $ids_format = implode( ',', array_fill( 0, \count( $exclude_ids ), '%d' ) );
95 $sql .= self::sql_conjunction( $sql ) . $wpdb->prepare( "{$id_column} NOT IN ($ids_format)", $exclude_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholders are generated by array_fill and the column is caller-owned SQL.
96 }
97
98 return $sql;
99 }
100
101 /**
102 * Apply wcpos_include/wcpos_exclude to WP query arguments that support include/exclude.
103 *
104 * @param array $args Query arguments.
105 * @param WP_REST_Request $request Request object.
106 *
107 * @return array
108 */
109 public static function apply_id_filters_to_args( array $args, WP_REST_Request $request ): array {
110 $include_ids = self::id_list_from_request( $request, 'wcpos_include' );
111 $exclude_ids = self::id_list_from_request( $request, 'wcpos_exclude' );
112 if ( ! empty( $include_ids ) ) {
113 $args['include'] = ! empty( $exclude_ids )
114 ? array_values( array_diff( $include_ids, $exclude_ids ) )
115 : $include_ids;
116 if ( empty( $args['include'] ) ) {
117 $args['include'] = array( 0 );
118 }
119 } elseif ( ! empty( $exclude_ids ) ) {
120 $args['exclude'] = $exclude_ids;
121 }
122
123 return $args;
124 }
125
126 /**
127 * Get the modified_after value as a Unix timestamp.
128 *
129 * @param WP_REST_Request $request Request object.
130 * @param bool $strict Whether invalid dates should return a WP_Error.
131 *
132 * @return int|null|WP_Error
133 */
134 public static function modified_after_timestamp( WP_REST_Request $request, bool $strict = false ) {
135 $modified_after = $request->get_param( 'modified_after' );
136 if ( ! $modified_after ) {
137 return null;
138 }
139
140 $timestamp = strtotime( $modified_after );
141 if ( false === $timestamp ) {
142 if ( $strict ) {
143 return new WP_Error(
144 'woocommerce_pos_rest_invalid_modified_after',
145 'Invalid modified_after parameter.',
146 array( 'status' => 400 )
147 );
148 }
149
150 return 0;
151 }
152
153 return $timestamp;
154 }
155
156 /**
157 * Get the modified_after value in MySQL GMT datetime format.
158 *
159 * @param WP_REST_Request $request Request object.
160 * @param bool $strict Whether invalid dates should return a WP_Error.
161 *
162 * @return string|null|WP_Error
163 */
164 public static function modified_after_gmt( WP_REST_Request $request, bool $strict = false ) {
165 $timestamp = self::modified_after_timestamp( $request, $strict );
166 if ( null === $timestamp || is_wp_error( $timestamp ) ) {
167 return $timestamp;
168 }
169
170 return gmdate( 'Y-m-d H:i:s', (int) $timestamp );
171 }
172
173 /**
174 * Convert a list of ids into fast-path response rows.
175 *
176 * @param array $ids ID list.
177 *
178 * @return array
179 */
180 public static function rows_from_ids( array $ids ): array {
181 return array_map(
182 static function ( $id ): array {
183 return array( 'id' => (int) $id );
184 },
185 $ids
186 );
187 }
188
189 /**
190 * Build a REST response with the fast-path headers.
191 *
192 * @param object $controller Controller that uses WCPOS_REST_API.
193 * @param array $results Response rows.
194 * @param float $start_time Request start time.
195 * @param bool $format Whether to run wcpos_format_all_posts_response().
196 *
197 * @return \WP_REST_Response
198 */
199 public static function response( $controller, array $results, float $start_time, bool $format = true ) {
200 $formatted_results = $format ? $controller->wcpos_format_all_posts_response( $results ) : $results;
201 $total = \count( $formatted_results );
202 $execution_time = microtime( true ) - $start_time;
203 $execution_time_ms = number_format( $execution_time * 1000, 2 );
204 $server_load = $controller->get_server_load();
205
206 $response = rest_ensure_response( $formatted_results );
207 $response->header( 'X-WP-Total', (string) $total );
208 $response->header( 'X-Execution-Time', $execution_time_ms . ' ms' );
209 $response->header( 'X-Server-Load', json_encode( $server_load ) );
210
211 return $response;
212 }
213
214 /**
215 * Build the common fast-path fetch failure response.
216 *
217 * @param string $log_message Message to write to the logger.
218 * @param string $public_message Message returned to API clients.
219 *
220 * @return WP_Error
221 */
222 public static function fetch_error( string $log_message, string $public_message ): WP_Error {
223 Logger::log( $log_message );
224
225 return new WP_Error(
226 'woocommerce_pos_rest_cannot_fetch',
227 $public_message,
228 array( 'status' => 500 )
229 );
230 }
231
232 /**
233 * Parse a request id-list parameter.
234 *
235 * @param WP_REST_Request $request Request object.
236 * @param string $param Parameter name.
237 *
238 * @return array<int,int>
239 */
240 private static function id_list_from_request( WP_REST_Request $request, string $param ): array {
241 $value = $request->get_param( $param );
242 if ( empty( $value ) ) {
243 return array();
244 }
245
246 $ids = \is_array( $value ) ? $value : explode( ',', (string) $value );
247 $ids = array_filter( array_map( 'absint', $ids ) );
248
249 return array_values( array_unique( $ids ) );
250 }
251
252 /**
253 * Return the next SQL conjunction for appending where predicates.
254 *
255 * @param string $sql Existing SQL statement.
256 *
257 * @return string
258 */
259 private static function sql_conjunction( string $sql ): string {
260 return false === stripos( $sql, ' where ' ) ? ' WHERE ' : ' AND ';
261 }
262 }
263