PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / trunk
Fluent Support – Helpdesk & Customer Support Ticket System vtrunk
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 1.5.5 1.5.6 All 67 releases
fluent-support / app / Http / Controllers / CustomerPortalController.php

CustomerPortalController.php in Fluent Support – Helpdesk & Customer Support Ticket System trunk, at app/Http/Controllers/CustomerPortalController.php

440 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Http\Controllers;
4
5 use Exception;
6 use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
7 use FluentSupport\App\Http\Requests\TicketResponseRequest;
8 use FluentSupport\App\Models\Product;
9 use FluentSupport\App\Models\Ticket;
10 use FluentSupport\App\Services\CustomerPortalService;
11 use FluentSupport\App\Services\Helper;
12 use FluentSupport\Framework\Http\Request\Request;
13 use FluentSupport\Framework\Support\Arr;
14
15 /**
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
21 */
22 class CustomerPortalController extends Controller
23 {
24 /**
25 * getTickets will generate ticket information with customer and agents by customer
26 * @param Request $request
27 * @return array|\WP_REST_Response
28 */
29 public function getTickets(Request $request)
30 {
31
32 $onBehalf = $this->sanitizeOnBehalf($request);
33
34 $userIP = $request->getIp();
35 $requestedStatus = $request->getSafe('filter_type', 'sanitize_text_field');
36 $ticketOptions = $request->getSafe([
37 'search' => 'sanitize_text_field',
38 'filters.product_id' => 'intval',
39 'sorting.sort_type' => 'sanitize_sql_orderby',
40 'sorting.sort_by' => 'sanitize_sql_orderby'
41 ]);
42
43 $customer = (new CustomerPortalService)->resolveCustomer($onBehalf, $userIP);
44
45 if (!$customer) {
46 return [
47 'tickets' => [
48 'data' => [],
49 'current_page' => $request->getSafe('page', 'intval', 1),
50 'last_page' => 1,
51 'per_page' => $request->getSafe('per_page', 'intval', 10),
52 'total' => 0,
53 'from' => null,
54 'to' => null
55 ]
56 ];
57 }
58
59 if (!$customer->canAccessPortal()) {
60 return $this->sendError([
61 'message' => __('Your account is not active. Please contact support.', 'fluent-support'),
62 'error_type' => '403'
63 ], 403);
64 }
65
66 $canAccess = apply_filters('fluent_support/can_customer_access_portal', true, $customer);
67
68 if (is_wp_error($canAccess)) {
69 return $this->sendError([
70 'message' => $canAccess->get_error_message(),
71 'error_type' => $canAccess->get_error_code()
72 ], 403);
73 }
74
75 $statuses = [
76 'open' => ['new', 'active', 'on-hold'],
77 'all' => [],
78 'closed' => ['closed']
79 ];
80
81 $statusFilter = $statuses[$requestedStatus] ?? $statuses['all'];
82
83 $sortBy = Arr::get($ticketOptions, 'sorting.sort_by', 'created_at');
84 $sortType = Arr::get($ticketOptions, 'sorting.sort_type', 'desc');
85
86 $ticketsQuery = Ticket::where('customer_id', $customer->id)
87 ->filterByStatues($statusFilter)
88 ->searchBy(Arr::get($ticketOptions, 'search'))
89 ->filterByProductId(Arr::get($ticketOptions, 'filters.product_id'))
90 ->orderBy($sortBy, $sortType);
91
92 do_action_ref_array('fluent_support/customer_portal/tickets_query', [&$ticketsQuery, $customer, $request]);
93
94 $tickets = $ticketsQuery->paginate($request->getInt('per_page', 10));
95
96 foreach ($tickets as $ticket) {
97 $ticket->human_date = sprintf(__('%s ago', 'fluent-support'), human_time_diff(strtotime($ticket->created_at), current_time('timestamp')));
98 $ticket->preview_response = $ticket->getLastResponse();
99 }
100
101 return [
102 'tickets' => $tickets
103 ];
104 }
105
106 /**
107 * createTicket method will create ticket submitted by customers
108 * @param Request $request
109 * @return array | \WP_REST_Response
110 */
111 public function createTicket(Request $request)
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
127 $dataRules = $this->app->applyCustomFilters('custom_field_required_before_ticket_create', [
128 'required_fields' => [
129 'title' => 'required',
130 'content' => 'required'
131 ],
132 'error_messages' => [
133 'title.required' => __('Title is required', 'fluent-support'),
134 'content.required' => __('Content is required', 'fluent-support')
135 ]
136 ]);
137
138 $settings = Helper::getOption('_ticket_form_settings', []);
139
140 if (!empty($settings['product_required_field']) && $settings['product_required_field'] === 'yes') {
141 $productCount = Product::count();
142 if ($productCount > 0) {
143 $dataRules['required_fields']['product_id'] = 'required';
144 $dataRules['error_messages']['product_id.required'] = __('Product is required', 'fluent-support');
145 }
146 }
147
148 $defaultData = [
149 'ticket_title' => $request->getSafe('title', 'sanitize_text_field'),
150 'ticket_content' => $request->getSafe('content', 'wp_kses_post')
151 ];
152
153 if ($request->has('product_id')) {
154 $defaultData['ticket_product_id'] = $request->getSafe('product_id', 'intval');
155 }
156
157 if ($request->has('client_priority')) {
158 $defaultData['ticket_client_priority'] = $request->getSafe('client_priority', 'sanitize_text_field');
159 }
160
161 $customData = $request->get('custom_data', []);
162 $customData = is_array($customData) ? map_deep($customData, 'sanitize_text_field') : [];
163
164 $dataRules = $this->app->applyCustomFilters('custom_field_required_by_conditions_before_ticket_create', [
165 'required_fields' => $dataRules['required_fields'],
166 'error_messages' => $dataRules['error_messages'],
167 'custom_data' => $customData,
168 'default_data' => $defaultData
169 ]);
170
171 if (!isset($dataRules['required_fields']) && !isset($dataRules['error_messages'])) {
172 return $this->sendError([
173 'message' => __('Invalid form data submitted', 'fluent-support'),
174 'error_type' => '400'
175 ], 400);
176 }
177
178 $data = $this->validate($request->get(), $dataRules['required_fields'], $dataRules['error_messages']);
179
180 $data['title'] = sanitize_text_field($data['title']);
181 $data['content'] = wp_kses_post($data['content']);
182 $data['custom_data'] = $customData;
183
184 $onBehalf = $this->sanitizeOnBehalf($request);
185 $userIP = $request->getIp();
186
187 if (!empty($onBehalf['last_ip_address'])) {
188 $userIP = $onBehalf['last_ip_address'];
189 }
190
191 try {
192 $customer = (new CustomerPortalService())->resolveCustomer($onBehalf, $userIP, true);
193
194 if (!$customer) {
195 return $this->sendError([
196 'message' => __('Unable to identify. Please make sure you have provided correct information.', 'fluent-support'),
197 'error_type' => '403'
198 ], 403);
199 }
200
201 $canCreateTicket = apply_filters('fluent_support/can_customer_create_ticket', true, $customer, $data);
202
203 if (!$canCreateTicket || is_wp_error($canCreateTicket)) {
204 $isWpError = is_wp_error($canCreateTicket);
205
206 $message = ($isWpError) ? $canCreateTicket->get_error_message() : __('Sorry you cannot create ticket', 'fluent-support');
207 $errorCode = ($isWpError) ? $canCreateTicket->get_error_code() : 'general_error';
208
209 return $this->sendError([
210 'message' => $message,
211 'error_type' => $errorCode
212 ]);
213 }
214
215 if ($messageId = Helper::generateMessageID($customer->email)) {
216 $data['message_id'] = $messageId;
217 }
218
219 $defaultMailbox = Helper::getDefaultMailBox();
220 $defaultMailboxId = $defaultMailbox ? $defaultMailbox->id : null;
221 $ticket = (new CustomerPortalService())->createTicket($customer, $data, $request->getSafe('mailbox_id', 'intval', $defaultMailboxId));
222
223 return [
224 'message' => __('Ticket has been created successfully', 'fluent-support'),
225 'ticket' => $ticket
226 ];
227 } catch (\Exception $e) {
228 return $this->sendError([
229 'message' => Helper::getSafeErrorMessage($e),
230 'error_type' => $e->getCode()
231 ]);
232 }
233 }
234
235 /**
236 * getTicket method will get the ticket information with customer and agent as well as response in a ticket by ticket id
237 * @param Request $request
238 * @param $ticket_id
239 * @return array|\WP_REST_Response
240 */
241 public function getTicket(Request $request, $ticket_id)
242 {
243 $customerAdditionalData = $this->getCustomerAdditionalData($request);
244
245 try {
246 return (new CustomerPortalService())->getTicket($customerAdditionalData, $ticket_id);
247 } catch (\Exception $e) {
248 return $this->sendError([
249 'message' => Helper::getSafeErrorMessage($e),
250 'error_type' => $e->getCode()
251 ]);
252 }
253 }
254
255 /**
256 * createResponse method will create response by customer in a ticket by ticket id
257 * @param Request $request
258 * @param $ticket_id
259 * @return array|\WP_REST_Response
260 * @throws \FluentSupport\Framework\Validator\ValidationException
261 */
262 public function createResponse(TicketResponseRequest $request, $ticket_id)
263 {
264
265 $customerAdditionalData = $this->getCustomerAdditionalData($request);
266
267 $ticket = Ticket::wherePublicIdentifier($ticket_id)->firstOrFail();
268
269 $data = $request->sanitize();
270
271 $canCreateResponse = apply_filters('fluent_support/can_customer_create_response', true, $ticket->customer, $ticket, $data);
272
273 if (!$canCreateResponse || is_wp_error($canCreateResponse)) {
274 return [
275 'type' => 'error',
276 'message' => (is_wp_error($canCreateResponse)) ? $canCreateResponse->get_error_message() : __('Sorry you cannot create response', 'fluent-support')
277 ];
278 }
279
280 try {
281 return (new CustomerPortalService())->createResponse($customerAdditionalData, $ticket_id, $data);
282 } catch (\Exception $e) {
283 return $this->sendError([
284 'message' => Helper::getSafeErrorMessage($e),
285 'error_type' => $e->getCode()
286 ]);
287 }
288 }
289
290 /**
291 * This `closeTicket` is responsible for closing ticket by ticket id
292 * @param Request $request
293 * @param $ticket_id
294 * @return array
295 */
296 public function closeTicket(Request $request, $ticket_id)
297 {
298 $customerAdditionalData = $this->getCustomerAdditionalData($request);
299
300 try {
301 return (new CustomerPortalService())->closeTicket($customerAdditionalData, $ticket_id);
302 } catch (Exception $e) {
303 return $this->sendError([
304 'message' => Helper::getSafeErrorMessage($e),
305 'error_type' => $e->getCode()
306 ]);
307 }
308 }
309
310 /**
311 * closeTicket method will re-open a ticket by customer using ticket id
312 * @param Request $request
313 * @param $ticket_id
314 * @return array
315 */
316 public function reOpenTicket(Request $request, $ticket_id)
317 {
318 $customerAdditionalData = $this->getCustomerAdditionalData($request);
319
320 try {
321 return (new CustomerPortalService())->reOpenTicket($customerAdditionalData, $ticket_id);
322 } catch (Exception $e) {
323 return $this->sendError([
324 'message' => Helper::getSafeErrorMessage($e),
325 'error_type' => $e->getCode()
326 ]);
327 }
328 }
329
330 public function agentFeedbackRating(Request $request, $ticketId)
331 {
332
333 $customerPortalService = new CustomerPortalService();
334
335 // just for validation
336 $ticket = Ticket::with(['customer'])->wherePublicIdentifier($ticketId)->firstOrFail();
337 $customerAdditionalData = $this->getCustomerAdditionalData($request);
338 $customer = $customerPortalService->getCustomer($customerAdditionalData, $ticket);
339 $customerPortalService->checkCustomerTicketAccess($customer, $ticket, 'feedback');
340
341 $conversationID = $request->getSafe('conversation_id', 'intval');
342 $approvalStatus = $request->getSafe('approval_status', 'sanitize_text_field');
343
344 try {
345 return $customerPortalService->addUserFeedback($approvalStatus, $conversationID, $ticket->id);
346 } catch (Exception $e) {
347 return $this->sendError([
348 'message' => Helper::getSafeErrorMessage($e),
349 'error_type' => $e->getCode()
350 ]);
351 }
352 }
353
354 /**
355 * getPublicOptions method will return the list of product and customer priorities
356 * @return array
357 */
358 public function getPublicOptions()
359 {
360 $products = Product::select(['id', 'title'])->orderedByTitle()->get();
361
362 return [
363 'support_products' => $products,
364 'customer_ticket_priorities' => Helper::customerTicketPriorities()
365 ];
366 }
367
368 /**
369 * getCustomFieldsRender method will return the list of custom fields
370 * @return array|array[]
371 */
372 public function getCustomFieldsRender()
373 {
374 if (!defined('FLUENTSUPPORTPRO')) {
375 return [
376 'custom_fields_rendered' => []
377 ];
378 }
379
380 return [
381 'custom_fields_rendered' => \FluentSupportPro\App\Services\CustomFieldsService::getRenderedPublicFields()
382 ];
383 }
384
385
386 /**
387 * logout method will logout the customer
388 * @return mixed
389 */
390 public function logout()
391 {
392 wp_logout();
393
394 return $this->sendSuccess([
395 'message' => __('You have been logged out', 'fluent-support')
396 ]);
397 }
398
399 private function getCustomerAdditionalData($request)
400 {
401
402 $customerAdditionalData = [
403 'intended_ticket_hash' => $request->getSafe('intended_ticket_hash', 'sanitize_text_field'),
404 'on_behalf' => $this->sanitizeOnBehalf($request),
405 'user_ip' => $request->getIp()
406 ];
407
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;
438 }
439 }
440