PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
← All changes | app/Http/Controllers/CustomerPortalController.php +40 -45 2.4.0 → 2.4.5 View file →
@@ -3,27 +3,24 @@
3 3 namespace FluentSupport\App\Http\Controllers;
4 4
5 5 use Exception;
6 6 use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
7 +use FluentSupport\App\Http\Policies\PortalPolicy;
7 8 use FluentSupport\App\Http\Requests\TicketResponseRequest;
8 9 use FluentSupport\App\Models\Product;
9 10 use FluentSupport\App\Models\Ticket;
10 11 use FluentSupport\App\Services\CustomerPortalService;
11 12 use FluentSupport\App\Services\Helper;
13 +use FluentSupport\App\Services\Tickets\AgentTicketAccess;
12 14 use FluentSupport\Framework\Http\Request\Request;
13 15 use FluentSupport\Framework\Support\Arr;
14 16
15 17 /**
16 - * CustomerPortalController class for REST API
17 - * This class is responsible for getting data for all request related to customer and customer portal
18 - * @package FluentSupport\App\Http\Controllers
19 - *
20 - * @version 1.0.0
18 + * Customer portal endpoints.
21 19 */
22 20 class CustomerPortalController extends Controller
23 21 {
24 22 /**
25 - * getTickets will generate ticket information with customer and agents by customer
26 23 * @param Request $request
27 24 * @return array|\WP_REST_Response
28 25 */
29 26 public function getTickets(Request $request)
@@ -88,8 +85,14 @@
88 85 ->searchBy(Arr::get($ticketOptions, 'search'))
89 86 ->filterByProductId(Arr::get($ticketOptions, 'filters.product_id'))
90 87 ->orderBy($sortBy, $sortType);
91 88
89 + // An agent acting for a customer only sees the tickets they could open themselves.
90 + // Site admins have full access and may not have an agent record at all.
91 + if ($onBehalf && !current_user_can('manage_options')) {
92 + (new AgentTicketAccess())->applyAccessScope($ticketsQuery);
93 + }
94 +
92 95 do_action_ref_array('fluent_support/customer_portal/tickets_query', [&$ticketsQuery, $customer, $request]);
93 96
94 97 $tickets = $ticketsQuery->paginate($request->getInt('per_page', 10));
95 98
@@ -98,20 +101,24 @@
98 101 $ticket->preview_response = $ticket->getLastResponse();
99 102 }
100 103
101 104 return [
102 - 'tickets' => $tickets
105 + 'tickets' => CustomerPortalService::hidePrivateTicketFields($tickets)
103 106 ];
104 107 }
105 108
106 109 /**
107 - * createTicket method will create ticket submitted by customers
108 110 * @param Request $request
109 111 * @return array | \WP_REST_Response
110 112 */
111 113 public function createTicket(Request $request)
112 114 {
113 - if (ReCaptchaHandler::isRecaptchaApplicable('ticket_form')) {
115 + // A remote portal filing for a customer can't answer a captcha. That takes a user
116 + // allowed to act on_behalf (an admin, or an agent who manages all tickets), checked
117 + // here, never a flag the client sends. Customers in the browser keep the captcha.
118 + $isOnBehalfClient = $this->sanitizeOnBehalf($request) && PortalPolicy::canActOnBehalf();
119 +
120 + if (!$isOnBehalfClient && ReCaptchaHandler::isRecaptchaApplicable('ticket_form')) {
114 121 $captchaResponse = $request->getSafe('g-recaptcha-response', 'sanitize_text_field');
115 122 $isValidCaptcha = $captchaResponse && ReCaptchaHandler::validateRecaptcha(
116 123 $captchaResponse, null, null, 'create_ticket'
117 124 );
@@ -187,8 +194,21 @@
187 194 if (!empty($onBehalf['last_ip_address'])) {
188 195 $userIP = $onBehalf['last_ip_address'];
189 196 }
190 197
198 + // An agent acting for a customer may only file tickets into mailboxes they can
199 + // access. Checked before the customer is resolved, which may create one.
200 + if ($onBehalf && !current_user_can('manage_options')) {
201 + $mailboxId = (new CustomerPortalService())->resolveMailboxId($request->getSafe('mailbox_id', 'intval'));
202 +
203 + if ($mailboxId && in_array((int) $mailboxId, (new AgentTicketAccess())->getRestrictedMailboxIds(), true)) {
204 + return $this->sendError([
205 + 'message' => __('You do not have access to this mailbox.', 'fluent-support'),
206 + 'error_type' => '403'
207 + ], 403);
208 + }
209 + }
210 +
191 211 try {
192 212 $customer = (new CustomerPortalService())->resolveCustomer($onBehalf, $userIP, true);
193 213
194 214 if (!$customer) {
@@ -217,13 +237,13 @@
217 237 }
218 238
219 239 $defaultMailbox = Helper::getDefaultMailBox();
220 240 $defaultMailboxId = $defaultMailbox ? $defaultMailbox->id : null;
221 - $ticket = (new CustomerPortalService())->createTicket($customer, $data, $request->getSafe('mailbox_id', 'intval', $defaultMailboxId));
241 + $ticket = (new CustomerPortalService())->createTicket($customer, $data, $request->getSafe('mailbox_id', 'intval', $defaultMailboxId), (bool) $onBehalf);
222 242
223 243 return [
224 244 'message' => __('Ticket has been created successfully', 'fluent-support'),
225 - 'ticket' => $ticket
245 + 'ticket' => CustomerPortalService::hidePrivateTicketFields($ticket)
226 246 ];
227 247 } catch (\Exception $e) {
228 248 return $this->sendError([
229 249 'message' => Helper::getSafeErrorMessage($e),
@@ -232,11 +252,10 @@
232 252 }
233 253 }
234 254
235 255 /**
236 - * getTicket method will get the ticket information with customer and agent as well as response in a ticket by ticket id
237 256 * @param Request $request
238 - * @param $ticket_id
257 + * @param int $ticket_id
239 258 * @return array|\WP_REST_Response
240 259 */
241 260 public function getTicket(Request $request, $ticket_id)
242 261 {
@@ -252,11 +271,10 @@
252 271 }
253 272 }
254 273
255 274 /**
256 - * createResponse method will create response by customer in a ticket by ticket id
257 - * @param Request $request
258 - * @param $ticket_id
275 + * @param TicketResponseRequest $request
276 + * @param int $ticket_id
259 277 * @return array|\WP_REST_Response
260 278 * @throws \FluentSupport\Framework\Validator\ValidationException
261 279 */
262 280 public function createResponse(TicketResponseRequest $request, $ticket_id)
@@ -287,11 +305,10 @@
287 305 }
288 306 }
289 307
290 308 /**
291 - * This `closeTicket` is responsible for closing ticket by ticket id
292 309 * @param Request $request
293 - * @param $ticket_id
310 + * @param int $ticket_id
294 311 * @return array
295 312 */
296 313 public function closeTicket(Request $request, $ticket_id)
297 314 {
@@ -307,11 +324,10 @@
307 324 }
308 325 }
309 326
310 327 /**
311 - * closeTicket method will re-open a ticket by customer using ticket id
312 328 * @param Request $request
313 - * @param $ticket_id
329 + * @param int $ticket_id
314 330 * @return array
315 331 */
316 332 public function reOpenTicket(Request $request, $ticket_id)
317 333 {
@@ -331,9 +347,9 @@
331 347 {
332 348
333 349 $customerPortalService = new CustomerPortalService();
334 350
335 - // just for validation
351 + // Throws unless the customer can access this ticket.
336 352 $ticket = Ticket::with(['customer'])->wherePublicIdentifier($ticketId)->firstOrFail();
337 353 $customerAdditionalData = $this->getCustomerAdditionalData($request);
338 354 $customer = $customerPortalService->getCustomer($customerAdditionalData, $ticket);
339 355 $customerPortalService->checkCustomerTicketAccess($customer, $ticket, 'feedback');
@@ -351,9 +367,10 @@
351 367 }
352 368 }
353 369
354 370 /**
355 - * getPublicOptions method will return the list of product and customer priorities
371 + * Products and customer ticket priorities.
372 + *
356 373 * @return array
357 374 */
358 375 public function getPublicOptions()
359 376 {
@@ -365,9 +382,8 @@
365 382 ];
366 383 }
367 384
368 385 /**
369 - * getCustomFieldsRender method will return the list of custom fields
370 386 * @return array|array[]
371 387 */
372 388 public function getCustomFieldsRender()
373 389 {
@@ -383,9 +399,8 @@
383 399 }
384 400
385 401
386 402 /**
387 - * logout method will logout the customer
388 403 * @return mixed
389 404 */
390 405 public function logout()
391 406 {
@@ -408,32 +423,12 @@
408 423 return $customerAdditionalData;
409 424 }
410 425
411 426 /**
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 427 * @param Request $request
419 428 * @return array
420 429 */
421 430 private function sanitizeOnBehalf($request)
422 431 {
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;
432 + return (new CustomerPortalService())->sanitizeOnBehalf($request->get('on_behalf', []));
438 433 }
439 434 }