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;
}
  