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

469 lines 18.6 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' => sanitize_textarea_field(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)
306 {
307 $calendarSlots = CalendarSlot::select(['id', 'title'])
308 ->where('calendar_id', $calendarId)
309 ->where('status', '!=', 'expired')
310 ->latest()
311 ->get();
312
313 $options = [];
314 foreach ($calendarSlots as $slot) {
315 $options[] = [
316 'id' => $slot->id,
317 'label' => $slot->title,
318 ];
319 }
320 return apply_filters('fluent_booking/calendar_event_options', $options, $calendarId);
321 }
322
323 public static function getCalendarOptionsByHost()
324 {
325 $calendars = Calendar::select(['id', 'title'])
326 ->when(!PermissionManager::hasAllCalendarAccess(true), function ($query) {
327 return $query->where('user_id', get_current_user_id());
328 })
329 ->with(['slots' => function ($query) {
330 $query->where('status', '!=', 'expired');
331 }])
332 ->latest()
333 ->get();
334
335 $formattedCalendars = [];
336 foreach ($calendars as $index => $calendar) {
337 $slots = Arr::get($calendar, 'slots');
338 if (!empty($slots)) {
339 $options = [];
340 foreach ($slots as $slot) {
341 $options[] = [
342 'label' => Arr::get($slot, 'title'),
343 'value' => Arr::get($slot, 'id')
344 ];
345 }
346 if (!empty($options)) {
347 $formattedCalendars[$index] = [
348 'label' => Arr::get($calendar, 'title'),
349 'options' => $options
350 ];
351 }
352 }
353 }
354 return $formattedCalendars;
355 }
356
357 public static function getCalendarOptionsByTitle($condition = '')
358 {
359 $calendarsQuery = Calendar::select(['id', 'title'])
360 ->where('status', '!=', 'expired')
361 ->with(['slots' => function ($query) {
362 $query->where('status', '!=', 'expired');
363 }]);
364
365 switch ($condition) {
366 case 'only_hosts':
367 $calendarsQuery->where('type', 'simple');
368 break;
369 case 'only_teams':
370 $calendarsQuery->where('type', 'team');
371 break;
372 case 'only_events':
373 $calendarsQuery->where('type', 'event');
374 break;
375 }
376
377 if (!PermissionManager::hasAllCalendarAccess(true)) {
378 $attachedCalendarIds = self::getAttachedCalendarIds($calendarsQuery);
379 $calendarsQuery->whereIn('id', $attachedCalendarIds);
380 }
381
382 $calendars = $calendarsQuery->latest()->get();
383
384 $formattedCalendars = [];
385 foreach ($calendars as $index => $calendar) {
386 $slots = Arr::get($calendar, 'slots');
387 if (!empty($slots)) {
388 $options = [];
389 foreach ($slots as $slot) {
390 $options[] = [
391 'id' => Arr::get($slot, 'id'),
392 'title' => Arr::get($slot, 'title')
393 ];
394 }
395 if (!empty($options)) {
396 $formattedCalendars[$index] = [
397 'id' => Arr::get($calendar, 'id'),
398 'title' => Arr::get($calendar, 'title'),
399 'options' => $options
400 ];
401 }
402 }
403 }
404 return apply_filters('fluent_booking/calendar_options_by_title', $formattedCalendars);
405 }
406
407 public static function getAttachedCalendarIds($calendarsQuery)
408 {
409 $userId = get_current_user_id();
410
411 $calendars = $calendarsQuery->get();
412
413 $calendarIds = [];
414 foreach ($calendars as $calendar) {
415 if ($calendar->user_id == $userId) {
416 $calendarIds[] = $calendar->id;
417 continue;
418 }
419
420 $events = Arr::get($calendar, 'slots', []);
421 foreach ($events as $event) {
422 $teamMembers = Arr::get($event, 'settings.team_members', []);
423 if (in_array($userId, $teamMembers)) {
424 $calendarIds[] = $calendar->id;
425 }
426 }
427 }
428
429 return $calendarIds;
430 }
431
432 public static function updateCalendarEventsSchedule($calendarId, $oldTimezone, $updatedTimezone)
433 {
434 $calendarEvents = CalendarSlot::query()->where('calendar_id', $calendarId)->get();
435
436 foreach ($calendarEvents as $event) {
437 if ($weeklySchedule = Arr::get($event->settings, 'weekly_schedules', [])) {
438 $originalSchedule = SanitizeService::weeklySchedules($weeklySchedule, 'UTC', $oldTimezone);
439 $weeklySchedule = SanitizeService::weeklySchedules($originalSchedule, $updatedTimezone, 'UTC');
440 }
441
442 if ($dateOverride = Arr::get($event->settings, 'date_overrides', [])) {
443 $originalOverride = SanitizeService::slotDateOverrides($dateOverride, 'UTC', $oldTimezone);
444 $dateOverride = SanitizeService::slotDateOverrides($originalOverride, $updatedTimezone, 'UTC');
445 }
446
447 $event->settings = [
448 'weekly_schedules' => $weeklySchedule,
449 'date_overrides' => $dateOverride
450 ];
451
452 $event->save();
453 }
454 }
455
456 private static function sanitize_mapped_data($settings)
457 {
458 $sanitizerMap = [
459 'value' => 'intval',
460 'unit' => 'sanitize_text_field',
461 'subject' => 'sanitize_text_field',
462 'body' => 'fcal_sanitize_html',
463 'additional_recipients' => 'sanitize_text_field'
464 ];
465
466 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
467 }
468 }
469