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

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

442 lines 15.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Http\Controllers;
4
5 use FluentCrm\App\Models\Subscriber;
6 use FluentSupport\App\Models\Customer;
7 use FluentSupport\Framework\Http\Request\Request;
8 use FluentSupport\App\Services\AvatarUploder;
9 use FluentSupport\App\Services\Helper;
10 use FluentSupport\Framework\Support\Arr;
11
12 /**
13 * CustomerController class for REST API
14 * This class is responsible for getting data for all request related to customer
15 * @package FluentSupport\App\Http\Controllers
16 *
17 * @version 1.0.0
18 */
19 class CustomerController extends Controller
20 {
21 /**
22 * Maximum number of customers accepted by a single bulk-delete request.
23 */
24 const BULK_DELETE_LIMIT = 100;
25
26 /**
27 * index method will return the list of customers
28 * @param Request $request
29 * @param Customer $customer
30 * @return array
31 */
32 public function index(Request $request, Customer $customer)
33 {
34 return [
35 'customers' => $customer->getCustomers($request->getSafe('search', 'sanitize_text_field'), $request->getSafe('status', 'sanitize_text_field')),
36 ];
37 }
38
39 public function customerField (Request $request,Customer $customer, $customer_id) {
40
41 $userID = $request->getSafe('user_id', 'intval');
42 return[
43 'customerField' => $customer->getCustomerField($customer_id,$userID)
44 ];
45 }
46
47
48 /**
49 * getCustomer method will return individual customer information by customer id
50 * This function will also get information about extra widgets, tickets and Fluent CRM
51 * @param Request $request
52 * @param Customer $customer
53 * @param $customer_id
54 * @return array
55 */
56 public function getCustomer(Request $request, Customer $customer, $customer_id)
57 {
58 $with = $request->get('with', null);
59 $with = is_array($with) ? array_map('sanitize_key', $with) : [];
60
61 return $customer->getCustomer($customer_id, $with);
62 }
63
64 /**
65 * Create method will create new customer
66 * @param Request $request
67 * @param Customer $customer
68 * @return array
69 * @throws \FluentSupport\Framework\Validator\ValidationException
70 */
71 public function create(Request $request, Customer $customer)
72 {
73 // Define expected fields with their sanitizers
74 $fields = [
75 'id' => 'intval',
76 'customer_id' => 'intval',
77 'avatar' => 'esc_url_raw',
78 'person_type' => 'sanitize_text_field',
79 'hash' => 'sanitize_text_field',
80 'description' => 'sanitize_text_field',
81 'photo' => 'esc_url_raw',
82 'email' => 'sanitize_email',
83 'first_name' => 'sanitize_text_field',
84 'last_name' => 'sanitize_text_field',
85 'title' => 'sanitize_text_field',
86 'user_id' => 'intval',
87 'remote_uid' => 'sanitize_text_field',
88 'status' => 'sanitize_text_field',
89 'address_line_1' => 'sanitize_textarea_field',
90 'address_line_2' => 'sanitize_textarea_field',
91 'city' => 'sanitize_text_field',
92 'state' => 'sanitize_text_field',
93 'zip' => 'sanitize_text_field',
94 'country' => 'sanitize_text_field',
95 'note' => 'sanitize_textarea_field',
96 'ip_address' => 'sanitize_text_field',
97 'last_ip_address' => 'sanitize_text_field',
98 ];
99
100 $data = $this->sanitizeRequestData($request, $fields);
101
102 $data = $this->validate($data, [
103 'email' => 'required|email|unique:fs_persons',
104 'first_name' => 'required',
105 'last_name' => 'nullable|string',
106 'title' => 'nullable|string',
107 'user_id' => 'nullable|integer',
108 'remote_uid' => 'nullable|string',
109 'status' => 'nullable|string',
110 'address_line_1' => 'nullable|string',
111 'address_line_2' => 'nullable|string',
112 'city' => 'nullable|string',
113 'state' => 'nullable|string',
114 'zip' => 'nullable|string',
115 'country' => 'nullable|string',
116 'note' => 'nullable|string',
117 'ip_address' => 'nullable|string',
118 'last_ip_address' => 'nullable|string',
119 ]);
120
121 return [
122 'message' => __('Customer has been added', 'fluent-support'),
123 'customer' => $customer->createCustomer($data)
124 ];
125 }
126
127 /**
128 * update method will update existing customer by customer id
129 * @param Request $request
130 * @param Customer $customer
131 * @param $customerId
132 * @return array
133 * @throws \FluentSupport\Framework\Validator\ValidationException
134 */
135 public function update(Request $request, Customer $customer, $customer_id)
136 {
137 // Sanitize only allowed fields and also sanitize any extra fields from hooks
138 $fields = [
139 'id' => 'intval',
140 'customer_id' => 'intval',
141 'avatar' => 'esc_url_raw',
142 'person_type' => 'sanitize_text_field',
143 'hash' => 'sanitize_text_field',
144 'description' => 'sanitize_text_field',
145 'photo' => 'esc_url_raw',
146 'email' => 'sanitize_email',
147 'first_name' => 'sanitize_text_field',
148 'last_name' => 'sanitize_text_field',
149 'title' => 'sanitize_text_field',
150 'user_id' => 'intval',
151 'remote_uid' => 'sanitize_text_field',
152 'status' => 'sanitize_text_field',
153 'address_line_1' => 'sanitize_textarea_field',
154 'address_line_2' => 'sanitize_textarea_field',
155 'city' => 'sanitize_text_field',
156 'state' => 'sanitize_text_field',
157 'zip' => 'sanitize_text_field',
158 'country' => 'sanitize_text_field',
159 'note' => 'sanitize_textarea_field',
160 'ip_address' => 'sanitize_text_field',
161 'last_ip_address' => 'sanitize_text_field',
162 ];
163
164 $data = $this->sanitizeRequestData($request, $fields);
165
166 $data = $this->validate($data, [
167 'email' => 'required|email',
168 'first_name' => 'required',
169 'last_name' => 'nullable|string',
170 'title' => 'nullable|string',
171 'user_id' => 'nullable|integer',
172 'remote_uid' => 'nullable|string',
173 'status' => 'nullable|string',
174 'address_line_1' => 'nullable|string',
175 'address_line_2' => 'nullable|string',
176 'city' => 'nullable|string',
177 'state' => 'nullable|string',
178 'zip' => 'nullable|string',
179 'country' => 'nullable|string',
180 'note' => 'nullable|string',
181 'ip_address' => 'nullable|string',
182 'last_ip_address' => 'nullable|string',
183 ]);
184
185 try {
186 return [
187 'message' => __('Customer has been updated', 'fluent-support'),
188 'customer' => $customer->updateCustomer($customer_id, $data)
189 ];
190 } catch (\Exception $e) {
191 return $this->sendError([
192 'message' => Helper::getSafeErrorMessage($e),
193 'errors' => [
194 'email' => [
195 'unique' => __('Email address has been assigned to other customer', 'fluent-support'),
196 ]
197 ]
198 ], 423);
199 }
200 }
201
202 /**
203 * delete method will delete a customer and all tickets by that customer
204 * @param Request $request
205 * @param Customer $customer
206 * @param int $customerId
207 * @return array
208 */
209 public function delete(Request $request, Customer $customer, $customer_id)
210 {
211 return $customer->deleteCustomer($customer_id);
212 }
213
214 /**
215 * bulkDelete method will delete multiple customers and all their tickets
216 * @param Request $request
217 * @param Customer $customer
218 * @return array
219 */
220 public function bulkDelete(Request $request, Customer $customer)
221 {
222 // Get and sanitize customer_ids before validation
223 $customerIds = $request->get('customer_ids', []);
224 $customerIds = is_array($customerIds) ? array_map('intval', $customerIds) : [];
225
226 // Filter out any zero values (from invalid input)
227 $customerIds = array_filter($customerIds, function ($id) {
228 return $id > 0;
229 });
230
231 $customerIds = array_values(array_unique($customerIds));
232
233 // Each id fans out into a full cascade delete (tickets, conversations,
234 // attachments), so an unbounded batch means an unbounded request.
235 $this->validate(['customer_ids' => $customerIds], [
236 'customer_ids' => 'required|array|min:1|max:' . self::BULK_DELETE_LIMIT,
237 'customer_ids.*' => 'required|integer|exists:fs_persons,id'
238 ]);
239
240 return $customer->bulkDeleteCustomers($customerIds);
241 }
242
243 /**
244 * addOrUpdateProfileImage method will update a customer avatar
245 * For a successful upload it's required to send file object, customer id and the user type(customer)
246 * @param Request $request
247 * @return array
248 */
249 public function addOrUpdateProfileImage(Request $request, AvatarUploder $avatarUploder)
250 {
251 try {
252 return $avatarUploder->addOrUpdateProfileImage($request->files(), $request->getSafe('customer_id', 'intval'), 'customer');
253 } catch (\Exception $e) {
254 return $this->sendError([
255 'message' => Helper::getSafeErrorMessage($e),
256 ],
257 $e->getCode()
258 );
259 }
260 }
261
262 /**
263 * resetAvatar method will restore a customer avatar
264 * For a successful upload it's required to send file object, customer id and the user type(customer)
265 *
266 * No Customer type-hint here: route-model binding resolves inside the
267 * permission callback, before any policy runs, which lets unauthenticated
268 * callers probe customer ID existence (FS-PERM-001). Resolve after auth.
269 * @param int|string $customer
270 * @return array
271 */
272 public function resetAvatar($customer)
273 {
274 try {
275 $customer = Customer::findOrFail((int) $customer);
276 $customer->restoreAvatar();
277
278 return [
279 'message' => __('Customer avatar reset to gravatar default', 'fluent-support'),
280 ];
281 } catch (\Exception $e) {
282 return [
283 'message' => Helper::getSafeErrorMessage($e)
284 ];
285 }
286 }
287
288 public function searchContact(Request $request)
289 {
290 $search = trim($request->getSafe('search', 'sanitize_text_field'));
291
292 // '*' is a WP_User_Query wildcard and survives sanitize_text_field, so a
293 // lone '*' would list every user on the site. Stripping it leaves
294 // WP_User_Query doing an exact match.
295 $search = trim(str_replace('*', '', $search));
296
297 if (!$search) {
298 return $this->sendError([
299 'message' => __('Please provide search string', 'fluent-support')
300 ]);
301 }
302
303 $isEmail = is_email($search);
304
305 // Require a meaningful prefix so the endpoint can't be walked one letter
306 // at a time. Emails are matched exactly, so they need no minimum.
307 if (!$isEmail && mb_strlen($search) < 3) {
308 return $this->sendError([
309 'message' => __('Please provide at least 3 characters to search', 'fluent-support')
310 ]);
311 }
312
313 if (Helper::hitRateLimit('fs_contact_search_' . get_current_user_id(), 60, 5 * MINUTE_IN_SECONDS)) {
314 return $this->sendError([
315 'message' => __('Too many contact searches. Please try again in a few minutes.', 'fluent-support')
316 ], 429);
317 }
318
319 // '%' and '_' are LIKE wildcards for the customer and CRM scopes below.
320 // Escape rather than strip: underscores are legitimate in emails.
321 global $wpdb;
322 $likeSearch = $wpdb->esc_like($search);
323
324 // search the existing customers first
325 if ($isEmail) {
326 $customers = Customer::select(['first_name', 'last_name', 'email', 'id', 'user_id'])
327 ->where('email', $search)
328 ->get();
329 } else {
330 $customers = Customer::select(['first_name', 'last_name', 'email', 'id', 'user_id'])
331 ->searchBy($likeSearch)
332 ->limit(10)
333 ->get();
334 }
335
336 if (!$customers->isEmpty()) {
337 return [
338 'type' => 'search_result',
339 'provider' => 'fluent_support',
340 'data' => $customers,
341 'is_email' => $isEmail,
342 'search' => $search
343 ];
344 }
345
346 // If FluentCRM exist then let's search for
347 if (defined('FLUENTCRM')) {
348
349 if ($isEmail) {
350 $contacts = \FluentCrm\App\Models\Subscriber::where('email', $search)
351 ->select(['first_name', 'last_name', 'email', 'id', 'user_id'])
352 ->get();
353 } else {
354
355 $contacts = \FluentCrm\App\Models\Subscriber::searchBy($likeSearch)
356 ->select(['first_name', 'last_name', 'email', 'id', 'user_id'])
357 ->limit(10)
358 ->get();
359 }
360
361 if (!$contacts->isEmpty()) {
362 return [
363 'type' => 'search_result',
364 'provider' => 'fluent_crm',
365 'data' => $contacts,
366 'is_email' => $isEmail
367 ];
368 }
369 }
370
371 // let's search from user's database
372 $user_query = new \WP_User_Query(array('search' => $search, 'number' => 10));
373
374 $users = $user_query->get_results();
375
376 if ($users) {
377 $formattedUsers = [];
378
379 foreach ($users as $user) {
380 $formattedUsers[] = [
381 'id' => $user->ID,
382 'first_name' => $user->first_name,
383 'last_name' => $user->last_name,
384 'user_id' => $user->ID,
385 'email' => $user->user_email
386 ];
387 }
388
389 return [
390 'type' => 'search_result',
391 'provider' => 'wp_users',
392 'data' => $formattedUsers,
393 'is_email' => $isEmail
394 ];
395 }
396
397 return [
398 'type' => 'none',
399 'provider' => 'none',
400 'data' => [],
401 'is_email' => $isEmail
402 ];
403
404 }
405
406 /**
407 * Sanitize request data for given fields. Uses Request::getSafe for known fields
408 * and falls back to sanitize_text_field for any other keys present in the raw request
409 * (useful when hooks inject extra data).
410 *
411 * @param Request $request
412 * @param array $fieldsMap associative array field => sanitizer callable name
413 * @return array
414 */
415 private function sanitizeRequestData(Request $request, array $fieldsMap)
416 {
417 $sanitized = [];
418
419 // Use getSafe for known fields
420 foreach ($fieldsMap as $field => $sanitizer) {
421 $sanitized[$field] = $request->getSafe($field, $sanitizer);
422 }
423
424 // Now sanitize any other incoming keys to avoid unsanitized data
425 $raw = $request->get();
426 foreach ($raw as $key => $value) {
427 if (array_key_exists($key, $sanitized)) {
428 continue;
429 }
430
431 if (is_array($value)) {
432 $sanitized[$key] = array_map('sanitize_text_field', $value);
433 } else {
434 // Fallback sanitizer for unknown fields
435 $sanitized[$key] = is_string($value) ? sanitize_text_field($value) : $value;
436 }
437 }
438
439 return $sanitized;
440 }
441 }
442