function calendar_get_available_faculty_schedule
Search API
7.x calendar.module | calendar_get_available_faculty_schedule($faculty_id, $event_type_cid, $month, $year) |
6.x calendar.module | calendar_get_available_faculty_schedule($faculty_id, $event_type_cid, $month, $year) |
Returns back an array of time slots available for this faculty member and event_type
We will convert all times to local timezone as well.
2 calls to calendar_get_available_faculty_schedule()
- calendar_display_schedule_appointment_page in modules/
calendar/ calendar.module - This is the page which lets students schedule an appointment with the faculty member supplied in the user_id.
- calendar_schedule_appointment_confirm_form_validate in modules/
calendar/ calendar.module - This is our last chance to validate the form before saving.
File
- modules/
calendar/ calendar.module, line 1414
Code
function calendar_get_available_faculty_schedule($faculty_id, $event_type_cid, $month, $year) {
$rtn = array();
$event_type_content = content_load($event_type_cid);
// Confirm that the faculty_id in this content matches our supplied faculty_id.
if ($event_type_content->field__faculty_id ['value'] != $faculty_id) {
fp_add_message(t("Invalid schedule link."));
fp_goto("<front>");
return;
}
$prevent_less_than_hours = intval($event_type_content->field__prevent_less_than_hours ['value']);
// Get this faculty user's timezone.
$faculty_user_id = db_get_user_id_from_cwid($faculty_id);
$tz = fp_get_user_timezone($faculty_user_id);
// Get all of our unavailable times into a single array, organized by days.
$unavailable_times = content_get_content_for_faculty_id('schedule_unavailable_time', $faculty_id);
$unavailable_by_days = array();
foreach ($unavailable_times as $ut_content) {
// If this is an ICS link, then skip for now.
if (trim($ut_content->field__ics_url ['value']) != '') {
continue;
}
$time_selector = trim($ut_content->field__time_selector ['value']);
if ($time_selector == "") {
$time_selector = "default";
}
foreach ($ut_content->field__days ['value'] as $dow) {
$start_end_array = array();
if ($time_selector == "manual") {
$st = $ut_content->field__start_time ['value'];
$et = $ut_content->field__end_time ['value'];
$start_end_array [] = array(
'start' => $st,
'end' => $et,
'convert_tz' => TRUE, // since these are in UTC, they will indeed need to be converted.
);
} // manual
else if ($time_selector == "default") {
// We will construct times based on the day_start_hour and stop_hour
$st = $ut_content->field__day_start_hour ['value'] . ":00:00";
$et = $ut_content->field__day_stop_hour ['value'] . ":00:00";
if ($et == "0:00:00") {
$et = "23:59:59"; // meaning, we are stopping at midnight
}
// We want to calculate out what our start time is LESS 1 second, so that we can begin accepting appointments
// on the time we selected.
$st_less_one_sec = $st; // default
$start_hour = intval($ut_content->field__day_start_hour ['value']);
if ($start_hour > 0) {
$st_less_one_sec = ($start_hour - 1) . ":59:59";
}
$et_plus_one_sec = $et; // default
$stop_hour = intval($ut_content->field__day_stop_hour ['value']);
if ($stop_hour < 23) {
$et_plus_one_sec = ($stop_hour + 1) . ":00:01";
}
// The first one is the start. We go from midnight to $st minus 1 second.
$start_end_array [] = array(
'start' => "00:00:00",
'end' => $st_less_one_sec,
'convert_tz' => FALSE, // these times are in the faculty member's time zone. They'll NOT need to be converted to UTC in the next step.
);
// The second one is the end. We go from $et (plus one second) to 23:59:59
$start_end_array [] = array(
'start' => $et_plus_one_sec,
'end' => "23:59:59",
'convert_tz' => FALSE, // these times are in the faculty member's time zone. They'll NOT need to be converted to UTC in the next step.
);
} // default
else if ($time_selector == "none") {
// The user doesn't work at all on the selected days.
// We go from 00:00:00 to 23:59:59
$start_end_array [] = array(
'start' => "00:00:00",
'end' => "23:59:59",
'convert_tz' => FALSE, // these times are in the faculty member's time zone. They'll NOT need to be converted to UTC in the next step.
);
} // none
// Now, populate our unavailable array
foreach ($start_end_array as $c => $details) {
$st = $details ['start'];
$et = $details ['end'];
$convert_tz = $details ['convert_tz'];
// st and et are in UTC. The original faculty user's timezone is tz.
// Let's first convert them to the ORIGINAL timezone FROM UTC.
$sample_date = '2020-01-01';
// Grab the UTC times. Since we only care about hours/minutes, it's OK that we use the same date.
$utc_st = strtotime($sample_date . " " . $st);
$utc_et = strtotime($sample_date . " " . $et);
$tz_st = $utc_st;
$tz_et = $utc_et;
if ($convert_tz == TRUE) {
// Convert the UTC time into faculty member's timezone
$tz_st = convert_time($utc_st, 'UTC', $tz);
$tz_et = convert_time($utc_et, 'UTC', $tz);
}
// The start and end times are now in the original faculty member's timezone.
$unavailable_by_days [$dow][] = array(
'start_time' => date("H:i", $tz_st),
'end_time' => date("H:i", $tz_et),
'tz' => $tz,
);
}
} // foreach field__days
} // foreach unavailable_times
// Add to the unavailable_by_days array:
// existing appointments on our FP calendar
$existing_appointments = calendar_get_appointments_for_faculty($faculty_id, date('Y-m-d', strtotime("NOW - 24 HOURS")), date('Y-m-d', strtotime('NOW + 4 MONTHS')), TRUE);
// Okay, we need to find out how long this event lasts + any buffer time.
// how_long_min is basically when we can create our "slots" for the user to select. So if the number is 20 minutes, and we start at 11:30am, then
// we'd see a slot at: 11:30am, 11:50am, 12:10pm, 12:30pm, and so on.
$how_long_min = intval($event_type_content->field__event_duration_minutes ['value']) + intval($event_type_content->field__event_buffer_minutes ['value']);
$how_long_sec = $how_long_min * 60;
// Next, we are going to go through every single day of this month, to find out what all our slots are.
$total_days = cal_days_in_month(CAL_GREGORIAN, $month, $year);
for ($day = 1; $day <= $total_days; $day++) {
$begin_time = "00:00:00";
$end_time = "23:59:59";
$skip_begin_slot = array();
// We DO NOT NEED TO CONVERT for this!
$begin_time_ts = strtotime("$year-$month-$day $begin_time");
$end_time_ts = strtotime("$year-$month-$day $end_time");
$day_of_week = intval(date('w', $begin_time_ts));
// Loop from our begin time to our end time, by how_long_sec each step. We then
// want to see if there were any conflicts. If not, then it's safe to permit that time slot in our $rtn array
// for this day.
$begin_slot_ts = $begin_time_ts;
for ($end_slot_ts = $begin_time_ts + $how_long_sec; $end_slot_ts <= $end_time_ts; $end_slot_ts += $how_long_sec) {
// Skip if it's before X hours from now, based on the event_type_content. This will also catch all the days that have already passed.
if ($begin_slot_ts < strtotime("NOW + $prevent_less_than_hours HOURS")) {
// get ready for next time through the loop
$begin_slot_ts = $end_slot_ts;
continue;
}
// Okay-- We have a slot of time-- from begin_slot_ts to end_slot_ts.
// check to see if there are any conflicts during this "slot".
$bool_conflict = FALSE;
// check our unavailable times
if (isset($unavailable_by_days [$day_of_week])) {
foreach ($unavailable_by_days [$day_of_week] as $details) {
$un_start_time = $details ['start_time']; // These are now in their original timezone!
$un_end_time = $details ['end_time']; // these are now in their original timezone!
$un_start_time_ts = strtotime("$year-$month-$day $un_start_time");
$un_end_time_ts = strtotime("$year-$month-$day $un_end_time");
// Now, does our "slot" conflict with this unavailable time window?
// Scenario #1: Our unavailable time completely engulfs our slot.
// Ex: { unavail [ slot ] }
if ($un_start_time_ts <= $begin_slot_ts && $un_end_time_ts >= $end_slot_ts) {
$bool_conflict = TRUE;
break;
}
// Scenario #2: Our slot completely engulfs our unavailable time window.
// Ex: [ slot { unavail } ]
if ($begin_slot_ts <= $un_start_time_ts && $end_slot_ts >= $un_end_time_ts) {
$bool_conflict = TRUE;
break;
}
// Scenario #3: Our unavail time starts before slot, but ends inside of the slot.
// Ex: { unavail [ slot } ]
if ($un_start_time_ts <= $begin_slot_ts && $un_end_time_ts >= $begin_slot_ts && $un_end_time_ts <= $end_slot_ts) {
$bool_conflict = TRUE;
break;
}
// Scenario #4: Our unavail time starts after slot begins
// Ex: [ slot { unavail ] }
if ($un_start_time_ts >= $begin_slot_ts && $un_start_time_ts <= $end_slot_ts && $un_end_time_ts >= $end_slot_ts) {
$bool_conflict = TRUE;
break;
}
} // foreach unavailable by days[dow]
}
// Okay, now check our existing_appointments, and see if there are any conflicts thee.
if (isset($existing_appointments [$year][$month][$day])) {
foreach ($existing_appointments [$year][$month][$day] as $appt) {
$un_start_time_ts = $appt ['start_ts']; // These are in UTC
$un_end_time_ts = $appt ['end_ts'];
// For reasons that aren't completely known to me, here we are going to add the time-converted
// begin_slot_ts to an array, which we'll use later to "prune" our return results.
// Now, does our "slot" conflict with this unavailable time window?
// Scenario #1: Our unavailable time completely engulfs our slot.
// Ex: { unavail [ slot ] }
if ($un_start_time_ts <= $begin_slot_ts && $un_end_time_ts >= $end_slot_ts) {
$bool_conflict = TRUE;
$skip_begin_slot [] = convert_time($begin_slot_ts, 'UTC', $tz);
break;
}
// Scenario #2: Our slot completely engulfs our unavailable time window.
// Ex: [ slot { unavail } ]
if ($begin_slot_ts <= $un_start_time_ts && $end_slot_ts >= $un_end_time_ts) {
$bool_conflict = TRUE;
$skip_begin_slot [] = convert_time($begin_slot_ts, 'UTC', $tz);
break;
}
// Scenario #3: Our unavail time stars before slot, but ends inside of the slot.
// Ex: { unavail [ slot } ]
if ($un_start_time_ts <= $begin_slot_ts && $un_end_time_ts >= $begin_slot_ts && $un_end_time_ts <= $end_slot_ts) {
$bool_conflict = TRUE;
$skip_begin_slot [] = convert_time($begin_slot_ts, 'UTC', $tz);
break;
}
// Scenario #4: Our unavail time stars after slot begins
// Ex: [ slot { unavail ] }
if ($un_start_time_ts >= $begin_slot_ts && $un_start_time_ts <= $end_slot_ts && $un_end_time_ts >= $end_slot_ts) {
$bool_conflict = TRUE;
$skip_begin_slot [] = convert_time($begin_slot_ts, 'UTC', $tz);
break;
}
} // foreach existing appt
} // if isset
////////////
// If we made it here, then there are no conflicts.
// Let's add it in to our rtn array.
if ($bool_conflict == FALSE) {
$begin_hm = date('g:ia', $begin_slot_ts);
$rtn [$year][$month][$day][$begin_slot_ts] = array(
'begin_slot_ts' => $begin_slot_ts,
'end_slot_ts' => $end_slot_ts,
'begin_hm' => $begin_hm,
'end_hm' => date('g:ia', $end_slot_ts),
'tz' => $tz,
'how_long_min' => $how_long_min,
'how_long_sec' => $how_long_sec,
);
}
// get ready for next time through the loop
$begin_slot_ts = $end_slot_ts;
} // for end_slot_ts
// Prune our rtn array based on the skip_begin_slot array. I know this is an inefficient hack. I am being
// slowly driven crazy by this timezone shit.
if (isset($rtn [$year][$month][$day])) {
foreach ($rtn [$year][$month][$day] as $bg => $deets) {
if (in_array($bg, $skip_begin_slot)) {
unset($rtn [$year][$month][$day][$bg]);
}
}
}
} // for day
return $rtn;
}