PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.0
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.6.0, at app/Http/Controllers/FrontendControllers/CustomerController.php

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