| @@ -45,9 +45,9 @@ | ||
| 45 | 45 | register_rest_route($namespace, '/' . $base, [ |
| 46 | 46 | [ |
| 47 | 47 | 'methods' => \WP_REST_Server::READABLE, |
| 48 | 48 | 'callback' => [$this, 'getEnquiries'], |
| 49 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 49 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 50 | 50 | ], |
| 51 | 51 | [ |
| 52 | 52 | 'methods' => \WP_REST_Server::CREATABLE, |
| 53 | 53 | 'callback' => [$this, 'createEnquiry'], |
| @@ -54,61 +54,93 @@ | ||
| 54 | 54 | 'permission_callback' => '__return_true', // Public endpoint |
| 55 | 55 | ], |
| 56 | 56 | ]); |
| 57 | 57 | |
| 58 | - // Single enquiry operations | |
| 58 | + // Single enquiry operations — read uses view, write uses respond, | |
| 59 | + // delete uses the dedicated delete cap (high sensitivity). | |
| 59 | 60 | register_rest_route($namespace, '/' . $base . '/(?P<id>[\d]+)', [ |
| 60 | 61 | [ |
| 61 | 62 | 'methods' => \WP_REST_Server::READABLE, |
| 62 | 63 | 'callback' => [$this, 'getEnquiry'], |
| 63 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 64 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 64 | 65 | ], |
| 65 | 66 | [ |
| 66 | 67 | 'methods' => \WP_REST_Server::EDITABLE, |
| 67 | 68 | 'callback' => [$this, 'updateEnquiry'], |
| 68 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 69 | + 'permission_callback' => [$this, 'checkCanRespond'], | |
| 69 | 70 | ], |
| 70 | 71 | [ |
| 71 | 72 | 'methods' => \WP_REST_Server::DELETABLE, |
| 72 | 73 | 'callback' => [$this, 'deleteEnquiry'], |
| 73 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 74 | + 'permission_callback' => [$this, 'checkCanDelete'], | |
| 74 | 75 | ], |
| 75 | 76 | ]); |
| 76 | 77 | |
| 77 | - // Bulk actions | |
| 78 | + // Bulk actions — operations include status change, mark-read, | |
| 79 | + // delete, etc. Gate on respond (the broadest mutation cap | |
| 80 | + // short of delete). Bulk-delete callers should re-check | |
| 81 | + // delete-cap inside the handler when the action is "delete". | |
| 78 | 82 | register_rest_route($namespace, '/' . $base . '/bulk', [ |
| 79 | 83 | [ |
| 80 | 84 | 'methods' => \WP_REST_Server::EDITABLE, |
| 81 | 85 | 'callback' => [$this, 'bulkAction'], |
| 82 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 86 | + 'permission_callback' => [$this, 'checkCanRespond'], | |
| 83 | 87 | ], |
| 84 | 88 | ]); |
| 85 | 89 | |
| 86 | - // Stats endpoint | |
| 90 | + // Stats endpoint — read-only aggregation, view cap is enough. | |
| 87 | 91 | register_rest_route($namespace, '/' . $base . '/stats', [ |
| 88 | 92 | [ |
| 89 | 93 | 'methods' => \WP_REST_Server::READABLE, |
| 90 | 94 | 'callback' => [$this, 'getStats'], |
| 91 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 95 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 92 | 96 | ], |
| 93 | 97 | ]); |
| 94 | 98 | |
| 95 | - // Respond to enquiry | |
| 99 | + // Respond to enquiry — explicit respond-cap. | |
| 96 | 100 | register_rest_route($namespace, '/' . $base . '/(?P<id>[\d]+)/respond', [ |
| 97 | 101 | [ |
| 98 | 102 | 'methods' => \WP_REST_Server::CREATABLE, |
| 99 | 103 | 'callback' => [$this, 'respondToEnquiry'], |
| 100 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 104 | + 'permission_callback' => [$this, 'checkCanRespond'], | |
| 101 | 105 | ], |
| 102 | 106 | ]); |
| 103 | 107 | } |
| 104 | 108 | |
| 105 | 109 | /** |
| 106 | - * Check admin permission | |
| 110 | + * Granular permission checks — one per operation so role bundles | |
| 111 | + * (Sales Agent, Front Desk, etc.) can actually use the parts of | |
| 112 | + * the enquiry surface their role grants. WP administrators pass | |
| 113 | + * every cap automatically via the Team module's admin-fallback | |
| 114 | + * filter, so `manage_options` doesn't need an explicit check | |
| 115 | + * here — it's covered by the cap. | |
| 107 | 116 | */ |
| 117 | + public function checkCanView(): bool | |
| 118 | + { | |
| 119 | + return current_user_can('yatra_view_enquiries'); | |
| 120 | + } | |
| 121 | + | |
| 122 | + public function checkCanRespond(): bool | |
| 123 | + { | |
| 124 | + return current_user_can('yatra_respond_to_enquiries'); | |
| 125 | + } | |
| 126 | + | |
| 127 | + public function checkCanDelete(): bool | |
| 128 | + { | |
| 129 | + return current_user_can('yatra_delete_enquiries'); | |
| 130 | + } | |
| 131 | + | |
| 132 | + /** | |
| 133 | + * @deprecated Kept for any external code (custom snippet, third- | |
| 134 | + * party integration) that hooked the old method name. New code | |
| 135 | + * should use checkCanView/Respond/Delete. Routes the call to | |
| 136 | + * the view-only cap so behaviour is at-least-as-strict as before | |
| 137 | + * for non-admin callers, and admin users keep passing via the | |
| 138 | + * admin-fallback layer. | |
| 139 | + */ | |
| 108 | 140 | public function checkAdminPermission(): bool |
| 109 | 141 | { |
| 110 | - return current_user_can('manage_options'); | |
| 142 | + return $this->checkCanView(); | |
| 111 | 143 | } |
| 112 | 144 | |
| 113 | 145 | /** |
| 114 | 146 | * GET /enquiries - List all enquiries |
| @@ -172,8 +204,21 @@ | ||
| 172 | 204 | if (empty($data)) { |
| 173 | 205 | $data = $request->get_params(); |
| 174 | 206 | } |
| 175 | 207 | |
| 208 | + // reCAPTCHA v3 (no-op unless the enquiry form is protected in settings). | |
| 209 | + $recaptcha = \Yatra\Services\RecaptchaService::verifyForm( | |
| 210 | + 'enquiry', | |
| 211 | + (string) ($data['recaptcha_token'] ?? ''), | |
| 212 | + $_SERVER['REMOTE_ADDR'] ?? null | |
| 213 | + ); | |
| 214 | + if (empty($recaptcha['success'])) { | |
| 215 | + return new WP_REST_Response([ | |
| 216 | + 'success' => false, | |
| 217 | + 'message' => $recaptcha['message'] ?? __('reCAPTCHA verification failed.', 'yatra'), | |
| 218 | + ], 400); | |
| 219 | + } | |
| 220 | + | |
| 176 | 221 | $result = $this->enquiryService->createEnquiry($data); |
| 177 | 222 | |
| 178 | 223 | if (!$result['success']) { |
| 179 | 224 | return new WP_REST_Response($result, 400); |
| @@ -241,8 +286,16 @@ | ||
| 241 | 286 | break; |
| 242 | 287 | |
| 243 | 288 | case 'mark_pending': |
| 244 | 289 | $result = $this->enquiryService->bulkUpdateStatus($ids, 'pending'); |
| 290 | + break; | |
| 291 | + | |
| 292 | + case 'mark_completed': | |
| 293 | + $result = $this->enquiryService->bulkUpdateStatus($ids, 'completed'); | |
| 294 | + break; | |
| 295 | + | |
| 296 | + case 'mark_closed': | |
| 297 | + $result = $this->enquiryService->bulkUpdateStatus($ids, 'closed'); | |
| 245 | 298 | break; |
| 246 | 299 | |
| 247 | 300 | case 'mark_spam': |
| 248 | 301 | $result = $this->enquiryService->bulkUpdateStatus($ids, 'spam'); |