PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.17
Booktics – Appointment Booking Calendar for Service Businesses v1.0.17
1.0.29 1.0.28 1.0.27 1.0.26 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 All 31 releases
booktics / core / service / controllers / service-controller.php

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

705 lines 26.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\Service\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Models\Service_Model;
7 use Booktics\Models\Team_Member_Model;
8 use Booktics\Services\Schedule_Manager;
9 use Booktics\Services\Schedule_Record;
10 use Booktics\Taxonomy\Category;
11 use Booktics\Service\Helpers\Service_Helper;
12 use Exception;
13 use WP_Error;
14 use WP_HTTP_Response;
15 use WP_REST_Server;
16
17 /**
18 * Service controller
19 *
20 * @package Booktics/Service
21 */
22 class Service_Controller extends Base_Rest_Controller {
23 /**
24 * Endpoint namespace
25 *
26 * @var string
27 */
28 protected $namespace = 'booktics/v1';
29
30 /**
31 * Route name
32 *
33 * @var string
34 */
35 protected $base = 'services';
36
37 /**
38 * Register routes
39 *
40 * @return void
41 */
42 public function register_routes(): void {
43 register_rest_route(
44 $this->namespace, $this->base, array(
45 array(
46 'methods' => WP_REST_Server::CREATABLE,
47 'callback' => array( $this, 'create_item' ),
48 'permission_callback' => array(
49 $this,
50 'create_item_permissions_check',
51 ),
52 ),
53 )
54 );
55
56 register_rest_route(
57 $this->namespace, $this->base, array(
58 array(
59 'methods' => WP_REST_Server::READABLE,
60 'callback' => array( $this, 'get_items' ),
61 'permission_callback' => array(
62 $this,
63 'get_items_permissions_check',
64 ),
65 ),
66 )
67 );
68
69 register_rest_route(
70 $this->namespace, $this->base, array(
71 array(
72 'methods' => WP_REST_Server::DELETABLE,
73 'callback' => array( $this, 'delete_items' ),
74 'permission_callback' => array(
75 $this,
76 'delete_items_permissions_check',
77 ),
78 ),
79 )
80 );
81
82 register_rest_route(
83 $this->namespace, $this->base . '/(?P<id>[\d]+)', array(
84 array(
85 'methods' => WP_REST_Server::READABLE,
86 'callback' => array( $this, 'get_item' ),
87 'permission_callback' => array(
88 $this,
89 'get_item_permissions_check',
90 ),
91 ),
92 )
93 );
94
95 register_rest_route(
96 $this->namespace, $this->base . '/(?P<id>[\d]+)', array(
97 array(
98 'methods' => WP_REST_Server::EDITABLE,
99 'callback' => array( $this, 'update_item' ),
100 'permission_callback' => array(
101 $this,
102 'update_item_permissions_check',
103 ),
104 ),
105 )
106 );
107
108 register_rest_route(
109 $this->namespace, $this->base . '/(?P<id>[\d]+)', array(
110 array(
111 'methods' => WP_REST_Server::DELETABLE,
112 'callback' => array( $this, 'delete_item' ),
113 'permission_callback' => array(
114 $this,
115 'delete_item_permissions_check',
116 ),
117 ),
118 )
119 );
120
121 register_rest_route(
122 $this->namespace, $this->base . '/(?P<id>[\d]+)/clone', array(
123 array(
124 'methods' => WP_REST_Server::CREATABLE,
125 'callback' => array( $this, 'clone_item' ),
126 'permission_callback' => array(
127 $this,
128 'create_item_permissions_check',
129 ),
130 ),
131 )
132 );
133 }
134
135 /**
136 * Get one item from the collection.
137 *
138 * @param $request
139 *
140 * @return WP_HTTP_Response
141 */
142 public function get_item( $request ) {
143 $id = intval( $request['id'] );
144
145 $service = Service_Model::find( $id );
146
147 if ( ! $service ) {
148 return $this->error( __( 'Service not found', 'booktics' ), 404 );
149 }
150 $service = $service->to_array();
151
152 $team_member_ids = is_array( $service['team_member'] )
153 ? array_unique( $service['team_member'] ) : array( $service['team_member'] );
154 $service['team_member_details'] = Service_Helper::team_member_details( $team_member_ids );
155 $service['team_member'] = $team_member_ids;
156
157 $category_ids = $this->get_service_category_ids( $service['id'] );
158 $service['category_details'] = Service_Helper::category_details( $category_ids );
159 $service['categories'] = $category_ids;
160
161 $schedule_record = ( new Schedule_Record() )
162 ->get_schedules( $id, 'service' );
163 $service['schedule'] = empty( $schedule_record['schedule'] ) ? (object) array()
164 : $schedule_record['schedule'];
165
166 $service['custom'] = $schedule_record['custom'];
167 $service['holiday'] = $schedule_record['holiday'];
168 $service['shortcode'] = '[booktics_service_view id="' . $service['id'] . '"]';
169 $service = apply_filters( 'booktics_service_format', $service );
170
171 return $this->response( $service );
172 }
173
174 /**
175 * Check if a given request has access to get a specific item.
176 *
177 * @param $request
178 *
179 * @return WP_Error
180 */
181 public function get_item_permissions_check( $request ): bool {
182 return true;
183 }
184
185 /**
186 * Get a collection of items.
187 *
188 * @param $request
189 *
190 * @return WP_HTTP_Response
191 */
192 public function get_items( $request ) {
193 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 10;
194 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
195
196 $args = array(
197 'per_page' => $per_page,
198 'paged' => $paged,
199 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering services by meta fields
200 'meta_query' => array(),
201 );
202
203 if ( ! empty( $request['team_member_id'] ) ) {
204 $args['meta_query'][] = array(
205 'key' => 'team_member',
206 'value' => sprintf( 'i:%d;', intval( $request['team_member_id'] ) ),
207 'compare' => 'LIKE',
208 );
209 }
210
211 if ( ! empty( $request['search'] ) ) {
212 $args['s'] = sanitize_text_field( $request['search'] );
213 }
214
215 if ( ! empty( $args['meta_query'] ) ) {
216 $args['meta_query']['relation'] = 'AND';
217 }
218
219 $args = apply_filters( 'booktics_service_args', $args, $request );
220
221 $data = Service_Model::paginate( $per_page, $paged, $args );
222
223 if ( ! $data ) {
224 return $this->error( __( 'No service found', 'booktics' ), 404 );
225 }
226
227 if ( ! empty( $request['category_id'] ) ) {
228 $service_ids_for_category = get_term_meta( absint( $request['category_id'] ), 'booktics_category_services', true );
229 }
230
231 if ( ! empty( $data['items'] ) ) {
232 $enhanced_items = array();
233 foreach ( $data['items'] as $service ) {
234 $service_array = $service->to_array();
235
236 if ( isset( $service_ids_for_category ) && ! in_array( $service_array['id'], $service_ids_for_category ) ) {
237 continue;
238 }
239
240 if ( ! empty( $service_array['team_member'] ) ) {
241 $team_member_ids = is_array( $service->team_member ) ? array_unique( $service->team_member ) : array( $service->team_member );
242 $service->team_member = $team_member_ids;
243 $service_array['team_member_details'] = Service_Helper::team_member_details( $team_member_ids );
244 }
245 $category_ids = $this->get_service_category_ids( $service_array['id'] );
246 if ( ! empty( $category_ids ) ) {
247 $service->categories = $category_ids;
248 $service_array['category_details'] = Service_Helper::category_details( $category_ids );
249 }
250 $service_array['shortcode'] = '[booktics_service_view id="' . $service_array['id'] . '"]';
251 $service_array = apply_filters( 'booktics_service_format', $service_array );
252 $enhanced_items[] = $service_array;
253 }
254 $data['items'] = $enhanced_items;
255 }
256
257 return $this->response( $data );
258 }
259
260 /**
261 * Get all category IDs linked to a given service
262 *
263 * @param int $service_id
264 * @return array
265 */
266 private function get_service_category_ids( int $service_id ): array {
267 $args = array(
268 'taxonomy' => 'booktics_service_category', // Category taxonomy
269 'hide_empty' => false,
270 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for services by category ID
271 'meta_query' => array(
272 array(
273 'key' => 'booktics_category_services',
274 'value' => sprintf( 'i:%d;', $service_id ),
275 'compare' => 'LIKE',
276 ),
277 ),
278 'fields' => 'ids', // returning only ids
279 );
280
281 $categories = get_terms( $args );
282
283 if ( is_wp_error( $categories ) || empty( $categories ) ) {
284 return [];
285 }
286
287 return $categories;
288 }
289
290
291 /**
292 * Check if a given request has access to get items.
293 *
294 * @param $request
295 *
296 * @return WP_Error
297 */
298 public function get_items_permissions_check( $request ): bool {
299 return true;
300 }
301
302 /**
303 * Create one item from the collection.
304 *
305 * @param $request
306 *
307 * @return WP_HTTP_Response
308 * @throws Exception
309 */
310 public function create_item( $request ) {
311 $data = $this->prepare_item_for_database( $request );
312 if ( is_wp_error( $data ) ) {
313 return $this->error( $data->get_error_message() );
314 }
315
316 $service = Service_Model::create( $data );
317
318 do_action( 'booktics_service_created', $service, $request );
319
320 if ( ! empty( $data['team_member'] ) && is_array( $data['team_member'] ) ) {
321 $this->update_team_members( $service->id, $data['team_member'] );
322 $this->remove_service_from_other_team_members( $service->id, $data['team_member'] );
323 }
324
325 if ( is_wp_error( $service ) ) {
326 return $this->error( $service->get_error_message() );
327 }
328 $service = $service->to_array();
329 $service['shortcode'] = '[booktics_service_view id="' . $service['id'] . '"]';
330 $service['category_details'] = Service_Helper::category_details( $service['categories'] );
331 $service['team_member_details'] = Service_Helper::team_member_details( $service['team_member'] );
332 $service = apply_filters( 'booktics_service_format', $service );
333
334 return $this->response( $service, __( 'Successfully created service', 'booktics' ) );
335 }
336
337 /**
338 * Check if a given request has access to create items.
339 *
340 * @param $request
341 *
342 * @return WP_Error
343 */
344 public function create_item_permissions_check( $request ): bool {
345 return current_user_can( 'manage_options' );
346 }
347
348 /**
349 * Update one item from the collection.
350 *
351 * @param $request
352 *
353 * @return WP_HTTP_Response
354 * @throws Exception
355 */
356 public function update_item( $request ) {
357 $id = (int) $request->get_param( 'id' );
358 $data = $this->prepare_item_for_database( $request );
359
360 if ( is_wp_error( $data ) ) {
361 return $this->error( $data->get_error_message() );
362 }
363
364 $service = Service_Model::find( $id );
365
366 if ( ! $service ) {
367 return $this->error( __( 'Service not found', 'booktics' ), 404 );
368 }
369
370 $result = $service->update( $data );
371
372 if ( is_wp_error( $result ) ) {
373 return $this->error( $result->get_error_message() );
374 }
375
376 if ( ! empty( $data['team_member'] ) && is_array( $data['team_member'] ) ) {
377 $this->update_team_members( $id, $data['team_member'] );
378 $this->remove_service_from_other_team_members( $id, $data['team_member'] );
379 }
380
381 $input_data = json_decode( $request->get_body(), true );
382 $this->update_service_schedule( $id, $input_data );
383
384 do_action( 'booktics_service_updated', $service, $request );
385
386 $response = $this->format_service_response( $id );
387 return $this->response( $response, __( 'Successfully updated service', 'booktics' ) );
388 }
389
390 /**
391 * Update team members for a service
392 */
393 private function update_team_members( $service_id, $team_members ) {
394 foreach ( $team_members as $team_member_id ) {
395 $team_member_model = ( new Team_Member_Model( $team_member_id ) );
396 $team_member = $team_member_model->find( $team_member_id );
397 $team_member_services = $team_member->services == '' ? array() : $team_member->services;
398
399 if ( ! in_array( (int) $service_id, $team_member_services ) ) {
400 $team_member_services[] = (int) $service_id;
401 if ( $team_member ) {
402 $team_member->update(
403 array(
404 'services' => $team_member_services,
405 )
406 );
407 }
408 }
409 }
410 }
411
412 /**
413 * Remove service from other team members not in the list
414 */
415 private function remove_service_from_other_team_members( $service_id, $team_members ) {
416 $other_team_members = ( new Team_Member_Model() )
417 ->where( 'services', $service_id, 'LIKE' )
418 ->all( array() );
419
420 if ( ! empty( $other_team_members['items'] ) ) {
421 foreach ( $other_team_members['items'] as $other_team_member ) {
422 if ( ! in_array( $other_team_member['id'], $team_members ) ) {
423 $other_services = $other_team_member['services'];
424 if ( ( $key = array_search( $service_id, $other_services ) ) !== false ) {
425 unset( $other_services[ $key ] );
426 ( new Team_Member_Model( $other_team_member['id'] ) )->update(
427 array(
428 'services' => array_values( $other_services ),
429 )
430 );
431 }
432 }
433 }
434 }
435 }
436
437 /**
438 * Update the service schedule if provided
439 */
440 private function update_service_schedule( $service_id, $input_data ) {
441 if ( isset( $input_data['schedule'] ) || isset( $input_data['custom'] ) || isset( $input_data['holiday'] ) ) {
442 $service_schedule = Schedule_Manager::make( $input_data );
443 $service_schedule->with_service_id( $service_id );
444 $service_schedule->save();
445 }
446 }
447
448 /**
449 * Format the service response after update
450 */
451 private function format_service_response( $service_id ) {
452 $response = Service_Model::find( $service_id )->to_array();
453 $response['shortcode'] = '[booktics_service_view id="' . $response['id'] . '"]';
454 $response['team_member_details'] = Service_Helper::team_member_details( $response['team_member'] );
455 if ( ! empty( $response['categories'] ) ) {
456 $category_ids = is_array( $response['categories'] ) ? $response['categories'] : array( $response['categories'] );
457 $response['category_details'] = Service_Helper::category_details( $category_ids );
458 }
459 return apply_filters( 'booktics_service_format', $response );
460 }
461
462 /**
463 * Check if a given request has access to update a specific item.
464 *
465 * @param $request
466 *
467 * @return WP_Error
468 */
469 public function update_item_permissions_check( $request ) {
470 return current_user_can( 'manage_options' );
471 }
472
473 /**
474 * Delete one item from the collection.
475 *
476 * @param $request
477 *
478 * @return WP_HTTP_Response
479 */
480 public function delete_item( $request ) {
481 $id = intval( $request['id'] );
482
483 $service = Service_Model::find( $id );
484
485 if ( ! $service ) {
486 return $this->error( __( 'Service not found', 'booktics' ), 404 );
487 }
488
489 $deleted = $service->delete();
490
491 if ( is_wp_error( $deleted ) ) {
492 return $this->error( $deleted->get_error_message() );
493 }
494
495 return $this->response( $service, __( 'Successfully deleted service', 'booktics' ) );
496 }
497
498 /**
499 * Check if a given request has access to delete a specific item.
500 *
501 * @param $request
502 *
503 * @return WP_Error
504 */
505 public function delete_item_permissions_check( $request ) {
506 return current_user_can( 'manage_options' );
507 }
508
509 /**
510 * Delete a collection of items
511 *
512 * @param $request
513 *
514 * @return WP_HTTP_Response
515 */
516 public function delete_items( $request ) {
517 $ids = isset( $request['ids'] ) ? $request['ids'] : array();
518
519 if ( ! is_array( $ids ) ) {
520 return $this->error( __( 'Ids should be an array of items ids', 'booktics' ) );
521 }
522
523 $count = 0;
524 $total = count( $ids );
525
526 foreach ( $ids as $id ) {
527 $service = Service_Model::find( $id );
528
529 if ( $service ) {
530 $service->delete();
531 ++$count;
532 }
533 }
534
535 if ( 0 === $count ) {
536 return $this->error( __( 'No item deleted.', 'booktics' ) );
537 }
538 /* translators: 1: deleted count, 2: total count */
539 $message = sprintf( __( 'Deleted %1$d items of %2$d', 'booktics' ), $count, $total );
540
541 return $this->response( array(), $message );
542 }
543
544 /**
545 * Check if a given request has access to delete a collection of items.
546 *
547 * @param $request
548 *
549 * @return WP_Error
550 */
551 public function delete_items_permissions_check( $request ) {
552 return current_user_can( 'manage_options' );
553 }
554
555 /**
556 * Prepare the item for create or update operation.
557 *
558 * @param $request
559 *
560 * @return WP_Error
561 */
562 public function prepare_item_for_database( $request ) {
563 $data = json_decode( $request->get_body(), true );
564 $validate = booktics_validate(
565 $data, array(
566 'title' => array(
567 'required',
568 ),
569 )
570 );
571
572 if ( is_wp_error( $validate ) ) {
573 return $validate;
574 }
575 if ( empty( $data['team_member'] ) ) {
576 $admin_query = new \WP_User_Query(
577 array(
578 'role' => array( 'administrator', 'booktics_team_member' ),
579 )
580 );
581 $admins = $admin_query->get_results();
582 $admin = reset( $admins );
583 $team_member_id = $admin->id;
584 $data['team_member'] = array( $team_member_id );
585 }
586
587 $data['title'] = ! empty( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '';
588 $data['description'] = ! empty( $data['description'] ) ? sanitize_text_field( $data['description'] ) : '';
589 $data['content'] = ! empty( $data['content'] ) ? sanitize_text_field( $data['content'] ) : '';
590 $data['link'] = ! empty( $data['link'] ) ? sanitize_text_field( $data['link'] ) : '';
591 $data['image'] = ! empty( $data['image'] ) ? esc_url_raw( $data['image'] ) : '';
592 $data['image_id'] = ! empty( $data['image_id'] ) ? esc_url_raw( $data['image_id'] ) : '';
593 $data['interval_time'] = ! empty( $data['interval_time'] ) ? sanitize_text_field( $data['interval_time'] ) : 0;
594 $data['before_buffer_time'] = ! empty( $data['before_buffer_time'] ) ? sanitize_text_field( $data['before_buffer_time'] ) : 0;
595 $data['after_buffer_time'] = ! empty( $data['after_buffer_time'] ) ? sanitize_text_field( $data['after_buffer_time'] ) : 0;
596 $data['show_end_time'] = ! empty( $data['show_end_time'] ) ? sanitize_text_field( $data['show_end_time'] ) : false;
597 $data['status'] = ! empty( $data['status'] ) ? sanitize_text_field( $data['status'] ) : 'deactive';
598 $data['team_member'] = empty( $data['team_member'] ) ? array() : array_unique( $data['team_member'] );
599 $data['categories'] = empty( $data['categories'] ) ? array() : array_unique( $data['categories'] );
600 $sanitized_durations = array();
601 if ( ! empty( $data['additional_durations'] ) ) {
602 foreach ( $data['additional_durations'] as $duration ) {
603 if ( is_array( $duration ) ) {
604 $sanitized_durations[] = array(
605 'id' => ! empty( $duration['id'] ) ? sanitize_text_field( $duration['id'] ) : '',
606 'name' => ! empty( $duration['name'] ) ? sanitize_text_field( $duration['name'] ) : '',
607 'duration_time' => ! empty( $duration['duration_time'] ) ? sanitize_text_field( $duration['duration_time'] ) : 0,
608 'duration_unit' => ! empty( $duration['duration_unit'] ) ? sanitize_text_field( $duration['duration_unit'] ) : '',
609 'charge_amount' => ! empty( $duration['charge_amount'] ) ? (float) number_format( (float) $duration['charge_amount'], 4, '.', '' ) : '0.0000',
610 'deposit_amount' => ! empty( $duration['deposit_amount'] ) ? (float) number_format( (float) $duration['deposit_amount'], 4, '.', '' ) : '0.0000',
611 );
612 }
613 }
614 } else {
615 $sanitized_durations = array(
616 array(
617 'id' => 'default',
618 'name' => 'default',
619 'duration_time' => 30,
620 'duration_unit' => 'min',
621 'charge_amount' => 0,
622 'deposit_amount' => 0,
623 ),
624 );
625 }
626 $data['additional_durations'] = $sanitized_durations;
627 $data = apply_filters( 'booktics_prepare_service_custom_fields', $data );
628 $image_url = $request->get_param( 'image' );
629 if ( $image_url ) {
630 $data['image'] = esc_url_raw( $image_url );
631 }
632
633 return $data;
634 }
635
636 /**
637 * Clone service from the record.
638 *
639 * @param $request
640 *
641 * @return WP_HTTP_Response
642 * @throws Exception
643 */
644 public function clone_item( $request ) {
645 $id = intval( $request['id'] );
646
647 $service = Service_Model::find( $id );
648
649 if ( ! $service ) {
650 return $this->error( __( 'Service not found', 'booktics' ), 404 );
651 }
652 $clone_data = [
653 'title' => $service->title . __( ' Copy', 'booktics' ),
654 'description' => $service->description,
655 'content' => $service->content,
656 'image' => $service->image,
657 'image_id' => $service->image_id,
658 'categories' => $service->categories,
659 'interval_time' => $service->interval_time,
660 'before_buffer_time' => $service->before_buffer_time,
661 'after_buffer_time' => $service->after_buffer_time,
662 'team_member' => $service->team_member,
663 'show_end_time' => $service->show_end_time,
664 'additional_durations' => $service->additional_durations,
665 'enable_custom_holiday' => $service->enable_custom_holiday,
666 'enable_custom_schedule' => $service->enable_custom_schedule,
667 'enable_additional_service_duration' => $service->enable_additional_service_duration,
668 ];
669
670 $clone_data = apply_filters( 'booktics_clone_service_data', $clone_data, $service );
671
672 $cloned = Service_Model::create( $clone_data );
673
674 if ( is_wp_error( $cloned ) ) {
675 return $this->error( $cloned->get_error_message() );
676 }
677
678 $schedule_record = ( new Schedule_Record() )
679 ->get_schedules( $id, 'service' );
680 $schedule_data['schedule'] = $schedule_record['schedule'];
681 $schedule_data['custom'] = $schedule_record['custom'];
682 $schedule_data['holiday'] = $schedule_record['holiday'];
683
684 $service_schedule = Schedule_Manager::make( $schedule_data );
685 $service_schedule->with_service_id( $cloned->id );
686 $service_schedule->save();
687
688 $team_member_details = Service_Helper::team_member_details( $service->team_member );
689 $cloned = $cloned->to_array();
690 $cloned['team_member_details'] = $team_member_details;
691 $cloned['category_details'] = Service_Helper::category_details( $cloned['categories'] );
692
693 $clone_schedule_record = ( new Schedule_Record() )
694 ->get_schedules( $cloned['id'], 'service' );
695 $cloned['schedule'] = empty( $clone_schedule_record['schedule'] ) ? (object) array()
696 : $clone_schedule_record['schedule'];
697 $cloned['custom'] = $clone_schedule_record['custom'];
698 $cloned['holiday'] = $clone_schedule_record['holiday'];
699 $cloned['shortcode'] = '[booktics_service_view id="' . $cloned['id'] . '"]';
700 $cloned = apply_filters( 'booktics_service_format', $cloned );
701
702 return $this->response( $cloned, __( 'Successfully cloned service', 'booktics' ) );
703 }
704 }
705