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 +68 -46 2.3.2 → 2.4.5 View file →
@@ -2,27 +2,25 @@
2 2
3 3 namespace FluentSupport\App\Http\Controllers;
4 4
5 5 use Exception;
6 +use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
7 +use FluentSupport\App\Http\Policies\PortalPolicy;
6 8 use FluentSupport\App\Http\Requests\TicketResponseRequest;
7 9 use FluentSupport\App\Models\Product;
8 10 use FluentSupport\App\Models\Ticket;
9 11 use FluentSupport\App\Services\CustomerPortalService;
10 12 use FluentSupport\App\Services\Helper;
13 +use FluentSupport\App\Services\Tickets\AgentTicketAccess;
11 14 use FluentSupport\Framework\Http\Request\Request;
12 15 use FluentSupport\Framework\Support\Arr;
13 16
14 17 /**
15 - * CustomerPortalController class for REST API
16 - * This class is responsible for getting data for all request related to customer and customer portal
17 - * @package FluentSupport\App\Http\Controllers
18 - *
19 - * @version 1.0.0
18 + * Customer portal endpoints.
20 19 */
21 20 class CustomerPortalController extends Controller
22 21 {
23 22 /**
24 - * getTickets will generate ticket information with customer and agents by customer
25 23 * @param Request $request
26 24 * @return array|\WP_REST_Response
27 25 */
28 26 public function getTickets(Request $request)
@@ -27,14 +25,9 @@
27 25 */
28 26 public function getTickets(Request $request)
29 27 {
30 28
31 - $onBehalf = $request->get('on_behalf', []);
32 - if ($onBehalf) {
33 - $onBehalf = array_map(function ($item) {
34 - return sanitize_text_field($item);
35 - }, $onBehalf);
36 - }
29 + $onBehalf = $this->sanitizeOnBehalf($request);
37 30
38 31 $userIP = $request->getIp();
39 32 $requestedStatus = $request->getSafe('filter_type', 'sanitize_text_field');
40 33 $ticketOptions = $request->getSafe([
@@ -59,9 +52,9 @@
59 52 ]
60 53 ];
61 54 }
62 55
63 - if ($customer->status !== 'active') {
56 + if (!$customer->canAccessPortal()) {
64 57 return $this->sendError([
65 58 'message' => __('Your account is not active. Please contact support.', 'fluent-support'),
66 59 'error_type' => '403'
67 60 ], 403);
@@ -92,8 +85,14 @@
92 85 ->searchBy(Arr::get($ticketOptions, 'search'))
93 86 ->filterByProductId(Arr::get($ticketOptions, 'filters.product_id'))
94 87 ->orderBy($sortBy, $sortType);
95 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 +
96 95 do_action_ref_array('fluent_support/customer_portal/tickets_query', [&$ticketsQuery, $customer, $request]);
97 96
98 97 $tickets = $ticketsQuery->paginate($request->getInt('per_page', 10));
99 98
@@ -102,19 +101,37 @@
102 101 $ticket->preview_response = $ticket->getLastResponse();
103 102 }
104 103
105 104 return [
106 - 'tickets' => $tickets
105 + 'tickets' => CustomerPortalService::hidePrivateTicketFields($tickets)
107 106 ];
108 107 }
109 108
110 109 /**
111 - * createTicket method will create ticket submitted by customers
112 110 * @param Request $request
113 111 * @return array | \WP_REST_Response
114 112 */
115 113 public function createTicket(Request $request)
116 114 {
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')) {
121 + $captchaResponse = $request->getSafe('g-recaptcha-response', 'sanitize_text_field');
122 + $isValidCaptcha = $captchaResponse && ReCaptchaHandler::validateRecaptcha(
123 + $captchaResponse, null, null, 'create_ticket'
124 + );
125 +
126 + if (!$isValidCaptcha) {
127 + return $this->sendError([
128 + 'message' => __('Your recaptcha is not verified', 'fluent-support'),
129 + 'error_type' => '422'
130 + ], 422);
131 + }
132 + }
133 +
117 134 $dataRules = $this->app->applyCustomFilters('custom_field_required_before_ticket_create', [
118 135 'required_fields' => [
119 136 'title' => 'required',
120 137 'content' => 'required'
@@ -168,19 +185,27 @@
168 185 $data = $this->validate($request->get(), $dataRules['required_fields'], $dataRules['error_messages']);
169 186
170 187 $data['title'] = sanitize_text_field($data['title']);
171 188 $data['content'] = wp_kses_post($data['content']);
189 + $data['custom_data'] = $customData;
172 190
173 - $onBehalf = $request->get('on_behalf', []);
191 + $onBehalf = $this->sanitizeOnBehalf($request);
174 192 $userIP = $request->getIp();
175 193
176 - if ($onBehalf) {
177 - $onBehalf = array_map(function ($item) {
178 - return sanitize_text_field($item);
179 - }, $onBehalf);
194 + if (!empty($onBehalf['last_ip_address'])) {
195 + $userIP = $onBehalf['last_ip_address'];
196 + }
180 197
181 - if (!empty($onBehalf['last_ip_address'])) {
182 - $userIP = $onBehalf['last_ip_address'];
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);
183 208 }
184 209 }
185 210
186 211 try {
@@ -212,13 +237,13 @@
212 237 }
213 238
214 239 $defaultMailbox = Helper::getDefaultMailBox();
215 240 $defaultMailboxId = $defaultMailbox ? $defaultMailbox->id : null;
216 - $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);
217 242
218 243 return [
219 244 'message' => __('Ticket has been created successfully', 'fluent-support'),
220 - 'ticket' => $ticket
245 + 'ticket' => CustomerPortalService::hidePrivateTicketFields($ticket)
221 246 ];
222 247 } catch (\Exception $e) {
223 248 return $this->sendError([
224 249 'message' => Helper::getSafeErrorMessage($e),
@@ -227,11 +252,10 @@
227 252 }
228 253 }
229 254
230 255 /**
231 - * getTicket method will get the ticket information with customer and agent as well as response in a ticket by ticket id
232 256 * @param Request $request
233 - * @param $ticket_id
257 + * @param int $ticket_id
234 258 * @return array|\WP_REST_Response
235 259 */
236 260 public function getTicket(Request $request, $ticket_id)
237 261 {
@@ -247,11 +271,10 @@
247 271 }
248 272 }
249 273
250 274 /**
251 - * createResponse method will create response by customer in a ticket by ticket id
252 - * @param Request $request
253 - * @param $ticket_id
275 + * @param TicketResponseRequest $request
276 + * @param int $ticket_id
254 277 * @return array|\WP_REST_Response
255 278 * @throws \FluentSupport\Framework\Validator\ValidationException
256 279 */
257 280 public function createResponse(TicketResponseRequest $request, $ticket_id)
@@ -282,11 +305,10 @@
282 305 }
283 306 }
284 307
285 308 /**
286 - * This `closeTicket` is responsible for closing ticket by ticket id
287 309 * @param Request $request
288 - * @param $ticket_id
310 + * @param int $ticket_id
289 311 * @return array
290 312 */
291 313 public function closeTicket(Request $request, $ticket_id)
292 314 {
@@ -302,11 +324,10 @@
302 324 }
303 325 }
304 326
305 327 /**
306 - * closeTicket method will re-open a ticket by customer using ticket id
307 328 * @param Request $request
308 - * @param $ticket_id
329 + * @param int $ticket_id
309 330 * @return array
310 331 */
311 332 public function reOpenTicket(Request $request, $ticket_id)
312 333 {
@@ -326,9 +347,9 @@
326 347 {
327 348
328 349 $customerPortalService = new CustomerPortalService();
329 350
330 - // just for validation
351 + // Throws unless the customer can access this ticket.
331 352 $ticket = Ticket::with(['customer'])->wherePublicIdentifier($ticketId)->firstOrFail();
332 353 $customerAdditionalData = $this->getCustomerAdditionalData($request);
333 354 $customer = $customerPortalService->getCustomer($customerAdditionalData, $ticket);
334 355 $customerPortalService->checkCustomerTicketAccess($customer, $ticket, 'feedback');
@@ -346,14 +367,15 @@
346 367 }
347 368 }
348 369
349 370 /**
350 - * getPublicOptions method will return the list of product and customer priorities
371 + * Products and customer ticket priorities.
372 + *
351 373 * @return array
352 374 */
353 375 public function getPublicOptions()
354 376 {
355 - $products = Product::select(['id', 'title'])->get();
377 + $products = Product::select(['id', 'title'])->orderedByTitle()->get();
356 378
357 379 return [
358 380 'support_products' => $products,
359 381 'customer_ticket_priorities' => Helper::customerTicketPriorities()
@@ -360,9 +382,8 @@
360 382 ];
361 383 }
362 384
363 385 /**
364 - * getCustomFieldsRender method will return the list of custom fields
365 386 * @return array|array[]
366 387 */
367 388 public function getCustomFieldsRender()
368 389 {
@@ -378,9 +399,8 @@
378 399 }
379 400
380 401
381 402 /**
382 - * logout method will logout the customer
383 403 * @return mixed
384 404 */
385 405 public function logout()
386 406 {
@@ -393,20 +413,22 @@
393 413
394 414 private function getCustomerAdditionalData($request)
395 415 {
396 416
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 417 $customerAdditionalData = [
405 418 'intended_ticket_hash' => $request->getSafe('intended_ticket_hash', 'sanitize_text_field'),
406 - 'on_behalf' => $onBehalf,
419 + 'on_behalf' => $this->sanitizeOnBehalf($request),
407 420 'user_ip' => $request->getIp()
408 421 ];
409 422
410 423 return $customerAdditionalData;
424 + }
425 +
426 + /**
427 + * @param Request $request
428 + * @return array
429 + */
430 + private function sanitizeOnBehalf($request)
431 + {
432 + return (new CustomerPortalService())->sanitizeOnBehalf($request->get('on_behalf', []));
411 433 }
412 434 }