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 -285 1.5.3 → 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,331 +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 -
124 - Attachment::whereNull('ticket_id')
125 - ->whereIn('file_hash', $attachments)
126 - ->whereNull('ticket_id')
127 - ->update([
128 - 'ticket_id' => $ticket->id,
129 - 'person_id' => $customer->id,
130 - 'status' => 'active'
131 - ]);
132 -
133 -
134 - $ticket->load('attachments');
135 - }
136 -
137 -
138 - if(defined('FLUENTSUPPORTPRO')) {
139 - $customData = Arr::get($data, 'custom_data');
140 - if($customData) {
141 - $customData = wp_unslash($customData);
142 - $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');
143 152 }
144 153 }
145 154
146 - do_action('fluent_support/ticket_created', $ticket, $customer);
147 -
148 - return [
149 - 'message' => __('Ticket has been created successfully', 'fluent-support'),
150 - 'ticket' => $ticket
155 + $defaultData = [
156 + 'ticket_title' => $request->getSafe('title', 'sanitize_text_field'),
157 + 'ticket_content' => $request->getSafe('content', 'wp_kses_post')
151 158 ];
152 - }
153 159
154 - public function getTicket(Request $request, $ticketId)
155 - {
156 - $ticket = Ticket::where('id', $ticketId)
157 - ->with([
158 - 'customer' => function ($query) {
159 - $query->select(['first_name', 'email', 'person_type', 'last_name', 'id']);
160 - }, 'agent' => function ($query) {
161 - $query->select(['first_name', 'email', 'person_type', 'last_name', 'id', 'title']);
162 - },
163 - 'product',
164 - 'attachments'
165 - ])
166 - ->first();
167 -
168 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
169 - $customer = $ticket->customer;
170 - } else {
171 - $customer = $this->resolveCustomer($request);
160 + if ($request->has('product_id')) {
161 + $defaultData['ticket_product_id'] = $request->getSafe('product_id', 'intval');
172 162 }
173 163
174 - if (!$customer) {
175 - return $this->sendError([
176 - 'message' => __('Sorry, You do not have permission to this support ticket', 'fluent-support'),
177 - 'error_type' => 'no_customer'
178 - ]);
164 + if ($request->has('client_priority')) {
165 + $defaultData['ticket_client_priority'] = $request->getSafe('client_priority', 'sanitize_text_field');
179 166 }
180 167
181 - if($customer->status == 'inactive') {
182 - return $this->sendError([
183 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
184 - 'error_type' => 'inactive_customer'
185 - ]);
186 - }
168 + $customData = $request->get('custom_data', []);
169 + $customData = is_array($customData) ? map_deep($customData, 'sanitize_text_field') : [];
187 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 + ]);
188 177
189 - if ($ticket->privacy == 'private' && $customer->id != $ticket->customer_id) {
178 + if (!isset($dataRules['required_fields']) && !isset($dataRules['error_messages'])) {
190 179 return $this->sendError([
191 - 'message' => __('You do not have permission to view this support ticket', 'fluent-support'),
192 - 'error_type' => 'permission_error'
193 - ]);
180 + 'message' => __('Invalid form data submitted', 'fluent-support'),
181 + 'error_type' => '400'
182 + ], 400);
194 183 }
195 184
196 - $responses = Conversation::where('ticket_id', $ticketId)
197 - ->with([
198 - 'person' => function ($query) {
199 - $query->select(['first_name', 'email', 'person_type', 'last_name', 'id', 'title']);
200 - },
201 - 'attachments'
202 - ])
203 - ->filterByType('response')
204 - ->orderBy('id', 'DESC')
205 - ->get();
185 + $data = $this->validate($request->get(), $dataRules['required_fields'], $dataRules['error_messages']);
206 186
207 - foreach ($responses as $response) {
208 - $response->content = make_clickable($response->content);
209 - if ($response->person) {
210 - $response->person->setHidden(['email']);
211 - }
212 - }
187 + $data['title'] = sanitize_text_field($data['title']);
188 + $data['content'] = wp_kses_post($data['content']);
189 + $data['custom_data'] = $customData;
213 190
214 - $ticket->content = make_clickable($ticket->content);
191 + $onBehalf = $this->sanitizeOnBehalf($request);
192 + $userIP = $request->getIp();
215 193
216 - if ($ticket->customer) {
217 - $ticket->customer->setHidden(['email']);
194 + if (!empty($onBehalf['last_ip_address'])) {
195 + $userIP = $onBehalf['last_ip_address'];
218 196 }
219 197
220 - if ($ticket->agent) {
221 - $ticket->agent->setHidden(['email']);
222 - }
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'));
223 202
224 - if ($ticket->status == 'closed') {
225 - $ticket->load('closed_by_person');
226 - if ($ticket->closed_by_person) {
227 - $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);
228 208 }
229 209 }
230 210
231 - if(defined('FLUENTSUPPORTPRO')) {
232 - $ticket->custom_fields = $ticket->customData('public', true);
233 - }
211 + try {
212 + $customer = (new CustomerPortalService())->resolveCustomer($onBehalf, $userIP, true);
234 213
235 - return [
236 - 'ticket' => $ticket,
237 - 'responses' => $responses,
238 - 'sign_on_id' => $ticket->customer_id
239 - ];
240 - }
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 + }
241 220
242 - public function createResponse(Request $request, $ticketId)
243 - {
244 - $data = $request->all();
245 - $this->validate($data, [
246 - 'content' => 'required'
247 - ]);
221 + $canCreateTicket = apply_filters('fluent_support/can_customer_create_ticket', true, $customer, $data);
248 222
249 - $data['content'] = wp_unslash(wp_kses_post($data['content']));
223 + if (!$canCreateTicket || is_wp_error($canCreateTicket)) {
224 + $isWpError = is_wp_error($canCreateTicket);
250 225
226 + $message = ($isWpError) ? $canCreateTicket->get_error_message() : __('Sorry you cannot create ticket', 'fluent-support');
227 + $errorCode = ($isWpError) ? $canCreateTicket->get_error_code() : 'general_error';
251 228
252 - $ticket = Ticket::with(['customer'])->findOrFail($ticketId);
229 + return $this->sendError([
230 + 'message' => $message,
231 + 'error_type' => $errorCode
232 + ]);
233 + }
253 234
254 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
255 - $customer = $ticket->customer;
256 - } else {
257 - $customer = $this->resolveCustomer($request);
258 - }
235 + if ($messageId = Helper::generateMessageID($customer->email)) {
236 + $data['message_id'] = $messageId;
237 + }
259 238
260 - if (!$customer) {
261 - return $this->sendError([
262 - 'message' => __('Sorry! No customer found', 'fluent-support')
263 - ]);
264 - }
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);
265 242
266 - 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) {
267 248 return $this->sendError([
268 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
269 - 'error_type' => 'inactive_customer'
249 + 'message' => Helper::getSafeErrorMessage($e),
250 + 'error_type' => $e->getCode()
270 251 ]);
271 252 }
253 + }
272 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);
273 263
274 - if ($ticket->privacy == 'private' && $customer->id != $ticket->customer_id) {
264 + try {
265 + return (new CustomerPortalService())->getTicket($customerAdditionalData, $ticket_id);
266 + } catch (\Exception $e) {
275 267 return $this->sendError([
276 - 'message' => __('Sorry! You can not reply to this ticket', 'fluent-support')
268 + 'message' => Helper::getSafeErrorMessage($e),
269 + 'error_type' => $e->getCode()
277 270 ]);
278 271 }
272 + }
279 273
280 - $data['conversation_type'] = 'response';
281 - $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 + {
282 282
283 - return [
284 - 'message' => __('Reply has been added', 'fluent-support'),
285 - 'response' => $responseData['response'],
286 - 'ticket' => $responseData['ticket']
287 - ];
288 - }
283 + $customerAdditionalData = $this->getCustomerAdditionalData($request);
289 284
290 - public function closeTicket(Request $request, $ticketId)
291 - {
292 - $ticket = Ticket::with(['customer'])->findOrFail($ticketId);
285 + $ticket = Ticket::wherePublicIdentifier($ticket_id)->firstOrFail();
293 286
294 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
295 - $customer = $ticket->customer;
296 - } else {
297 - $customer = $this->resolveCustomer($request);
298 - }
287 + $data = $request->sanitize();
299 288
300 - if (!$customer) {
301 - return $this->sendError([
302 - 'message' => __('Sorry! No customer found', 'fluent-support')
303 - ]);
304 - }
289 + $canCreateResponse = apply_filters('fluent_support/can_customer_create_response', true, $ticket->customer, $ticket, $data);
305 290
306 - if($customer->status == 'inactive') {
307 - return $this->sendError([
308 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
309 - 'error_type' => 'inactive_customer'
310 - ]);
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 + ];
311 296 }
312 297
313 -
314 - if ($customer->id != $ticket->customer_id) {
298 + try {
299 + return (new CustomerPortalService())->createResponse($customerAdditionalData, $ticket_id, $data);
300 + } catch (\Exception $e) {
315 301 return $this->sendError([
316 - 'message' => __('Sorry! You can not close this ticket', 'fluent-support')
302 + 'message' => Helper::getSafeErrorMessage($e),
303 + 'error_type' => $e->getCode()
317 304 ]);
318 305 }
319 -
320 - return [
321 - 'message' => __('Ticket has been closed', 'fluent_support'),
322 - 'ticket' => (new TicketService())->close($ticket, $customer)
323 - ];
324 306 }
325 307
326 - 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)
327 314 {
328 - $ticket = Ticket::with(['customer'])->findOrFail($ticketId);
315 + $customerAdditionalData = $this->getCustomerAdditionalData($request);
329 316
330 - if ($request->get('intended_ticket_hash') && Helper::isPublicSignedTicketEnabled()) {
331 - $customer = $ticket->customer;
332 - } else {
333 - $customer = $this->resolveCustomer($request);
334 - }
335 -
336 - if($customer->status == 'inactive') {
317 + try {
318 + return (new CustomerPortalService())->closeTicket($customerAdditionalData, $ticket_id);
319 + } catch (Exception $e) {
337 320 return $this->sendError([
338 - 'message' => __('Sorry, You do not have access to customer portal', 'fluent-support'),
339 - 'error_type' => 'inactive_customer'
321 + 'message' => Helper::getSafeErrorMessage($e),
322 + 'error_type' => $e->getCode()
340 323 ]);
341 324 }
325 + }
342 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);
343 335
344 - if (!$customer) {
336 + try {
337 + return (new CustomerPortalService())->reOpenTicket($customerAdditionalData, $ticket_id);
338 + } catch (Exception $e) {
345 339 return $this->sendError([
346 - 'message' => __('Sorry! No customer found', 'fluent-support')
340 + 'message' => Helper::getSafeErrorMessage($e),
341 + 'error_type' => $e->getCode()
347 342 ]);
348 343 }
344 + }
349 345
350 - if ($customer->id != $ticket->customer_id) {
351 - return $this->sendError([
352 - 'message' => __('Sorry! You can not close this ticket', 'fluent-support')
353 - ]);
354 - }
346 + public function agentFeedbackRating(Request $request, $ticketId)
347 + {
355 348
349 + $customerPortalService = new CustomerPortalService();
356 350
357 - return [
358 - 'message' => __('Ticket has been opened again', 'fluent_support'),
359 - 'ticket' => (new TicketService())->reopen($ticket, $customer)
360 - ];
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');
361 356
357 + $conversationID = $request->getSafe('conversation_id', 'intval');
358 + $approvalStatus = $request->getSafe('approval_status', 'sanitize_text_field');
362 359
363 - }
364 -
365 - private function resolveCustomer($request, $forceCreate = false)
366 - {
367 - $onBehalf = $request->get('on_behalf');
368 -
369 - if (!$onBehalf) {
370 - $user = get_user_by('ID', get_current_user_id());
371 - if (!$user) {
372 - return false;
373 - }
374 - $onBehalf = [
375 - 'user_id' => $user->id,
376 - 'email' => $user->user_email,
377 - 'last_ip_address' => $request->getIp()
378 - ];
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 + ]);
379 367 }
380 -
381 - if ($forceCreate) {
382 - return Customer::maybeCreateCustomer($onBehalf);
383 - }
384 -
385 - return Customer::getCustomerFromData($onBehalf);
386 368 }
387 369
370 + /**
371 + * Products and customer ticket priorities.
372 + *
373 + * @return array
374 + */
388 375 public function getPublicOptions()
389 376 {
390 - $products = Product::select(['id', 'title'])->get();
377 + $products = Product::select(['id', 'title'])->orderedByTitle()->get();
391 378
392 379 return [
393 380 'support_products' => $products,
394 381 'customer_ticket_priorities' => Helper::customerTicketPriorities()
@@ -394,11 +381,14 @@
394 381 'customer_ticket_priorities' => Helper::customerTicketPriorities()
395 382 ];
396 383 }
397 384
385 + /**
386 + * @return array|array[]
387 + */
398 388 public function getCustomFieldsRender()
399 389 {
400 - if(!defined('FLUENTSUPPORTPRO')) {
390 + if (!defined('FLUENTSUPPORTPRO')) {
401 391 return [
402 392 'custom_fields_rendered' => []
403 393 ];
404 394 }
@@ -403,26 +393,42 @@
403 393 ];
404 394 }
405 395
406 396 return [
407 - 'custom_fields_rendered' => \FluentSupportPro\App\Services\CustomFieldsService::getRenderedPublicFields()
397 + 'custom_fields_rendered' => \FluentSupportPro\App\Services\CustomFieldsService::getRenderedPublicFields()
408 398 ];
399 + }
409 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 + ]);
410 412 }
411 413
412 - private function resolveMailboxId($request)
414 + private function getCustomerAdditionalData($request)
413 415 {
414 - if ($mailboxId = $request->get('mailbox_id')) {
415 - $mailbox = MailBox::find($mailboxId);
416 - if ($mailbox) {
417 - return $mailbox->id;
418 - }
419 - }
420 416
421 - $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 + ];
422 422
423 - if ($mailbox) {
424 - return $mailbox->id;
425 - }
426 - 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', []));
427 433 }
428 434 }