PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.1.2
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.1.2
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 1.7.2 All 33 releases
fluent-booking / app / Services / LandingPage / LandingPageHandler.php

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

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