PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
fluent-support / app / Hooks / Handlers / AuthHandler.php

AuthHandler.php in Fluent Support – Helpdesk & Customer Support Ticket System 2.4.5, at app/Hooks/Handlers/AuthHandler.php

637 lines 23.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Hooks\Handlers;
4
5 use FluentSupport\App\App;
6 use FluentSupport\App\Modules\PermissionManager;
7 use FluentSupport\App\Services\FluentAuthBridge;
8 use FluentSupport\App\Services\Helper;
9 use FluentSupport\App\Services\Includes\CountryNames;
10 use FluentSupport\App\Vite;
11 use FluentSupport\Framework\Support\Arr;
12 use FluentSupport\App\Models\Meta;
13
14 class AuthHandler
15 {
16
17 protected $loaded = false;
18
19 public function init()
20 {
21 add_shortcode('fluent_support_login', array($this, 'loginForm'));
22 add_shortcode('fluent_support_signup', array($this, 'registrationForm'));
23 add_shortcode('fluent_support_auth', array($this, 'authForm'));
24 add_shortcode('fluent_support_reset_password', array($this, 'restPasswordForm'));
25 add_action('wp_ajax_fluent_support_renew_rest_nonce', [$this, 'maybeRenewNonce']);
26
27 FluentAuthBridge::register();
28 }
29
30 /**
31 * @param $attributes
32 * @return string
33 */
34 public function loginForm($attributes)
35 {
36 if (get_current_user_id()) {
37 return '<p>' . __('You are already logged in.', 'fluent-support') . '</p>';
38 }
39
40 $attributes = $this->getShortcodes($attributes);
41 if (!empty($attributes['redirect-to'])) {
42 $redirect = $attributes['redirect-to'];
43 } else {
44 $redirect = Helper::getPortalBaseUrl();
45 }
46
47
48 $this->handleAlreadyLoggedIn($attributes);
49
50 if ($this->authProvider() == 'fluent_auth') {
51 $attributes['redirect_to'] = $redirect;
52 return $this->renderFluentAuthForms($attributes);
53 }
54
55 $this->loadAssets();
56
57 $return = '<div class="fst_login_form_auth_wrapper">';
58 $return .= '<div id="fst_login_form" class="fst_login_wrapper">';
59
60
61 /*
62 * Filter login form
63 *
64 * @since v1.0.0
65 *
66 * @param array $loginArgs
67 */
68 $loginArgs = apply_filters('fluent_support/login_form_args', [
69 'echo' => false,
70 'redirect' => $redirect,
71 'remember' => true,
72 'value_remember' => true,
73 ]);
74
75 $return .= wp_login_form($loginArgs);
76
77 if ($attributes['show-signup'] == 'true') {
78 $return .= '<p style="text-align: center">'
79 . __('Not registered?', 'fluent-support')
80 . ' <a href="#" id="fs_show_signup">'
81 . __('Create an account', 'fluent-support')
82 . '</a></p>';
83 }
84
85 if ($attributes['show-reset-password'] == 'true') {
86 $return .= '<p style="text-align: center">'
87 . __('Forgot your password?', 'fluent-support')
88 . ' <a href="#" id="fs_show_reset_password">'
89 . __('Reset password', 'fluent-support')
90 . '</a></p>';
91 }
92
93 $return .= '</div>';
94
95 if ($attributes['show-signup'] == 'true') {
96 $return .= do_shortcode('[fluent_support_signup hide=true]');
97 }
98
99 if ($attributes['show-reset-password'] == 'true') {
100 $return .= do_shortcode('[fluent_support_reset_password hide=true]');
101 }
102
103 $return .= '</div>';
104 return $return;
105 }
106
107 /**
108 * @param $attributes
109 * @return string
110 */
111 public function registrationForm($attributes)
112 {
113 if (get_current_user_id()) {
114 return '<p>' . __('You are already logged in.', 'fluent-support') . '</p>';
115 }
116
117 $attributes = $this->getShortcodes($attributes);
118 $this->handleAlreadyLoggedIn($attributes);
119
120 if (!Helper::isSignupEnabled()) {
121 if ($attributes['hide'] == 'true') {
122 return '';
123 }
124
125 return '<p>' . __('User registration is not enabled on this site.', 'fluent-support') . '</p>';
126 }
127
128 if ($this->authProvider() == 'fluent_auth') {
129 return FluentAuthBridge::render(function () use ($attributes) {
130 return (new \FluentAuth\App\Hooks\Handlers\CustomAuthHandler())->registrationForm($attributes);
131 }, $attributes['redirect_to']);
132 }
133
134 $customFieldsKey = Helper::getCustomRegistrationFieldKeys();
135
136 if (!empty($customFieldsKey)) {
137 add_filter('fluent_support/registration_form_fields', function($fields) use ($customFieldsKey) {
138 return $this->addCustomFieldsToRegistrationForm($fields,$customFieldsKey);
139 });
140 }
141
142 $registrationFields = static::getSignupFields();
143 $hide = $attributes['hide'] == 'true' ? 'hide' : '';
144
145 $this->loadAssets($hide);
146
147 return $this->buildRegistrationForm($registrationFields, $hide, $attributes);
148 }
149
150 private function buildRegistrationForm($registrationFields, $hide, $attributes)
151 {
152 $registrationForm = '<div class="fst_registration_wrapper ' . $hide . '"><form id="fstRegistrationForm" class="fs_registration_form" method="post" name="fs_registration_form">';
153
154 foreach ($registrationFields as $fieldName => $registrationField) {
155 $registrationForm .= $this->renderField($fieldName, $registrationField);
156 }
157
158 $registrationForm .= '<input type="hidden" name="__redirect_to" value="' . esc_url($attributes['redirect-to']) . '">';
159 $registrationForm .= '<input type="hidden" name="_fsupport_signup_nonce" value="' . wp_create_nonce('fluent_support_signup_nonce') . '">';
160 $registrationForm .= '<button type="submit" id="fst_submit">' . $this->submitBtnLoadingSvg() . '<span>' . __('Signup', 'fluent-support') . '</span></button>';
161
162 $registrationForm .= '</form>';
163
164 $registrationForm .= apply_filters('fluent_support/before_registration_form_close', '', $registrationFields, $attributes);
165
166 if ($hide) {
167 $registrationForm .= '<p style="text-align: center">'
168 . __('Already have an account?', 'fluent-support')
169 . ' <a href="#" id="fs_show_login">'
170 . __('Login', 'fluent-support')
171 . '</a></p>';
172 }
173
174 $registrationForm .= '</div>';
175
176 return $registrationForm;
177 }
178
179 public function restPasswordForm($attributes)
180 {
181 if (get_current_user_id()) {
182 return '<p>' . __('You are already logged in.', 'fluent-support') . '</p>';
183 }
184
185 $attributes = $this->getShortcodes($attributes);
186
187 if ($this->authProvider() == 'fluent_auth') {
188 return FluentAuthBridge::render(function () use ($attributes) {
189 return (new \FluentAuth\App\Hooks\Handlers\CustomAuthHandler())->restPasswordForm($attributes);
190 }, $attributes['redirect_to']);
191 }
192
193 $this->handleAlreadyLoggedIn($attributes);
194
195 $resetPasswordFields = static::resetPasswordFields();
196 $hide = $attributes['hide'] == 'true' ? 'hide' : '';
197
198 $this->loadAssets($hide);
199
200 return $this->buildResetPassForm($resetPasswordFields, $hide, $attributes);
201 }
202
203 private function buildResetPassForm($resetPasswordFields, $hide, $attributes)
204 {
205 $restePasswordForm = '<div class="fst_reset_pass_wrapper ' . $hide . '"><form id="fstResetPasswordForm" class="fs_reset_pass_form" method="post" name="fs_reset_pass_form">';
206
207 foreach ($resetPasswordFields as $fieldName => $resetPasswordField) {
208 $restePasswordForm .= $this->renderField($fieldName, $resetPasswordField);
209 }
210
211 $restePasswordForm .= '<input type="hidden" name="__redirect_to" value="' . esc_url($attributes['redirect-to']) . '">';
212 $restePasswordForm .= '<input type="hidden" name="_fsupport_reset_pass_nonce" value="' . wp_create_nonce('fluent_support_reset_pass_nonce') . '">';
213 $restePasswordForm .= '<button type="submit" id="fst_reset_pass">' . $this->submitBtnLoadingSvg() . '<span>' . __('Reset Password', 'fluent-support') . '</span></button>';
214
215 $restePasswordForm .= '</form>';
216
217 $restePasswordForm .= '</div>';
218
219 return $restePasswordForm;
220 }
221
222 /**
223 * Login form with signup and reset links, or FluentAuth's form when it is the provider.
224 *
225 * @param $attributes
226 * @return string
227 */
228 public function authForm($attributes)
229 {
230 if (get_current_user_id()) {
231 // translators: %s is the URL to the support portal
232 return '<p>' . sprintf(__('You are already logged in. <a href="%s">Go to support portal</a>', 'fluent-support'), Helper::getPortalBaseUrl()) . '</p>';
233 }
234
235 $attributes = $this->getShortcodes($attributes, true);
236
237 if ($this->authProvider() == 'fluent_auth') {
238 return $this->renderFluentAuthForms($attributes);
239 }
240
241 return $this->loginForm($attributes);
242 }
243
244 /**
245 * FluentAuth's login form, plus its signup and reset forms (hidden) when the shortcode
246 * shows them. Its "Create an Account" and "Reset Password" links open a form that must
247 * already be on the page, and its own authForm() always adds reset, so we pick the
248 * forms here. FluentAuth renders and processes each one.
249 *
250 * @param array $attributes
251 * @return string
252 */
253 private function renderFluentAuthForms($attributes)
254 {
255 return FluentAuthBridge::render(function () use ($attributes) {
256 $handler = new \FluentAuth\App\Hooks\Handlers\CustomAuthHandler();
257 $hidden = array_merge($attributes, ['hide' => 'true']);
258
259 $html = '<div class="fls_auth_wrapper">' . $handler->loginForm($attributes);
260
261 if ($attributes['show-signup'] == 'true') {
262 $html .= $handler->registrationForm($hidden);
263 }
264
265 if ($attributes['show-reset-password'] == 'true') {
266 $html .= $handler->restPasswordForm($hidden);
267 }
268
269 return $html . '</div>';
270 }, $attributes['redirect_to']);
271 }
272
273 /**
274 * @param $fieldName
275 * @param $field
276 * @return string
277 */
278 private function renderField($fieldName, $field)
279 {
280 $fieldType = Arr::get($field, 'type');
281 $isRequired = Arr::get($field, 'required');
282 $isRequired = $isRequired ? 'is-required' : '';
283
284 $html = '<div class="fst_field_group fst_field_' . $fieldName . '">';
285
286 if ($label = Arr::get($field, 'label')) {
287 $html .= '<div class="fst_field_label ' . $isRequired . '"><label for="' . Arr::get($field, 'id') . '">' . $label . '</label></div>';
288 }
289
290 $textTypes = ['text', 'email', 'password', 'number', 'date'];
291 $selectTypes = ['select'];
292
293 if (in_array($fieldType, $textTypes)) {
294 $html .= $this->renderTextInput($fieldName, $field);
295 } elseif (in_array($fieldType, $selectTypes)) {
296 $html .= $this->renderSelectInput($fieldName, $field);
297 } else {
298 return '';
299 }
300
301 $html = $html . '</div>';
302
303 return '<div class="fst_registration_fields">' . $html . '</div>';
304 }
305
306 private function renderTextInput($fieldName, $field)
307 {
308 $inputAtts = array_filter([
309 'type' => esc_attr(Arr::get($field, 'type', '')),
310 'id' => esc_attr(Arr::get($field, 'id', '')),
311 'placeholder' => esc_attr(Arr::get($field, 'placeholder', '')),
312 'name' => esc_attr($fieldName),
313 'required' => Arr::get($field, 'required') ? 'required' : '',
314 ]);
315
316 $atts = '';
317
318 foreach ($inputAtts as $attKey => $att) {
319 $atts .= $attKey . '="' . $att . '" ';
320 }
321
322 return '<div class="fs_input_wrap"><input ' . $atts . ' /></div>';
323 }
324
325 /**
326 * @param string $fieldName
327 * @param array $field Field config with options and attributes.
328 * @return string
329 */
330 private function renderSelectInput($fieldName, $field)
331 {
332 $choices = Arr::get($field, 'options', []);
333 $placeholder = Arr::get($field, 'placeholder', '');
334
335 $options = '<option value="" disabled selected>' . esc_attr($placeholder) . '</option>';
336
337 foreach ($choices as $value => $optionData) {
338 $options .= '<option value="' . esc_attr($value) . '">' . esc_attr($optionData) . '</option>';
339 }
340
341 $selectAtts = [
342 'id' => esc_attr(Arr::get($field, 'id', '')),
343 'name' => esc_attr($fieldName),
344 'required' => Arr::get($field, 'required') ? 'required' : '',
345 ];
346
347 $select = '<div class="fs_input_wrap"><select ' . $this->renderAttributes($selectAtts) . '>' . $options . '</select></div>';
348
349 return $select;
350 }
351
352 private function renderAttributes($attributes)
353 {
354 $atts = '';
355
356 foreach ($attributes as $attKey => $att) {
357 $atts .= $attKey . '="' . $att . '" ';
358 }
359
360 return $atts;
361 }
362
363 /**
364 * @return mixed
365 */
366 public static function getSignupFields()
367 {
368 /*
369 * Filter signup form field
370 *
371 * @since v1.0.0
372 *
373 * @param array $fields Form fields
374 */
375 return apply_filters('fluent_support/registration_form_fields', [
376 'first_name' => [
377 'required' => true,
378 'type' => 'text',
379 'label' => __('First name', 'fluent-support'),
380 'id' => 'fst_first_name',
381 'placeholder' => __('First name', 'fluent-support')
382 ],
383 'last_name' => [
384 'type' => 'text',
385 'label' => __('Last Name', 'fluent-support'),
386 'id' => 'fst_last_name',
387 'placeholder' => __('Last name', 'fluent-support')
388 ],
389 'username' => [
390 'required' => true,
391 'type' => 'text',
392 'label' => __('Username', 'fluent-support'),
393 'id' => 'fst_username',
394 'placeholder' => __('Username', 'fluent-support')
395 ],
396 'email' => [
397 'required' => true,
398 'type' => 'email',
399 'label' => __('Email Address', 'fluent-support'),
400 'id' => 'fst_email',
401 'placeholder' => __('Your Email Address', 'fluent-support')
402 ],
403 'password' => [
404 'required' => true,
405 'type' => 'password',
406 'label' => __('Password', 'fluent-support'),
407 'id' => 'fst_password',
408 'placeholder' => __('Password', 'fluent-support')
409 ]
410 ]);
411 }
412
413 private function addCustomFieldsToRegistrationForm($fields, $customFieldsKey) {
414 $customFields = $this->allCustomFields();
415
416 foreach ($customFieldsKey as $key) {
417 if (isset($customFields[$key])) {
418 $fields[$key] = $customFields[$key];
419 }
420 }
421
422 return $fields;
423 }
424
425 public static function allCustomFields(): array {
426 $countryList = CountryNames::get();
427
428 return apply_filters('fluent_support/custom_registration_form_fields', [
429 'address_line_1' => [
430 'required' => false,
431 'type' => 'text',
432 'label' => __('Address Line 1', 'fluent-support'),
433 'id' => 'fst_address_line_1',
434 'placeholder' => __('Address Line 1', 'fluent-support'),
435 ],
436 'address_line_2' => [
437 'required' => false,
438 'type' => 'text',
439 'label' => __('Address Line 2', 'fluent-support'),
440 'id' => 'fst_address_line_2',
441 'placeholder' => __('Address Line 2', 'fluent-support'),
442 ],
443 'city' => [
444 'required' => false,
445 'type' => 'text',
446 'label' => __('City', 'fluent-support'),
447 'id' => 'fst_city',
448 'placeholder' => __('City', 'fluent-support'),
449 ],
450 'zip' => [
451 'required' => false,
452 'type' => 'text',
453 'label' => __('Zip', 'fluent-support'),
454 'id' => 'fst_zip',
455 'placeholder' => __('Zip', 'fluent-support'),
456 ],
457 'state' => [
458 'required' => false,
459 'type' => 'text',
460 'label' => __('State', 'fluent-support'),
461 'id' => 'fst_state',
462 'placeholder' => __('State', 'fluent-support'),
463 ],
464 'country' => [
465 'required' => false,
466 'type' => 'select',
467 'label' => __('Country', 'fluent-support'),
468 'id' => 'fst_country',
469 'placeholder' => __('Select a Country', 'fluent-support'),
470 'options' => $countryList,
471 ],
472 ]);
473 }
474
475 public static function resetPasswordFields()
476 {
477 /*
478 * Filter reset password form field
479 *
480 * @since v1.5.7
481 *
482 * @param array $fields Form fields
483 */
484 return apply_filters('fluent_support/reset_password_form', [
485 'user_login' => [
486 'required' => true,
487 'type' => 'text',
488 'label' => __('Email Address', 'fluent-support'),
489 'id' => 'fst_reset_email',
490 'placeholder' => __('Your Email Address', 'fluent-support')
491 ]
492 ]);
493 }
494
495 protected function submitBtnLoadingSvg()
496 {
497 $loadingIcon = '<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
498 width="40px" height="20px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
499 <path fill="#000" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
500 <animateTransform attributeType="xml"
501 attributeName="transform"
502 type="rotate"
503 from="0 25 25"
504 to="360 25 25"
505 dur="0.6s"
506 repeatCount="indefinite"/>
507 </path>
508 </svg>';
509
510 /*
511 * Filter signup form loading icon
512 *
513 * @since v1.0.0
514 *
515 * @param string $loadingIcon this accepts html element
516 */
517 return apply_filters('fluent_support/signup_loading_icon', $loadingIcon);
518 }
519
520 protected function getShortcodes($attributes, $isAuthForm = false)
521 {
522 /*
523 * Filter the default attributes of the auth shortcodes.
524 *
525 * @since v1.0.0
526 *
527 * @param array $shortCodeDefaults
528 */
529 $shortCodeDefaults = apply_filters('fluent_support/auth_shortcode_defaults', [
530 'auto-redirect' => false,
531 'redirect-to' => Helper::getPortalBaseUrl(),
532 'hide' => false,
533 'show-signup' => $isAuthForm ? 'true' : 'false',
534 'show-reset-password' => $isAuthForm ? 'true' : 'false',
535 ]);
536
537 $attributes = shortcode_atts($shortCodeDefaults, $attributes);
538
539 // No signup form or link while the site does not allow registration.
540 if (!Helper::isSignupEnabled()) {
541 $attributes['show-signup'] = 'false';
542 }
543
544 if (isset($attributes['redirect-to'])) {
545 $attributes['redirect-to'] = esc_url_raw($attributes['redirect-to']);
546 $attributes['redirect_to'] = $attributes['redirect-to'];
547 }
548
549 return $attributes;
550
551 }
552
553 protected function handleAlreadyLoggedIn($attributes)
554 {
555 if (get_current_user_id() && !wp_is_json_request() && is_singular()) {
556 if ($attributes['auto-redirect'] === 'true') {
557 $redirect = $attributes['redirect-to'];
558 ?>
559 <script type="text/javascript">
560 document.addEventListener("DOMContentLoaded", function () {
561 var redirect = "<?php echo esc_url($redirect); ?>";
562 window.location.replace(redirect);
563 });
564 </script>
565 <?php
566 }
567 die();
568 }
569 }
570
571 public function loadAssets($hide = '')
572 {
573 if ($this->loaded) {
574 return false;
575 }
576
577 $app = App::getInstance();
578 $assets = $app['url.assets'];
579
580 if (is_rtl()) {
581 wp_enqueue_style('fluent_support_login_style_rtl', $assets . 'admin/css/all_public-rtl.css', [], FLUENT_SUPPORT_VERSION);
582 } else {
583 wp_enqueue_style('fluent_support_login_style', Vite::getEnqueuePath('admin/css/all_public.css'), [], FLUENT_SUPPORT_VERSION);
584 }
585
586 wp_enqueue_script('fluent_support_login_helper', Vite::getEnqueuePath('portal/js/login_helper.js'), [], FLUENT_SUPPORT_VERSION);
587
588 $reCaptchaSettingsData = Meta::where('object_type', '_fs_recaptcha_settings')->first();
589 $reCaptchaData = ($reCaptchaSettingsData) ? Helper::safeUnserialize($reCaptchaSettingsData->value, []) : '';
590
591 if (!empty($reCaptchaData) && isset($reCaptchaData['is_enabled']) && $reCaptchaData['is_enabled'] == "true") {
592 unset($reCaptchaData['secretKey']);
593 $recaptchaVersion = $reCaptchaData["reCaptcha_version"];
594 $reCaptchaApiUrl = 'https://www.google.com/recaptcha/api.js';
595
596 if ("recaptcha_v3" === $recaptchaVersion) {
597 $reCaptchaApiUrl .= '?render=' . $reCaptchaData["siteKey"];
598 }
599
600 wp_enqueue_script('recaptcha', $reCaptchaApiUrl);
601 }
602
603 wp_localize_script('fluent_support_login_helper', 'fluentSupportPublic', [
604 'signup' => rest_url($app->config->get('app.rest_namespace') . '/' . $app->config->get('app.rest_version')) . '/signup',
605 'login' => rest_url($app->config->get('app.rest_namespace') . '/' . $app->config->get('app.rest_version')) . '/login',
606 'nonce' => wp_create_nonce('wp_rest'),
607 'hide' => $hide,
608 'redirect_fallback' => Helper::getPortalBaseUrl(),
609 'fsupport_login_nonce' => wp_create_nonce('fsupport_login_nonce'),
610 'resetPass' => rest_url($app->config->get('app.rest_namespace') . '/' . $app->config->get('app.rest_version')) . '/reset_pass',
611 'reCaptchaSettingsData' => $reCaptchaData,
612 'fs_two_fa' => rest_url($app->config->get('app.rest_namespace') . '/' . $app->config->get('app.rest_version')) . '/two_fa'
613 ]);
614
615
616 $this->loaded = true;
617 }
618
619 public function maybeRenewNonce()
620 {
621 if (!PermissionManager::currentUserPermissions()) {
622 wp_send_json([
623 'error' => __('You do not have permission to do this.', 'fluent-support')
624 ], 403);
625 }
626
627 wp_send_json([
628 'nonce' => wp_create_nonce('wp_rest')
629 ], 200);
630 }
631
632 private function authProvider()
633 {
634 return \FluentSupport\App\Services\Helper::getAuthProvider();
635 }
636 }
637