| @@ -2,8 +2,9 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace FluentSupport\App\Http\Controllers; |
| 4 | 4 | |
| 5 | 5 | use Exception; |
| 6 | +use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler; | |
| 6 | 7 | use FluentSupport\App\Http\Requests\TicketResponseRequest; |
| 7 | 8 | use FluentSupport\App\Models\Product; |
| 8 | 9 | use FluentSupport\App\Models\Ticket; |
| 9 | 10 | use FluentSupport\App\Services\CustomerPortalService; |
| @@ -27,14 +28,9 @@ | ||
| 27 | 28 | */ |
| 28 | 29 | public function getTickets(Request $request) |
| 29 | 30 | { |
| 30 | 31 | |
| 31 | - $onBehalf = $request->get('on_behalf', []); | |
| 32 | - if ($onBehalf) { | |
| 33 | - $onBehalf = array_map(function ($item) { | |
| 34 | - return sanitize_text_field($item); | |
| 35 | - }, $onBehalf); | |
| 36 | - } | |
| 32 | + $onBehalf = $this->sanitizeOnBehalf($request); | |
| 37 | 33 | |
| 38 | 34 | $userIP = $request->getIp(); |
| 39 | 35 | $requestedStatus = $request->getSafe('filter_type', 'sanitize_text_field'); |
| 40 | 36 | $ticketOptions = $request->getSafe([ |
| @@ -59,9 +55,9 @@ | ||
| 59 | 55 | ] |
| 60 | 56 | ]; |
| 61 | 57 | } |
| 62 | 58 | |
| 63 | - if ($customer->status !== 'active') { | |
| 59 | + if (!$customer->canAccessPortal()) { | |
| 64 | 60 | return $this->sendError([ |
| 65 | 61 | 'message' => __('Your account is not active. Please contact support.', 'fluent-support'), |
| 66 | 62 | 'error_type' => '403' |
| 67 | 63 | ], 403); |
| @@ -113,8 +109,22 @@ | ||
| 113 | 109 | * @return array | \WP_REST_Response |
| 114 | 110 | */ |
| 115 | 111 | public function createTicket(Request $request) |
| 116 | 112 | { |
| 113 | + if (ReCaptchaHandler::isRecaptchaApplicable('ticket_form')) { | |
| 114 | + $captchaResponse = $request->getSafe('g-recaptcha-response', 'sanitize_text_field'); | |
| 115 | + $isValidCaptcha = $captchaResponse && ReCaptchaHandler::validateRecaptcha( | |
| 116 | + $captchaResponse, null, null, 'create_ticket' | |
| 117 | + ); | |
| 118 | + | |
| 119 | + if (!$isValidCaptcha) { | |
| 120 | + return $this->sendError([ | |
| 121 | + 'message' => __('Your recaptcha is not verified', 'fluent-support'), | |
| 122 | + 'error_type' => '422' | |
| 123 | + ], 422); | |
| 124 | + } | |
| 125 | + } | |
| 126 | + | |
| 117 | 127 | $dataRules = $this->app->applyCustomFilters('custom_field_required_before_ticket_create', [ |
| 118 | 128 | 'required_fields' => [ |
| 119 | 129 | 'title' => 'required', |
| 120 | 130 | 'content' => 'required' |
| @@ -168,20 +178,15 @@ | ||
| 168 | 178 | $data = $this->validate($request->get(), $dataRules['required_fields'], $dataRules['error_messages']); |
| 169 | 179 | |
| 170 | 180 | $data['title'] = sanitize_text_field($data['title']); |
| 171 | 181 | $data['content'] = wp_kses_post($data['content']); |
| 182 | + $data['custom_data'] = $customData; | |
| 172 | 183 | |
| 173 | - $onBehalf = $request->get('on_behalf', []); | |
| 184 | + $onBehalf = $this->sanitizeOnBehalf($request); | |
| 174 | 185 | $userIP = $request->getIp(); |
| 175 | 186 | |
| 176 | - if ($onBehalf) { | |
| 177 | - $onBehalf = array_map(function ($item) { | |
| 178 | - return sanitize_text_field($item); | |
| 179 | - }, $onBehalf); | |
| 180 | - | |
| 181 | - if (!empty($onBehalf['last_ip_address'])) { | |
| 182 | - $userIP = $onBehalf['last_ip_address']; | |
| 183 | - } | |
| 187 | + if (!empty($onBehalf['last_ip_address'])) { | |
| 188 | + $userIP = $onBehalf['last_ip_address']; | |
| 184 | 189 | } |
| 185 | 190 | |
| 186 | 191 | try { |
| 187 | 192 | $customer = (new CustomerPortalService())->resolveCustomer($onBehalf, $userIP, true); |
| @@ -351,9 +356,9 @@ | ||
| 351 | 356 | * @return array |
| 352 | 357 | */ |
| 353 | 358 | public function getPublicOptions() |
| 354 | 359 | { |
| 355 | - $products = Product::select(['id', 'title'])->get(); | |
| 360 | + $products = Product::select(['id', 'title'])->orderedByTitle()->get(); | |
| 356 | 361 | |
| 357 | 362 | return [ |
| 358 | 363 | 'support_products' => $products, |
| 359 | 364 | 'customer_ticket_priorities' => Helper::customerTicketPriorities() |
| @@ -393,20 +398,42 @@ | ||
| 393 | 398 | |
| 394 | 399 | private function getCustomerAdditionalData($request) |
| 395 | 400 | { |
| 396 | 401 | |
| 397 | - $onBehalf = $request->get('on_behalf', []); | |
| 398 | - if ($onBehalf) { | |
| 399 | - $onBehalf = array_map(function ($item) { | |
| 400 | - return sanitize_text_field($item); | |
| 401 | - }, $onBehalf); | |
| 402 | - } | |
| 403 | - | |
| 404 | 402 | $customerAdditionalData = [ |
| 405 | 403 | 'intended_ticket_hash' => $request->getSafe('intended_ticket_hash', 'sanitize_text_field'), |
| 406 | - 'on_behalf' => $onBehalf, | |
| 404 | + 'on_behalf' => $this->sanitizeOnBehalf($request), | |
| 407 | 405 | 'user_ip' => $request->getIp() |
| 408 | 406 | ]; |
| 409 | 407 | |
| 410 | 408 | return $customerAdditionalData; |
| 409 | + } | |
| 410 | + | |
| 411 | + /** | |
| 412 | + * Read the `on_behalf` identity payload from the request in a predictable shape. | |
| 413 | + * Only an array of scalars is accepted; anything else (a scalar, or nested | |
| 414 | + * arrays) is discarded so callers can never hand a non-string to | |
| 415 | + * sanitize_text_field(). An empty result makes resolveCustomer() fall back to | |
| 416 | + * the logged-in user instead of a request-supplied identity. | |
| 417 | + * | |
| 418 | + * @param Request $request | |
| 419 | + * @return array | |
| 420 | + */ | |
| 421 | + private function sanitizeOnBehalf($request) | |
| 422 | + { | |
| 423 | + $onBehalf = $request->get('on_behalf', []); | |
| 424 | + | |
| 425 | + if (!is_array($onBehalf)) { | |
| 426 | + return []; | |
| 427 | + } | |
| 428 | + | |
| 429 | + $sanitized = []; | |
| 430 | + | |
| 431 | + foreach ($onBehalf as $key => $value) { | |
| 432 | + if (is_scalar($value)) { | |
| 433 | + $sanitized[sanitize_text_field($key)] = sanitize_text_field($value); | |
| 434 | + } | |
| 435 | + } | |
| 436 | + | |
| 437 | + return $sanitized; | |
| 411 | 438 | } |
| 412 | 439 | } |