PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.0
1.7.0 1.6.6 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 All 50 releases
← All changes | app/Http/Routes/WebRoutes.php +74 -4 1.3.21 → 1.7.0 View file →
@@ -23,8 +23,9 @@
23 23 use FluentCart\App\Vite;
24 24 use FluentCart\Framework\Support\Arr;
25 25 use FluentCart\App\Services\Renderer\CheckoutRenderer;
26 26 use FluentCart\App\Services\Renderer\ModalCheckoutRenderer;
27 +use FluentCart\App\Services\Renderer\OrderReviewRenderer;
27 28
28 29 class WebRoutes
29 30 {
30 31 public static function register()
@@ -29,11 +30,19 @@
29 30 {
30 31 public static function register()
31 32 {
32 33
34 + // Late on init, deliberately. These routes do not merely register — they
35 + // TAKE OVER the request, render a full page and die(). At the default
36 + // priority this callback is queued at plugin-include time, so it runs
37 + // before anything that registers on init from `fluentcart_loaded` (every
38 + // add-on, including FluentCart Pro). A route that renders and dies before
39 + // those listeners exist silently drops whatever they would have rendered
40 + // — which is why the saved-payment-method picker and the save-my-card
41 + // consent box appeared on the checkout page but never in modal checkout.
33 42 add_action('init', function () {
34 43 self::registerRoutes();
35 - });
44 + }, 99);
36 45 }
37 46
38 47 public static function renderModalCheckout() {
39 48 add_action('wp_footer', function () {
@@ -143,9 +152,35 @@
143 152 $coupons = App::request()->get('coupons', '');
144 153 if ($coupons) {
145 154 $coupons = explode(',', $coupons);
146 155 $coupons = array_map('sanitize_text_field', $coupons);
147 - $cart->applyCoupon($coupons);
156 + $couponResult = $cart->applyCoupon($coupons);
157 +
158 +
159 + $couponErrors = [];
160 + if (is_wp_error($couponResult)) {
161 + $couponErrors[] = esc_html($couponResult->get_error_message());
162 + } elseif (is_array($couponResult)) {
163 + $perCouponResults = Arr::get($couponResult, 'coupon_results', []);
164 + foreach ($coupons as $code) {
165 + foreach ($perCouponResults as $resultCode => $result) {
166 + if (strcasecmp((string) $resultCode, (string) $code) === 0) {
167 + $errorMessage = Arr::get($result, 'error', '');
168 + if ($errorMessage !== '') {
169 + $couponErrors[] = esc_html($errorMessage);
170 + }
171 + break;
172 + }
173 + }
174 + }
175 + }
176 +
177 + if ($couponErrors) {
178 + $checkoutData = is_array($cart->checkout_data) ? $cart->checkout_data : [];
179 + $checkoutData['__checkout_error_notices'] = $couponErrors;
180 + $cart->checkout_data = $checkoutData;
181 + $cart->save();
182 + }
148 183 }
149 184
150 185 $target_path = (new StoreSettings())->getCheckoutPage();
151 186
@@ -215,9 +250,9 @@
215 250 case 'fluent_cart_payment_authenticate':
216 251 (new PayPalPartnerRenderer($request->mode))->render(
217 252 $request->all()
218 253 );
219 - break;
254 + return true;
220 255 case 'download-by-id':
221 256 case 'download-file':
222 257 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in view template
223 258 echo (new FileDownloader())->index(App::request());
@@ -248,10 +283,41 @@
248 283 'wp_head' => false,
249 284 'wp_footer' => false,
250 285 ]);
251 286 return true;
252 -
253 287
288 + case 'order-review':
289 + ob_start();
290 + $orderFound = (new OrderReviewRenderer(
291 + sanitize_text_field($request->get('order_hash', ''))
292 + ))->render();
293 + $orderReview = ob_get_clean();
294 +
295 + // A bad or stale link is a page that does not exist, and
296 + // says so to crawlers as well as people.
297 + if (!$orderFound) {
298 + status_header(404);
299 + }
300 +
301 + // wp_head/wp_footer left on, unlike the receipt: the review
302 + // form's script and stylesheet are enqueued during render()
303 + // and there is nowhere else for them to print. It also lets
304 + // the theme dress a page the customer reaches from an email.
305 + // wp_head/wp_footer forced on. This route renders the whole
306 + // document, so nothing else prints them — and the view's
307 + // is_page() guard would otherwise skip both on a site whose
308 + // front page is static, silently dropping every asset the
309 + // review form (and Pro's photo fields) enqueued.
310 + FrontendView::make(
311 + __('Review Your Order', 'fluent-cart'),
312 + $orderReview,
313 + [
314 + 'wp_head' => true,
315 + 'wp_footer' => true,
316 + ]
317 + );
318 + return true;
319 +
254 320 case 'print-invoice':
255 321 return self::handlePrintRoute('invoice');
256 322
257 323 case 'print-packing-slip':
@@ -333,8 +399,12 @@
333 399 }
334 400
335 401 private static function handlePrintRoute($method): bool
336 402 {
403 + if (!is_user_logged_in() || !current_user_can('manage_options')) {
404 + return false;
405 + }
406 +
337 407 $order = App::request()->get('order');
338 408 if (!empty($order)) {
339 409 PrintService::$method($order);
340 410 return true;