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