PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.19
Booktics – Appointment Booking Calendar for Service Businesses v1.0.19
1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 All 27 releases
booktics / core / cart / controllers / cart-controller.php

cart-controller.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.19, at core/cart/controllers/cart-controller.php

406 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Cart\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Cart\Cart_Manager;
7 use Booktics\Models\Service_Model;
8 use Booktics\Models\Team_Member_Model;
9 use WP_Error;
10 use WP_REST_Response;
11 use WP_REST_Server;
12
13 class Cart_Controller extends Base_Rest_Controller {
14
15 /**
16 * Endpoint namespace
17 *
18 * @var string
19 */
20 protected $namespace = 'booktics/v1';
21
22 /**
23 * Route name
24 *
25 * @var string
26 */
27 protected $base = 'carts';
28
29 /**
30 * @var Cart_Manager
31 */
32 private $cart_manager;
33
34 /**
35 * Initialize CartController with a new CartManager instance
36 */
37 public function __construct() {
38 $this->cart_manager = new Cart_Manager();
39 }
40
41 /**
42 * Register REST API routes for cart functionality
43 *
44 * @return void
45 */
46 public function register_routes(): void {
47 register_rest_route(
48 $this->namespace,
49 $this->base,
50 array(
51 'methods' => WP_REST_Server::CREATABLE,
52 'callback' => array( $this, 'add_to_cart' ),
53 'permission_callback' => '__return_true',
54 )
55 );
56
57 register_rest_route(
58 $this->namespace,
59 $this->base . '/(?P<uuid>[a-zA-Z0-9-]+)',
60 array(
61 'methods' => WP_REST_Server::DELETABLE,
62 'callback' => array( $this, 'delete_from_cart' ),
63 'permission_callback' => '__return_true',
64 )
65 );
66
67 register_rest_route(
68 $this->namespace,
69 $this->base,
70 array(
71 'methods' => WP_REST_Server::EDITABLE,
72 'callback' => array( $this, 'sync_cart' ),
73 'permission_callback' => array( $this, 'is_user_logged_in' ),
74 )
75 );
76
77 register_rest_route(
78 $this->namespace,
79 $this->base . '/(?P<uuid>[a-zA-Z0-9-]+)',
80 array(
81 'methods' => WP_REST_Server::EDITABLE,
82 'callback' => array( $this, 'edit_cart_item' ),
83 'permission_callback' => '__return_true',
84 )
85 );
86
87 register_rest_route(
88 $this->namespace,
89 $this->base,
90 array(
91 'methods' => WP_REST_Server::READABLE,
92 'callback' => array( $this, 'get_cart_items' ),
93 'permission_callback' => '__return_true',
94 )
95 );
96 }
97
98 /**
99 * Prepare and validate cart item data
100 *
101 * @param array $data
102 *
103 * @return []|array|bool|WP_Error
104 */
105 private function prepare_item_to_save( array $data ) {
106 $validated = booktics_validate(
107 $data, array(
108 'date' => array( 'required' ),
109 'start_time' => array( 'required' ),
110 'service_id' => array( 'required' ),
111 'team_member_id' => array( 'required' ),
112 )
113 );
114
115 if ( is_wp_error( $validated ) ) {
116 return $validated;
117 }
118
119 $item_validation = $this->validate_cart_item( $data );
120 if ( is_wp_error( $item_validation ) ) {
121 return $item_validation;
122 }
123
124 return $item_validation;
125 }
126
127 /**
128 * Add an item to the cart
129 *
130 * @param $request
131 *
132 * @return array|\WP_HTTP_Response
133 */
134 public function add_to_cart( $request ) {
135 $params = json_decode( $request->get_body(), true );
136 $validated = $this->prepare_item_to_save( $params );
137
138 if ( is_wp_error( $validated ) ) {
139 return $this->error(
140 __( 'Cart validation error', 'booktics' ),
141 422,
142 'Validation',
143 $validated->errors
144 );
145 }
146 // Calculate end time from start time and total duration
147 $total_duration_min = $this->calculate_total_duration( $validated );
148 $validated['end_time'] = $this->get_endtime_from_starttime($validated['start_time'], $total_duration_min);
149
150 $response = $this->cart_manager->add_to_cart( $validated );
151 $response['service'] = $this->get_service( $response['service_id'] );
152 $response['team'] = $this->get_team_member( $response['team_member_id'] );
153
154 // Allow booktics-pro to add location details
155 $response = apply_filters( 'booktics_cart_item_response', $response );
156
157 return $this->response( $response, __( 'Item added to cart', 'booktics' ), 201 );
158 }
159
160 /**
161 * Delete an item from the user's cart
162 *
163 * @param $request
164 *
165 * @return array|\WP_HTTP_Response
166 */
167 public function delete_from_cart( $request ) {
168 $uuid = sanitize_text_field( $request->get_param( 'uuid' ) );
169
170 if ( empty( $uuid ) ) {
171 return $this->error(
172 __( 'UUID is required', 'booktics' ),
173 422,
174 'Validation'
175 );
176 }
177
178 $this->cart_manager->remove_cart_item( $uuid );
179
180 return $this->response( array(), __( 'Successfully deleted item from cart.', 'booktics' ) );
181 }
182
183 /**
184 * Synchronize cart session data to database for logged-in user
185 *
186 * @param $request
187 *
188 * @return array|\WP_HTTP_Response
189 */
190 public function sync_cart( $request ) {
191 $user_id = get_current_user_id();
192 $this->cart_manager->sync_session_to_db( $user_id );
193
194 $response = $this->cart_manager->get_cart();
195
196 return $this->response( $response, __( 'Successfully synced cart.', 'booktics' ) );
197 }
198
199 /**
200 * Get all items from the cart
201 *
202 * @return array|\WP_HTTP_Response
203 */
204 public function get_cart_items() {
205 $response = $this->cart_manager->get_cart();
206 $response['items'] = isset( $response['items'] ) ? array_map(
207 function ( $item ) {
208 if ( ! is_array( $item ) ) {
209 $item = (array) $item;
210 }
211
212 $item['service'] = $this->get_service( $item['service_id'] );
213 $item['team'] = $this->get_team_member( $item['team_member_id'] );
214
215 // Allow booktics-pro to add location details
216 $item = apply_filters( 'booktics_cart_item_response', $item );
217
218 return $item;
219 }, $response['items']
220 ) : array();
221
222 return $this->response( $response, __( 'Cart items fetched successfully', 'booktics' ) );
223 }
224
225 /**
226 * Check if the current user is logged in
227 *
228 * @return bool True if user is logged in, false otherwise
229 */
230 public function is_user_logged_in() {
231 return is_user_logged_in();
232 }
233
234 /**
235 * Validate service, team member and price
236 *
237 * @param array $data Cart item data
238 *
239 * @return array|WP_Error Returns true if valid, WP_Error otherwise
240 */
241 private function validate_cart_item( array $data ) {
242 $error = new WP_Error();
243
244 $service = ( new Service_Model() )->find( $data['service_id'] );
245 if ( ! $service ) {
246 $error->add( 'invalid_service', __( 'Invalid service selected', 'booktics' ) );
247 }
248
249 $team_member = ( new Team_Member_Model() )->find( $data['team_member_id'] );
250 if ( empty( $team_member ) ) {
251 $error->add( 'invalid_team_member', __( 'Invalid team member selected', 'booktics' ) );
252 }
253
254 $service_price = 0;
255 $addition_durations = $service->additional_durations;
256 if ( ! empty( $addition_durations ) && isset( $data['additional_duration_id'] ) ) {
257 $addition_durations = array_filter(
258 $addition_durations, function ( $duration ) use ( $data ) {
259 return $duration['id'] === $data['additional_duration_id'];
260 }
261 );
262 $addition_duration = reset( $addition_durations );
263 $service_price = $addition_duration['charge_amount'] ?? 0;
264 }
265 $data['price'] = $service_price;
266
267 return $error->has_errors() ? $error : $data;
268 }
269
270 /**
271 * Edit a cart item
272 *
273 * @param $request
274 *
275 * @return array|\WP_HTTP_Response
276 */
277 public function edit_cart_item( $request ) {
278 $uuid = sanitize_text_field( (string) $request->get_param( 'uuid' ) );
279 $params = json_decode( $request->get_body(), true );
280
281 if ( empty( $uuid ) ) {
282 return $this->error(
283 __( 'UUID is required', 'booktics' ),
284 422,
285 'Validation'
286 );
287 }
288
289 $validated = $this->prepare_item_to_save( $params );
290 if ( is_wp_error( $validated ) ) {
291 return $this->error(
292 __( 'Cart validation error', 'booktics' ),
293 422,
294 'Validation',
295 $validated->errors
296 );
297 }
298
299 // Add UUID to validated data
300 $validated['uuid'] = $uuid;
301
302 // Update the cart item
303 $updated_item = $this->cart_manager->edit_cart_item( $uuid, $validated );
304
305 if ( empty( $updated_item ) ) {
306 return $this->error(
307 __( 'Cart item not found', 'booktics' ),
308 404,
309 'NotFound'
310 );
311 }
312
313 // Allow booktics-pro to add location details
314 $updated_item = apply_filters( 'booktics_cart_item_response', $updated_item );
315
316 return $this->response(
317 $updated_item,
318 __( 'Successfully updated cart item', 'booktics' )
319 );
320 }
321
322 /**
323 * @param $service_id
324 *
325 * @return array
326 */
327 public function get_service( $service_id ): array {
328 $service = Service_Model::find( $service_id );
329 if ( empty( $service ) ) {
330 return array();
331 }
332
333 return array(
334 'id' => $service->id,
335 'title' => $service->title,
336 );
337 }
338
339 /**
340 * @param $team_member_id
341 *
342 * @return array
343 */
344 public function get_team_member( $team_member_id ): array {
345 $team_member = ( new Team_Member_Model() )->find( $team_member_id );
346 if ( empty( $team_member ) ) {
347 return array();
348 }
349 $team_member = $team_member->to_array();
350
351 return array(
352 'id' => $team_member['id'],
353 'display_name' => $team_member['display_name'] ?? '',
354 'image' => $team_member['image'] ?? '',
355 );
356 }
357
358 /**
359 * Calculate total duration for a cart item (appointment)
360 *
361 * @param array $item Cart item data
362 *
363 * @return int Total duration in minutes
364 */
365 private function calculate_total_duration( $item ) {
366 $service_model = Service_Model::find( $item['service_id'] );
367 if ( ! $service_model ) {
368 return 0;
369 }
370 $service_duration = array_filter(
371 $service_model->additional_durations, function ( $duration ) use ( $item ) {
372 return $item['additional_duration_id'] === $duration['id'];
373 }
374 );
375 if ( empty( $service_duration ) ) {
376 return 0;
377 }
378 $service_duration = reset( $service_duration );
379 $total_duration_min = $service_duration['duration_unit'] === 'min'
380 ? $service_duration['duration_time']
381 : ( $service_duration['duration_time'] * 60 );
382 if ( ! empty( $item['extra_services'] ) ) {
383 $total_duration_min = apply_filters( 'booktics_extra_service_duration', $total_duration_min, $item['extra_services'] );
384 }
385
386 $total_duration_min = apply_filters( 'booktics_calculate_total_duration', $total_duration_min, $item );
387
388 return $total_duration_min;
389 }
390
391 /**
392 * Get end time for appointments from start time and total duration
393 *
394 * @param $start_time start time of the appointment
395 * @param $total_duration_min total duration of the appointment in minutes including service time and extra service time
396 *
397 * @return string end time of the appointment
398 */
399 private function get_endtime_from_starttime( $start_time, $total_duration_min ) {
400 $start_time = strtotime( $start_time );
401 $end_time = strtotime( '+' . $total_duration_min . ' minutes', $start_time );
402
403 return gmdate( 'H:i', $end_time );
404 }
405 }
406