PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.1.1
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.1.1
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 / CalendarService.php

CalendarService.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.1.1, at app/Services/CalendarService.php

531 lines 22.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;
4
5 use FluentBooking\App\Models\Calendar;
6 use FluentBooking\App\Models\CalendarSlot;
7 use FluentBooking\App\Models\Availability;
8 use FluentBooking\App\Services\Helper;
9 use FluentBooking\Framework\Support\Arr;
10
11 class CalendarService
12 {
13 public static function createCalendar($data, $useCurrentUser = false, $isFileInput = false)
14 {
15 $calendarData = self::prepareCalendarData($data, $useCurrentUser, $isFileInput);
16
17 if (is_wp_error($calendarData)) {
18 return new \WP_Error($calendarData->get_error_code(), $calendarData->get_error_message());
19 }
20
21 $calendarData['slug'] = sanitize_title($calendarData['title']);
22
23 if (!Helper::isCalendarSlugAvailable($calendarData['slug'], true)) {
24 $calendarData['slug'] .= '-' . time();
25 }
26
27 $calendar = Calendar::create($calendarData);
28
29 $calendarMetas = self::prepareCalendarMetas(Arr::get($data, 'metas', []));
30
31 $calendar->metas()->createMany($calendarMetas);
32
33 $availabilitiesData = Arr::get($data, 'availabilities', []);
34
35 $createdAvailabilities = self::createAvailabilities($calendar, $availabilitiesData);
36
37 $eventsData = Arr::get($data, 'events', []);
38
39 self::createCalendarEvents($calendar, $eventsData, $createdAvailabilities);
40
41 do_action('fluent_booking/after_create_calendar', $calendar);
42
43 return [
44 'calendar' => $calendar
45 ];
46 }
47
48 public static function createAvailabilities($calendar, $availabilitiesData)
49 {
50 $createdAvailabilities = [];
51
52 foreach ($availabilitiesData as $existingId => $availabilityData) {
53 $availability = Arr::only($availabilityData, ['key', 'value']);
54 $availability['value']['timezone'] = $calendar->author_timezone;
55 $availability['object_id'] = $calendar->user_id;
56 $availabilityModel = Availability::create($availability);
57 $createdAvailabilities[$existingId] = $availabilityModel->id;
58 }
59
60 return $createdAvailabilities;
61 }
62
63 public static function createCalendarEvents($calendar, $eventsData, $availabilities = [])
64 {
65 $defaultEventData = (new CalendarSlot())->getEventDefaultData($calendar);
66
67 if (empty($eventsData)) {
68 $eventsData = [$defaultEventData];
69 }
70
71 $createEventsData = [];
72
73 $createEventMetasData = [];
74
75 foreach ($eventsData as $eventData) {
76 $eventMetas = Arr::get($eventData, 'event_metas', []);
77
78 $eventData = self::prepareEventData($eventData, $calendar, $availabilities);
79
80 $createEventData = wp_parse_args($eventData, $defaultEventData);
81
82 $createEventData['slug'] = Helper::generateSlotSlug((int)$createEventData['duration'] . 'min', $calendar);
83
84 $createEventData['settings'] = wp_parse_args($createEventData['settings'], $defaultEventData['settings']);
85
86 $createEventsData[] = Arr::only($createEventData, (new CalendarSlot())->getFillable());
87
88 $createEventMetasData[] = $eventMetas;
89 }
90
91 $createdEvents = $calendar->events()->createMany($createEventsData);
92
93 foreach ($createdEvents as $index => $event) {
94 $eventMetasData = Arr::get($createEventMetasData, $index, []);
95
96 $eventMetasData = self::prepareEventMetas($eventMetasData);
97
98 $event->event_metas()->createMany($eventMetasData);
99 }
100
101 return $createdEvents;
102 }
103
104 protected static function prepareCalendarData($calendarData, $useCurrentUser = false, $isFileInput = false)
105 {
106 if (!$calendarData) {
107 return new \WP_Error('invalid_data', esc_html__('Invalid JSON Data', 'fluent-booking'));
108 }
109
110 $preparedData = [
111 'title' => sanitize_text_field(Arr::get($calendarData, 'title')),
112 'description' => sanitize_textarea_field(Arr::get($calendarData, 'description')),
113 'user_id' => intval(Arr::get($calendarData, 'user_id')),
114 'status' => sanitize_text_field(Arr::get($calendarData, 'status', 'active')),
115 'type' => sanitize_text_field(Arr::get($calendarData, 'type', 'simple')),
116 'event_type' => sanitize_text_field(Arr::get($calendarData, 'event_type')),
117 'account_type' => sanitize_text_field(Arr::get($calendarData, 'account_type')),
118 'visibility' => sanitize_text_field(Arr::get($calendarData, 'visibility')),
119 'author_timezone' => sanitize_text_field(Arr::get($calendarData, 'author_timezone')),
120 ];
121
122 if ($useCurrentUser || !Arr::get($preparedData, 'user_id')) {
123 $preparedData['user_id'] = get_current_user_id();
124 }
125
126 if (!Arr::get($preparedData, 'author_timezone')) {
127 $preparedData['author_timezone'] = wp_timezone_string();
128 }
129
130 if (!in_array(Arr::get($preparedData, 'type'), ['simple', 'team', 'event'])) {
131 $preparedData['type'] = 'simple';
132 }
133
134 $user = get_user_by('ID', $preparedData['user_id']);
135
136 if (!$user) {
137 return new \WP_Error('invalid_user', esc_html__('Invalid User ID', 'fluent-booking'));
138 }
139
140 $isHostCalendar = $preparedData['type'] == 'simple' ? true : false;
141
142 if ($isHostCalendar || !$preparedData['title']) {
143 $preparedData['title'] = is_email($user->user_login) ? explode('@', $user->user_login)[0] : $user->user_login;
144 }
145
146 $firstCalendar = Calendar::where('user_id', $preparedData['user_id'])->where('type', 'simple')->first();
147
148 if ($isHostCalendar && $firstCalendar) {
149 if ($isFileInput) {
150 return new \WP_Error('calendar_exists', esc_html__('The user already has a calendar. Please delete it first to create a new one', 'fluent-booking'));
151 }
152 return $firstCalendar;
153 }
154
155 return $preparedData;
156 }
157
158 protected static function prepareCalendarMetas($calendarMetas)
159 {
160 $preparedCalendarMetas = [];
161
162 foreach ($calendarMetas as $calendarMeta) {
163 if (empty($calendarMeta['key']) || empty($calendarMeta['value'])) {
164 continue;
165 }
166
167 $value = $calendarMeta['value'];
168
169 $preparedCalendarMetas[] = [
170 'key' => sanitize_text_field($calendarMeta['key']),
171 'value' => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value),
172 'object_type' => sanitize_text_field($calendarMeta['object_type'])
173 ];
174 }
175
176 return $preparedCalendarMetas;
177 }
178
179 protected static function prepareEventData($eventData, $calendar, $availabilities = [])
180 {
181 $preparedEventData = [
182 'title' => sanitize_text_field(Arr::get($eventData, 'title')),
183 'duration' => (int)Arr::get($eventData, 'duration', 30),
184 'description' => wp_kses_post(Arr::get($eventData, 'description')),
185 'type' => sanitize_text_field(Arr::get($eventData, 'type')),
186 'status' => sanitize_text_field(Arr::get($eventData, 'status', 'active')),
187 'color_schema' => sanitize_text_field(Arr::get($eventData, 'color_schema', '#0099ff')),
188 'event_type' => sanitize_text_field(Arr::get($eventData, 'event_type')),
189 'availability_id' => (int)self::prepareAvailabilityId(Arr::get($eventData, 'availability_id', 0), $availabilities),
190 'availability_type' => sanitize_text_field(Arr::get($eventData, 'availability_type')),
191 'location_type' => sanitize_text_field(Arr::get($eventData, 'location_type')),
192 'location_settings' => SanitizeService::locationSettings(Arr::get($eventData, 'location_settings', [])),
193 'max_book_per_slot' => (int)Arr::get($eventData, 'max_book_per_slot', 1),
194 'is_display_spots' => (bool)Arr::get($eventData, 'is_display_spots', false),
195 ];
196
197 if (!empty($eventData['hash'])) {
198 $hash = sanitize_text_field($eventData['hash']);
199 if (!CalendarSlot::where('hash', $hash)->exists()) {
200 $preparedEventData['hash'] = $hash;
201 }
202 }
203
204 $eventSettings = Arr::get($eventData, 'settings', []);
205
206 if (!$eventSettings) {
207 return $preparedEventData;
208 }
209
210 $preparedEventData['settings'] = [
211 'schedule_type' => sanitize_text_field(Arr::get($eventSettings, 'schedule_type')),
212 'weekly_schedules' => SanitizeService::weeklySchedules(Arr::get($eventSettings, 'weekly_schedules', []), $calendar->author_timezone, 'UTC', true),
213 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), $calendar->author_timezone, 'UTC', null, true),
214 'range_type' => sanitize_text_field(Arr::get($eventSettings, 'range_type')),
215 'range_days' => (int)(Arr::get($eventSettings, 'range_days', 60)) ?: 60,
216 'range_date_between' => SanitizeService::rangeDateBetween(Arr::get($eventSettings, 'range_date_between', ['', ''])),
217 'schedule_conditions' => SanitizeService::scheduleConditions(Arr::get($eventSettings, 'schedule_conditions', [])),
218 'common_schedule' => Arr::isTrue($eventSettings, 'common_schedule', false),
219 'buffer_time_before' => sanitize_text_field(Arr::get($eventSettings, 'buffer_time_before', '0')),
220 'buffer_time_after' => sanitize_text_field(Arr::get($eventSettings, 'buffer_time_after', '0')),
221 'slot_interval' => sanitize_text_field(Arr::get($eventSettings, 'slot_interval', '')),
222 'team_members' => array_map('intval', Arr::get($eventSettings, 'team_members', [])),
223 'multi_duration' => [
224 'enabled' => Arr::isTrue($eventSettings, 'multi_duration.enabled'),
225 'default_duration' => Arr::get($eventSettings, 'multi_duration.default_duration', ''),
226 'available_durations' => array_map('sanitize_text_field', Arr::get($eventSettings, 'multi_duration.available_durations', []))
227 ],
228 'booking_frequency' => [
229 'enabled' => Arr::isTrue($eventSettings, 'booking_frequency.enabled'),
230 'limits' => self::sanitize_mapped_data(Arr::get($eventSettings, 'booking_frequency.limits', []))
231 ],
232 'booking_duration' => [
233 'enabled' => Arr::isTrue($eventSettings, 'booking_duration.enabled'),
234 'limits' => self::sanitize_mapped_data(Arr::get($eventSettings, 'booking_duration.limits', []))
235 ],
236 'lock_timezone' => [
237 'enabled' => Arr::isTrue($eventSettings, 'lock_timezone.enabled'),
238 'timezone' => sanitize_text_field(Arr::get($eventSettings, 'lock_timezone.timezone'))
239 ],
240 'booking_title' => sanitize_text_field(Arr::get($eventSettings, 'booking_title')),
241 'submit_button_text' => sanitize_text_field(Arr::get($eventSettings, 'submit_button_text')),
242 'custom_redirect' => [
243 'enabled' => Arr::isTrue($eventSettings, 'custom_redirect.enabled'),
244 'redirect_url' => sanitize_url(Arr::get($eventSettings, 'custom_redirect.redirect_url')),
245 'is_query_string' => Arr::get($eventSettings, 'custom_redirect.is_query_string') == 'yes' ? 'yes' : 'no',
246 'query_string' => sanitize_text_field(Arr::get($eventSettings, 'custom_redirect.query_string')),
247 ],
248 'requires_confirmation' => [
249 'enabled' => Arr::isTrue($eventSettings, 'requires_confirmation.enabled'),
250 'type' => sanitize_text_field(Arr::get($eventSettings, 'requires_confirmation.type')),
251 'condition' => [
252 'unit' => sanitize_text_field(Arr::get($eventSettings, 'requires_confirmation.condition.unit')),
253 'value' => intval(Arr::get($eventSettings, 'requires_confirmation.condition.value'))
254 ]
255 ],
256 'multiple_booking' => [
257 'enabled' => Arr::isTrue($eventSettings, 'multiple_booking.enabled'),
258 'limit' => intval(Arr::get($eventSettings, 'multiple_booking.limit'))
259 ],
260 'can_not_cancel' => [
261 'enabled' => Arr::isTrue($eventSettings, 'can_not_cancel.enabled'),
262 'type' => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.type')),
263 'message' => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.message')),
264 'condition' => [
265 'unit' => sanitize_text_field(Arr::get($eventSettings, 'can_not_cancel.condition.unit')),
266 'value' => intval(Arr::get($eventSettings, 'can_not_cancel.condition.value'))
267 ]
268 ],
269 'can_not_reschedule' => [
270 'enabled' => Arr::isTrue($eventSettings, 'can_not_reschedule.enabled'),
271 'type' => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.type')),
272 'message' => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.message')),
273 'condition' => [
274 'unit' => sanitize_text_field(Arr::get($eventSettings, 'can_not_reschedule.condition.unit')),
275 'value' => intval(Arr::get($eventSettings, 'can_not_reschedule.condition.value'))
276 ]
277 ]
278 ];
279
280 return $preparedEventData;
281 }
282
283 protected static function prepareEventMetas($eventMetasData)
284 {
285 $preparedEventMetas = [];
286
287 foreach ($eventMetasData as $eventMeta) {
288 if (empty($eventMeta['key']) || empty($eventMeta['value'])) {
289 continue;
290 }
291
292 $value = $eventMeta['value'];
293
294 if ($eventMeta['key'] == 'email_notification') {
295 $value = self::updateNotificationImageUrl($value);
296 }
297
298 $preparedEventMetas[] = [
299 'key' => sanitize_text_field($eventMeta['key']),
300 'value' => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value),
301 'object_type' => sanitize_text_field($eventMeta['object_type'])
302 ];
303 }
304
305 return $preparedEventMetas;
306 }
307
308 protected static function prepareAvailabilityId($availabilityId, $availabilities)
309 {
310 if ($availabilityId && isset($availabilities[$availabilityId])) {
311 return $availabilities[$availabilityId];
312 }
313
314 if ($availabilities) {
315 $firstKey = array_key_first($availabilities);
316 return $availabilities[$firstKey];
317 }
318
319 return $availabilityId;
320 }
321
322 protected static function updateNotificationImageUrl($notifications)
323 {
324 $formattedNotifications = [];
325
326 foreach ($notifications as $key => $notification) {
327 $emailBody = Arr::get($notification, 'email.body');
328 if (!$emailBody) {
329 continue;
330 }
331
332 $newImageUrl = FLUENT_BOOKING_URL . 'assets/images';
333 $pattern = '/(https:\/\/[^"]*?' . preg_quote('assets/images', '/') . ')/';
334 $emailBody = preg_replace($pattern, $newImageUrl, $emailBody);
335 $notification['email']['body'] = $emailBody;
336
337 $formattedNotifications[$key] = $notification;
338 }
339
340 return $formattedNotifications;
341 }
342
343 public static function getSlotOptions($calendarId = null, $userId = null)
344 {
345 $calendarSlots = CalendarSlot::select(['id', 'title'])
346 ->when($calendarId, function ($query) use ($calendarId) {
347 return $query->where('calendar_id', $calendarId);
348 })
349 ->when($userId, function ($query) use ($userId) {
350 return $query->where('user_id', $userId);
351 })
352 ->where('status', '!=', 'expired')
353 ->latest()
354 ->get();
355
356 $options = [];
357 foreach ($calendarSlots as $slot) {
358 $options[] = [
359 'id' => $slot->id,
360 'label' => $slot->title,
361 ];
362 }
363 return apply_filters('fluent_booking/calendar_event_options', $options, $calendarId);
364 }
365
366 public static function getCalendarOptionsByHost()
367 {
368 $calendars = Calendar::select(['id', 'title'])
369 ->when(!PermissionManager::hasAllCalendarAccess(true), function ($query) {
370 return $query->where('user_id', get_current_user_id());
371 })
372 ->with(['slots' => function ($query) {
373 $query->where('status', '!=', 'expired');
374 }])
375 ->latest()
376 ->get();
377
378 $formattedCalendars = [];
379 foreach ($calendars as $index => $calendar) {
380 $slots = Arr::get($calendar, 'slots');
381 if (!empty($slots)) {
382 $options = [];
383 foreach ($slots as $slot) {
384 $options[] = [
385 'label' => Arr::get($slot, 'title'),
386 'value' => Arr::get($slot, 'id')
387 ];
388 }
389 if (!empty($options)) {
390 $formattedCalendars[$index] = [
391 'label' => Arr::get($calendar, 'title'),
392 'options' => $options
393 ];
394 }
395 }
396 }
397 return $formattedCalendars;
398 }
399
400 public static function getCalendarOptionsByTitle($condition = '')
401 {
402 $calendarsQuery = Calendar::select(['id', 'title', 'user_id'])
403 ->where('status', '!=', 'expired')
404 ->with(['slots' => function ($query) {
405 $query->where('status', '!=', 'expired');
406 }]);
407
408 switch ($condition) {
409 case 'only_hosts':
410 $calendarsQuery->where('type', 'simple');
411 break;
412 case 'only_teams':
413 $calendarsQuery->where('type', 'team');
414 break;
415 case 'only_events':
416 $calendarsQuery->where('type', 'event');
417 break;
418 }
419
420 if (!PermissionManager::hasAllCalendarAccess(true)) {
421 $attachedCalendarIds = self::getAttachedCalendarIds($calendarsQuery);
422 $calendarsQuery->whereIn('id', $attachedCalendarIds);
423 }
424
425 $calendars = $calendarsQuery->latest()->get();
426
427 $formattedCalendars = [];
428 foreach ($calendars as $index => $calendar) {
429 $slots = Arr::get($calendar, 'slots');
430 if (!empty($slots)) {
431 $options = [];
432 foreach ($slots as $slot) {
433 $options[] = [
434 'id' => Arr::get($slot, 'id'),
435 'title' => Arr::get($slot, 'title')
436 ];
437 }
438 if (!empty($options)) {
439 $formattedCalendars[$index] = [
440 'id' => Arr::get($calendar, 'id'),
441 'title' => Arr::get($calendar, 'title'),
442 'options' => $options
443 ];
444 }
445 }
446 }
447 return apply_filters('fluent_booking/calendar_options_by_title', $formattedCalendars);
448 }
449
450 public static function getAttachedCalendarIds($calendarsQuery)
451 {
452 $userId = get_current_user_id();
453
454 $calendars = $calendarsQuery->get();
455
456 $calendarIds = [];
457 foreach ($calendars as $calendar) {
458 if ($calendar->user_id == $userId) {
459 $calendarIds[] = $calendar->id;
460 continue;
461 }
462
463 $events = Arr::get($calendar, 'slots', []);
464 foreach ($events as $event) {
465 $teamMembers = Arr::get($event, 'settings.team_members', []);
466 if (in_array($userId, $teamMembers)) {
467 $calendarIds[] = $calendar->id;
468 }
469 }
470 }
471
472 return $calendarIds;
473 }
474
475 public static function isSharedCalendar($calendar)
476 {
477 $calendarEvents = $calendar->events;
478
479 $userId = get_current_user_id();
480
481 foreach ($calendarEvents as $event) {
482 if ($event->user_id == $userId) {
483 return true;
484 }
485 $teamMembers = Arr::get($event, 'settings.team_members', []);
486 if (in_array($userId, $teamMembers)) {
487 return true;
488 }
489 }
490
491 return false;
492 }
493
494 public static function updateCalendarEventsSchedule($calendarId, $oldTimezone, $updatedTimezone)
495 {
496 $calendarEvents = CalendarSlot::query()->where('calendar_id', $calendarId)->get();
497
498 foreach ($calendarEvents as $event) {
499 if ($weeklySchedule = Arr::get($event->settings, 'weekly_schedules', [])) {
500 $originalSchedule = SanitizeService::weeklySchedules($weeklySchedule, 'UTC', $oldTimezone);
501 $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC', true);
502 }
503
504 if ($dateOverride = Arr::get($event->settings, 'date_overrides', [])) {
505 $originalOverride = SanitizeService::slotDateOverrides($dateOverride, 'UTC', $oldTimezone);
506 $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC', null, true);
507 }
508
509 $event->settings = [
510 'weekly_schedules' => $weeklySchedule,
511 'date_overrides' => $dateOverride
512 ];
513
514 $event->save();
515 }
516 }
517
518 private static function sanitize_mapped_data($settings)
519 {
520 $sanitizerMap = [
521 'value' => 'intval',
522 'unit' => 'sanitize_text_field',
523 'subject' => 'sanitize_text_field',
524 'body' => 'fcal_sanitize_html',
525 'additional_recipients' => 'sanitize_text_field'
526 ];
527
528 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
529 }
530 }
531