PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.18
Booktics – Appointment Booking Calendar for Service Businesses v1.0.18
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 / team-member / controllers / team-member-controller.php

team-member-controller.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.18, at core/team-member/controllers/team-member-controller.php

702 lines 24.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Team_Member\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Abstracts\User_Model;
7 use Booktics\Models\Service_Model;
8 use Booktics\Models\Team_Member_Model;
9 use Booktics\Services\Schedule_Manager;
10 use Booktics\Services\Schedule_Record;
11 use Booktics\Team_Member\Handlers\Team_Member_Event_Handler;
12 use Exception;
13 use WP_Error;
14 use WP_HTTP_Response;
15 use WP_REST_Response;
16 use WP_REST_Server;
17
18 /**
19 * Team member controller
20 *
21 * @package Booktics/Team_Member
22 */
23 class Team_Member_Controller extends Base_Rest_Controller {
24
25 /**
26 * Endpoint namespace
27 *
28 * @var string
29 */
30 protected $namespace = 'booktics/v1';
31
32 /**
33 * Route name
34 *
35 * @var string
36 */
37 protected $base = 'team-members';
38
39 /**
40 * Register routes
41 *
42 * @return void
43 */
44 public function register_routes(): void {
45 register_rest_route(
46 $this->namespace, $this->base, array(
47 array(
48 'methods' => WP_REST_Server::CREATABLE,
49 'callback' => array( $this, 'create_item' ),
50 'permission_callback' => array(
51 $this,
52 'create_team_member_permission',
53 ),
54 ),
55 array(
56 'methods' => WP_REST_Server::READABLE,
57 'callback' => array( $this, 'get_items' ),
58 'permission_callback' => array(
59 $this,
60 'get_team_member_permission',
61 ),
62 ),
63 array(
64 'methods' => WP_REST_Server::DELETABLE,
65 'callback' => array( $this, 'delete_items' ),
66 'permission_callback' => array(
67 $this,
68 'delete_team_member_permission',
69 ),
70 ),
71 )
72 );
73
74 register_rest_route(
75 $this->namespace, $this->base.'/profile', array(
76 array(
77 'methods' => WP_REST_Server::READABLE,
78 'callback' => array( $this, 'get_profile' ),
79 'permission_callback' => array(
80 $this,
81 'get_team_member_profile_permission',
82 ),
83 ),
84 )
85 );
86
87 register_rest_route(
88 $this->namespace, '/' . $this->base . '/(?P<id>[\d]+)', array(
89 array(
90 'methods' => WP_REST_Server::EDITABLE,
91 'callback' => array( $this, 'update_item' ),
92 'permission_callback' => array(
93 $this,
94 'update_team_member_permission',
95 ),
96 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
97 ),
98 array(
99 'methods' => WP_REST_Server::DELETABLE,
100 'callback' => array( $this, 'delete_item' ),
101 'permission_callback' => array(
102 $this,
103 'delete_team_member_permission',
104 ),
105 ),
106 array(
107 'method' => WP_REST_Server::READABLE,
108 'callback' => array( $this, 'get_item' ),
109 'permission_callback' => array(
110 $this,
111 'get_team_member_permission',
112 ),
113 ),
114 )
115 );
116
117 register_rest_route(
118 $this->namespace, '/' . $this->base . '/(?P<id>[\d]+)/reinvites', array(
119 array(
120 'methods' => WP_REST_Server::READABLE,
121 'callback' => array( $this, 'reinvite' ),
122 'permission_callback' => array(
123 $this,
124 'create_team_member_permission',
125 ),
126 ),
127 )
128 );
129 }
130
131 /**
132 * Check if a given request has access to create items.
133 *
134 * @param $request
135 *
136 * @return mixed
137 */
138 public function create_team_member_permission( $request ) {
139 return current_user_can( 'manage_options' );
140 }
141
142 /**
143 * Prepare and validate team member data
144 *
145 * @param $request
146 *
147 * @return array|bool|WP_Error
148 */
149 protected function prepare_team_member_data( $request ) {
150 $input_data = json_decode( $request->get_body(), true );
151
152 $validate = booktics_validate(
153 $input_data, array(
154 'first_name' => array(
155 'required',
156 ),
157 'email' => array(
158 'required',
159 'unique:wp_users,email',
160 ),
161 )
162 );
163
164 if ( is_wp_error( $validate ) ) {
165 return $validate;
166 }
167
168 return array(
169 'first_name' => $input_data['first_name'],
170 'last_name' => $input_data['last_name'],
171 'user_email' => $input_data['email'],
172 'user_login' => User_Model::generate_username( $input_data['email'] ),
173 'phone' => $input_data['phone'],
174 'email' => $input_data['email'],
175 'status' => 'pending',
176 'enable_custom_holiday' => $input_data['enable_custom_holiday'] ?? false,
177 'enable_custom_schedule' => $input_data['enable_custom_schedule'] ?? false,
178 'enable_custom_availability' => $input_data['enable_custom_availability'] ?? false,
179 );
180 }
181
182 /**
183 * Store new team member data
184 *
185 * @param $request
186 *
187 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
188 */
189 public function create_item( $request ) {
190 $prepared_data = $this->prepare_team_member_data( $request );
191 if ( is_wp_error( $prepared_data ) ) {
192 return $this->error(
193 $prepared_data->get_error_message(),
194 422,
195 'Validation',
196 $prepared_data->errors
197 );
198 }
199
200 $all_team_members = ( new Team_Member_Model() )->all( array() );
201 if ( ! booktics_is_pro_active() && isset( $all_team_members['total'] ) && $all_team_members['total'] >= 3 ) {
202 return $this->error(
203 'Team member limit reached for free plan.',
204 422,
205 'Validation'
206 );
207 }
208
209 $team_member = new Team_Member_Model();
210 $created = $team_member->save( $prepared_data );
211
212 $image_url = $request->get_param( 'image' );
213 if ( $image_url ) {
214 $team_member->update( array( 'image' => esc_url_raw( $image_url ) ) );
215 }
216
217 if ( ! $created ) {
218 return $this->error( __( 'Team member create error', 'booktics' ), 500 );
219 }
220
221 do_action( 'booktics_team_member_created', $team_member, $request );
222
223 ( new Team_Member_Event_Handler() )->on_team_member_create( $created );
224
225 return $this->response( $team_member->find( $created )->to_array(), __( 'Successfully created team member', 'booktics' ), 201 );
226 }
227
228 /**
229 * Check if a given request has access to get all team member
230 *
231 * @param $request
232 *
233 * @return bool
234 */
235 public function get_team_member_permission( $request ) {
236 // Admins and team members always have access
237 if ( booktics_is_admin_or_team_member() ) {
238 return true;
239 }
240
241 $nonce = $request->get_header( 'X-WP-Nonce' );
242 if ( $nonce && wp_verify_nonce( $nonce, 'wp_rest' ) ) {
243 return true;
244 }
245
246 return new WP_Error(
247 'booktics_forbidden',
248 __( 'You are not allowed to view team member data.', 'booktics' ),
249 array( 'status' => 403 )
250 );
251 }
252
253 /**
254 * Get paginated list items for team member
255 *
256 * @param $request
257 *
258 * @return WP_Error|WP_HTTP_Response|WP_REST_Response
259 */
260 public function get_items( $request ) {
261 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 20;
262 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
263 $first_name = ! empty( $request['first_name'] ) ? sanitize_text_field( $request['first_name'] ) : '';
264 $last_name = ! empty( $request['last_name'] ) ? sanitize_text_field( $request['last_name'] ) : '';
265 $email = ! empty( $request['email'] ) ? sanitize_email( $request['email'] ) : '';
266 $phone = ! empty( $request['phone'] ) ? sanitize_text_field( $request['phone'] ) : '';
267 $user_name = ! empty( $request['user_name'] ) ? sanitize_text_field( $request['user_name'] ) : '';
268 $status = ! empty( $request['status'] ) ? sanitize_text_field( $request['status'] ) : '';
269 $service_id = ! empty( $request['service_id'] ) ? sanitize_text_field( $request['service_id'] ) : '';
270 $search = ! empty( $request['search'] ) ? sanitize_text_field( $request['search'] ) : '';
271
272 $team_members = ( new Team_Member_Model() )
273 ->when(
274 $first_name, function ( $q, $v ) {
275 $q->where( 'first_name', $v, 'LIKE' );
276 }
277 )
278 ->when(
279 $last_name, function ( $q, $v ) {
280 $q->where( 'last_name', $v, 'LIKE' );
281 }
282 )
283 ->when(
284 $email, function ( $q, $v ) {
285 $q->where( 'user_email', $v, 'LIKE' );
286 }
287 )
288 ->when(
289 $phone, function ( $q, $v ) {
290 $q->where( 'phone', $v, 'LIKE' );
291 }
292 )
293 ->when(
294 $user_name, function ( $q, $v ) {
295 $q->where( 'user_login', $v, 'LIKE' );
296 }
297 )
298 ->when(
299 $service_id, function ( $q, $v ) {
300 $q->where( 'services', $v, 'LIKE' );
301 }
302 )
303 ->when(
304 $status, function ( $q, $v ) {
305 $q->where( 'status', $v, 'LIKE' );
306 }
307 )
308 ->when(
309 $search, function ( $q, $v ) {
310 $q->where( 'first_name', $v, 'LIKE' )
311 ->or_where( 'last_name', $v, 'LIKE' )
312 ->or_where( 'phone', $v, 'LIKE' );
313 }
314 )->all(
315 array(
316 'paged' => $paged,
317 'number' => $per_page,
318 )
319 );
320
321 // Mask sensitive private information fields based on user role
322 $is_admin = current_user_can( 'manage_options' );
323 $is_team_member = booktics_is_team_member();
324 $current_user_id = get_current_user_id();
325
326 foreach ( $team_members['items'] as &$team_member ) {
327 $team_member_array = is_array( $team_member ) ? $team_member : ( is_object( $team_member ) ? get_object_vars( $team_member ) : array() );
328
329 $team_member_wp_user_id = $team_member_array['wp_user_id'] ?? null;
330 $is_viewing_self = ( (int)$team_member_wp_user_id == (int)$current_user_id );
331
332 if ( ! $is_admin && ( ! $is_team_member || ! $is_viewing_self ) ) {
333 // Mask sensitive fields with empty string for API consistency
334 $team_member_array['user_email'] = '';
335 $team_member_array['email'] = '';
336 $team_member_array['phone'] = '';
337 $team_member_array['user_login'] = '';
338 $team_member_array['wp_user_id'] = '';
339 $team_member = $team_member_array;
340 }
341 }
342
343 return $this->response( $team_members );
344 }
345
346 /**
347 * Checks if a given request has access to update a team member.
348 *
349 * @param $request
350 *
351 * @return bool
352 */
353 public function update_team_member_permission( $request ) {
354 return current_user_can( 'manage_options' );
355 }
356
357 /**
358 * Updates a single team member.
359 *
360 * @param $request
361 *
362 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
363 * @throws Exception
364 */
365 public function update_item( $request ) {
366 if ( ! isset( $request['id'] ) ) {
367 return $this->error( __( 'Invalid team member id', 'booktics' ), 400 );
368 }
369 $team_member = ( new Team_Member_Model( $request['id'] ) );
370
371 if ( isset( $request['password'] ) ) {
372 $current_password = sanitize_text_field( $request['current_password'] ?? '' );
373 $new_password = sanitize_text_field( $request['password'] ?? '' );
374 $confirm_password = sanitize_text_field( $request['confirm_password'] ?? '' );
375
376 $validate = booktics_validate(
377 array(
378 'current_password' => $current_password,
379 'password' => $new_password,
380 'confirm_password' => $confirm_password,
381 ), array(
382 'current_password' => array( 'required' ),
383 'password' => array( 'required' ),
384 'confirm_password' => array( 'required' ),
385 )
386 );
387
388 if ( is_wp_error( $validate ) ) {
389 return $validate;
390 }
391
392 $user = wp_get_current_user();
393
394 if ( ! wp_check_password( $current_password, $user->user_pass, $user->ID ) ) {
395 return $this->error( __( 'Current password is incorrect.', 'booktics' ), 401 );
396 }
397
398 wp_set_password( $new_password, $user->ID );
399
400 return $this->response(
401 $team_member->find( $request['id'] )->to_array(),
402 __( 'Successfully updated team member', 'booktics' )
403 );
404 }
405
406 $prepared_data = $this->prepare_team_member_update( $request );
407 if ( is_wp_error( $prepared_data ) ) {
408 return $this->error( $prepared_data->get_error_message(), 422, 'Validation', $prepared_data->errors );
409 }
410
411 $updated = $team_member->update( $prepared_data );
412 // First handle adding team member to new services
413 foreach ( $prepared_data['services'] as $service ) {
414 $service_model = Service_Model::find( $service );
415 $service_team_member = $service_model->team_member == '' ? array() : $service_model->team_member;
416 if ( ! in_array( (int) $team_member->id, $service_team_member ) ) {
417 $service_team_member[] = (int) $request['id'];
418 if ( $service_model ) {
419 $service_model->update( array( 'team_member' => $service_team_member ) );
420 }
421 }
422 }
423
424 // Then remove team member from services they're no longer assigned to
425 $services = Service_Model::query()
426 ->where( 'team_member', $request['id'], 'LIKE' )
427 ->where_not_ids( $prepared_data['services'] )
428 ->get();
429
430 foreach ( $services as $service ) {
431 if ( $service->team_member ) {
432 $service_team_members = array_diff( $service->team_member, array( (int) $request['id'] ) );
433 Service_Model::find( $service->id )->update( array( 'team_member' => $service_team_members ) );
434 }
435 }
436 $image_url = $request->get_param( 'image' );
437 if ( $image_url ) {
438 $team_member->update( array( 'image' => esc_url_raw( $image_url ) ) );
439 }
440
441 if ( ! $updated ) {
442 return $this->error( __( 'Team member update error', 'booktics' ), 500 );
443 }
444 if ( isset( $request['schedule'] ) || isset( $request['custom'] ) || isset( $request['holiday'] ) ) {
445 $input_data = json_decode( $request->get_body(), true );
446 $team_member_schedule = Schedule_Manager::make( $input_data );
447 $team_member_schedule->with_team_member_id( $request['id'] );
448 $team_member_schedule->save();
449 }
450
451 do_action( 'booktics_team_member_updated', $team_member, $request );
452
453 $team_member = $team_member->find( $request['id'] )->to_array();
454 $schedule_record = ( new Schedule_Record() )->get_schedules( $request['id'] );
455 $team_member['schedule'] = empty( $schedule_record['schedule'] )
456 ? (object) array() : $schedule_record['schedule'];
457 $team_member['custom'] = $schedule_record['custom'];
458 $team_member['holiday'] = $schedule_record['holiday'];
459
460 return $this->response(
461 $team_member,
462 __( 'Successfully updated team member', 'booktics' )
463 );
464 }
465
466 /**
467 * Prepare data for team member update
468 *
469 * @param $request
470 *
471 * @return array|bool|WP_Error
472 */
473 public function prepare_team_member_update( $request ) {
474 $input_data = json_decode( $request->get_body(), true );
475 $validation_rules = array(
476 'first_name' => array(
477 'required',
478 ),
479 'email' => array(
480 'required',
481 'unique:wp_users,email,' . $request['id'],
482 ),
483 );
484 $update_validation_rules = array();
485 $validated_data = array();
486 foreach ( $input_data as $key => $value ) {
487 $validated_data[ $key ] = $value;
488 if ( ! isset( $validation_rules[ $key ] ) ) {
489 continue;
490 }
491 $update_validation_rules[ $key ] = $validation_rules[ $key ];
492 if ( 'email' == $key ) {
493 $update_validation_rules[ $key ] = $validation_rules[ $key ];
494 $validated_data['user_email'] = sanitize_email( $value );
495 }
496 }
497 $validate = booktics_validate( $input_data, $update_validation_rules );
498
499 if ( is_wp_error( $validate ) ) {
500 return $validate;
501 }
502
503 $validated_data['services'] = ! empty( $input_data['services'] ) ? $input_data['services'] : array();
504 //$validated_data['status'] = 'active';
505
506 return $validated_data;
507 }
508
509 /**
510 * Checks if a given request has access to delete a team member.
511 *
512 * @param $request
513 *
514 * @return bool
515 */
516 public function delete_team_member_permission( $request ) {
517 return current_user_can( 'manage_options' );
518 }
519
520 /**
521 * Delete a team member
522 *
523 * @param $request
524 *
525 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
526 */
527 public function delete_item( $request ) {
528 $id = intval( $request['id'] );
529
530 $team_member = new Team_Member_Model( $id );
531 $previous = $team_member->find( $id );
532
533 // Remove team member from assigned services
534 $services = Service_Model::query()->where( 'team_member', $id, 'LIKE' )->get();
535 foreach ( $services as $service ) {
536 if ( $service->team_member ) {
537 $service_team_members = array_diff( $service->team_member, array( $id ) );
538 Service_Model::find( $service->id )->update( array( 'team_member' => $service_team_members ) );
539 }
540 }
541
542 $deleted = $team_member->delete();
543
544 if ( ! $deleted ) {
545 return $this->error( __( 'Team member delete error', 'booktics' ), 500 );
546 }
547
548 do_action( 'booktics_team_members_deleted', $request );
549
550 return $this->response( $previous, __( 'Successfully deleted team member', 'booktics' ) );
551 }
552
553 /**
554 * Delete multiple team member
555 *
556 * @param $request
557 *
558 * @return array|WP_Error|WP_HTTP_Response
559 */
560 public function delete_items( $request ) {
561 $ids = ! empty( $request['ids'] ) ? $request['ids'] : array();
562
563 if ( ! $ids ) {
564 return $this->error( __( 'Team member ids cannot be empty', 'booktics' ), 500 );
565 }
566 $count = 0;
567 foreach ( $ids as $id ) {
568 $team_member = new Team_Member_Model( $id );
569 if ( $team_member->delete() ) {
570 ++$count;
571 }
572 }
573 if ( 0 == $count ) {
574 return $this->error( __( 'Team member delete error', 'booktics' ), 500 );
575 }
576
577 do_action( 'booktics_team_members_deleted', $request );
578
579 return $this->response( array(), __( 'Successfully deleted team member', 'booktics' ) );
580 }
581
582 /**
583 * Get single team member
584 *
585 * @param $request
586 *
587 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
588 */
589 public function get_item( $request ) {
590 $id = intval( $request['id'] );
591 $team_member = new Team_Member_Model( $id );
592 $team_member = $team_member->find( $id );
593 if ( ! $team_member ) {
594 return $this->error( __( 'Team member not found', 'booktics' ), 404 );
595 }
596 $team_member = $team_member->to_array();
597 $schedule_record = ( new Schedule_Record() )->get_schedules( $request['id'] );
598 $team_member['schedule'] = ! empty( $schedule_record['schedule'] ) ? $schedule_record['schedule'] : $team_member['schedule'];
599 $team_member['custom'] = ! empty( $schedule_record['custom'] ) ? $schedule_record['custom'] : $team_member['custom'];
600 $team_member['holiday'] = ! empty( $schedule_record['holiday'] ) ? $schedule_record['holiday'] : $team_member['holiday'];
601
602 // Add filter to allow modifying team member data
603 $team_member = apply_filters( 'booktics_team_member_data', $team_member, $id );
604
605 // Mask PII fields based on user role
606 $is_admin = current_user_can( 'manage_options' );
607 $is_team_member = booktics_is_team_member();
608 $current_user_id = get_current_user_id();
609
610 // Get the team member's WP user ID to check if viewing own record
611 $team_member_wp_user_id = $team_member['wp_user_id'] ?? null;
612 $is_viewing_self = ( $team_member_wp_user_id == $current_user_id );
613
614 // If not admin and (not team member OR viewing someone else's record)
615 if ( ! $is_admin && ( ! $is_team_member || ! $is_viewing_self ) ) {
616 // Mask sensitive fields with empty string for API consistency
617 $team_member['user_email'] = '';
618 $team_member['email'] = '';
619 $team_member['phone'] = '';
620 $team_member['user_login'] = '';
621 $team_member['wp_user_id'] = '';
622 }
623
624 return $this->response( $team_member, __( 'Team member successfully fetched', 'booktics' ) );
625 }
626
627 /**
628 * Check if a given request has access to create items.
629 *
630 * @param $request
631 *
632 * @return mixed
633 */
634 public function create_team_member_schedule_permission( $request ) {
635 return current_user_can( 'manage_options' );
636 }
637
638 /**
639 * Team member reinvite
640 *
641 * @param $request
642 *
643 * @return array|WP_HTTP_Response
644 */
645 public function reinvite( $request ) {
646 $id = intval( $request['id'] );
647
648 $invite = ( new Team_Member_Event_Handler() )->on_team_member_create( $id );
649
650 if ( ! $invite ) {
651 return $this->error( __( 'Team member reinvite error', 'booktics' ) );
652 }
653
654 return $this->response( array(), __( 'Team member reinvited successfully', 'booktics' ) );
655 }
656
657 /**
658 * Check if a given request has access to get team member profile.
659 *
660 * @return bool
661 */
662 public function get_team_member_profile_permission() {
663 // Team members can view their own profile
664 if ( booktics_is_admin_or_team_member() ) {
665 return true;
666 }
667
668 return new WP_Error(
669 'booktics_forbidden',
670 __( 'You are not allowed to view team member profile.', 'booktics' ),
671 array( 'status' => 403 )
672 );
673 }
674
675 /**
676 * Get team member profile
677 *
678 * @param $request
679 *
680 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
681 */
682 public function get_profile( $request ) {
683 $id = get_current_user_id();
684
685 $team_member = new Team_Member_Model( $id );
686 $team_member = $team_member->find( $id );
687 if ( ! $team_member ) {
688 return $this->error( __( 'Team member not found', 'booktics' ), 404 );
689 }
690 $team_member = $team_member->to_array();
691 $schedule_record = ( new Schedule_Record() )->get_schedules( $id );
692 $team_member['schedule'] = ! empty( $schedule_record['schedule'] ) ? $schedule_record['schedule'] : $team_member['schedule'];
693 $team_member['custom'] = ! empty( $schedule_record['custom'] ) ? $schedule_record['custom'] : $team_member['custom'];
694 $team_member['holiday'] = ! empty( $schedule_record['holiday'] ) ? $schedule_record['holiday'] : $team_member['holiday'];
695
696 // Add filter to allow modifying team member data
697 $team_member = apply_filters( 'booktics_team_member_data', $team_member, $id );
698
699 return $this->response( $team_member, __( 'Team member successfully fetched', 'booktics' ) );
700 }
701 }
702