PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 1.2.0 All 47 releases
fluent-cart / app / Http / Routes / WebRoutes.php

WebRoutes.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Http/Routes/WebRoutes.php

383 lines 12.7 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\Routes;
4
5 use FluentCart\Api\Resource\FrontendResource\CartResource;
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\App;
8
9 use FluentCart\App\Helpers\CartHelper;
10 use FluentCart\App\Helpers\Helper;
11 use FluentCart\App\Hooks\Handlers\ShortCodes\Checkout\CheckoutPageHandler;
12 use FluentCart\App\Hooks\Handlers\ShortCodes\ReceiptHandler;
13 use FluentCart\App\Http\Controllers\WebController\FileDownloader;
14 use FluentCart\App\Models\Cart;
15 use FluentCart\App\Models\ProductVariation;
16 use FluentCart\App\Modules\PaymentMethods\PayPalGateway\API\PayPalPartnerRenderer;
17 use FluentCart\App\Modules\Templating\AssetLoader;
18 use FluentCart\App\Services\FrontendView;
19 use FluentCart\App\Services\PrintService;
20 use FluentCart\App\Services\Renderer\CartRenderer;
21 use FluentCart\App\Services\TemplateService;
22 use FluentCart\App\Services\URL;
23 use FluentCart\App\Vite;
24 use FluentCart\Framework\Support\Arr;
25 use FluentCart\App\Services\Renderer\CheckoutRenderer;
26 use FluentCart\App\Services\Renderer\ModalCheckoutRenderer;
27
28 class WebRoutes
29 {
30 public static function register()
31 {
32
33 // Late on init, deliberately. These routes do not merely register — they
34 // TAKE OVER the request, render a full page and die(). At the default
35 // priority this callback is queued at plugin-include time, so it runs
36 // before anything that registers on init from `fluentcart_loaded` (every
37 // add-on, including FluentCart Pro). A route that renders and dies before
38 // those listeners exist silently drops whatever they would have rendered
39 // — which is why the saved-payment-method picker and the save-my-card
40 // consent box appeared on the checkout page but never in modal checkout.
41 add_action('init', function () {
42 self::registerRoutes();
43 }, 99);
44 }
45
46 public static function renderModalCheckout() {
47 add_action('wp_footer', function () {
48 if (!AssetLoader::isFrontendAssetsMarked() && !AssetLoader::isFluentCartContext()) {
49 return;
50 }
51
52 AssetLoader::loadModalCheckoutAssets();
53
54 if (
55 isset($_SERVER['HTTP_SEC_FETCH_DEST']) &&
56 $_SERVER['HTTP_SEC_FETCH_DEST'] === 'iframe'
57 ) {
58 return;
59 }
60
61 $cart = CartHelper::getCart();
62
63 if (!$cart) {
64 $cart = new Cart();
65 }
66
67 (new ModalCheckoutRenderer($cart))->render();
68 });
69 }
70
71 public static function registerRoutes()
72 {
73 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
74 $page = sanitize_text_field(wp_unslash($_REQUEST['fluent-cart'] ?? ''));
75
76 if (empty($page)) {
77 return;
78 }
79
80 $page = sanitize_text_field($page);
81
82 if (!$page) {
83 return;
84 }
85
86 if ($page === 'instant_checkout') {
87 $requestData = App::request()->all();
88 $variationId = sanitize_text_field(App::request()->get('item_id'));
89 $quantity = App::request()->get('quantity', 1);
90 // Raw request flag; actual validation and normalization
91 // happens in CartResource.
92 $isCustom = (bool)Arr::get($requestData, 'is_custom', false);
93
94 if(!$isCustom) {
95 if (!is_numeric($variationId)) {
96 return;
97 }
98 }
99
100 if (is_numeric($quantity)) {
101 $quantity = intval($quantity);
102 $quantity = max($quantity, 1);
103 } else {
104 $quantity = 1;
105 }
106
107 if($isCustom) {
108 $variation = apply_filters('fluent_cart/cart/validate_custom_item', null, [
109 'item_id' => $variationId,
110 'quantity' => $quantity,
111 'is_custom' => $isCustom,
112 'action' => 'instant_checkout',
113 ]);
114
115 if (!is_object($variation)) {
116 $variation = (object) $variation;
117 }
118
119 } else {
120 $variation = ProductVariation::query()->find($variationId);
121 }
122
123 if (empty($variation)) {
124 return;
125 }
126
127 $soldIndividually = $isCustom
128 ? !empty($variation->sold_individually)
129 : (bool) $variation->soldIndividually();
130
131 if ($soldIndividually) {
132 $quantity = 1;
133 }
134
135 $cart = CartResource::generateCartForInstantCheckout($variationId, $quantity, [
136 'is_custom' => $isCustom,
137 'variation' => $variation
138 ]);
139
140 if (is_wp_error($cart)) {
141 (new CheckoutPageHandler())->enqueueStyles();
142 ob_start();
143 (new CartRenderer([]))->renderEmpty(
144 $cart->get_error_message()
145 );
146 $view = ob_get_clean();
147 FrontendView::make(__('Product Not Found', 'fluent-cart'), $view);
148 die();
149 }
150
151 $coupons = App::request()->get('coupons', '');
152 if ($coupons) {
153 $coupons = explode(',', $coupons);
154 $coupons = array_map('sanitize_text_field', $coupons);
155 $couponResult = $cart->applyCoupon($coupons);
156
157
158 $couponErrors = [];
159 if (is_wp_error($couponResult)) {
160 $couponErrors[] = esc_html($couponResult->get_error_message());
161 } elseif (is_array($couponResult)) {
162 $perCouponResults = Arr::get($couponResult, 'coupon_results', []);
163 foreach ($coupons as $code) {
164 foreach ($perCouponResults as $resultCode => $result) {
165 if (strcasecmp((string) $resultCode, (string) $code) === 0) {
166 $errorMessage = Arr::get($result, 'error', '');
167 if ($errorMessage !== '') {
168 $couponErrors[] = esc_html($errorMessage);
169 }
170 break;
171 }
172 }
173 }
174 }
175
176 if ($couponErrors) {
177 $checkoutData = is_array($cart->checkout_data) ? $cart->checkout_data : [];
178 $checkoutData['__checkout_error_notices'] = $couponErrors;
179 $cart->checkout_data = $checkoutData;
180 $cart->save();
181 }
182 }
183
184 $target_path = (new StoreSettings())->getCheckoutPage();
185
186 $redirectTo = App::request()->get('redirect_to');
187 if (!empty($redirectTo)) {
188 $url = sanitize_url(wp_unslash($redirectTo));
189 $allowedHosts = [parse_url(home_url(), PHP_URL_HOST)];
190 $allowedHosts = apply_filters('fluent_cart/instant_checkout/allowed_redirect_hosts', $allowedHosts, [
191 'allowed_hosts' => $allowedHosts
192 ]);
193 if (!empty($url) && filter_var($url, FILTER_VALIDATE_URL) && in_array(parse_url($url, PHP_URL_HOST), $allowedHosts, true)) {
194 $target_path = $url;
195 }
196 }
197
198
199 // Step 1: Get current query string and parse to array
200
201 $queryArray = [];
202 $queryString = Arr::get( App::request()->server(), 'QUERY_STRING');
203 if ( isset($queryString) ) {
204 parse_str($queryString, $queryArray);
205 }
206
207 unset($queryArray['fluent-cart']);
208 unset($queryArray['item_id']);
209 unset($queryArray['quantity']);
210 unset($queryArray['coupons']);
211 unset($queryArray['redirect_to']);
212
213 if (isset($queryArray['is_custom'])) {
214 unset($queryArray['is_custom']);
215 }
216
217 $queryArray['fct_cart_hash'] = $cart->cart_hash;
218
219 $redirect_url = URL::appendQueryParams($target_path, $queryArray);
220
221 wp_redirect($redirect_url);
222 exit;
223 }
224
225 $served = self::handleMainRoutes($page);
226
227 // Handle faker routes if enabled and not already served
228 if (!$served && App::config()->get('using_faker') === true) {
229 $served = FakerRoutes::handle($page);
230 }
231
232 if (has_action('fluent_cart_action_' . $page)) {
233 $requestData = App::request()->all();
234 do_action('fluent_cart_action_' . $page, $requestData);
235 die();
236 }
237
238 if ($served) {
239 die();
240 }
241
242 return '';
243 }
244
245 private static function handleMainRoutes($page): bool
246 {
247 $request = App::request();
248 switch ($page) {
249 case 'fluent_cart_payment_authenticate':
250 (new PayPalPartnerRenderer($request->mode))->render(
251 $request->all()
252 );
253 return true;
254 case 'download-by-id':
255 case 'download-file':
256 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in view template
257 echo (new FileDownloader())->index(App::request());
258 return true;
259
260 case 'receipt':
261 $receiptHandler = new ReceiptHandler();
262
263 $view = $receiptHandler->renderRedirectPage([
264 'type' => 'receipt'
265 ]);
266
267 FrontendView::make(
268 __('Order Receipt', 'fluent-cart'),
269 $view,
270 [
271 'styles' => [
272 'public/checkout/style/confirmation.scss'
273 ],
274 'scripts' => [
275 [
276 'source' => 'public/lib/printThis-2.0.0.min.js',
277 'isStatic' => true
278 ],
279 'public/print/Print.js'
280 ],
281 'enqueue_prefix' => 'fluent-cart-checkout-order-receipt',
282 'wp_head' => false,
283 'wp_footer' => false,
284 ]);
285 return true;
286
287
288 case 'print-invoice':
289 return self::handlePrintRoute('invoice');
290
291 case 'print-packing-slip':
292 return self::handlePrintRoute('packingSlip');
293
294 case 'print-delivery-slip':
295 return self::handlePrintRoute('deliverySlip');
296
297 case 'print-shipping-slip':
298 return self::handlePrintRoute('shippingSlip');
299
300 case 'print-dispatch-slip':
301 return self::handlePrintRoute('dispatchSlip');
302
303 case 'modal_checkout':
304 return self::handleModalCheckout();
305
306 default:
307 return false;
308 }
309 }
310
311 private static function handleModalCheckout(): bool
312 {
313 $variationId = App::request()->get('item_id');
314 $quantity = App::request()->get('quantity', 1);
315
316 if (!is_numeric($variationId)) {
317 return false;
318 }
319
320 if (is_numeric($quantity)) {
321 $quantity = intval($quantity);
322 $quantity = max($quantity, 1);
323 } else {
324 $quantity = 1;
325 }
326
327 $variation = ProductVariation::query()->find($variationId);
328
329 if (empty($variation)) {
330 return false;
331 }
332
333 $soldIndividually = $variation->soldIndividually();
334
335 if ($soldIndividually) {
336 $quantity = 1;
337 }
338
339 $cart = CartResource::generateCartForInstantCheckout($variationId, $quantity);
340
341 if ($cart) {
342 $modalCheckoutRenderer = new ModalCheckoutRenderer($cart);
343 ob_start();
344 $modalCheckoutRenderer->renderForm();
345 $checkoutContent = ob_get_clean();
346
347 AssetLoader::loadCheckoutAssets($cart);
348
349 Vite::enqueueStyle(
350 'fluentcart-modal-checkout-iframe-css',
351 'public/checkout/style/checkout-iframe.scss'
352 );
353
354 // Vite::enqueueScript(
355 // 'fluentcart-modal-checkout-form-js',
356 // 'public/checkout/ModalCheckoutForm.js',
357 // []
358 // );
359
360 FrontendView::make(__('Checkout', 'fluent-cart'), $checkoutContent);
361 die();
362 }
363
364 return false;
365
366
367 }
368
369 private static function handlePrintRoute($method): bool
370 {
371 if (!is_user_logged_in() || !current_user_can('manage_options')) {
372 return false;
373 }
374
375 $order = App::request()->get('order');
376 if (!empty($order)) {
377 PrintService::$method($order);
378 return true;
379 }
380 return false;
381 }
382 }
383