PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.1.2
Fluent Support – Helpdesk & Customer Support Ticket System v2.1.2
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 +296 -145 1.5.52.1.2 View file →
@@ -1,16 +1,13 @@
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\Conversation;
5 +use FluentCrm\App\Models\Subscriber;
7 6 use FluentSupport\App\Models\Customer;
8 -use FluentSupport\App\Models\Ticket;
7 +use FluentSupport\Framework\Http\Request\Request;
8 +use FluentSupport\App\Services\AvatarUploder;
9 9 use FluentSupport\App\Services\Helper;
10 -use FluentSupport\App\Services\Includes\FileSystem;
11 -use FluentSupport\App\Services\ProfileInfoService;
12 -use FluentSupport\Framework\Request\Request;
13 10 use FluentSupport\Framework\Support\Arr;
14 11
15 12 /**
16 13 * CustomerController class for REST API
@@ -23,41 +20,23 @@
23 20 {
24 21 /**
25 22 * index method will return the list of customers
26 23 * @param Request $request
24 + * @param Customer $customer
27 25 * @return array
28 26 */
29 - public function index(Request $request)
27 + public function index(Request $request, Customer $customer)
30 28 {
31 - //Add order by selected by suer
32 - $customersQuery = Customer::orderBy('id', 'DESC')
33 - ->orderBy($request->get('order_by', 'id'), $request->get('order_type', 'ASC'));
29 + return [
30 + 'customers' => $customer->getCustomers($request->getSafe('search', 'sanitize_text_field'), $request->getSafe('status', 'sanitize_text_field')),
31 + ];
32 + }
34 33
35 - //Filter query based on the search item
36 - if ($request->get('search')) {
37 - $customersQuery->searchBy($request->get('search'));
38 - }
34 + public function customerField (Request $request,Customer $customer, $customer_id) {
39 35
40 - $status = $request->get('status');
41 - //Filter customer by selected status
42 - if ($status && $status != 'all') {
43 - $customersQuery->filterByStatues([$status]);
44 - }
45 -
46 - $customers = $customersQuery->paginate();
47 -
48 - //Get total ticket and responses in ticket by individual customer
49 - foreach ($customers as $customer) {
50 - $customer->total_tickets = $customer->getTicketCounts();
51 - $customer->total_responses = $customer->getResponseCounts();
52 - if ($customer->user_id) {
53 - //Get profile link, if they are WP user
54 - $customer->user_profile = admin_url('user-edit.php?user_id=' . $customer->user_id);
55 - }
56 - }
57 -
58 - return [
59 - 'customers' => $customers,
36 + $userID = $request->getSafe('user_id', 'intval');
37 + return[
38 + 'customerField' => $customer->getCustomerField($customer_id,$userID)
60 39 ];
61 40 }
62 41
63 42
@@ -64,75 +43,80 @@
64 43 /**
65 44 * getCustomer method will return individual customer information by customer id
66 45 * This function will also get information about extra widgets, tickets and Fluent CRM
67 46 * @param Request $request
68 - * @param $customerId
47 + * @param Customer $customer
48 + * @param $customer_id
69 49 * @return array
70 50 */
71 - public function getCustomer(Request $request, $customerId)
51 + public function getCustomer(Request $request, Customer $customer, $customer_id)
72 52 {
73 - $customer = Customer::findOrFail($customerId);
53 + $with = $request->get('with', null);
54 + $with = is_array($with) ? array_map('sanitize_key', $with) : [];
74 55
75 - $data = [
76 - 'customer' => $customer
77 - ];
78 -
79 - $with = $request->get('with', []);
80 -
81 - if (in_array('widgets', $with)) {
82 - $data['widgets'] = ProfileInfoService::getProfileExtraWidgets($customer);
83 - }
84 -
85 - if (in_array('tickets', $with)) {
86 - $data['tickets'] = Ticket::select(['id', 'title', 'status', 'customer_id', 'created_at'])
87 - ->where('customer_id', $customer->id)
88 - ->orderBy('id', 'DESC')
89 - ->limit(20)
90 - ->get();
91 - }
92 -
93 - if(in_array('fluentcrm_profile', $with)) {
94 - $data['fluentcrm_profile'] = Helper::getFluentCrmContactData($customer);
95 - }
96 -
97 - return $data;
98 -
56 + return $customer->getCustomer($customer_id, $with);
99 57 }
100 58
101 59 /**
102 60 * Create method will create new customer
103 61 * @param Request $request
62 + * @param Customer $customer
104 63 * @return array
105 64 * @throws \FluentSupport\Framework\Validator\ValidationException
106 65 */
107 - public function create(Request $request)
66 + public function create(Request $request, Customer $customer)
108 67 {
109 - $data = $request->all();
110 - $this->validate($data, [
111 - 'email' => 'required|email|unique:fs_persons'
112 - ]);
68 + // Define expected fields with their sanitizers
69 + $fields = [
70 + 'id' => 'intval',
71 + 'customer_id' => 'intval',
72 + 'avatar' => 'esc_url_raw',
73 + 'person_type' => 'sanitize_text_field',
74 + 'hash' => 'sanitize_text_field',
75 + 'description' => 'sanitize_text_field',
76 + 'photo' => 'esc_url_raw',
77 + 'email' => 'sanitize_email',
78 + 'first_name' => 'sanitize_text_field',
79 + 'last_name' => 'sanitize_text_field',
80 + 'title' => 'sanitize_text_field',
81 + 'user_id' => 'intval',
82 + 'remote_uid' => 'sanitize_text_field',
83 + 'status' => 'sanitize_text_field',
84 + 'address_line_1' => 'sanitize_textarea_field',
85 + 'address_line_2' => 'sanitize_textarea_field',
86 + 'city' => 'sanitize_text_field',
87 + 'state' => 'sanitize_text_field',
88 + 'zip' => 'sanitize_text_field',
89 + 'country' => 'sanitize_text_field',
90 + 'note' => 'sanitize_textarea_field',
91 + 'ip_address' => 'sanitize_text_field',
92 + 'last_ip_address' => 'sanitize_text_field',
93 + ];
113 94
114 - $email = $data['email'];
95 + $data = $this->sanitizeRequestData($request, $fields);
115 96
116 - $data = Arr::only($data, (new Customer)->getFillable());
97 + $data = $this->validate($data, [
98 + 'email' => 'required|email|unique:fs_persons',
99 + 'first_name' => 'required',
100 + 'last_name' => 'nullable|string',
101 + 'title' => 'nullable|string',
102 + 'user_id' => 'nullable|integer',
103 + 'remote_uid' => 'nullable|string',
104 + 'status' => 'nullable|string',
105 + 'address_line_1' => 'nullable|string',
106 + 'address_line_2' => 'nullable|string',
107 + 'city' => 'nullable|string',
108 + 'state' => 'nullable|string',
109 + 'zip' => 'nullable|string',
110 + 'country' => 'nullable|string',
111 + 'note' => 'nullable|string',
112 + 'ip_address' => 'nullable|string',
113 + 'last_ip_address' => 'nullable|string',
114 + ]);
117 115
118 - $user = get_user_by('email', $email);
119 -
120 - if ($user) {
121 - $data['user_id'] = $user->ID;
122 - if (empty($data['first_name'])) {
123 - $data['first_name'] = $user->first_name;
124 - }
125 - if (empty($data['last_name'])) {
126 - $data['last_name'] = $user->last_name;
127 - }
128 - }
129 -
130 - $customer = Customer::create($data);
131 -
132 116 return [
133 117 'message' => __('Customer has been added', 'fluent-support'),
134 - 'customer' => $customer
118 + 'customer' => $customer->createCustomer($data)
135 119 ];
136 120 }
137 121
138 122 /**
@@ -137,116 +121,283 @@
137 121
138 122 /**
139 123 * update method will update existing customer by customer id
140 124 * @param Request $request
125 + * @param Customer $customer
141 126 * @param $customerId
142 127 * @return array
143 128 * @throws \FluentSupport\Framework\Validator\ValidationException
144 129 */
145 - public function update(Request $request, $customerId)
130 + public function update(Request $request, Customer $customer, $customer_id)
146 131 {
147 - $customer = Customer::findOrFail($customerId);
148 - $data = $request->all();
149 - $this->validate($data, [
132 + // Sanitize only allowed fields and also sanitize any extra fields from hooks
133 + $fields = [
134 + 'id' => 'intval',
135 + 'customer_id' => 'intval',
136 + 'avatar' => 'esc_url_raw',
137 + 'person_type' => 'sanitize_text_field',
138 + 'hash' => 'sanitize_text_field',
139 + 'description' => 'sanitize_text_field',
140 + 'photo' => 'esc_url_raw',
141 + 'email' => 'sanitize_email',
142 + 'first_name' => 'sanitize_text_field',
143 + 'last_name' => 'sanitize_text_field',
144 + 'title' => 'sanitize_text_field',
145 + 'user_id' => 'intval',
146 + 'remote_uid' => 'sanitize_text_field',
147 + 'status' => 'sanitize_text_field',
148 + 'address_line_1' => 'sanitize_textarea_field',
149 + 'address_line_2' => 'sanitize_textarea_field',
150 + 'city' => 'sanitize_text_field',
151 + 'state' => 'sanitize_text_field',
152 + 'zip' => 'sanitize_text_field',
153 + 'country' => 'sanitize_text_field',
154 + 'note' => 'sanitize_textarea_field',
155 + 'ip_address' => 'sanitize_text_field',
156 + 'last_ip_address' => 'sanitize_text_field',
157 + ];
158 +
159 + $data = $this->sanitizeRequestData($request, $fields);
160 +
161 + $data = $this->validate($data, [
150 162 'email' => 'required|email',
151 - 'first_name' => 'required'
163 + 'first_name' => 'required',
164 + 'last_name' => 'nullable|string',
165 + 'title' => 'nullable|string',
166 + 'user_id' => 'nullable|integer',
167 + 'remote_uid' => 'nullable|string',
168 + 'status' => 'nullable|string',
169 + 'address_line_1' => 'nullable|string',
170 + 'address_line_2' => 'nullable|string',
171 + 'city' => 'nullable|string',
172 + 'state' => 'nullable|string',
173 + 'zip' => 'nullable|string',
174 + 'country' => 'nullable|string',
175 + 'note' => 'nullable|string',
176 + 'ip_address' => 'nullable|string',
177 + 'last_ip_address' => 'nullable|string',
152 178 ]);
153 179
154 - if ($otherCustomer = Customer::where('id', '!=', $customerId)->where('email', $data['email'])->first()) {
180 + try {
181 + return [
182 + 'message' => __('Customer has been updated', 'fluent-support'),
183 + 'customer' => $customer->updateCustomer($customer_id, $data)
184 + ];
185 + } catch (\Exception $e) {
155 186 return $this->sendError([
156 - 'message' => __('Another Customer has same email address', 'fluent-support'),
187 + 'message' => Helper::getSafeErrorMessage($e),
157 188 'errors' => [
158 189 'email' => [
159 - 'unique' => __('Email address has been assigned to other customer', 'fluent-support')
190 + 'unique' => __('Email address has been assigned to other customer', 'fluent-support'),
160 191 ]
161 192 ]
162 193 ], 423);
163 194 }
195 + }
164 196
165 - $validKeys = (new Customer)->getFillable();
166 - unset($validKeys['hash']);
167 - unset($validKeys['user_id']);
197 + /**
198 + * delete method will delete a customer and all tickets by that customer
199 + * @param Request $request
200 + * @param Customer $customer
201 + * @param int $customerId
202 + * @return array
203 + */
204 + public function delete(Request $request, Customer $customer, $customer_id)
205 + {
206 + return $customer->deleteCustomer($customer_id);
207 + }
168 208
169 - $updateData = Arr::only($data, $validKeys);
209 + /**
210 + * bulkDelete method will delete multiple customers and all their tickets
211 + * @param Request $request
212 + * @param Customer $customer
213 + * @return array
214 + */
215 + public function bulkDelete(Request $request, Customer $customer)
216 + {
217 + // Get and sanitize customer_ids before validation
218 + $customerIds = $request->get('customer_ids', []);
219 + $customerIds = is_array($customerIds) ? array_map('intval', $customerIds) : [];
170 220
171 - $user = get_user_by('email', $data['email']);
221 + // Filter out any zero values (from invalid input)
222 + $customerIds = array_filter($customerIds, function ($id) {
223 + return $id > 0;
224 + });
172 225
173 - if ($user) {
174 - $updateData['user_id'] = $user->ID;
175 - }
226 + $this->validate(['customer_ids' => $customerIds], [
227 + 'customer_ids' => 'required|array|min:1',
228 + 'customer_ids.*' => 'required|integer|exists:fs_persons,id'
229 + ]);
176 230
177 - Customer::where('id', $customer->id)
178 - ->update($updateData);
231 + return $customer->bulkDeleteCustomers($customerIds);
232 + }
179 233
180 - return [
181 - 'message' => __('Customer has been updated', 'fluent-support'),
182 - 'customer' => Customer::findOrFail($customerId)
183 - ];
234 + /**
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 + * @param Request $request
238 + * @return array
239 + */
240 + public function addOrUpdateProfileImage(Request $request, AvatarUploder $avatarUploder)
241 + {
242 + try {
243 + return $avatarUploder->addOrUpdateProfileImage($request->files(), $request->getSafe('customer_id', 'intval'), 'customer');
244 + } catch (\Exception $e) {
245 + return $this->sendError([
246 + 'message' => Helper::getSafeErrorMessage($e),
247 + ],
248 + $e->getCode()
249 + );
250 + }
184 251 }
185 252
186 253 /**
187 - * delete method will delete a customer and all ticket by that customer
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)
188 256 * @param Request $request
189 - * @param $customerId
257 + * @param $id
190 258 * @return array
191 259 */
192 - public function delete(Request $request, $customerId)
260 + public function resetAvatar(Customer $customer)
193 261 {
194 - $customer = Customer::findOrFail($customerId);
262 + try {
263 + $customer->restoreAvatar();
195 264
196 - $tickets = Ticket::where('customer_id', $customer->id)->get();
265 + return [
266 + 'message' => __('Customer avatar reset to gravatar default', 'fluent-support'),
267 + ];
268 + } catch (\Exception $e) {
269 + return [
270 + 'message' => Helper::getSafeErrorMessage($e)
271 + ];
272 + }
273 + }
197 274
198 - foreach ($tickets as $ticket) {
199 - $ticket->deleteTicket();
275 + public function searchContact(Request $request)
276 + {
277 + $search = $request->getSafe('search', 'sanitize_text_field');
278 + if (!$search) {
279 + return $this->sendError([
280 + 'message' => __('Please provide search string', 'fluent-support')
281 + ]);
200 282 }
201 283
202 - $customer->delete();
284 + $isEmail = is_email($search);
203 285
286 + // search the existing customers first
287 + if ($isEmail) {
288 + $customers = Customer::select(['first_name', 'last_name', 'email', 'id', 'user_id'])
289 + ->where('email', $search)
290 + ->get();
291 + } else {
292 + $customers = Customer::select(['first_name', 'last_name', 'email', 'id', 'user_id'])
293 + ->searchBy($search)
294 + ->limit(10)
295 + ->get();
296 + }
297 +
298 + if (!$customers->isEmpty()) {
299 + return [
300 + 'type' => 'search_result',
301 + 'provider' => 'fluent_support',
302 + 'data' => $customers,
303 + 'is_email' => $isEmail,
304 + 'search' => $search
305 + ];
306 + }
307 +
308 + // If FluentCRM exist then let's search for
309 + if (defined('FLUENTCRM')) {
310 +
311 + if ($isEmail) {
312 + $contacts = \FluentCrm\App\Models\Subscriber::where('email', $search)
313 + ->select(['first_name', 'last_name', 'email', 'id', 'user_id'])
314 + ->get();
315 + } else {
316 +
317 + $contacts = \FluentCrm\App\Models\Subscriber::searchBy($search)
318 + ->select(['first_name', 'last_name', 'email', 'id', 'user_id'])
319 + ->limit(10)
320 + ->get();
321 + }
322 +
323 + if (!$contacts->isEmpty()) {
324 + return [
325 + 'type' => 'search_result',
326 + 'provider' => 'fluent_crm',
327 + 'data' => $contacts,
328 + 'is_email' => $isEmail
329 + ];
330 + }
331 + }
332 +
333 + // let's search from user's database
334 + $user_query = new \WP_User_Query(array('search' => $search, 'number' => 10));
335 +
336 + $users = $user_query->get_results();
337 +
338 + if ($users) {
339 + $formattedUsers = [];
340 +
341 + foreach ($users as $user) {
342 + $formattedUsers[] = [
343 + 'id' => $user->ID,
344 + 'first_name' => $user->first_name,
345 + 'last_name' => $user->last_name,
346 + 'user_id' => $user->ID,
347 + 'email' => $user->user_email
348 + ];
349 + }
350 +
351 + return [
352 + 'type' => 'search_result',
353 + 'provider' => 'wp_users',
354 + 'data' => $formattedUsers,
355 + 'is_email' => $isEmail
356 + ];
357 + }
358 +
204 359 return [
205 - 'message' => __('Customer Deleted Successfully', 'fluent-support')
360 + 'type' => 'none',
361 + 'provider' => 'none',
362 + 'data' => [],
363 + 'is_email' => $isEmail
206 364 ];
365 +
207 366 }
208 367
209 368 /**
210 - * addOrUpdateProfileImage method will update a customer avatar
369 + * Sanitize request data for given fields. Uses Request::getSafe for known fields
370 + * and falls back to sanitize_text_field for any other keys present in the raw request
371 + * (useful when hooks inject extra data).
372 + *
211 373 * @param Request $request
374 + * @param array $fieldsMap associative array field => sanitizer callable name
212 375 * @return array
213 376 */
214 - public function addOrUpdateProfileImage(Request $request)
377 + private function sanitizeRequestData(Request $request, array $fieldsMap)
215 378 {
216 - $allowExtension = [
217 - 'jpeg', 'jpe', 'jpg', 'png'
218 - ];
379 + $sanitized = [];
219 380
220 - $customer_id = $request->get('customer_id');
221 - $file = $request->files();
222 -
223 - $ext = $file['file']->getClientOriginalExtension();
224 -
225 - if(!in_array($ext, $allowExtension)){
226 - return $this->sendError([
227 - 'message' => __('Unsupported file submitted, please select an image file', 'fluent-support')
228 - ]);
381 + // Use getSafe for known fields
382 + foreach ($fieldsMap as $field => $sanitizer) {
383 + $sanitized[$field] = $request->getSafe($field, $sanitizer);
229 384 }
230 385
231 - $customer = Customer::findOrFail($customer_id);
386 + // Now sanitize any other incoming keys to avoid unsanitized data
387 + $raw = $request->get();
388 + foreach ($raw as $key => $value) {
389 + if (array_key_exists($key, $sanitized)) {
390 + continue;
391 + }
232 392
233 - $uploadedImage = FileSystem::setSubDir('customer_avatars')->put($file);
234 -
235 - if($avatar = $uploadedImage[0]['url']){
236 - $customer->avatar = $avatar;
237 - $customer->save();
238 -
239 - return[
240 - 'message' => __('Profile picture has been updated successfully', 'fluent-support'),
241 - 'image' => $customer->avatar,
242 - 'customer' => $customer
243 - ];
393 + if (is_array($value)) {
394 + $sanitized[$key] = array_map('sanitize_text_field', $value);
395 + } else {
396 + // Fallback sanitizer for unknown fields
397 + $sanitized[$key] = is_string($value) ? sanitize_text_field($value) : $value;
398 + }
244 399 }
245 400
246 - else{
247 - return $this->sendError([
248 - 'message' => __('Something went wrong while updating the profile picture', 'fluent-support')
249 - ]);
250 - }
401 + return $sanitized;
251 402 }
252 403 }