PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.7.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.7.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 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 1.7.0, at app/Services/CalendarService.php

493 lines 19.3 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 have 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'),
213 'date_overrides' => SanitizeService::slotDateOverrides(Arr::get($eventSettings, 'date_overrides', []), $calendar->author_timezone, 'UTC'),
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 ];
241
242 return $preparedEventData;
243 }
244
245 protected static function prepareEventMetas($eventMetasData)
246 {
247 $preparedEventMetas = [];
248
249 foreach ($eventMetasData as $eventMeta) {
250 if (empty($eventMeta['key']) || empty($eventMeta['value'])) {
251 continue;
252 }
253
254 $value = $eventMeta['value'];
255
256 if ($eventMeta['key'] == 'email_notification') {
257 $value = self::updateNotificationImageUrl($value);
258 }
259
260 $preparedEventMetas[] = [
261 'key' => sanitize_text_field($eventMeta['key']),
262 'value' => is_array($value) ? self::sanitize_mapped_data($value) : sanitize_text_field($value),
263 'object_type' => sanitize_text_field($eventMeta['object_type'])
264 ];
265 }
266
267 return $preparedEventMetas;
268 }
269
270 protected static function prepareAvailabilityId($availabilityId, $availabilities)
271 {
272 if ($availabilityId && isset($availabilities[$availabilityId])) {
273 return $availabilities[$availabilityId];
274 }
275
276 if ($availabilities) {
277 $firstKey = array_key_first($availabilities);
278 return $availabilities[$firstKey];
279 }
280
281 return $availabilityId;
282 }
283
284 protected static function updateNotificationImageUrl($notifications)
285 {
286 $formattedNotifications = [];
287
288 foreach ($notifications as $key => $notification) {
289 $emailBody = Arr::get($notification, 'email.body');
290 if (!$emailBody) {
291 continue;
292 }
293
294 $newImageUrl = FLUENT_BOOKING_URL . 'assets/images';
295 $pattern = '/(https:\/\/[^"]*?' . preg_quote('assets/images', '/') . ')/';
296 $emailBody = preg_replace($pattern, $newImageUrl, $emailBody);
297 $notification['email']['body'] = $emailBody;
298
299 $formattedNotifications[$key] = $notification;
300 }
301
302 return $formattedNotifications;
303 }
304
305 public static function getSlotOptions($calendarId = null, $userId = null)
306 {
307 $calendarSlots = CalendarSlot::select(['id', 'title'])
308 ->when($calendarId, function ($query) use ($calendarId) {
309 return $query->where('calendar_id', $calendarId);
310 })
311 ->when($userId, function ($query) use ($userId) {
312 return $query->where('user_id', $userId);
313 })
314 ->where('status', '!=', 'expired')
315 ->latest()
316 ->get();
317
318 $options = [];
319 foreach ($calendarSlots as $slot) {
320 $options[] = [
321 'id' => $slot->id,
322 'label' => $slot->title,
323 ];
324 }
325 return apply_filters('fluent_booking/calendar_event_options', $options, $calendarId);
326 }
327
328 public static function getCalendarOptionsByHost()
329 {
330 $calendars = Calendar::select(['id', 'title'])
331 ->when(!PermissionManager::hasAllCalendarAccess(true), function ($query) {
332 return $query->where('user_id', get_current_user_id());
333 })
334 ->with(['slots' => function ($query) {
335 $query->where('status', '!=', 'expired');
336 }])
337 ->latest()
338 ->get();
339
340 $formattedCalendars = [];
341 foreach ($calendars as $index => $calendar) {
342 $slots = Arr::get($calendar, 'slots');
343 if (!empty($slots)) {
344 $options = [];
345 foreach ($slots as $slot) {
346 $options[] = [
347 'label' => Arr::get($slot, 'title'),
348 'value' => Arr::get($slot, 'id')
349 ];
350 }
351 if (!empty($options)) {
352 $formattedCalendars[$index] = [
353 'label' => Arr::get($calendar, 'title'),
354 'options' => $options
355 ];
356 }
357 }
358 }
359 return $formattedCalendars;
360 }
361
362 public static function getCalendarOptionsByTitle($condition = '')
363 {
364 $calendarsQuery = Calendar::select(['id', 'title', 'user_id'])
365 ->where('status', '!=', 'expired')
366 ->with(['slots' => function ($query) {
367 $query->where('status', '!=', 'expired');
368 }]);
369
370 switch ($condition) {
371 case 'only_hosts':
372 $calendarsQuery->where('type', 'simple');
373 break;
374 case 'only_teams':
375 $calendarsQuery->where('type', 'team');
376 break;
377 case 'only_events':
378 $calendarsQuery->where('type', 'event');
379 break;
380 }
381
382 if (!PermissionManager::hasAllCalendarAccess(true)) {
383 $attachedCalendarIds = self::getAttachedCalendarIds($calendarsQuery);
384 $calendarsQuery->whereIn('id', $attachedCalendarIds);
385 }
386
387 $calendars = $calendarsQuery->latest()->get();
388
389 $formattedCalendars = [];
390 foreach ($calendars as $index => $calendar) {
391 $slots = Arr::get($calendar, 'slots');
392 if (!empty($slots)) {
393 $options = [];
394 foreach ($slots as $slot) {
395 $options[] = [
396 'id' => Arr::get($slot, 'id'),
397 'title' => Arr::get($slot, 'title')
398 ];
399 }
400 if (!empty($options)) {
401 $formattedCalendars[$index] = [
402 'id' => Arr::get($calendar, 'id'),
403 'title' => Arr::get($calendar, 'title'),
404 'options' => $options
405 ];
406 }
407 }
408 }
409 return apply_filters('fluent_booking/calendar_options_by_title', $formattedCalendars);
410 }
411
412 public static function getAttachedCalendarIds($calendarsQuery)
413 {
414 $userId = get_current_user_id();
415
416 $calendars = $calendarsQuery->get();
417
418 $calendarIds = [];
419 foreach ($calendars as $calendar) {
420 if ($calendar->user_id == $userId) {
421 $calendarIds[] = $calendar->id;
422 continue;
423 }
424
425 $events = Arr::get($calendar, 'slots', []);
426 foreach ($events as $event) {
427 $teamMembers = Arr::get($event, 'settings.team_members', []);
428 if (in_array($userId, $teamMembers)) {
429 $calendarIds[] = $calendar->id;
430 }
431 }
432 }
433
434 return $calendarIds;
435 }
436
437 public static function isSharedCalendar($calendar)
438 {
439 $calendarEvents = $calendar->events;
440
441 $userId = get_current_user_id();
442
443 foreach ($calendarEvents as $event) {
444 if ($event->user_id == $userId) {
445 return true;
446 }
447 $teamMembers = Arr::get($event, 'settings.team_members', []);
448 if (in_array($userId, $teamMembers)) {
449 return true;
450 }
451 }
452
453 return false;
454 }
455
456 public static function updateCalendarEventsSchedule($calendarId, $oldTimezone, $updatedTimezone)
457 {
458 $calendarEvents = CalendarSlot::query()->where('calendar_id', $calendarId)->get();
459
460 foreach ($calendarEvents as $event) {
461 if ($weeklySchedule = Arr::get($event->settings, 'weekly_schedules', [])) {
462 $originalSchedule = SanitizeService::weeklySchedules($weeklySchedule, 'UTC', $oldTimezone);
463 $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC');
464 }
465
466 if ($dateOverride = Arr::get($event->settings, 'date_overrides', [])) {
467 $originalOverride = SanitizeService::slotDateOverrides($dateOverride, 'UTC', $oldTimezone);
468 $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC');
469 }
470
471 $event->settings = [
472 'weekly_schedules' => $weeklySchedule,
473 'date_overrides' => $dateOverride
474 ];
475
476 $event->save();
477 }
478 }
479
480 private static function sanitize_mapped_data($settings)
481 {
482 $sanitizerMap = [
483 'value' => 'intval',
484 'unit' => 'sanitize_text_field',
485 'subject' => 'sanitize_text_field',
486 'body' => 'fcal_sanitize_html',
487 'additional_recipients' => 'sanitize_text_field'
488 ];
489
490 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
491 }
492 }
493