PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.0
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
← All changes | app/Http/Controllers/CustomerController.php +45 -7 2.2.12.4.0 View file →
@@ -18,8 +18,13 @@
18 18 */
19 19 class CustomerController extends Controller
20 20 {
21 21 /**
22 + * Maximum number of customers accepted by a single bulk-delete request.
23 + */
24 + const BULK_DELETE_LIMIT = 100;
25 +
26 + /**
22 27 * index method will return the list of customers
23 28 * @param Request $request
24 29 * @param Customer $customer
25 30 * @return array
@@ -222,10 +227,14 @@
222 227 $customerIds = array_filter($customerIds, function ($id) {
223 228 return $id > 0;
224 229 });
225 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.
226 235 $this->validate(['customer_ids' => $customerIds], [
227 - 'customer_ids' => 'required|array|min:1',
236 + 'customer_ids' => 'required|array|min:1|max:' . self::BULK_DELETE_LIMIT,
228 237 'customer_ids.*' => 'required|integer|exists:fs_persons,id'
229 238 ]);
230 239
231 240 return $customer->bulkDeleteCustomers($customerIds);
@@ -252,15 +261,19 @@
252 261
253 262 /**
254 263 * resetAvatar method will restore a customer avatar
255 264 * 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
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
258 270 * @return array
259 271 */
260 - public function resetAvatar(Customer $customer)
272 + public function resetAvatar($customer)
261 273 {
262 274 try {
275 + $customer = Customer::findOrFail((int) $customer);
263 276 $customer->restoreAvatar();
264 277
265 278 return [
266 279 'message' => __('Customer avatar reset to gravatar default', 'fluent-support'),
@@ -273,9 +286,15 @@
273 286 }
274 287
275 288 public function searchContact(Request $request)
276 289 {
277 - $search = $request->getSafe('search', 'sanitize_text_field');
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 +
278 297 if (!$search) {
279 298 return $this->sendError([
280 299 'message' => __('Please provide search string', 'fluent-support')
281 300 ]);
@@ -282,8 +301,27 @@
282 301 }
283 302
284 303 $isEmail = is_email($search);
285 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 +
286 324 // search the existing customers first
287 325 if ($isEmail) {
288 326 $customers = Customer::select(['first_name', 'last_name', 'email', 'id', 'user_id'])
289 327 ->where('email', $search)
@@ -289,9 +327,9 @@
289 327 ->where('email', $search)
290 328 ->get();
291 329 } else {
292 330 $customers = Customer::select(['first_name', 'last_name', 'email', 'id', 'user_id'])
293 - ->searchBy($search)
331 + ->searchBy($likeSearch)
294 332 ->limit(10)
295 333 ->get();
296 334 }
297 335
@@ -313,9 +351,9 @@
313 351 ->select(['first_name', 'last_name', 'email', 'id', 'user_id'])
314 352 ->get();
315 353 } else {
316 354
317 - $contacts = \FluentCrm\App\Models\Subscriber::searchBy($search)
355 + $contacts = \FluentCrm\App\Models\Subscriber::searchBy($likeSearch)
318 356 ->select(['first_name', 'last_name', 'email', 'id', 'user_id'])
319 357 ->limit(10)
320 358 ->get();
321 359 }