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