PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.20
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.20
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
fluent-cart / app / Http / Controllers / FrontendControllers / CustomerController.php

CustomerController.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.20, at app/Http/Controllers/FrontendControllers/CustomerController.php

479 lines 17.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Http\Controllers\FrontendControllers;
4
5 use FluentCart\Api\Resource\FrontendResource\CustomerAddressResource;
6 use FluentCart\Api\Resource\FrontendResource\CustomerResource;
7 use FluentCart\App\Helpers\AddressHelper;
8 use FluentCart\App\Helpers\CustomerHelper;
9 use FluentCart\App\Hooks\Cart\WebCheckoutHandler;
10 use FluentCart\App\Http\Controllers\Controller;
11 use FluentCart\App\Http\Requests\CustomerRequest;
12 use FluentCart\App\Http\Requests\FrontendRequests\CustomerAddressRequest;
13 use FluentCart\App\Models\CustomerAddresses;
14 use FluentCart\App\Services\Localization\LocalizationManager;
15 use FluentCart\App\Services\Renderer\AddressSelectRenderer;
16 use FluentCart\App\Services\Renderer\CheckoutFieldsSchema;
17 use FluentCart\Framework\Http\Request\Request;
18 use FluentCart\Framework\Support\Arr;
19 use FluentCart\App\Helpers\CartHelper;
20 use FluentCart\Framework\Support\Str;
21
22 class CustomerController extends Controller
23 {
24 public function index(Request $request)
25 {
26 return ['customers' => CustomerResource::get($request->all())];
27 }
28
29 public function store(CustomerRequest $request)
30 {
31 $data = $request->getSafe($request->sanitize());
32 $isCreated = CustomerResource::create($data);
33
34 if (is_wp_error($isCreated)) {
35 return $isCreated;
36 }
37 return $this->response->sendSuccess($isCreated);
38 }
39
40 public function updateDetails(CustomerRequest $request, $customerId)
41 {
42 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
43 if (empty($customer) || $customer->id != $customerId) {
44 return $this->sendError([
45 'message' => __('You are not authorized to update this customer', 'fluent-cart')
46 ]);
47 }
48 $data = $request->getSafe($request->sanitize());
49 $isUpdated = CustomerResource::update($data, $customerId);
50 if (is_wp_error($isUpdated)) {
51 return $isUpdated;
52 }
53 return $this->response->sendSuccess($isUpdated);
54 }
55
56 public function getDetails(Request $request, $customerId)
57 {
58 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
59 if (empty($customer) || $customer->id != $customerId) {
60 return $this->sendError([
61 'message' => __('You are not authorized to view this customer', 'fluent-cart')
62 ]);
63 }
64 return CustomerResource::find($customerId, ['with' => $request->get('with', [])]);
65 }
66
67 public function getAddress(Request $request, $customerId)
68 {
69 return CustomerAddressResource::get([
70 'customer_id' => $customerId,
71 'type' => $request->type
72 ]);
73 }
74
75 public function updateAddressSelect(Request $request, $customerAddressId)
76 {
77 $address = CustomerAddressResource::find($customerAddressId, ['with' => $request->get('with', [])]);
78
79 if (!$address) {
80 return $this->sendError([
81 'message' => __('Address not found', 'fluent-cart')
82 ]);
83 }
84
85 // Verify address belongs to current customer before any cart mutation
86 $customerId = Arr::get($address, 'address.customer_id');
87 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
88 if (empty($customer) || $customer->id != $customerId) {
89 return $this->sendError([
90 'message' => __('You are not authorized to view this address', 'fluent-cart')
91 ]);
92 }
93
94 //update address into cart
95 $addressId = Arr::get($address, 'address.id');
96 $country = Arr::get($address, 'address.country');
97 $state = Arr::get($address, 'address.state');
98 $type = Arr::get($address, 'address.type', 'billing');
99 $cart = CartHelper::getCart($request->getSafe('fct_cart_hash', 'sanitize_text_field'));
100
101 $checkoutData = Arr::wrap($cart->checkout_data);
102 Arr::set($checkoutData, 'form_data.' . $type . '_address_id', $addressId);
103 Arr::set($checkoutData, 'form_data.' . $type . '_country', $country);
104 Arr::set($checkoutData, 'form_data.' . $type . '_state', $state);
105
106 if ($type === 'billing' && Arr::get($checkoutData, 'form_data.ship_to_different', 'no') === 'no') {
107 Arr::set($checkoutData, 'form_data.shipping_address_id', $addressId);
108 Arr::set($checkoutData, 'form_data.shipping_country', $country);
109 Arr::set($checkoutData, 'form_data.shipping_state', $state);
110 }
111
112 $cart->checkout_data = $checkoutData;
113 $cart->save();
114
115 $formattedAddress = Arr::get($address, 'address.formatted_address');
116
117 // Use output buffering to generate HTML
118 ob_start();
119
120 // Extract the address parts
121 $addressParts = [
122 trim(Arr::get($formattedAddress, 'address_1') ?? ''),
123 trim(Arr::get($formattedAddress, 'address_2') ?? ''),
124 trim(Arr::get($formattedAddress, 'city') ?? ''),
125 trim(Arr::get($formattedAddress, 'state') ?? ''),
126 trim(Arr::get($formattedAddress, 'country') ?? ''),
127 ];
128
129 // Filter out empty or null parts
130 $addressParts = array_filter($addressParts, function ($part) {
131 return $part !== '';
132 });
133
134 // Join parts with comma and space
135 $addressString = implode(', ', $addressParts);
136
137 do_action('fluent_cart/views/checkout_page_form_address_info_wrapper', [
138 'name' => Arr::get($address, 'address.name'),
139 'phone' => Arr::get($address, 'address.phone'),
140 'label' => Arr::get($address, 'address.label'),
141 'address' => $addressString,
142 ]);
143 $htmlOutput = ob_get_clean();
144
145 $result = (new WebCheckoutHandler())->handleGetCheckoutSummaryViewAjax();
146
147 return $this->response->sendSuccess([
148 'message' => __('Address Attached', 'fluent-cart'),
149 'data' => $htmlOutput,
150 'fragments' => $result['fragments']
151 ]);
152 }
153
154
155 public function createAddress(Request $request) //CustomerAddressRequest
156 {
157 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
158
159 if (empty($customer)) {
160 return $this->sendError([
161 'message' => __('You don\'t have any associated account', 'fluent-cart')
162 ]);
163 }
164
165 $customerId = $customer->id;
166 $data = $request->all();//$request->getSafe($request->sanitize());
167
168 $validatedData = $this->validateAddressData($data);
169 if (is_wp_error($validatedData)) {
170 wp_send_json([
171 'status' => 'failed',
172 'errors' => $validatedData->get_error_data(),
173 ], 422);
174 }
175
176
177 // Sanitize all fields with special handling for emails
178 $sanitized = [];
179
180 foreach ($validatedData as $key => $value) {
181 if (is_array($value)) {
182 // Recursively sanitize nested arrays
183 $sanitized[$key] = array_map('sanitize_text_field', $value);
184 continue;
185 }
186
187 // If key contains "email", sanitize as email
188 if (stripos($key, 'email') !== false && !empty($value)) {
189 $sanitized[$key] = sanitize_email($value);
190 continue;
191 }
192
193 // Default text sanitization
194 $sanitized[$key] = sanitize_text_field($value);
195 }
196
197 $data = $sanitized;
198
199
200 $data = self::formattedAddress($data);
201
202 // Validate label length
203 if (!empty($data['label']) && strlen($data['label']) > 15) {
204 return $this->sendError([
205 'message' => __('Label must not exceed 15 characters.', 'fluent-cart')
206 ]);
207 }
208
209 $data['status'] = 'active';
210 $isCreated = CustomerAddressResource::create($data, ['id' => $customerId]);
211
212 if (is_wp_error($isCreated)) {
213 return $isCreated;
214 }
215
216
217 $isCreated = $isCreated['data'];
218
219 $cart = CartHelper::getCart();
220 $requiredShipping = $cart->requireShipping();
221 $type = Arr::get($data, 'type', 'billing');
222 $config = [
223 'type' => $type,
224 'product_type' => $requiredShipping ? 'physical' : 'digital',
225 'with_shipping' => $requiredShipping
226 ];
227
228 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
229 $addresses = AddressHelper::getCustomerValidatedAddresses($config, $customer);
230 $address = AddressHelper::getPrimaryAddress($addresses, $config, $customer, $type);
231 $requirementsFields = CheckoutFieldsSchema::getCheckoutFieldsRequirements(
232 $type,
233 Arr::get($config, 'product_type'),
234 Arr::get($config, 'with_shipping')
235 );
236
237 ob_start();
238 (new AddressSelectRenderer(
239 $addresses,
240 $address,
241 $requirementsFields,
242 $type
243 ))->renderAddressSelector();
244 $selectors = ob_get_clean();
245
246
247 return $this->response->sendSuccess([
248 'message' => __('Customer address created successfully!', 'fluent-cart'),
249 'fragment' => [
250 [
251 'selector' => '[data-fluent-cart-checkout-page-form-address-modal-address-selector-button-wrapper]',
252 'content' => $selectors,
253 'type' => 'replace'
254 ]
255 ]
256 ]);
257 }
258
259 private function validateAddressData($data)
260 {
261 $type = sanitize_text_field(Arr::get($data, 'type'));
262 $fulfillmentType = sanitize_text_field(Arr::get($data, 'product_type'));
263
264 // Address creation validates against its own type's rules only — no shipping merge
265 $validations = array_filter(CheckoutFieldsSchema::getCheckoutFieldsRequirements($type, $fulfillmentType, false));
266
267 // Name fields are validated via basic_info, not address sections
268 unset($validations['full_name'], $validations['first_name'], $validations['last_name'], $validations['company_name']);
269
270 $address = [];
271 foreach ($validations as $key => $validation) {
272 $address[$key] = Arr::get($data, $type . '_' . $key, '');
273 }
274
275 $country = $this->resolveCountryForValidation($address, $type);
276 $errors = [];
277
278 foreach ($validations as $key => $rule) {
279 $value = Arr::get($address, $key, '');
280 $prefixedKey = $type . '_' . $key;
281 $titledKey = Str::headline($key);
282
283 $fieldErrors = $this->validateAddressField($key, $value, $rule, $titledKey, $country);
284 if (!empty($fieldErrors)) {
285 $errors[$prefixedKey] = $fieldErrors;
286 }
287 }
288
289 if (!empty($errors)) {
290 return new \Wp_Error('validation_error', __('Validation error', 'fluent-cart'), $errors);
291 }
292
293 return $data;
294 }
295
296 private function resolveCountryForValidation(array $address, string $addressType): string
297 {
298 $country = Arr::get($address, 'country', '');
299 if (!empty($country)) {
300 return $country;
301 }
302
303 // Fall back to store country only when the country field is disabled by admin
304 $fieldSettings = CheckoutFieldsSchema::getFieldsSettings();
305 $countryEnabled = Arr::get($fieldSettings, $addressType . '_address.country.enabled', 'no') === 'yes';
306
307 if (!$countryEnabled) {
308 return (new \FluentCart\Api\StoreSettings())->get('store_country') ?: '';
309 }
310
311 return '';
312 }
313
314 private function validateAddressField(string $field, $value, string $rule, string $label, string $country): array
315 {
316 $localization = LocalizationManager::getInstance();
317
318 switch ($field) {
319 case 'country':
320 return $this->validateCountryField($value, $rule, $label, $localization);
321 case 'state':
322 return $this->validateStateField($value, $rule, $label, $country, $localization);
323 case 'postcode':
324 if ($rule === 'required' && empty($value)) {
325 return ['required' => sprintf(__('%s is required.', 'fluent-cart'), $label)];
326 }
327 if (!empty($value) && !empty($country) && $localization->postcode->isValid($value, $country) === false) {
328 return ['invalid' => sprintf(__('%s is invalid.', 'fluent-cart'), $label)];
329 }
330 return [];
331 default:
332 if ($rule === 'required' && empty($value)) {
333 return ['required' => sprintf(__('%s is required.', 'fluent-cart'), $label)];
334 }
335 return [];
336 }
337 }
338
339 private function validateCountryField($value, string $rule, string $label, LocalizationManager $localization): array
340 {
341 if ($rule === 'required' && empty($value)) {
342 return ['required' => sprintf(__('%s is required.', 'fluent-cart'), $label)];
343 }
344
345 if (!empty($value) && !Arr::has($localization->countries(), $value)) {
346 return ['invalid' => sprintf(__('%s is invalid.', 'fluent-cart'), $label)];
347 }
348
349 return [];
350 }
351
352 private function validateStateField($value, string $rule, string $label, string $country, LocalizationManager $localization): array
353 {
354 if (empty($country)) {
355 return [];
356 }
357
358 $states = $localization->statesOptions($country);
359 if (empty($states)) {
360 return [];
361 }
362
363 $stateValues = array_column($states, 'value');
364
365 if ($rule === 'required' && empty($value)) {
366 return ['required' => sprintf(__('%s is required.', 'fluent-cart'), $label)];
367 }
368
369 if (!empty($value) && !in_array($value, $stateValues)) {
370 return ['invalid' => sprintf(__('%s is invalid.', 'fluent-cart'), $label)];
371 }
372
373 return [];
374 }
375
376 public function updateAddress(CustomerAddressRequest $request)
377 {
378 $data = $request->getSafe($request->sanitize());
379 $data = self::formattedAddress($data);
380 $id = Arr::get($request->get('address'), 'id');
381
382 $address = CustomerAddresses::query()->findOrFail($id);
383
384 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
385 if (empty($customer) || $customer->id != $address->customer_id) {
386 $this->sendError([
387 'message' => __('You are not authorized to update this address', 'fluent-cart')
388 ]);
389 }
390
391 if ($address->update($data)) {
392 return $this->response->sendSuccess([
393 'message' => __('Address updated successfully', 'fluent-cart')
394 ]);
395 }
396
397 return $this->sendError([
398 'message' => __('Failed to update address', 'fluent-cart')
399 ]);
400 }
401
402 public function removeAddress(Request $request)
403 {
404
405 $id = Arr::get($request->address, 'id', false);
406
407 $address = CustomerAddresses::query()->findOrFail($id);
408
409 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
410 if (empty($customer) || $customer->id != $address->customer_id) {
411 $this->sendError([
412 'message' => __('You are not authorized to delete this address', 'fluent-cart')
413 ]);
414 }
415
416 if ($address->delete()) {
417 return $this->response->sendSuccess([
418 'message' => __('Address deleted successfully', 'fluent-cart')
419 ]);
420 }
421
422 return $this->sendError([
423 'message' => __('Failed to delete address', 'fluent-cart')
424 ]);
425 }
426
427 public function setAddressPrimary(Request $request, $customerId)
428 {
429 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
430 if (empty($customer) || $customer->id != $customerId) {
431 return $this->sendError([
432 'message' => __('You are not authorized to update this address', 'fluent-cart')
433 ]);
434 }
435
436 $isUpdated = CustomerAddressResource::makePrimary($customerId, $request->all());
437
438 if (is_wp_error($isUpdated)) {
439 return $isUpdated;
440 }
441 return $this->response->sendSuccess($isUpdated);
442 }
443
444 public function getCustomerOrders(Request $request, $customerId): array
445 {
446
447 $customer = \FluentCart\Api\Resource\CustomerResource::getCurrentCustomer();
448 if (empty($customer) || $customer->id != $customerId) {
449 return [
450 'orders' => []
451 ];
452 }
453 return [
454 'orders' => CustomerResource::getOrders($request->all(), $customerId)
455 ];
456 }
457
458 private static function formattedAddress(array $data = []): array
459 {
460 $address = [];
461
462 foreach ($data as $key => $value) {
463 if (strpos($key, "billing_") === 0) {
464 $newKey = str_replace("billing_", "", $key);
465 $address[$newKey] = $value;
466 }
467 if (strpos($key, "shipping_") === 0) {
468 $newKey = str_replace("shipping_", "", $key);
469 $address[$newKey] = $value;
470 }
471 }
472
473 $address['type'] = Arr::get($data, 'type');
474
475 return $address;
476 }
477
478 }
479