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/CustomerController.php +40 -45 2.3.1 → 2.4.5 View file →
@@ -9,18 +9,18 @@
9 9 use FluentSupport\App\Services\Helper;
10 10 use FluentSupport\Framework\Support\Arr;
11 11
12 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
13 + * Customer endpoints.
18 14 */
19 15 class CustomerController extends Controller
20 16 {
21 17 /**
22 - * index method will return the list of customers
18 + * Each id cascades into tickets, conversations and attachments, so the batch is capped.
19 + */
20 + const BULK_DELETE_LIMIT = 100;
21 +
22 + /**
23 23 * @param Request $request
24 24 * @param Customer $customer
25 25 * @return array
26 26 */
@@ -40,13 +40,13 @@
40 40 }
41 41
42 42
43 43 /**
44 - * getCustomer method will return individual customer information by customer id
45 - * This function will also get information about extra widgets, tickets and Fluent CRM
44 + * `with` can add widgets, tickets and FluentCRM data.
45 + *
46 46 * @param Request $request
47 47 * @param Customer $customer
48 - * @param $customer_id
48 + * @param int $customer_id
49 49 * @return array
50 50 */
51 51 public function getCustomer(Request $request, Customer $customer, $customer_id)
52 52 {
@@ -56,9 +56,8 @@
56 56 return $customer->getCustomer($customer_id, $with);
57 57 }
58 58
59 59 /**
60 - * Create method will create new customer
61 60 * @param Request $request
62 61 * @param Customer $customer
63 62 * @return array
64 63 * @throws \FluentSupport\Framework\Validator\ValidationException
@@ -64,9 +63,16 @@
64 63 * @throws \FluentSupport\Framework\Validator\ValidationException
65 64 */
66 65 public function create(Request $request, Customer $customer)
67 66 {
68 - // Define expected fields with their sanitizers
67 + // The WordPress user is linked through the email only, so a client user_id could
68 + // otherwise make the new record another account's portal identity.
69 + if ($request->get('user_id')) {
70 + return $this->sendError([
71 + 'message' => __('user_id is not accepted when creating a customer. The WordPress user is linked by the customer email.', 'fluent-support')
72 + ], 422);
73 + }
74 +
69 75 $fields = [
70 76 'id' => 'intval',
71 77 'customer_id' => 'intval',
72 78 'avatar' => 'esc_url_raw',
@@ -77,9 +83,8 @@
77 83 'email' => 'sanitize_email',
78 84 'first_name' => 'sanitize_text_field',
79 85 'last_name' => 'sanitize_text_field',
80 86 'title' => 'sanitize_text_field',
81 - 'user_id' => 'intval',
82 87 'remote_uid' => 'sanitize_text_field',
83 88 'status' => 'sanitize_text_field',
84 89 'address_line_1' => 'sanitize_textarea_field',
85 90 'address_line_2' => 'sanitize_textarea_field',
@@ -98,9 +103,8 @@
98 103 'email' => 'required|email|unique:fs_persons',
99 104 'first_name' => 'required',
100 105 'last_name' => 'nullable|string',
101 106 'title' => 'nullable|string',
102 - 'user_id' => 'nullable|integer',
103 107 'remote_uid' => 'nullable|string',
104 108 'status' => 'nullable|string',
105 109 'address_line_1' => 'nullable|string',
106 110 'address_line_2' => 'nullable|string',
@@ -119,18 +123,16 @@
119 123 ];
120 124 }
121 125
122 126 /**
123 - * update method will update existing customer by customer id
124 127 * @param Request $request
125 128 * @param Customer $customer
126 - * @param $customerId
129 + * @param int $customer_id
127 130 * @return array
128 131 * @throws \FluentSupport\Framework\Validator\ValidationException
129 132 */
130 133 public function update(Request $request, Customer $customer, $customer_id)
131 134 {
132 - // Sanitize only allowed fields and also sanitize any extra fields from hooks
133 135 $fields = [
134 136 'id' => 'intval',
135 137 'customer_id' => 'intval',
136 138 'avatar' => 'esc_url_raw',
@@ -194,12 +196,13 @@
194 196 }
195 197 }
196 198
197 199 /**
198 - * delete method will delete a customer and all tickets by that customer
200 + * Also deletes the customer's tickets.
201 + *
199 202 * @param Request $request
200 203 * @param Customer $customer
201 - * @param int $customerId
204 + * @param int $customer_id
202 205 * @return array
203 206 */
204 207 public function delete(Request $request, Customer $customer, $customer_id)
205 208 {
@@ -206,9 +209,10 @@
206 209 return $customer->deleteCustomer($customer_id);
207 210 }
208 211
209 212 /**
210 - * bulkDelete method will delete multiple customers and all their tickets
213 + * Also deletes the customers' tickets.
214 + *
211 215 * @param Request $request
212 216 * @param Customer $customer
213 217 * @return array
214 218 */
@@ -213,19 +217,19 @@
213 217 * @return array
214 218 */
215 219 public function bulkDelete(Request $request, Customer $customer)
216 220 {
217 - // Get and sanitize customer_ids before validation
218 221 $customerIds = $request->get('customer_ids', []);
219 222 $customerIds = is_array($customerIds) ? array_map('intval', $customerIds) : [];
220 223
221 - // Filter out any zero values (from invalid input)
222 224 $customerIds = array_filter($customerIds, function ($id) {
223 225 return $id > 0;
224 226 });
225 227
228 + $customerIds = array_values(array_unique($customerIds));
229 +
226 230 $this->validate(['customer_ids' => $customerIds], [
227 - 'customer_ids' => 'required|array|min:1',
231 + 'customer_ids' => 'required|array|min:1|max:' . self::BULK_DELETE_LIMIT,
228 232 'customer_ids.*' => 'required|integer|exists:fs_persons,id'
229 233 ]);
230 234
231 235 return $customer->bulkDeleteCustomers($customerIds);
@@ -231,11 +235,10 @@
231 235 return $customer->bulkDeleteCustomers($customerIds);
232 236 }
233 237
234 238 /**
235 - * addOrUpdateProfileImage method will update a customer avatar
236 - * For a successful upload it's required to send file object, customer id and the user type(customer)
237 239 * @param Request $request
240 + * @param AvatarUploder $avatarUploder
238 241 * @return array
239 242 */
240 243 public function addOrUpdateProfileImage(Request $request, AvatarUploder $avatarUploder)
241 244 {
@@ -250,17 +253,18 @@
250 253 }
251 254 }
252 255
253 256 /**
254 - * resetAvatar method will restore a customer avatar
255 - * For a successful upload it's required to send file object, customer id and the user type(customer)
256 - * @param Request $request
257 - * @param $id
257 + * No Customer type-hint: route-model binding runs before the policy, which would
258 + * let unauthenticated callers probe customer IDs. Resolve after auth.
259 + *
260 + * @param int|string $customer
258 261 * @return array
259 262 */
260 - public function resetAvatar(Customer $customer)
263 + public function resetAvatar($customer)
261 264 {
262 265 try {
266 + $customer = Customer::findOrFail((int) $customer);
263 267 $customer->restoreAvatar();
264 268
265 269 return [
266 270 'message' => __('Customer avatar reset to gravatar default', 'fluent-support'),
@@ -275,11 +279,10 @@
275 279 public function searchContact(Request $request)
276 280 {
277 281 $search = trim($request->getSafe('search', 'sanitize_text_field'));
278 282
279 - // '*' is a WP_User_Query wildcard and survives sanitize_text_field, so a
280 - // lone '*' would list every user on the site (FS-SEC-014). Stripping it
281 - // leaves WP_User_Query doing an exact match.
283 + // '*' is a WP_User_Query wildcard that survives sanitize_text_field.
284 + // A lone '*' would list every user.
282 285 $search = trim(str_replace('*', '', $search));
283 286
284 287 if (!$search) {
285 288 return $this->sendError([
@@ -288,10 +291,9 @@
288 291 }
289 292
290 293 $isEmail = is_email($search);
291 294
292 - // Require a meaningful prefix so the endpoint can't be walked one letter
293 - // at a time. Emails are matched exactly, so they need no minimum.
295 + // Stops walking the list one letter at a time. Emails match exactly, so no minimum.
294 296 if (!$isEmail && mb_strlen($search) < 3) {
295 297 return $this->sendError([
296 298 'message' => __('Please provide at least 3 characters to search', 'fluent-support')
297 299 ]);
@@ -302,14 +304,13 @@
302 304 'message' => __('Too many contact searches. Please try again in a few minutes.', 'fluent-support')
303 305 ], 429);
304 306 }
305 307
306 - // '%' and '_' are LIKE wildcards for the customer and CRM scopes below.
307 - // Escape rather than strip: underscores are legitimate in emails.
308 + // Escape LIKE wildcards rather than strip them: '_' is valid in emails.
308 309 global $wpdb;
309 310 $likeSearch = $wpdb->esc_like($search);
310 311
311 - // search the existing customers first
312 + // Customers first, then FluentCRM contacts, then WP users.
312 313 if ($isEmail) {
313 314 $customers = Customer::select(['first_name', 'last_name', 'email', 'id', 'user_id'])
314 315 ->where('email', $search)
315 316 ->get();
@@ -329,9 +330,8 @@
329 330 'search' => $search
330 331 ];
331 332 }
332 333
333 - // If FluentCRM exist then let's search for
334 334 if (defined('FLUENTCRM')) {
335 335
336 336 if ($isEmail) {
337 337 $contacts = \FluentCrm\App\Models\Subscriber::where('email', $search)
@@ -354,9 +354,8 @@
354 354 ];
355 355 }
356 356 }
357 357
358 - // let's search from user's database
359 358 $user_query = new \WP_User_Query(array('search' => $search, 'number' => 10));
360 359
361 360 $users = $user_query->get_results();
362 361
@@ -390,11 +389,10 @@
390 389
391 390 }
392 391
393 392 /**
394 - * Sanitize request data for given fields. Uses Request::getSafe for known fields
395 - * and falls back to sanitize_text_field for any other keys present in the raw request
396 - * (useful when hooks inject extra data).
393 + * Known fields use their mapped sanitizer. Any other keys (e.g. added by hooks)
394 + * get sanitize_text_field.
397 395 *
398 396 * @param Request $request
399 397 * @param array $fieldsMap associative array field => sanitizer callable name
400 398 * @return array
@@ -402,14 +400,12 @@
402 400 private function sanitizeRequestData(Request $request, array $fieldsMap)
403 401 {
404 402 $sanitized = [];
405 403
406 - // Use getSafe for known fields
407 404 foreach ($fieldsMap as $field => $sanitizer) {
408 405 $sanitized[$field] = $request->getSafe($field, $sanitizer);
409 406 }
410 407
411 - // Now sanitize any other incoming keys to avoid unsanitized data
412 408 $raw = $request->get();
413 409 foreach ($raw as $key => $value) {
414 410 if (array_key_exists($key, $sanitized)) {
415 411 continue;
@@ -417,9 +413,8 @@
417 413
418 414 if (is_array($value)) {
419 415 $sanitized[$key] = array_map('sanitize_text_field', $value);
420 416 } else {
421 - // Fallback sanitizer for unknown fields
422 417 $sanitized[$key] = is_string($value) ? sanitize_text_field($value) : $value;
423 418 }
424 419 }
425 420