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 +291 -282 1.4.7 → 2.4.5 View file →
@@ -1,65 +1,102 @@
1 1 <?php
2 2
3 3 namespace FluentSupport\App\Http\Controllers;
4 4
5 -use FluentSupport\App\Models\Attachment;
6 -use FluentSupport\App\Models\Customer;
7 -use FluentSupport\App\Models\MailBox;
5 +use Exception;
6 +use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
7 +use FluentSupport\App\Http\Policies\PortalPolicy;
8 +use FluentSupport\App\Http\Requests\TicketResponseRequest;
8 9 use FluentSupport\App\Models\Product;
9 -use FluentSupport\App\Models\Conversation;
10 10 use FluentSupport\App\Models\Ticket;
11 +use FluentSupport\App\Services\CustomerPortalService;
11 12 use FluentSupport\App\Services\Helper;
12 -use FluentSupport\App\Services\Includes\FileSystem;
13 -use FluentSupport\App\Services\Tickets\ResponseService;
14 -use FluentSupport\App\Services\Tickets\TicketService;
15 -use FluentSupport\Framework\Request\Request;
13 +use FluentSupport\App\Services\Tickets\AgentTicketAccess;
14 +use FluentSupport\Framework\Http\Request\Request;
16 15 use FluentSupport\Framework\Support\Arr;
17 16
17 +/**
18 + * Customer portal endpoints.
19 + */
18 20 class CustomerPortalController extends Controller
19 21 {
22 + /**
23 + * @param Request $request
24 + * @return array|\WP_REST_Response
25 + */
20 26 public function getTickets(Request $request)
21 27 {
22 - $customer = $this->resolveCustomer($request);
23 28
29 + $onBehalf = $this->sanitizeOnBehalf($request);
30 +
31 + $userIP = $request->getIp();
32 + $requestedStatus = $request->getSafe('filter_type', 'sanitize_text_field');
33 + $ticketOptions = $request->getSafe([
34 + 'search' => 'sanitize_text_field',
35 + 'filters.product_id' => 'intval',
36 + 'sorting.sort_type' => 'sanitize_sql_orderby',
37 + 'sorting.sort_by' => 'sanitize_sql_orderby'
38 + ]);
39 +
40 + $customer = (new CustomerPortalService)->resolveCustomer($onBehalf, $userIP);
41 +
24 42 if (!$customer) {
43 + return [
44 + 'tickets' => [
45 + 'data' => [],
46 + 'current_page' => $request->getSafe('page', 'intval', 1),
47 + 'last_page' => 1,
48 + 'per_page' => $request->getSafe('per_page', 'intval', 10),
49 + 'total' => 0,
50 + 'from' => null,
51 + 'to' => null
52 + ]
53 + ];
54 + }
55 +
56 + if (!$customer->canAccessPortal()) {
25 57 return $this->sendError([
26 - 'message' => __('No Customer Found', 'fluent-support'),
27 - 'error_type' => 'no_customer'
28 - ]);
58 + 'message' => __('Your account is not active. Please contact support.', 'fluent-support'),
59 + 'error_type' => '403'
60 + ], 403);
29 61 }
30 62
31 - if($customer->status == 'inactive') {
63 + $canAccess = apply_filters('fluent_support/can_customer_access_portal', true, $customer);
64 +
65 + if (is_wp_error($canAccess)) {
32 66 return $this->sendError([
33 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
34 - 'error_type' => 'inactive_customer'
35 - ]);
67 + 'message' => $canAccess->get_error_message(),
68 + 'error_type' => $canAccess->get_error_code()
69 + ], 403);
36 70 }
37 71
38 - $statuses = Arr::get([
72 + $statuses = [
39 73 'open' => ['new', 'active', 'on-hold'],
40 74 'all' => [],
41 75 'closed' => ['closed']
42 - ], $request->get('filter_type', ''));
76 + ];
43 77
44 - $ticketsQuery = Ticket::with([
45 - 'customer' => function ($query) {
46 - $query->select(['first_name', 'last_name', 'id']);
47 - }, 'agent' => function ($query) {
48 - $query->select(['first_name', 'last_name', 'id']);
49 - }
50 - ])
51 - ->where('customer_id', $customer->id)
52 - ->orderBy('id', 'DESC');
78 + $statusFilter = $statuses[$requestedStatus] ?? $statuses['all'];
53 79
54 - $ticketsQuery->where('customer_id', $customer->id);
80 + $sortBy = Arr::get($ticketOptions, 'sorting.sort_by', 'created_at');
81 + $sortType = Arr::get($ticketOptions, 'sorting.sort_type', 'desc');
55 82
56 - if ($statuses) {
57 - $ticketsQuery->whereIn('status', $statuses);
83 + $ticketsQuery = Ticket::where('customer_id', $customer->id)
84 + ->filterByStatues($statusFilter)
85 + ->searchBy(Arr::get($ticketOptions, 'search'))
86 + ->filterByProductId(Arr::get($ticketOptions, 'filters.product_id'))
87 + ->orderBy($sortBy, $sortType);
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);
58 93 }
59 94
60 - $tickets = $ticketsQuery->paginate();
95 + do_action_ref_array('fluent_support/customer_portal/tickets_query', [&$ticketsQuery, $customer, $request]);
61 96
97 + $tickets = $ticketsQuery->paginate($request->getInt('per_page', 10));
98 +
62 99 foreach ($tickets as $ticket) {
63 100 $ticket->human_date = sprintf(__('%s ago', 'fluent-support'), human_time_diff(strtotime($ticket->created_at), current_time('timestamp')));
64 101 $ticket->preview_response = $ticket->getLastResponse();
65 102 }
@@ -64,328 +101,281 @@
64 101 $ticket->preview_response = $ticket->getLastResponse();
65 102 }
66 103
67 104 return [
68 - 'tickets' => $tickets
105 + 'tickets' => CustomerPortalService::hidePrivateTicketFields($tickets)
69 106 ];
70 107 }
71 108
109 + /**
110 + * @param Request $request
111 + * @return array | \WP_REST_Response
112 + */
72 113 public function createTicket(Request $request)
73 114 {
74 - $data = $request->all();
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();
75 119
76 - $this->validate($data, [
77 - 'title' => 'required',
78 - 'content' => 'required'
79 - ]);
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 + );
80 125
81 - $data['title'] = sanitize_text_field(wp_unslash($data['title']));
82 -
83 - $data['content'] = wp_unslash(wp_kses_post($data['content']));
84 -
85 - $customer = $this->resolveCustomer($request, true);
86 -
87 - if($customer->status == 'inactive') {
88 - return $this->sendError([
89 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
90 - 'error_type' => 'inactive_customer'
91 - ]);
126 + if (!$isValidCaptcha) {
127 + return $this->sendError([
128 + 'message' => __('Your recaptcha is not verified', 'fluent-support'),
129 + 'error_type' => '422'
130 + ], 422);
131 + }
92 132 }
93 133
94 - if (!$customer) {
95 - return $this->sendError([
96 - 'message' => __('No Customer Found', 'fluent-support'),
97 - 'error_type' => 'no_customer'
98 - ]);
99 - }
134 + $dataRules = $this->app->applyCustomFilters('custom_field_required_before_ticket_create', [
135 + 'required_fields' => [
136 + 'title' => 'required',
137 + 'content' => 'required'
138 + ],
139 + 'error_messages' => [
140 + 'title.required' => __('Title is required', 'fluent-support'),
141 + 'content.required' => __('Content is required', 'fluent-support')
142 + ]
143 + ]);
100 144
101 - $data['customer_id'] = $customer->id;
102 - $data['product_source'] = 'local';
103 - $data['mailbox_id'] = $this->resolveMailboxId($request);
145 + $settings = Helper::getOption('_ticket_form_settings', []);
104 146
105 - $disabledFields = apply_filters('fluent_support/disabled_ticket_fields', []);
106 -
107 - if(!in_array('priority', $disabledFields)) {
108 - $data['priority'] = sanitize_text_field($data['client_priority']);
109 - $data['client_priority'] = sanitize_text_field($data['client_priority']);
110 - }
111 -
112 - if(!in_array('product_services', $disabledFields)) {
113 - unset($data['product_id']);
114 - }
115 -
116 - $data['source'] = 'web';
117 -
118 - $data = apply_filters('fluent_support/create_ticket_data', $data, $customer);
119 - do_action('fluent_support/before_ticket_create', $data, $customer);
120 - $ticket = Ticket::create($data);
121 -
122 - if ($attachments = Arr::get($data, 'attachments') && !in_array('file_upload', $disabledFields)) {
123 - Attachment::whereNull('ticket_id')
124 - ->whereIn('file_hash', $attachments)
125 - ->whereNull('ticket_id')
126 - ->update([
127 - 'ticket_id' => $ticket->id,
128 - 'person_id' => $customer->id,
129 - 'status' => 'active'
130 - ]);
131 - $ticket->load('attachments');
132 - }
133 -
134 -
135 - if(defined('FLUENTSUPPORTPRO')) {
136 - $customData = Arr::get($data, 'custom_data');
137 - if($customData) {
138 - $customData = wp_unslash($customData);
139 - $ticket->syncCustomFields($customData);
147 + if (!empty($settings['product_required_field']) && $settings['product_required_field'] === 'yes') {
148 + $productCount = Product::count();
149 + if ($productCount > 0) {
150 + $dataRules['required_fields']['product_id'] = 'required';
151 + $dataRules['error_messages']['product_id.required'] = __('Product is required', 'fluent-support');
140 152 }
141 153 }
142 154
143 - do_action('fluent_support/ticket_created', $ticket, $customer);
144 -
145 - return [
146 - 'message' => __('Ticket has been created successfully', 'fluent-support'),
147 - 'ticket' => $ticket
155 + $defaultData = [
156 + 'ticket_title' => $request->getSafe('title', 'sanitize_text_field'),
157 + 'ticket_content' => $request->getSafe('content', 'wp_kses_post')
148 158 ];
149 - }
150 159
151 - public function getTicket(Request $request, $ticketId)
152 - {
153 - $ticket = Ticket::where('id', $ticketId)
154 - ->with([
155 - 'customer' => function ($query) {
156 - $query->select(['first_name', 'email', 'person_type', 'last_name', 'id']);
157 - }, 'agent' => function ($query) {
158 - $query->select(['first_name', 'email', 'person_type', 'last_name', 'id', 'title']);
159 - },
160 - 'product',
161 - 'attachments'
162 - ])
163 - ->first();
164 -
165 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
166 - $customer = $ticket->customer;
167 - } else {
168 - $customer = $this->resolveCustomer($request);
160 + if ($request->has('product_id')) {
161 + $defaultData['ticket_product_id'] = $request->getSafe('product_id', 'intval');
169 162 }
170 163
171 - if (!$customer) {
172 - return $this->sendError([
173 - 'message' => __('Sorry, You do not have permission to this support ticket', 'fluent-support'),
174 - 'error_type' => 'no_customer'
175 - ]);
164 + if ($request->has('client_priority')) {
165 + $defaultData['ticket_client_priority'] = $request->getSafe('client_priority', 'sanitize_text_field');
176 166 }
177 167
178 - if($customer->status == 'inactive') {
179 - return $this->sendError([
180 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
181 - 'error_type' => 'inactive_customer'
182 - ]);
183 - }
168 + $customData = $request->get('custom_data', []);
169 + $customData = is_array($customData) ? map_deep($customData, 'sanitize_text_field') : [];
184 170
171 + $dataRules = $this->app->applyCustomFilters('custom_field_required_by_conditions_before_ticket_create', [
172 + 'required_fields' => $dataRules['required_fields'],
173 + 'error_messages' => $dataRules['error_messages'],
174 + 'custom_data' => $customData,
175 + 'default_data' => $defaultData
176 + ]);
185 177
186 - if ($ticket->privacy == 'private' && $customer->id != $ticket->customer_id) {
178 + if (!isset($dataRules['required_fields']) && !isset($dataRules['error_messages'])) {
187 179 return $this->sendError([
188 - 'message' => __('You do not have permission to view this support ticket', 'fluent-support'),
189 - 'error_type' => 'permission_error'
190 - ]);
180 + 'message' => __('Invalid form data submitted', 'fluent-support'),
181 + 'error_type' => '400'
182 + ], 400);
191 183 }
192 184
193 - $responses = Conversation::where('ticket_id', $ticketId)
194 - ->with([
195 - 'person' => function ($query) {
196 - $query->select(['first_name', 'email', 'person_type', 'last_name', 'id', 'title']);
197 - },
198 - 'attachments'
199 - ])
200 - ->filterByType('response')
201 - ->orderBy('id', 'DESC')
202 - ->get();
185 + $data = $this->validate($request->get(), $dataRules['required_fields'], $dataRules['error_messages']);
203 186
204 - foreach ($responses as $response) {
205 - $response->content = make_clickable($response->content);
206 - if ($response->person) {
207 - $response->person->setHidden(['email']);
208 - }
209 - }
187 + $data['title'] = sanitize_text_field($data['title']);
188 + $data['content'] = wp_kses_post($data['content']);
189 + $data['custom_data'] = $customData;
210 190
211 - $ticket->content = make_clickable($ticket->content);
191 + $onBehalf = $this->sanitizeOnBehalf($request);
192 + $userIP = $request->getIp();
212 193
213 - if ($ticket->customer) {
214 - $ticket->customer->setHidden(['email']);
194 + if (!empty($onBehalf['last_ip_address'])) {
195 + $userIP = $onBehalf['last_ip_address'];
215 196 }
216 197
217 - if ($ticket->agent) {
218 - $ticket->agent->setHidden(['email']);
219 - }
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'));
220 202
221 - if ($ticket->status == 'closed') {
222 - $ticket->load('closed_by_person');
223 - if ($ticket->closed_by_person) {
224 - $ticket->closed_by_person->setVisible(['first_name', 'last_name', 'id', 'full_name', 'photo']);
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);
225 208 }
226 209 }
227 210
228 - if(defined('FLUENTSUPPORTPRO')) {
229 - $ticket->custom_fields = $ticket->customData('public', true);
230 - }
211 + try {
212 + $customer = (new CustomerPortalService())->resolveCustomer($onBehalf, $userIP, true);
231 213
232 - return [
233 - 'ticket' => $ticket,
234 - 'responses' => $responses,
235 - 'sign_on_id' => $ticket->customer_id
236 - ];
237 - }
214 + if (!$customer) {
215 + return $this->sendError([
216 + 'message' => __('Unable to identify. Please make sure you have provided correct information.', 'fluent-support'),
217 + 'error_type' => '403'
218 + ], 403);
219 + }
238 220
239 - public function createResponse(Request $request, $ticketId)
240 - {
241 - $data = $request->all();
242 - $this->validate($data, [
243 - 'content' => 'required'
244 - ]);
221 + $canCreateTicket = apply_filters('fluent_support/can_customer_create_ticket', true, $customer, $data);
245 222
246 - $data['content'] = wp_unslash(wp_kses_post($data['content']));
223 + if (!$canCreateTicket || is_wp_error($canCreateTicket)) {
224 + $isWpError = is_wp_error($canCreateTicket);
247 225
226 + $message = ($isWpError) ? $canCreateTicket->get_error_message() : __('Sorry you cannot create ticket', 'fluent-support');
227 + $errorCode = ($isWpError) ? $canCreateTicket->get_error_code() : 'general_error';
248 228
249 - $ticket = Ticket::with(['customer'])->findOrFail($ticketId);
229 + return $this->sendError([
230 + 'message' => $message,
231 + 'error_type' => $errorCode
232 + ]);
233 + }
250 234
251 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
252 - $customer = $ticket->customer;
253 - } else {
254 - $customer = $this->resolveCustomer($request);
255 - }
235 + if ($messageId = Helper::generateMessageID($customer->email)) {
236 + $data['message_id'] = $messageId;
237 + }
256 238
257 - if (!$customer) {
258 - return $this->sendError([
259 - 'message' => __('Sorry! No customer found', 'fluent-support')
260 - ]);
261 - }
239 + $defaultMailbox = Helper::getDefaultMailBox();
240 + $defaultMailboxId = $defaultMailbox ? $defaultMailbox->id : null;
241 + $ticket = (new CustomerPortalService())->createTicket($customer, $data, $request->getSafe('mailbox_id', 'intval', $defaultMailboxId), (bool) $onBehalf);
262 242
263 - if($customer->status == 'inactive') {
243 + return [
244 + 'message' => __('Ticket has been created successfully', 'fluent-support'),
245 + 'ticket' => CustomerPortalService::hidePrivateTicketFields($ticket)
246 + ];
247 + } catch (\Exception $e) {
264 248 return $this->sendError([
265 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
266 - 'error_type' => 'inactive_customer'
249 + 'message' => Helper::getSafeErrorMessage($e),
250 + 'error_type' => $e->getCode()
267 251 ]);
268 252 }
253 + }
269 254
255 + /**
256 + * @param Request $request
257 + * @param int $ticket_id
258 + * @return array|\WP_REST_Response
259 + */
260 + public function getTicket(Request $request, $ticket_id)
261 + {
262 + $customerAdditionalData = $this->getCustomerAdditionalData($request);
270 263
271 - if ($ticket->privacy == 'private' && $customer->id != $ticket->customer_id) {
264 + try {
265 + return (new CustomerPortalService())->getTicket($customerAdditionalData, $ticket_id);
266 + } catch (\Exception $e) {
272 267 return $this->sendError([
273 - 'message' => __('Sorry! You can not reply to this ticket', 'fluent-support')
268 + 'message' => Helper::getSafeErrorMessage($e),
269 + 'error_type' => $e->getCode()
274 270 ]);
275 271 }
272 + }
276 273
277 - $data['conversation_type'] = 'response';
278 - $responseData = (new ResponseService())->createResponse($data, $customer, $ticket);
274 + /**
275 + * @param TicketResponseRequest $request
276 + * @param int $ticket_id
277 + * @return array|\WP_REST_Response
278 + * @throws \FluentSupport\Framework\Validator\ValidationException
279 + */
280 + public function createResponse(TicketResponseRequest $request, $ticket_id)
281 + {
279 282
280 - return [
281 - 'message' => __('Reply has been added', 'fluent-support'),
282 - 'response' => $responseData['response'],
283 - 'ticket' => $responseData['ticket']
284 - ];
285 - }
283 + $customerAdditionalData = $this->getCustomerAdditionalData($request);
286 284
287 - public function closeTicket(Request $request, $ticketId)
288 - {
289 - $ticket = Ticket::with(['customer'])->findOrFail($ticketId);
285 + $ticket = Ticket::wherePublicIdentifier($ticket_id)->firstOrFail();
290 286
291 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
292 - $customer = $ticket->customer;
293 - } else {
294 - $customer = $this->resolveCustomer($request);
295 - }
287 + $data = $request->sanitize();
296 288
297 - if (!$customer) {
298 - return $this->sendError([
299 - 'message' => __('Sorry! No customer found', 'fluent-support')
300 - ]);
301 - }
289 + $canCreateResponse = apply_filters('fluent_support/can_customer_create_response', true, $ticket->customer, $ticket, $data);
302 290
303 - if($customer->status == 'inactive') {
304 - return $this->sendError([
305 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
306 - 'error_type' => 'inactive_customer'
307 - ]);
291 + if (!$canCreateResponse || is_wp_error($canCreateResponse)) {
292 + return [
293 + 'type' => 'error',
294 + 'message' => (is_wp_error($canCreateResponse)) ? $canCreateResponse->get_error_message() : __('Sorry you cannot create response', 'fluent-support')
295 + ];
308 296 }
309 297
310 -
311 - if ($customer->id != $ticket->customer_id) {
298 + try {
299 + return (new CustomerPortalService())->createResponse($customerAdditionalData, $ticket_id, $data);
300 + } catch (\Exception $e) {
312 301 return $this->sendError([
313 - 'message' => __('Sorry! You can not close this ticket', 'fluent-support')
302 + 'message' => Helper::getSafeErrorMessage($e),
303 + 'error_type' => $e->getCode()
314 304 ]);
315 305 }
316 -
317 - return [
318 - 'message' => __('Ticket has been closed', 'fluent_support'),
319 - 'ticket' => (new TicketService())->close($ticket, $customer)
320 - ];
321 306 }
322 307
323 - public function reOpenTicket(Request $request, $ticketId)
308 + /**
309 + * @param Request $request
310 + * @param int $ticket_id
311 + * @return array
312 + */
313 + public function closeTicket(Request $request, $ticket_id)
324 314 {
325 - $ticket = Ticket::with(['customer'])->findOrFail($ticketId);
315 + $customerAdditionalData = $this->getCustomerAdditionalData($request);
326 316
327 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
328 - $customer = $ticket->customer;
329 - } else {
330 - $customer = $this->resolveCustomer($request);
331 - }
332 -
333 - if($customer->status == 'inactive') {
317 + try {
318 + return (new CustomerPortalService())->closeTicket($customerAdditionalData, $ticket_id);
319 + } catch (Exception $e) {
334 320 return $this->sendError([
335 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
336 - 'error_type' => 'inactive_customer'
321 + 'message' => Helper::getSafeErrorMessage($e),
322 + 'error_type' => $e->getCode()
337 323 ]);
338 324 }
325 + }
339 326
327 + /**
328 + * @param Request $request
329 + * @param int $ticket_id
330 + * @return array
331 + */
332 + public function reOpenTicket(Request $request, $ticket_id)
333 + {
334 + $customerAdditionalData = $this->getCustomerAdditionalData($request);
340 335
341 - if (!$customer) {
336 + try {
337 + return (new CustomerPortalService())->reOpenTicket($customerAdditionalData, $ticket_id);
338 + } catch (Exception $e) {
342 339 return $this->sendError([
343 - 'message' => __('Sorry! No customer found', 'fluent-support')
340 + 'message' => Helper::getSafeErrorMessage($e),
341 + 'error_type' => $e->getCode()
344 342 ]);
345 343 }
344 + }
346 345
347 - if ($customer->id != $ticket->customer_id) {
348 - return $this->sendError([
349 - 'message' => __('Sorry! You can not close this ticket', 'fluent-support')
350 - ]);
351 - }
346 + public function agentFeedbackRating(Request $request, $ticketId)
347 + {
352 348
349 + $customerPortalService = new CustomerPortalService();
353 350
354 - return [
355 - 'message' => __('Ticket has been opened again', 'fluent_support'),
356 - 'ticket' => (new TicketService())->reopen($ticket, $customer)
357 - ];
351 + // Throws unless the customer can access this ticket.
352 + $ticket = Ticket::with(['customer'])->wherePublicIdentifier($ticketId)->firstOrFail();
353 + $customerAdditionalData = $this->getCustomerAdditionalData($request);
354 + $customer = $customerPortalService->getCustomer($customerAdditionalData, $ticket);
355 + $customerPortalService->checkCustomerTicketAccess($customer, $ticket, 'feedback');
358 356
357 + $conversationID = $request->getSafe('conversation_id', 'intval');
358 + $approvalStatus = $request->getSafe('approval_status', 'sanitize_text_field');
359 359
360 - }
361 -
362 - private function resolveCustomer($request, $forceCreate = false)
363 - {
364 - $onBehalf = $request->get('on_behalf');
365 -
366 - if (!$onBehalf) {
367 - $user = get_user_by('ID', get_current_user_id());
368 - if (!$user) {
369 - return false;
370 - }
371 - $onBehalf = [
372 - 'user_id' => $user->id,
373 - 'email' => $user->user_email,
374 - 'last_ip_address' => $request->getIp()
375 - ];
360 + try {
361 + return $customerPortalService->addUserFeedback($approvalStatus, $conversationID, $ticket->id);
362 + } catch (Exception $e) {
363 + return $this->sendError([
364 + 'message' => Helper::getSafeErrorMessage($e),
365 + 'error_type' => $e->getCode()
366 + ]);
376 367 }
377 -
378 - if ($forceCreate) {
379 - return Customer::maybeCreateCustomer($onBehalf);
380 - }
381 -
382 - return Customer::getCustomerFromData($onBehalf);
383 368 }
384 369
370 + /**
371 + * Products and customer ticket priorities.
372 + *
373 + * @return array
374 + */
385 375 public function getPublicOptions()
386 376 {
387 - $products = Product::select(['id', 'title'])->get();
377 + $products = Product::select(['id', 'title'])->orderedByTitle()->get();
388 378
389 379 return [
390 380 'support_products' => $products,
391 381 'customer_ticket_priorities' => Helper::customerTicketPriorities()
@@ -391,11 +381,14 @@
391 381 'customer_ticket_priorities' => Helper::customerTicketPriorities()
392 382 ];
393 383 }
394 384
385 + /**
386 + * @return array|array[]
387 + */
395 388 public function getCustomFieldsRender()
396 389 {
397 - if(!defined('FLUENTSUPPORTPRO')) {
390 + if (!defined('FLUENTSUPPORTPRO')) {
398 391 return [
399 392 'custom_fields_rendered' => []
400 393 ];
401 394 }
@@ -400,26 +393,42 @@
400 393 ];
401 394 }
402 395
403 396 return [
404 - 'custom_fields_rendered' => \FluentSupportPro\App\Services\CustomFieldsService::getRenderedPublicFields()
397 + 'custom_fields_rendered' => \FluentSupportPro\App\Services\CustomFieldsService::getRenderedPublicFields()
405 398 ];
399 + }
406 400
401 +
402 + /**
403 + * @return mixed
404 + */
405 + public function logout()
406 + {
407 + wp_logout();
408 +
409 + return $this->sendSuccess([
410 + 'message' => __('You have been logged out', 'fluent-support')
411 + ]);
407 412 }
408 413
409 - private function resolveMailboxId($request)
414 + private function getCustomerAdditionalData($request)
410 415 {
411 - if ($mailboxId = $request->get('mailbox_id')) {
412 - $mailbox = MailBox::find($mailboxId);
413 - if ($mailbox) {
414 - return $mailbox->id;
415 - }
416 - }
417 416
418 - $mailbox = Helper::getDefaultMailBox();
417 + $customerAdditionalData = [
418 + 'intended_ticket_hash' => $request->getSafe('intended_ticket_hash', 'sanitize_text_field'),
419 + 'on_behalf' => $this->sanitizeOnBehalf($request),
420 + 'user_ip' => $request->getIp()
421 + ];
419 422
420 - if ($mailbox) {
421 - return $mailbox->id;
422 - }
423 - return null;
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', []));
424 433 }
425 434 }