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 / Coupons_Controller.php

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

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