PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
fluent-booking / app / Services / LandingPage / LandingPageHandler.php

LandingPageHandler.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.5.0, at app/Services/LandingPage/LandingPageHandler.php

473 lines 17.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Services\LandingPage;
4
5 use FluentBooking\App\App;
6 use FluentBooking\App\Hooks\Handlers\FrontEndHandler;
7 use FluentBooking\App\Models\Booking;
8 use FluentBooking\App\Models\Calendar;
9 use FluentBooking\App\Models\CalendarSlot;
10 use FluentBooking\App\Services\CalendarEventService;
11 use FluentBooking\App\Services\BookingFieldService;
12 use FluentBooking\App\Services\BookingService;
13 use FluentBooking\App\Services\Helper;
14 use FluentBooking\Framework\Support\Arr;
15 use FluentBooking\Framework\Support\Collection;
16 use FluentBooking\App\Vite;
17
18 class LandingPageHandler
19 {
20 public function boot()
21 {
22 if (defined('FLUENT_BOOKING_LANDING_SLUG')) {
23 add_action('template_redirect', [$this, 'handleSlugDefinedPage'], 1);
24 }
25
26 if (isset($_GET['fluent-booking'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
27 add_action('init', [$this, 'handleUrlParamsPage'], 100);
28 }
29 }
30
31 public function handleSlugDefinedPage()
32 {
33 global $wp;
34 // remove starting / and end / from $uri
35 $uri = trim($wp->request, '/');
36 $urlParts = explode('/', $uri);
37
38 if ($urlParts[0] != FLUENT_BOOKING_LANDING_SLUG || count($urlParts) < 2) {
39 return;
40 }
41
42 $authorSlug = sanitize_text_field($urlParts[1]);
43
44 if (isset($_GET['embedded'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
45 $this->handleEmbeddedStyle();
46 }
47
48 $this->routeView($authorSlug, Arr::get($urlParts, 2, null));
49 }
50
51 public function handleUrlParamsPage()
52 {
53 $route = isset($_GET['fluent-booking']) ? sanitize_text_field(wp_unslash($_GET['fluent-booking'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
54
55 if (isset($_GET['embedded'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
56 $this->handleEmbeddedStyle();
57 }
58
59 if ($route == 'booking') {
60 $this->handleAfterBookingPage();
61 return;
62 }
63
64 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
65
66 if (empty($request['host'])) {
67 do_action('fluent_booking/landing_page_route_' . $route, $request);
68 return;
69 }
70
71 $authorSlug = sanitize_text_field($request['host']);
72
73 $slotSlug = null;
74
75 if (!empty($request['event'])) {
76 $slotSlug = sanitize_text_field($request['event']);
77 }
78
79 $this->routeView($authorSlug, $slotSlug);
80 }
81
82 public function routeView($authorSlug, $slotSlug = null)
83 {
84 $calendar = Calendar::where('slug', $authorSlug)->first();
85
86 if (!$calendar) {
87 return;
88 }
89
90 $sharingSettings = LandingPageHelper::getSettings($calendar);
91
92 if (Arr::get($sharingSettings, 'enabled') != 'yes') {
93 return '';
94 }
95
96 if ($slotSlug) {
97 $slot = CalendarSlot::where('calendar_id', $calendar->id)
98 ->where('slug', $slotSlug)
99 ->first();
100 if (!$slot) {
101 return;
102 }
103 return $this->renderBookingView($calendar, $slot);
104 }
105
106 $this->renderHostView($calendar);
107 }
108
109 private function renderHostView($calendar)
110 {
111 global $wp;
112 $settings = LandingPageHelper::getSettings($calendar, 'public');
113
114 $activeEvents = CalendarSlot::where('calendar_id', $calendar->id)
115 ->where('status', 'active');
116
117 if ($settings['show_type'] != 'all') {
118 $activeEvents = $activeEvents->whereIn('id', $settings['enabled_slots']);
119 }
120
121 $activeEvents = $activeEvents->get();
122
123 $activeEvents = CalendarEventService::processEvents($calendar, $activeEvents);
124
125 $metaDescription = Helper::excerpt($calendar->description);
126
127 $calendar->description = wpautop($calendar->description);
128
129 $authorProfile = $calendar->getAuthorProfile(true);
130
131 $globalVars = (new FrontEndHandler())->getGlobalVars();
132
133 $currentUrl = home_url($wp->request);
134
135 $globalVars['is_landing_page'] = true;
136 $globalVars['is_pretty_url'] = defined('FLUENT_BOOKING_LANDING_SLUG');
137 $globalVars['base_url'] = rtrim($currentUrl, '/');
138
139 $jsVars = [
140 'fluentCalendarPublicVars' => $globalVars,
141 'fcal_landing_page' => true
142 ];
143
144 $extraJsFiles = [];
145
146 foreach ($activeEvents as $activeEvent) {
147 $event = clone $activeEvent;
148 $vars = (new FrontEndHandler())->getCalendarEventVars($calendar, $event);
149 $extraJs = $this->getEventLandingExtraJsFiles($vars['form_fields'], $event);
150 if ($extraJs) {
151 $vars['lazy_js_files'] = $extraJs;
152 }
153 $jsVars['fcal_public_vars_' . $calendar->id . '_' . $activeEvent->id] = $vars;
154 $activeEvent->locations = $activeEvent->defaultLocationHtml();
155 do_action_ref_array('fluent_booking/landing_page_event', [&$activeEvent]);
156 }
157
158 $data = [
159 'calendar' => $calendar,
160 'events' => $activeEvents,
161 'author' => $authorProfile,
162 'title' => $calendar->title .' - '.get_bloginfo('name'),
163 'description' => $metaDescription,
164 'url' => home_url($wp->request),
165 'embedded' => isset($_GET['embedded']) ? true : false, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
166 'css_files' => [
167 Vite::styleUrl('saas_css')
168 ],
169 'js_files' => [
170 'fluent-booking-public-js' => Vite::scriptUrl('public_app'),
171 ],
172 'js_vars' => $jsVars,
173 'header_js_files' => [
174 'fluent_booking_team_app-js' => Vite::scriptUrl('team_app')
175 ]
176 ];
177
178 if ($extraJsFiles) {
179 $extraJsFiles = array_unique($extraJsFiles);
180 $data['js_files'] = array_merge($data['js_files'], $extraJsFiles);
181 }
182
183 $data = apply_filters('fluent_booking/host_view_page_vars', $data, $calendar, $activeEvents, $authorProfile);
184
185 $app = App::getInstance();
186 status_header(200);
187 $app->view->render('landing.author_landing', $data);
188 exit(200);
189 }
190
191 private function renderBookingView($calendar, $calendarEvent, $existingBooking = null, $isReschedule = false)
192 {
193 if (!$isReschedule) {
194 $settings = LandingPageHelper::getSettings($calendar, 'public');
195 if ($settings['show_type'] != 'all') {
196 if (!in_array($calendarEvent->id, $settings['enabled_slots'])) {
197 return '';
198 }
199 }
200 }
201
202 global $wp;
203
204 $calendarEvent->max_lookup_date = $calendarEvent->getMaxLookUpDate();
205 $calendarEvent->min_lookup_date = $calendarEvent->getMinLookUpDate();
206
207 if (!empty($_REQUEST['booking_id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
208 $bookingHash = sanitize_text_field(wp_unslash($_REQUEST['booking_id'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
209 $booking = Booking::where('hash', $bookingHash)
210 ->where('event_id', $calendarEvent->id)
211 ->first();
212 if ($booking) {
213 $this->showBookingConfimationPage($booking, $calendarEvent);
214 }
215 }
216
217 $authorProfile = $calendarEvent->getAuthorProfile(true);
218
219 $calendarEvent->pre_selects = false;
220
221 if (gmdate('m') != gmdate('m', strtotime($calendarEvent->min_lookup_date))) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
222 $calendarEvent->pre_selects = [
223 'month' => gmdate('m', strtotime($calendarEvent->min_lookup_date)), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
224 'year' => gmdate('Y', strtotime($calendarEvent->min_lookup_date)) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
225 ];
226 }
227
228 $assetUrl = App::getInstance('url.assets');
229
230 $eventVars = (new FrontEndHandler())->getCalendarEventVars($calendar, $calendarEvent);
231
232 $publicCss = 'saas_css';
233
234 $title = $calendarEvent->title . ' ' . __('with', 'fluent-booking') . ' ' . $authorProfile['name'];
235
236 $title = apply_filters('fluent_booking/booking_confirmation_page_title', $title, $calendarEvent, $authorProfile);
237
238 $data = [
239 'calendar' => $calendar,
240 'calendar_event' => $calendarEvent,
241 'author' => $authorProfile,
242 'title' => $title,
243 'description' => substr(strip_shortcodes(wp_strip_all_tags(str_replace(PHP_EOL, ' ', $calendarEvent->description))), 0, 300) . '...',
244 'url' => home_url($wp->request),
245 'embedded' => isset($_GET['embedded']) ? true : false, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
246 'css_files' => [
247 Vite::styleUrl($publicCss)
248 ],
249 'js_files' => [
250 'fluent-booking-public-js' => Vite::scriptUrl('public_app'),
251 ],
252 'js_vars' => [
253 'fluentCalendarPublicVars' => (new FrontEndHandler())->getGlobalVars(),
254 'fcal_public_vars_' . $calendar->id . '_' . $calendarEvent->id => $eventVars,
255 ]
256 ];
257
258 $extraJs = $this->getEventLandingExtraJsFiles($eventVars['form_fields'], $calendarEvent);
259
260 if ($extraJs) {
261 $data['js_files'] = wp_parse_args($data['js_files'], $extraJs);
262 add_action('fluent_booking/author_landing_head', function () use ($assetUrl) {
263 ?>
264 <style>
265 .fcal_phone_wrapper .flag {
266 background: url(<?php echo esc_url($assetUrl.'images/flags_responsive.png'); ?>) no-repeat;
267 background-size: 100%;
268 }
269 </style>
270 <?php
271 });
272 }
273
274 $data = apply_filters('fluent_booking/event_landing_page_vars', $data, $calendar, $calendarEvent, $existingBooking);
275
276 $app = App::getInstance();
277
278 status_header(200);
279 $app->view->render('landing.booking', $data);
280 exit(200);
281 }
282
283 private function showBookingConfimationPage($booking, $actionType = 'confirmation')
284 {
285 $validActions = ['confirmation'];
286
287 if (in_array($booking->status, ['scheduled', 'pending', 'rescheduled'])) {
288 $validActions = array_merge($validActions, ['reschedule', 'cancel']);
289 }
290
291 if (!in_array($actionType, $validActions)) {
292 $actionType = 'confirmation';
293 }
294
295 if ($actionType == 'reschedule') {
296 $this->handleRescheduleView($booking);
297 }
298
299 if ($actionType == 'confirmation' && !empty($_REQUEST['ics']) && $_REQUEST['ics'] == 'download') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
300 $icsText = BookingService::generateBookingICS($booking);
301 // Output the ICS text
302 header('Content-Type: text/calendar; charset=utf-8');
303 header('Content-Disposition: attachment; filename=event.ics');
304 echo $icsText; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
305 die();
306 }
307
308 global $wp;
309
310 $calendarEvent = $booking->calendar_event;
311
312 $responseHtml = BookingService::getBookingConfirmationHtml($booking, $actionType);
313
314 $authorProfile = $calendarEvent->getAuthorProfile(true);
315
316 $publicCss = 'saas_public_css';
317
318 $data = [
319 'title' => __('Confirmation: ', 'fluent-booking') . $calendarEvent->title . ' ' . __('with', 'fluent-booking') . ' ' . $authorProfile['name'],
320 'body' => $responseHtml,
321 'description' => substr(strip_shortcodes(wp_strip_all_tags(str_replace(PHP_EOL, ' ', $calendarEvent->description))), 0, 300) . '...',
322 'css_files' => [
323 Vite::styleUrl($publicCss)
324 ],
325 'js_files' => [],
326 'js_vars' => [],
327 'author' => $authorProfile,
328 'slot' => $calendarEvent,
329 'url' => home_url($wp->request),
330 'action_type' => $actionType,
331 'embedded' => isset($_GET['embedded']) ? true : false, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
332 'theme' => Arr::get(get_option('_fluent_booking_settings'), 'theme','system-default'),
333 'back_button' => [
334 'show' => true,
335 'text' => __('Back to home', 'fluent-booking'),
336 'url' => site_url('/')
337 ]
338 ];
339
340 if ($actionType == 'cancel') {
341 $data['js_files']['fluent-booking-public-manage-meeting-js'] = Vite::scriptUrl('manage_meeting');
342 }
343
344 $data = apply_filters('fluent_booking/booking_confirmation_page_vars', $data, $booking, $calendarEvent);
345
346 $app = App::getInstance();
347 status_header(200);
348 $app->view->render('landing.confirmation_page', $data);
349 exit(200);
350 }
351
352 private function handleAfterBookingPage()
353 {
354 $bookingHash = sanitize_text_field(wp_unslash(Arr::get($_REQUEST, 'meeting_hash'))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
355 if (!$bookingHash) {
356 return;
357 }
358
359 $booking = Booking::where('hash', $bookingHash)->first();
360 if (!$booking) {
361 return;
362 }
363
364 $type = Arr::get($_REQUEST, 'type', 'confirmation'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
365
366 $this->showBookingConfimationPage($booking, $type);
367 }
368
369 private function handleRescheduleView(Booking $booking)
370 {
371 add_filter('fluent_booking/public_event_vars', function ($eventVars) use ($booking) {
372 $onlyFields = ['name', 'email', 'rescheduling_reason'];
373
374 $formFields = $eventVars['form_fields'];
375
376 $formFields = Collection::make($formFields)->filter(function ($field) use ($onlyFields) {
377 return in_array($field['name'], $onlyFields);
378 })->map(function ($item) {
379 $item['disabled'] = $item['name'] == 'rescheduling_reason' ? false : true;
380 return $item;
381 })->toArray();
382
383 $formFields[] = [
384 'type' => 'hidden',
385 'name' => 'rescheduling_hash',
386 'enabled' => true
387 ];
388
389 $eventVars['rescheduling'] = 'yes';
390 $eventVars['form_fields'] = array_values($formFields);
391 unset($eventVars['payment_items']);
392 unset($eventVars['payment_methods']);
393
394 return $eventVars;
395 }, 10, 1);
396
397 add_filter('fluent_calendar/global_booking_vars', function ($vars) use ($booking) {
398 $vars['current_person'] = [
399 'name' => trim($booking->first_name . ' ' . $booking->last_name),
400 'email' => $booking->email,
401 'rescheduling_hash' => $booking->hash
402 ];
403
404 return $vars;
405 }, 10, 1);
406
407 add_filter('fluent_booking/public_event_vars', function ($vars, $calendarEvent) {
408 $vars['i18']['Schedule_Meeting'] = __('Confirm Reschedule', 'fluent-booking');
409 $vars['i18']['Continue_to_Payments'] = __('Confirm Reschedule', 'fluent-booking');
410 $vars['i18']['Confirm_Payment'] = __('Confirm Reschedule', 'fluent-booking');
411 return $vars;
412 }, 10, 2);
413
414 add_action('fluent_booking/before_calendar_event_landing_page', function ($calendarEvent) use ($booking) {
415 ?>
416 <div class="fcal_rescheduling_wrap">
417 <h3> <?php esc_html_e('You are rescheduling the booking: ', 'fluent-booking');
418 echo wp_kses_post($booking->getFullBookingDateTimeText($booking->person_time_zone, true)); ?>
419 (<?php echo esc_html($booking->person_time_zone); ?>) </h3>
420 </div>
421 <?php
422 }, 10, 1);
423
424 add_action('fluent_booking/author_landing_head', function () {
425 ?>
426 <style>
427 .fluent_booking_app {
428 margin-top: 0 !important;
429 padding-top: 0 !important;
430 }
431 </style>
432 <?php
433 });
434
435 $this->renderBookingView($booking->calendar, $booking->calendar_event, $booking, true);
436 }
437
438 private function handleEmbeddedStyle()
439 {
440 add_action('wp_print_styles', function () {
441 global $wp_styles;
442 if($wp_styles) {
443 foreach ($wp_styles->queue as $style) {
444 $src = $wp_styles->registered[$style]->src;
445 if (
446 (strpos($src, 'fluentbooking') === false) &&
447 (strpos($src, 'fluent_booking') === false)
448 ) {
449 wp_dequeue_style($wp_styles->registered[$style]->handle);
450 }
451 }
452 }
453 }, 100);
454 }
455
456 public function getEventLandingExtraJsFiles($formFields, $calendarEvent)
457 {
458 $files = [];
459 $assetUrl = App::getInstance('url.assets');
460
461 if (BookingFieldService::hasPhoneNumberField($formFields)) {
462 $files['fluent-booking-phone-field-js'] = Vite::scriptUrl('phone_field');
463 }
464
465 if ($calendarEvent->type == 'paid') {
466 $files['fluent-booking-checkout-sdk-stripe-js'] = 'https://js.stripe.com/v3/';
467 $files['fluent-booking-checkout-handler-stripe-js'] = $assetUrl . 'public/js/stripe-checkout.js';
468 }
469
470 return $files;
471 }
472 }
473