PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.6
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.6
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 1.3.19 All 49 releases
← All changes | app/Hooks/Handlers/ShortCodes/CustomerProfileHandler.php +76 -7 1.3.27 → 1.6.6 View file →
@@ -6,12 +6,19 @@
6 6 use FluentCart\Api\PaymentMethods;
7 7 use FluentCart\Api\Resource\CustomerResource;
8 8 use FluentCart\Api\StoreSettings;
9 9 use FluentCart\App\App;
10 +use FluentCart\App\Services\CustomerIdentity\EmailClaimPortal;
11 +use FluentCart\App\Services\CustomerIdentity\CustomerRecoveryService;
12 +use FluentCart\App\Services\CustomerIdentity\EmailClaimService;
13 +use FluentCart\App\Services\CustomerIdentity\EmailVerificationService;
14 +use FluentCart\App\Helpers\CurrenciesHelper;
10 15 use FluentCart\App\Helpers\Helper;
11 16 use FluentCart\App\Models\Subscription;
12 17 use FluentCart\App\Modules\Templating\AssetLoader;
18 +use FluentCart\App\Services\Renderer\CheckoutFieldsSchema;
13 19 use FluentCart\App\Services\TemplateService;
20 +use FluentCart\App\Services\DateTime\DayjsFormatter;
14 21 use FluentCart\App\Services\Translations\TransStrings;
15 22 use FluentCart\App\Vite;
16 23 use FluentCart\Framework\Support\Arr;
17 24 use FluentCart\Framework\Support\Str;
@@ -36,8 +43,18 @@
36 43 public static function register()
37 44 {
38 45 parent::register();
39 46
47 + add_action(CustomerRecoveryService::HOOK, [CustomerRecoveryService::class, 'run']);
48 +
49 + add_action('template_redirect', function () {
50 + $redirect = EmailClaimPortal::handleSubmission();
51 + if ($redirect) {
52 + wp_safe_redirect($redirect);
53 + exit;
54 + }
55 + });
56 +
40 57 // Add wildcard customer profile pages
41 58 // add a custom permalink endpoint
42 59 add_action('init', function () {
43 60 $pageSlug = (new StoreSettings())->getCustomerDashboardPageSlug();
@@ -58,15 +75,26 @@
58 75 public function render(?array $viewData = null)
59 76 {
60 77 if (!is_user_logged_in()) {
61 78 ob_start();
62 - $redirectUrl = (new StoreSettings())->getCustomerProfilePage();
79 + $redirectUrl = $this->resolveLoginRedirectUrl(
80 + (new StoreSettings())->getCustomerProfilePage()
81 + );
82 +
83 + $claimToken = Arr::get($_GET, EmailClaimService::QUERY_TOKEN, '');
84 + if (is_string($claimToken) && $claimToken !== '') {
85 + $redirectUrl = add_query_arg(EmailClaimService::QUERY_TOKEN, sanitize_text_field(wp_unslash($claimToken)), (new StoreSettings())->getCustomerProfilePage());
86 + }
87 +
63 88 if (defined('FLUENT_AUTH_VERSION') && (new \FluentAuth\App\Hooks\Handlers\CustomAuthHandler())->isEnabled()) {
64 89 ?>
65 90 <div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #CBD5E0; border-radius: 8px;" class="fct_auth_wrap">
66 91 <h4><?php echo esc_html__('Please log in to access your customer portal.', 'fluent-cart'); ?></h4>
67 92 <?php
68 - echo do_shortcode('[fluent_auth redirect_to="' . $redirectUrl . '"]');
93 + // The URL travels inside a double-quoted shortcode attribute, where a
94 + // bracket or quote would truncate the shortcode. Carry them encoded.
95 + $attributeUrl = str_replace(['[', ']', '"'], ['%5B', '%5D', '%22'], $redirectUrl);
96 + echo do_shortcode('[fluent_auth redirect_to="' . $attributeUrl . '"]');
69 97 echo '</div>';
70 98 } else {
71 99 ?>
72 100 <div class="fct_auth_wrap">
@@ -85,8 +113,34 @@
85 113
86 114 $this->renderCustomerAppContainer();
87 115 }
88 116
117 + /**
118 + * Resolve where a logged-out visitor should land once they have logged in.
119 + *
120 + * `redirect_to` is attacker-supplied, so it is only honoured when
121 + * wp_validate_redirect() accepts it. That compares the parsed host against the
122 + * site host. A string-prefix comparison must not be used here: a hostile host
123 + * can be built by suffixing the site host, or by placing the site host in the
124 + * userinfo position ahead of an `@`, and both keep the site URL as a prefix
125 + * while resolving somewhere else entirely.
126 + *
127 + * @param string $fallbackUrl Where to send the visitor when no usable target was supplied.
128 + * @return string
129 + */
130 + public function resolveLoginRedirectUrl($fallbackUrl)
131 + {
132 + if (empty($_GET['redirect_to']) || !is_string($_GET['redirect_to'])) {
133 + return $fallbackUrl;
134 + }
135 +
136 + $intendedRedirectUrl = sanitize_url(wp_unslash($_GET['redirect_to']));
137 +
138 + $validatedUrl = wp_validate_redirect($intendedRedirectUrl, '');
139 +
140 + return $validatedUrl ? $validatedUrl : $fallbackUrl;
141 + }
142 +
89 143 public function renderCustomerAppContainer()
90 144 {
91 145
92 146 // Enqueue global styles
@@ -93,8 +147,15 @@
93 147 Vite::enqueueStyle( 'fluent-cart-customer-profile-global',
94 148 'public/customer-profile/style/customer-profile-global.scss',
95 149 );
96 150
151 + // Gate before custom endpoint callbacks or the dashboard load customer data.
152 + $verificationNotice = EmailClaimPortal::render();
153 + if (EmailVerificationService::isRequired(get_current_user_id())) {
154 + echo $verificationNotice; // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in the view
155 + return;
156 + }
157 +
97 158 $customEndpointContent = $this->maybeCustomEndpointContent();
98 159
99 160 if(!$customEndpointContent) {
100 161 (new static())->enqueueStyles();
@@ -101,9 +162,10 @@
101 162 }
102 163
103 164 $colors = self::generateCssColorVariables(Arr::get($this->shortCodeAttributes, 'colors', ''));
104 165 add_action('fluent_cart/customer_menu', array($this, 'renderCustomerMenu'));
105 - add_action('fluent_cart/customer_app', function () use ($customEndpointContent) {
166 + add_action('fluent_cart/customer_app', function () use ($customEndpointContent, $verificationNotice) {
167 + echo $verificationNotice; // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped in the view
106 168 if($customEndpointContent) {
107 169 echo $customEndpointContent; // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
108 170 } else {
109 171 AssetLoader::loadCustomerDashboardAssets();
@@ -349,8 +411,11 @@
349 411 'fluentcart_customer_profile_vars' => [
350 412 'app_slug' => $pageSlug,
351 413 'app_url' => TemplateService::getCustomerProfileUrl(),
352 414 'shop' => $shopLocalizationData,
415 + 'currency_signs' => array_map(function ($sign) {
416 + return html_entity_decode($sign, ENT_QUOTES, 'UTF-8');
417 + }, CurrenciesHelper::getCurrencySigns()),
353 418 'trans' => TransStrings::getCustomerProfileString(),
354 419 'download_url_base' => site_url('fluent-cart/download-file/?fluent_cart_download=true'),
355 420 'placeholder_image' => Vite::getAssetUrl('images/placeholder.svg'),
356 421 'stripe_pub_key' => apply_filters('fluent_cart/payment_methods/stripe_pub_key', ''),
@@ -364,15 +429,19 @@
364 429 'me' => [
365 430 'email' => $currentCustomer ? $currentCustomer->email : '',
366 431 'first_name' => $currentCustomer ? $currentCustomer->first_name : '',
367 432 'last_name' => $currentCustomer ? $currentCustomer->last_name : '',
368 - 'photo' => $currentCustomer ? $currentCustomer->photo : ''
369 -
370 433 ],
371 434 'logout_url' => wp_logout_url(home_url()),
372 - 'datei18' => TransStrings::dateTimeStrings(),
435 + 'datei18' => DayjsFormatter::localizedStrings(),
373 436 'el_strings' => TransStrings::elStrings(),
374 - 'wp_locale' => get_locale()
437 + 'wp_locale' => get_locale(),
438 + 'is_company_name_enabled' => CheckoutFieldsSchema::isCompanyNameEnabled(),
439 + 'is_company_name_required' => CheckoutFieldsSchema::isCompanyNameRequired(),
440 + 'is_vat_number_enabled' => CheckoutFieldsSchema::isVatNumberEnabled(),
441 + 'is_vat_number_required' => CheckoutFieldsSchema::isVatNumberRequired(),
442 + 'is_legal_registration_id_enabled' => CheckoutFieldsSchema::isLegalRegistrationIdEnabled(),
443 + 'is_legal_registration_id_required' => CheckoutFieldsSchema::isLegalRegistrationIdRequired()
375 444 ],
376 445 'fluentCartRestVars' => [
377 446 'rest' => Helper::getRestInfo(),
378 447 ],