| @@ -5,8 +5,10 @@ | ||
| 5 | 5 | * @package Timetics |
| 6 | 6 | */ |
| 7 | 7 | namespace Timetics\Core\Customers; |
| 8 | 8 | |
| 9 | +defined( 'ABSPATH' ) || exit; | |
| 10 | + | |
| 9 | 11 | use Timetics\Base\Api; |
| 10 | 12 | use Timetics\Core\Bookings\Api_Booking; |
| 11 | 13 | use Timetics\Utils\Singleton; |
| 12 | 14 | use WP_HTTP_Response; |
| @@ -57,9 +59,9 @@ | ||
| 57 | 59 | [ |
| 58 | 60 | 'methods' => \WP_REST_Server::DELETABLE, |
| 59 | 61 | 'callback' => [$this, 'bulk_delete'], |
| 60 | 62 | 'permission_callback' => function () { |
| 61 | - return current_user_can( 'manage_timetics' ); | |
| 63 | + return current_user_can( 'manage_options' ); | |
| 62 | 64 | }, |
| 63 | 65 | ], |
| 64 | 66 | ] |
| 65 | 67 | ); |
| @@ -73,24 +75,31 @@ | ||
| 73 | 75 | $this->namespace, '/' . $this->rest_base . '/(?P<customer_id>[\d]+)', [ |
| 74 | 76 | [ |
| 75 | 77 | 'methods' => \WP_REST_Server::READABLE, |
| 76 | 78 | 'callback' => [$this, 'get_item'], |
| 77 | - 'permission_callback' => function () { | |
| 78 | - return current_user_can( 'timetics-customer' ); | |
| 79 | + 'permission_callback' => function ( $request ) { | |
| 80 | + $customer_id = (int) $request['customer_id']; | |
| 81 | + | |
| 82 | + if ( timetics_can_view_all_data() || get_current_user_id() === $customer_id ) { | |
| 83 | + return true; | |
| 84 | + } | |
| 85 | + | |
| 86 | + // manage_timetics is not admin-only — staff may only view a | |
| 87 | + // customer they actually have a visible booking with. | |
| 88 | + return current_user_can( 'manage_timetics' ) | |
| 89 | + && in_array( $customer_id, timetics_get_visible_customer_ids(), true ); | |
| 79 | 90 | }, |
| 80 | 91 | ], |
| 81 | 92 | [ |
| 82 | 93 | 'methods' => \WP_REST_Server::EDITABLE, |
| 83 | - 'callback' => [$this, 'update_item'], | |
| 84 | - 'permission_callback' => function () { | |
| 85 | - return current_user_can( 'timetics-customer' ); | |
| 86 | - }, | |
| 94 | + 'callback' => [$this, 'update_item'], | |
| 95 | + 'permission_callback' => [$this, 'update_item_permission_callback'], | |
| 87 | 96 | ], |
| 88 | 97 | [ |
| 89 | 98 | 'methods' => \WP_REST_Server::DELETABLE, |
| 90 | 99 | 'callback' => [$this, 'delete_item'], |
| 91 | 100 | 'permission_callback' => function () { |
| 92 | - return current_user_can( 'timetics-customer' ); | |
| 101 | + return current_user_can( 'manage_options' ); | |
| 93 | 102 | }, |
| 94 | 103 | ], |
| 95 | 104 | ] |
| 96 | 105 | ); |
| @@ -100,9 +109,9 @@ | ||
| 100 | 109 | [ |
| 101 | 110 | 'methods' => \WP_REST_Server::READABLE, |
| 102 | 111 | 'callback' => [$this, 'search_items'], |
| 103 | 112 | 'permission_callback' => function () { |
| 104 | - return current_user_can( 'edit_posts' ); | |
| 113 | + return current_user_can( 'manage_timetics' ); | |
| 105 | 114 | }, |
| 106 | 115 | ], |
| 107 | 116 | ] |
| 108 | 117 | ); |
| @@ -116,10 +125,17 @@ | ||
| 116 | 125 | $this->namespace, '/' . $this->rest_base . '/(?P<customer_id>[\d]+)/bookings', [ |
| 117 | 126 | [ |
| 118 | 127 | 'methods' => \WP_REST_Server::READABLE, |
| 119 | 128 | 'callback' => [$this, 'get_bookings'], |
| 120 | - 'permission_callback' => function () { | |
| 121 | - return current_user_can( 'timetics-customer' ); | |
| 129 | + 'permission_callback' => function ( $request ) { | |
| 130 | + $customer_id = (int) $request['customer_id']; | |
| 131 | + | |
| 132 | + if ( timetics_can_view_all_data() || get_current_user_id() === $customer_id ) { | |
| 133 | + return true; | |
| 134 | + } | |
| 135 | + | |
| 136 | + return current_user_can( 'manage_timetics' ) | |
| 137 | + && in_array( $customer_id, timetics_get_visible_customer_ids(), true ); | |
| 122 | 138 | }, |
| 123 | 139 | ], |
| 124 | 140 | ] |
| 125 | 141 | ); |
| @@ -135,15 +151,20 @@ | ||
| 135 | 151 | public function get_items( $request ) { |
| 136 | 152 | $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 20; |
| 137 | 153 | $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1; |
| 138 | 154 | |
| 139 | - $customer = Customer::all( | |
| 140 | - [ | |
| 141 | - 'number' => $per_page, | |
| 142 | - 'paged' => $paged, | |
| 143 | - ] | |
| 144 | - ); | |
| 155 | + $args = [ | |
| 156 | + 'number' => $per_page, | |
| 157 | + 'paged' => $paged, | |
| 158 | + ]; | |
| 145 | 159 | |
| 160 | + // Staff only see customers from their own bookings, administrators see everyone. | |
| 161 | + if ( ! timetics_can_view_all_data() ) { | |
| 162 | + $args['include'] = timetics_get_visible_customer_ids( get_current_user_id() ); | |
| 163 | + } | |
| 164 | + | |
| 165 | + $customer = Customer::all( $args ); | |
| 166 | + | |
| 146 | 167 | $items = []; |
| 147 | 168 | |
| 148 | 169 | foreach ( $customer['items'] as $item ) { |
| 149 | 170 | $items[] = $this->prepare_item( $item->ID ); |
| @@ -208,15 +229,24 @@ | ||
| 208 | 229 | $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 20; |
| 209 | 230 | $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1; |
| 210 | 231 | $search = ! empty( $request['search'] ) ? sanitize_text_field( $request['search'] ) : ''; |
| 211 | 232 | |
| 233 | + $query_args = array( | |
| 234 | + 'role' => 'timetics-customer', | |
| 235 | + 'number' => $per_page, | |
| 236 | + 'paged' => $paged, | |
| 237 | + ); | |
| 238 | + | |
| 239 | + // Staff only see customers from their own bookings, administrators see everyone. | |
| 240 | + if ( ! timetics_can_view_all_data() ) { | |
| 241 | + $query_args['include'] = timetics_get_visible_customer_ids( get_current_user_id() ); | |
| 242 | + } | |
| 243 | + | |
| 212 | 244 | // Get search. |
| 213 | 245 | $users = new WP_User_Query( |
| 214 | - array( | |
| 215 | - 'role' => 'timetics-customer', | |
| 216 | - 'number' => $per_page, | |
| 217 | - 'paged' => $paged, | |
| 218 | - | |
| 246 | + array_merge( | |
| 247 | + $query_args, | |
| 248 | + array( | |
| 219 | 249 | // @codingStandardsIgnoreStart |
| 220 | 250 | 'meta_query' => array( |
| 221 | 251 | 'relation' => 'OR', |
| 222 | 252 | array( |
| @@ -240,8 +270,9 @@ | ||
| 240 | 270 | 'compare' => 'LIKE', |
| 241 | 271 | ), |
| 242 | 272 | ), |
| 243 | 273 | // @codingStandardsIgnoreEnd |
| 274 | + ) | |
| 244 | 275 | ) |
| 245 | 276 | ); |
| 246 | 277 | |
| 247 | 278 | // Prepare items for response. |
| @@ -274,8 +305,37 @@ | ||
| 274 | 305 | return $this->save_customer( $request ); |
| 275 | 306 | } |
| 276 | 307 | |
| 277 | 308 | /** |
| 309 | + * Update customer Permission check | |
| 310 | + * | |
| 311 | + * @param WP_Rest_Request $request | |
| 312 | + * | |
| 313 | + * @return JSON | WP_Error | |
| 314 | + */ | |
| 315 | + public function update_item_permission_callback( $request ) { | |
| 316 | + $customer_id = (int) $request['customer_id']; | |
| 317 | + | |
| 318 | + // Admins can always update any customer. manage_timetics is not | |
| 319 | + // admin-only — every timetics-staff account has it — so it must not | |
| 320 | + // grant edit access to someone else's customer record. | |
| 321 | + if ( current_user_can( 'manage_options' ) ) { | |
| 322 | + return true; | |
| 323 | + } | |
| 324 | + | |
| 325 | + // Customers can update themselves with a valid nonce | |
| 326 | + $nonce = $request->get_header( 'X-WP-Nonce' ); | |
| 327 | + if ( ! empty( $nonce ) && wp_verify_nonce( $nonce, 'wp_rest' ) ) { | |
| 328 | + $current_user_id = get_current_user_id(); | |
| 329 | + if ( $customer_id === $current_user_id && $current_user_id > 0 ) { | |
| 330 | + return true; | |
| 331 | + } | |
| 332 | + } | |
| 333 | + | |
| 334 | + return false; | |
| 335 | + } | |
| 336 | + | |
| 337 | + /** | |
| 278 | 338 | * Update customer |
| 279 | 339 | * |
| 280 | 340 | * @param WP_Rest_Request $request |
| 281 | 341 | * |
| @@ -494,9 +554,8 @@ | ||
| 494 | 554 | 'last_name' => $customer->get_last_name(), |
| 495 | 555 | 'email' => $customer->get_email(), |
| 496 | 556 | 'phone' => $customer->get_phone(), |
| 497 | 557 | 'image' => $customer->get_image(), |
| 498 | - 'password' => $customer->get_password(), | |
| 499 | 558 | 'total_booking' => $customer->get_total_booking(), |
| 500 | 559 | ]; |
| 501 | 560 | } |
| 502 | 561 | |
| @@ -541,8 +600,9 @@ | ||
| 541 | 600 | 'post_type' => 'timetics-booking', |
| 542 | 601 | 'posts_per_page' => $per_page, |
| 543 | 602 | 'paged' => $paged, |
| 544 | 603 | 'post_status' => 'any', |
| 604 | + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering bookings by customer | |
| 545 | 605 | 'meta_query' => array( |
| 546 | 606 | 'relation' => 'OR', |
| 547 | 607 | array( |
| 548 | 608 | 'key' => '_tt_booking_customer', |
| @@ -559,9 +619,9 @@ | ||
| 559 | 619 | $items = []; |
| 560 | 620 | $booking = new Api_Booking(); |
| 561 | 621 | |
| 562 | 622 | foreach ( $bookings->posts as $item ) { |
| 563 | - $items[] = $booking->prepare_item( $item->ID ); | |
| 623 | + $items[] = $booking->prepare_item( $item->ID, false ); | |
| 564 | 624 | } |
| 565 | 625 | |
| 566 | 626 | $data = [ |
| 567 | 627 | 'success' => 1, |