PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
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 / V1 / Coupons_Controller.php

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

301 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Coupons_Controller.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\API\V1;
9
10 \defined( 'ABSPATH' ) || die;
11
12 if ( ! class_exists( 'WC_REST_Coupons_Controller' ) ) {
13 return;
14 }
15
16 use WCPOS\WooCommercePOS\Services\Permission_Rules;
17 use Exception;
18 use WC_Coupon;
19 use WC_REST_Coupons_Controller;
20 use WCPOS\WooCommercePOS\Logger;
21 use WP_Error;
22 use WP_REST_Request;
23 use WP_REST_Response;
24
25 /**
26 * Coupons controller class.
27 *
28 * Extends WC_REST_Coupons_Controller directly under the wcpos/v1
29 * namespace and adds POS-specific behaviour (UUID, permissions,
30 * optimised bulk-ID queries).
31 */
32 class Coupons_Controller extends WC_REST_Coupons_Controller {
33 use Traits\Uuid_Handler;
34 use Traits\WCPOS_REST_API;
35
36 /**
37 * Endpoint namespace.
38 *
39 * @var string
40 */
41 protected $namespace = 'wcpos/v1';
42
43 /**
44 * Store the request object for use in lifecycle methods.
45 *
46 * @var WP_REST_Request
47 */
48 protected $wcpos_request;
49
50 /**
51 * Read coupons with the POS grant scoped to this handler.
52 *
53 * @param WP_REST_Request $request Full request details.
54 * @return WP_REST_Response|WP_Error
55 */
56 public function get_items( $request ) {
57 Permission_Rules::install_wc_filter( 'coupons', 'v1' );
58 try {
59 return parent::get_items( $request );
60 } finally {
61 Permission_Rules::uninstall_wc_filter();
62 }
63 }
64
65 /**
66 * Read coupons with the POS grant scoped to this handler.
67 *
68 * @param WP_REST_Request $request Full request details.
69 * @return WP_REST_Response|WP_Error
70 */
71 public function get_item( $request ) {
72 Permission_Rules::install_wc_filter( 'coupons', 'v1' );
73 try {
74 return parent::get_item( $request );
75 } finally {
76 Permission_Rules::uninstall_wc_filter();
77 }
78 }
79
80 /**
81 * Create a single coupon.
82 *
83 * @param WP_REST_Request $request Full details about the request.
84 *
85 * @return WP_Error|WP_REST_Response
86 */
87 public function create_item( $request ) {
88 $invalid_meta = $this->wcpos_sanitize_meta_data_param( $request );
89 if ( is_wp_error( $invalid_meta ) ) {
90 return $invalid_meta;
91 }
92
93 return parent::create_item( $request );
94 }
95
96 /**
97 * Update a single coupon.
98 *
99 * @param WP_REST_Request $request Full details about the request.
100 *
101 * @return WP_Error|WP_REST_Response
102 */
103 public function update_item( $request ) {
104 $invalid_meta = $this->wcpos_sanitize_meta_data_param( $request );
105 if ( is_wp_error( $invalid_meta ) ) {
106 return $invalid_meta;
107 }
108
109 return parent::update_item( $request );
110 }
111
112 /**
113 * Dispatch request to parent controller, or override if needed.
114 *
115 * @param mixed $dispatch_result Dispatch result, will be used if not empty.
116 * @param WP_REST_Request $request Request used to generate the response.
117 * @param string $route Route matched for the request.
118 * @param array $handler Route handler used for the request.
119 * @return mixed
120 */
121 public function wcpos_dispatch_request( $dispatch_result, WP_REST_Request $request, $route, $handler ) {
122 $this->wcpos_request = $request;
123
124 add_filter( 'woocommerce_rest_prepare_shop_coupon_object', array( $this, 'wcpos_coupon_response' ), 10, 3 );
125 // The post-date touch that used to be installed here is now registered
126 // unconditionally at plugins_loaded (Sync\Coupon_Modified_Date), so it also
127 // covers wp-admin/WP-CLI/third-party coupon saves this dispatch never saw.
128
129 /**
130 * Check if the request is for all coupons and if the 'posts_per_page' is set to -1.
131 * Optimised query for getting all coupon IDs.
132 */
133 if ( Bulk_ID_Fast_Path::supports_request( $request ) ) {
134 return $this->wcpos_get_all_posts( $request );
135 }
136
137 return $dispatch_result;
138 }
139
140 /**
141 * Delegate the read decision, preserving WooCommerce's request-dependent checks.
142 *
143 * @param WP_REST_Request $request Full request details.
144 * @return bool|WP_Error
145 */
146 public function get_items_permissions_check( $request ) {
147 return Permission_Rules::verdict( 'coupons', 'read', (int) $request['id'], 0, 'v1', $request->get_params() );
148 }
149
150 /**
151 * Delegate the read decision, preserving WooCommerce's request-dependent checks.
152 *
153 * @param WP_REST_Request $request Full request details.
154 * @return bool|WP_Error
155 */
156 public function get_item_permissions_check( $request ) {
157 return Permission_Rules::verdict( 'coupons', 'read', (int) $request['id'], 0, 'v1', $request->get_params() );
158 }
159
160 /**
161 * Authorize coupon read access for POS users.
162 *
163 * @deprecated Use Permission_Rules::wc_filter().
164 * @param bool $permission Incoming WC permission.
165 * @param string $context Permission context.
166 * @param int $object_id Target object ID.
167 * @param string $post_type WC object type.
168 * @return bool
169 */
170 public function wcpos_check_permissions( $permission, $context, $object_id, $post_type ) {
171 return Permission_Rules::wc_filter( $permission, $context, $object_id, $post_type, 'coupons', 'v1' );
172 }
173
174 /**
175 * Get the query params for collections.
176 *
177 * @return array $params The collection parameters.
178 */
179 public function get_collection_params() {
180 $params = parent::get_collection_params();
181
182 // LANE SCOPE — v1 ONLY, deliberately NOT ported to v2 (lane audit 2026-08-10).
183 // `orderby=code` has NO caller: the client's coupon list marks its `code`
184 // column "disableSort": true and defaults to sorting on date_created_gmt
185 // (monorepo packages/core/.../ui-settings/initial-settings.json), and the
186 // coupon query hook only ever rewrites date_created/date_modified
187 // (packages/query/src/hooks/coupons.ts). The v2 Catalog_Proxy_Controller
188 // therefore leaves the wc/v3 enum unchanged on purpose. If a future
189 // cashier-facing "sort by code" lands, port it deliberately — do not add
190 // it back just to make the lanes look symmetrical.
191 // Ensure 'orderby' is set and is an array before attempting to modify it.
192 if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) {
193 $params['orderby']['enum'] = array_unique( array_merge( $params['orderby']['enum'], array( 'code' ) ) );
194 }
195
196 return $params;
197 }
198
199 /**
200 * Prepare objects query.
201 *
202 * @param WP_REST_Request $request Full details about the request.
203 *
204 * @return array|WP_Error
205 */
206 protected function prepare_objects_query( $request ) {
207 $args = parent::prepare_objects_query( $request );
208
209 // Coupon code is stored as post_title.
210 if ( isset( $request['orderby'] ) && 'code' === $request['orderby'] ) {
211 $args['orderby'] = 'title';
212 }
213
214 if ( ! empty( $request['wcpos_include'] ) ) {
215 $args['post__in'] = array_map( 'intval', (array) $request['wcpos_include'] );
216 }
217
218 if ( ! empty( $request['wcpos_exclude'] ) ) {
219 $args['post__not_in'] = array_map( 'intval', (array) $request['wcpos_exclude'] );
220 }
221
222 return $args;
223 }
224
225 /**
226 * Filter coupon object returned from the REST API.
227 *
228 * @param WP_REST_Response $response The response object.
229 * @param WC_Coupon $coupon Coupon object used to create response.
230 * @param WP_REST_Request $request Request object.
231 *
232 * @return WP_REST_Response
233 */
234 public function wcpos_coupon_response( WP_REST_Response $response, WC_Coupon $coupon, WP_REST_Request $request ): WP_REST_Response {
235 $data = $response->get_data();
236
237 // Add the UUID to the coupon response.
238 // The retained coupon seam delegates to Uuid_Handler's shared Pos_Uuid path.
239 $this->maybe_add_coupon_uuid( $coupon );
240
241 // Parse the meta data before returning the response.
242 $data['meta_data'] = $this->wcpos_parse_meta_data( $coupon );
243
244 // Estimate response size and log if excessive.
245 $this->wcpos_estimate_response_size( $data, $coupon->get_id(), 'Coupon' );
246
247 // Set changes to the response data.
248 $response->set_data( $data );
249
250 return $response;
251 }
252
253 /**
254 * Ensure the coupon has a valid UUID.
255 *
256 * Retains the legacy controller seam while delegating to the trait's shared
257 * Pos_Uuid path. That path no longer depends on WC_Data::get_type().
258 *
259 * @param WC_Coupon $coupon The coupon object.
260 */
261 private function maybe_add_coupon_uuid( WC_Coupon $coupon ): void {
262 $this->maybe_add_post_uuid( $coupon );
263 }
264
265 /**
266 * Returns array of all coupon IDs.
267 *
268 * @param WP_REST_Request $request Full details about the request.
269 *
270 * @return WP_REST_Response|WP_Error
271 */
272 public function wcpos_get_all_posts( $request ) {
273 global $wpdb;
274
275 $start_time = microtime( true );
276 $select_fields = Bulk_ID_Fast_Path::select_fields( $request, 'ID', 'post_modified_gmt' );
277
278 $sql = "SELECT DISTINCT {$select_fields} FROM {$wpdb->posts}";
279 $sql .= " WHERE post_type = 'shop_coupon' AND post_status = 'publish'";
280
281 $modified_after_date = Bulk_ID_Fast_Path::modified_after_gmt( $request, true );
282 if ( is_wp_error( $modified_after_date ) ) {
283 return $modified_after_date;
284 }
285 if ( $modified_after_date ) {
286 $sql .= $wpdb->prepare( ' AND post_modified_gmt > %s', $modified_after_date );
287 }
288
289 $sql = Bulk_ID_Fast_Path::append_id_filters_sql( $sql, $request, "{$wpdb->posts}.ID" );
290 $sql .= " ORDER BY {$wpdb->posts}.post_date DESC";
291
292 try {
293 $results = $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is built with prepare() above.
294
295 return Bulk_ID_Fast_Path::response( $this, $results, $start_time );
296 } catch ( Exception $e ) {
297 return Bulk_ID_Fast_Path::fetch_error( 'Error fetching coupon IDs: ' . $e->getMessage(), 'Error fetching coupon IDs.' );
298 }
299 }
300 }
301